diff options
-rw-r--r-- | Cargo.lock | 5 | ||||
-rw-r--r-- | cli/tests/unit/webcrypto_test.ts | 23 | ||||
-rw-r--r-- | ext/crypto/00_crypto.js | 68 | ||||
-rw-r--r-- | ext/crypto/Cargo.toml | 1 | ||||
-rw-r--r-- | ext/crypto/lib.rs | 75 |
5 files changed, 162 insertions, 10 deletions
diff --git a/Cargo.lock b/Cargo.lock index 10bd939df..7bbe0d637 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -733,6 +733,7 @@ dependencies = [ "serde", "sha-1", "sha2", + "spki", "tokio", "uuid", ] @@ -3302,9 +3303,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "987637c5ae6b3121aba9d513f869bd2bff11c4cc086c22473befd6649c0bd521" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" dependencies = [ "der", ] diff --git a/cli/tests/unit/webcrypto_test.ts b/cli/tests/unit/webcrypto_test.ts index 1c4788f87..b38a9934f 100644 --- a/cli/tests/unit/webcrypto_test.ts +++ b/cli/tests/unit/webcrypto_test.ts @@ -441,7 +441,7 @@ const asn1AlgorithmIdentifier = new Uint8Array([ 0x05, 0x00, // NULL ]); -unitTest(async function rsaExportPkcs8() { +unitTest(async function rsaExport() { for (const algorithm of ["RSASSA-PKCS1-v1_5", "RSA-PSS", "RSA-OAEP"]) { const keyPair = await crypto.subtle.generateKey( { @@ -458,21 +458,34 @@ unitTest(async function rsaExportPkcs8() { assert(keyPair.publicKey); assertEquals(keyPair.privateKey.extractable, true); - const exportedKey = await crypto.subtle.exportKey( + const exportedPrivateKey = await crypto.subtle.exportKey( "pkcs8", keyPair.privateKey, ); - assert(exportedKey); - assert(exportedKey instanceof ArrayBuffer); + assert(exportedPrivateKey); + assert(exportedPrivateKey instanceof ArrayBuffer); - const pkcs8 = new Uint8Array(exportedKey); + const pkcs8 = new Uint8Array(exportedPrivateKey); assert(pkcs8.length > 0); assertEquals( pkcs8.slice(4, asn1AlgorithmIdentifier.byteLength + 4), asn1AlgorithmIdentifier, ); + + const exportedPublicKey = await crypto.subtle.exportKey( + "spki", + keyPair.publicKey, + ); + + const spki = new Uint8Array(exportedPublicKey); + assert(spki.length > 0); + + assertEquals( + spki.slice(4, asn1AlgorithmIdentifier.byteLength + 1), + asn1AlgorithmIdentifier.slice(3), + ); } }); diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index fdd0f612d..f0ba0b4bf 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -1278,6 +1278,28 @@ // 3. return data.buffer; } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2. + const data = await core.opAsync( + "op_crypto_export_key", + { + key: innerKey, + format: "spki", + algorithm: "RSASSA-PKCS1-v1_5", + }, + ); + + // 3. + return data.buffer; + } default: throw new DOMException("Not implemented", "NotSupportedError"); } @@ -1307,6 +1329,29 @@ // 3. return data.buffer; } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2. + const data = await core.opAsync( + "op_crypto_export_key", + { + key: innerKey, + format: "spki", + algorithm: "RSA-PSS", + hash: key[_algorithm].hash.name, + }, + ); + + // 3. + return data.buffer; + } default: throw new DOMException("Not implemented", "NotSupportedError"); } @@ -1336,6 +1381,29 @@ // 3. return data.buffer; } + case "spki": { + // 1. + if (key[_type] !== "public") { + throw new DOMException( + "Key is not a public key", + "InvalidAccessError", + ); + } + + // 2. + const data = await core.opAsync( + "op_crypto_export_key", + { + key: innerKey, + format: "spki", + algorithm: "RSA-OAEP", + hash: key[_algorithm].hash.name, + }, + ); + + // 3. + return data.buffer; + } default: throw new DOMException("Not implemented", "NotSupportedError"); } diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml index 2aa55599e..d04f8dc33 100644 --- a/ext/crypto/Cargo.toml +++ b/ext/crypto/Cargo.toml @@ -24,5 +24,6 @@ rsa = { version = "0.5.0", default-features = false, features = ["std"] } serde = { version = "1.0.129", features = ["derive"] } sha-1 = "0.9.7" sha2 = "0.9.5" +spki = "0.4.1" tokio = { version = "1.10.1", features = ["full"] } uuid = { version = "0.8.2", features = ["v4"] } diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index a562eaf01..2db629c94 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -271,6 +271,7 @@ pub async fn op_crypto_generate_key( pub enum KeyFormat { Raw, Pkcs8, + Spki, } #[derive(Deserialize)] @@ -631,7 +632,27 @@ pub async fn op_crypto_export_key( Ok(pk_info.to_der().as_ref().to_vec().into()) } - // TODO(@littledivy): spki + KeyFormat::Spki => { + // public_key is a PKCS#1 DER-encoded public key + + let subject_public_key = &args.key.data; + + // the SPKI structure + let key_info = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifier { + // rsaEncryption(1) + oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"), + // parameters field should not be ommited (None). + // It MUST have ASN.1 type NULL. + parameters: Some(asn1::Any::from(asn1::Null)), + }, + subject_public_key, + }; + + // Infallible based on spec because of the way we import and generate keys. + let spki_der = key_info.to_vec().unwrap(); + Ok(spki_der.into()) + } // TODO(@littledivy): jwk _ => unreachable!(), } @@ -668,7 +689,31 @@ pub async fn op_crypto_export_key( Ok(pk_info.to_der().as_ref().to_vec().into()) } - // TODO(@littledivy): spki + KeyFormat::Spki => { + // Intentionally unused but required. Not encoded into SPKI (see below). + let _hash = args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))?; + + // public_key is a PKCS#1 DER-encoded public key + let subject_public_key = &args.key.data; + + // the SPKI structure + let key_info = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifier { + // rsaEncryption(1) + oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"), + // parameters field should not be ommited (None). + // It MUST have ASN.1 type NULL. + parameters: Some(asn1::Any::from(asn1::Null)), + }, + subject_public_key, + }; + + // Infallible based on spec because of the way we import and generate keys. + let spki_der = key_info.to_vec().unwrap(); + Ok(spki_der.into()) + } // TODO(@littledivy): jwk _ => unreachable!(), } @@ -705,7 +750,31 @@ pub async fn op_crypto_export_key( Ok(pk_info.to_der().as_ref().to_vec().into()) } - // TODO(@littledivy): spki + KeyFormat::Spki => { + // Intentionally unused but required. Not encoded into SPKI (see below). + let _hash = args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))?; + + // public_key is a PKCS#1 DER-encoded public key + let subject_public_key = &args.key.data; + + // the SPKI structure + let key_info = spki::SubjectPublicKeyInfo { + algorithm: spki::AlgorithmIdentifier { + // rsaEncryption(1) + oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"), + // parameters field should not be ommited (None). + // It MUST have ASN.1 type NULL. + parameters: Some(asn1::Any::from(asn1::Null)), + }, + subject_public_key, + }; + + // Infallible based on spec because of the way we import and generate keys. + let spki_der = key_info.to_vec().unwrap(); + Ok(spki_der.into()) + } // TODO(@littledivy): jwk _ => unreachable!(), } |