-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9 from invisal/rsa-export-key
RSAKey can export to different key format
- Loading branch information
Showing
10 changed files
with
257 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
export interface RSAKey { | ||
export interface RSAKeyParams { | ||
n: bigint; | ||
e?: bigint; | ||
d?: bigint; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import { RSAKeyParams } from "./common.ts"; | ||
import { bignum_to_byte } from "../helper.ts"; | ||
import { encode } from "./../../src/utility/encode.ts"; | ||
|
||
function ber_size_bytes(size: number): number[] { | ||
// The BER Length | ||
// The second component in the TLV structure of a BER element is the length. | ||
// This specifies the size in bytes of the encoded value. For the most part, | ||
// this uses a straightforward binary encoding of the integer value | ||
// (for example, if the encoded value is five bytes long, then it is encoded as | ||
// 00000101 binary, or 0x05 hex), but if the value is longer than 127 bytes then | ||
// it is necessary to use multiple bytes to encode the length. In that case, the | ||
// first byte has the leftmost bit set to one and the remaining seven bits are | ||
// used to specify the number of bytes required to encode the full length. For example, | ||
// if there are 500 bytes in the length (hex 0x01F4), then the encoded length will actually | ||
// consist of three bytes: 82 01 F4. | ||
// | ||
// Note that there is an alternate form for encoding the length called the indefinite form. | ||
// In this mechanism, only a part of the length is given at a time, similar to the chunked encoding | ||
// that is available in HTTP 1.1. However, this form is not used in LDAP, as specified in RFC 2251 | ||
// section 5.1. | ||
// https://docs.oracle.com/cd/E19476-01/821-0510/def-basic-encoding-rules.html | ||
|
||
if (size <= 127) return [size]; | ||
|
||
const bytes = []; | ||
while (size > 0) { | ||
bytes.push(size & 0xff); | ||
size = size >> 8; | ||
} | ||
|
||
bytes.reverse(); | ||
return [0x80 + bytes.length, ...bytes]; | ||
} | ||
|
||
function add_line_break(base64_str: string): string { | ||
const lines = []; | ||
for (let i = 0; i < base64_str.length; i += 64) { | ||
lines.push(base64_str.substr(i, 64)); | ||
} | ||
|
||
return lines.join("\n"); | ||
} | ||
|
||
function ber_generate_integer_list(order: number[][]) { | ||
let content: number[] = []; | ||
|
||
for (const item of order) { | ||
if ((item[0] & 0x80) > 0) { | ||
content = content.concat( | ||
[0x02, ...ber_size_bytes(item.length + 1), 0x0, ...item], | ||
); | ||
} else { | ||
content = content.concat( | ||
[0x02, ...ber_size_bytes(item.length), ...item], | ||
); | ||
} | ||
} | ||
|
||
return content; | ||
} | ||
|
||
export function rsa_export_pkcs8_public(key: RSAKeyParams) { | ||
const n = bignum_to_byte(key.n); | ||
const e = bignum_to_byte(key.e || 0n); | ||
|
||
// deno-fmt-ignore | ||
const other = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]; | ||
|
||
// Key sequence | ||
const content = ber_generate_integer_list([n, e]); | ||
const keySequence = [ | ||
0x30, | ||
...ber_size_bytes(content.length), | ||
...content, | ||
]; | ||
|
||
// Bitstring | ||
const bitString = [ | ||
0x03, | ||
...ber_size_bytes(keySequence.length + 1), | ||
0x00, | ||
...keySequence, | ||
]; | ||
|
||
const ber = [ | ||
0x30, | ||
...ber_size_bytes(other.length + bitString.length), | ||
...other, | ||
...bitString, | ||
]; | ||
|
||
return "-----BEGIN PUBLIC KEY-----\n" + | ||
add_line_break(encode.binary(ber).base64()) + | ||
"\n-----END PUBLIC KEY-----\n"; | ||
} | ||
|
||
export function rsa_export_pkcs8_private(key: RSAKeyParams) { | ||
const n = bignum_to_byte(key.n); | ||
const e = bignum_to_byte(key.e || 0n); | ||
const d = bignum_to_byte(key.d || 0n); | ||
const q = bignum_to_byte(key.q || 0n); | ||
const p = bignum_to_byte(key.p || 0n); | ||
const dp = bignum_to_byte(key.dp || 0n); | ||
const dq = bignum_to_byte(key.dq || 0n); | ||
const qi = bignum_to_byte(key.qi || 0n); | ||
|
||
const content = ber_generate_integer_list([n, e, d, p, q, dp, dq, qi]); | ||
|
||
const ber = encode.binary([ | ||
0x30, | ||
...ber_size_bytes(content.length + 3), | ||
0x02, | ||
0x01, | ||
0x00, | ||
...content, | ||
]).base64(); | ||
|
||
return "-----BEGIN RSA PRIVATE KEY-----\n" + add_line_break(ber) + | ||
"\n-----END RSA PRIVATE KEY-----\n"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { RSAKeyParams, JSONWebKey } from "./common.ts"; | ||
import { encode } from "./../../src/utility/encode.ts"; | ||
import { | ||
rsa_export_pkcs8_private, | ||
rsa_export_pkcs8_public, | ||
} from "./export_key.ts"; | ||
|
||
export class RSAKey { | ||
public n: bigint; | ||
public e?: bigint; | ||
public d?: bigint; | ||
public p?: bigint; | ||
public q?: bigint; | ||
public dp?: bigint; | ||
public dq?: bigint; | ||
public qi?: bigint; | ||
public length: number; | ||
|
||
constructor(params: RSAKeyParams) { | ||
this.n = params.n; | ||
this.e = params.e; | ||
this.d = params.d; | ||
this.p = params.p; | ||
this.q = params.q; | ||
this.dp = params.dp; | ||
this.dq = params.dq; | ||
this.qi = params.qi; | ||
this.length = params.length; | ||
} | ||
|
||
public pem(): string { | ||
if (this.d) { | ||
return rsa_export_pkcs8_private(this); | ||
} else { | ||
return rsa_export_pkcs8_public(this); | ||
} | ||
} | ||
|
||
public jwk(): JSONWebKey { | ||
let jwk: JSONWebKey = { | ||
kty: "RSA", | ||
n: encode.bigint(this.n).base64url(), | ||
}; | ||
|
||
if (this.d) jwk = { ...jwk, d: encode.bigint(this.d).base64url() }; | ||
if (this.e) jwk = { ...jwk, e: encode.bigint(this.e).base64url() }; | ||
if (this.p) jwk = { ...jwk, p: encode.bigint(this.p).base64url() }; | ||
if (this.q) jwk = { ...jwk, q: encode.bigint(this.q).base64url() }; | ||
if (this.dp) jwk = { ...jwk, dp: encode.bigint(this.dp).base64url() }; | ||
if (this.dq) jwk = { ...jwk, dq: encode.bigint(this.dq).base64url() }; | ||
if (this.qi) jwk = { ...jwk, qi: encode.bigint(this.qi).base64url() }; | ||
|
||
return jwk; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { RSA } from "./../../mod.ts"; | ||
import { | ||
assertEquals, | ||
} from "https://deno.land/[email protected]/testing/asserts.ts"; | ||
|
||
const privateKeyRaw = Deno.readTextFileSync("./tests/rsa/private.pem"); | ||
const publicKeyRaw = "-----BEGIN PUBLIC KEY-----\n" + | ||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArlKJ591/fYCKhdflQSNi\n" + | ||
"xBhWutUtW5y3l5vFzTxiKE4e9jykJ0Sr7U6GkwjmvplTV7Wgx4zhRr3tYrMqmQ+s\n" + | ||
"/byRK3f2bb+zXF9+fnKGuP7Fp2oYprW3MKxKgNxjRzmx2x7LaV11dHFQv6oigeV2\n" + | ||
"cyY5XB/GnEWUyHY7fCJIJIRdxuskt+77NAU0vrA/ntbWzFFsPP5xWJ8ns/ojTvwu\n" + | ||
"+LT++fpBD3X1nTUR/LzlRgGxGqPHYRCHvY8B2FSPL8ukqfXI3LkvCM77zeR5lwPq\n" + | ||
"IqDFVWcP6TNsOXccqDtBiA3+A6TS3nGmOu3NbZdefkzJlXq2D0xuW6ql0WqBM0Vu\n" + | ||
"bwIDAQAB\n" + | ||
"-----END PUBLIC KEY-----\n"; | ||
|
||
Deno.test("RSA - PKCS8 to PKCS8", async () => { | ||
assertEquals(RSA.importKey(privateKeyRaw).pem(), privateKeyRaw); | ||
assertEquals(RSA.importKey(publicKeyRaw).pem(), publicKeyRaw); | ||
}); | ||
|
||
Deno.test("RSA - JWK to PKCS8", async () => { | ||
const jwk = { | ||
kty: "RSA", | ||
n: "rlKJ591_fYCKhdflQSNixBhWutUtW5y3l5vFzTxiKE4e9jykJ0Sr7U6GkwjmvplTV7Wgx4zhRr3tYrMqmQ-s_byRK3f2bb-zXF9-fnKGuP7Fp2oYprW3MKxKgNxjRzmx2x7LaV11dHFQv6oigeV2cyY5XB_GnEWUyHY7fCJIJIRdxuskt-77NAU0vrA_ntbWzFFsPP5xWJ8ns_ojTvwu-LT--fpBD3X1nTUR_LzlRgGxGqPHYRCHvY8B2FSPL8ukqfXI3LkvCM77zeR5lwPqIqDFVWcP6TNsOXccqDtBiA3-A6TS3nGmOu3NbZdefkzJlXq2D0xuW6ql0WqBM0Vubw", | ||
e: "AQAB", | ||
}; | ||
|
||
assertEquals(RSA.importKey(jwk).pem(), publicKeyRaw); | ||
}); | ||
|
||
Deno.test("RSA - PKCS8 to JWK", async () => { | ||
const jwk = { | ||
kty: "RSA", | ||
n: "rlKJ591_fYCKhdflQSNixBhWutUtW5y3l5vFzTxiKE4e9jykJ0Sr7U6GkwjmvplTV7Wgx4zhRr3tYrMqmQ-s_byRK3f2bb-zXF9-fnKGuP7Fp2oYprW3MKxKgNxjRzmx2x7LaV11dHFQv6oigeV2cyY5XB_GnEWUyHY7fCJIJIRdxuskt-77NAU0vrA_ntbWzFFsPP5xWJ8ns_ojTvwu-LT--fpBD3X1nTUR_LzlRgGxGqPHYRCHvY8B2FSPL8ukqfXI3LkvCM77zeR5lwPqIqDFVWcP6TNsOXccqDtBiA3-A6TS3nGmOu3NbZdefkzJlXq2D0xuW6ql0WqBM0Vubw", | ||
e: "AQAB", | ||
}; | ||
|
||
const actualJwk = RSA.importKey(publicKeyRaw).jwk(); | ||
assertEquals(actualJwk.n, jwk.n); | ||
assertEquals(actualJwk.e, jwk.e); | ||
}); |