diff options
author | Yacine Hmito <yacinehmito@users.noreply.github.com> | 2021-12-09 20:32:55 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-09 20:32:55 +0100 |
commit | a3d024ac2ec8d73f7bf268823866d2342d1c1eb1 (patch) | |
tree | d4f2583e379376fc56dc60ef3a977251a4d90c77 /ext/crypto/lib.rs | |
parent | e70dc534607f5c3f168d57e3b11997df10d05099 (diff) |
feat(crypto): support `importKey` in SPKI format (#12921)
This commit adds support for `spki` key format for `crypto.subtle.importKey` for the RSA* algorithms.
Diffstat (limited to 'ext/crypto/lib.rs')
-rw-r--r-- | ext/crypto/lib.rs | 397 |
1 files changed, 363 insertions, 34 deletions
diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index dcf03d64c..d811e504d 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -40,6 +40,7 @@ use rsa::padding::PaddingScheme; use rsa::pkcs1::der::Decodable; use rsa::pkcs1::der::Encodable; use rsa::pkcs1::FromRsaPrivateKey; +use rsa::pkcs1::FromRsaPublicKey; use rsa::pkcs1::ToRsaPrivateKey; use rsa::pkcs8::der::asn1; use rsa::pkcs8::FromPrivateKey; @@ -277,10 +278,16 @@ pub enum KeyFormat { #[derive(Deserialize)] #[serde(rename_all = "lowercase")] +pub enum KeyType { + Secret, + Private, + Public, +} + +#[derive(Deserialize)] +#[serde(rename_all = "lowercase")] pub struct KeyData { - // TODO(littledivy): Kept here to be used to importKey() in future. - #[allow(dead_code)] - r#type: KeyFormat, + r#type: KeyType, data: ZeroCopyBuf, } @@ -458,8 +465,7 @@ pub async fn op_crypto_verify_key( let verification = match algorithm { Algorithm::RsassaPkcs1v15 => { - let public_key: RsaPublicKey = - RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key(); + let public_key = read_rsa_public_key(args.key)?; let (padding, hashed) = match args .hash .ok_or_else(|| type_error("Missing argument hash".to_string()))? @@ -515,8 +521,7 @@ pub async fn op_crypto_verify_key( .salt_length .ok_or_else(|| type_error("Missing argument saltLength".to_string()))? as usize; - let public_key: RsaPublicKey = - RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key(); + let public_key = read_rsa_public_key(args.key)?; let rng = OsRng; let (padding, hashed) = match args @@ -903,6 +908,17 @@ pub struct EncryptArg { length: Option<usize>, } +fn read_rsa_public_key(key_data: KeyData) -> Result<RsaPublicKey, AnyError> { + let public_key = match key_data.r#type { + KeyType::Private => { + RsaPrivateKey::from_pkcs1_der(&*key_data.data)?.to_public_key() + } + KeyType::Public => RsaPublicKey::from_pkcs1_der(&*key_data.data)?, + KeyType::Secret => unreachable!("unexpected KeyType::Secret"), + }; + Ok(public_key) +} + pub async fn op_crypto_encrypt_key( _state: Rc<RefCell<OpState>>, args: EncryptArg, @@ -913,8 +929,7 @@ pub async fn op_crypto_encrypt_key( match algorithm { Algorithm::RsaOaep => { - let public_key: RsaPublicKey = - RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key(); + let public_key = read_rsa_public_key(args.key)?; let label = args.label.map(|l| String::from_utf8_lossy(&*l).to_string()); let mut rng = OsRng; let padding = match args @@ -1230,7 +1245,7 @@ pub async fn op_crypto_import_key( // 2-3. let pk_info = rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| { - custom_error("DOMExceptionOperationError", e.to_string()) + custom_error("DOMExceptionDataError", e.to_string()) })?; // 4-5. @@ -1248,7 +1263,12 @@ pub async fn op_crypto_import_key( SHA384_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha384), // sha512WithRSAEncryption SHA512_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha512), - _ => return Err(type_error("Unsupported algorithm".to_string())), + _ => { + return Err(custom_error( + "DOMExceptionDataError", + "Unsupported algorithm".to_string(), + )) + } }; // 7. @@ -1262,17 +1282,17 @@ pub async fn op_crypto_import_key( } // 8-9. - let private_key = - rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err( - |e| custom_error("DOMExceptionOperationError", e.to_string()), - )?; + let private_key = rsa::pkcs1::RsaPrivateKey::from_der( + pk_info.private_key, + ) + .map_err(|e| custom_error("DOMExceptionDataError", e.to_string()))?; let bytes_consumed = private_key.encoded_len().map_err(|e| { custom_error("DOMExceptionDataError", e.to_string()) })?; if bytes_consumed - != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) + != spki::der::Length::new(pk_info.private_key.len() as u16) { return Err(custom_error( "DOMExceptionDataError", @@ -1288,7 +1308,78 @@ pub async fn op_crypto_import_key( modulus_length: Some(private_key.modulus.as_bytes().len() * 8), }) } - // TODO(@littledivy): spki + KeyFormat::Spki => { + let hash = args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))?; + + // 2-3. + let pk_info = + spki::SubjectPublicKeyInfo::from_der(data).map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + // 4-5. + let alg = pk_info.algorithm.oid; + + // 6. + let pk_hash = match alg { + // rsaEncryption + RSA_ENCRYPTION_OID => None, + // sha1WithRSAEncryption + SHA1_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha1), + // sha256WithRSAEncryption + SHA256_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha256), + // sha384WithRSAEncryption + SHA384_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha384), + // sha512WithRSAEncryption + SHA512_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha512), + _ => { + return Err(custom_error( + "DOMExceptionDataError", + "Unsupported algorithm".to_string(), + )) + } + }; + + // 7. + if let Some(pk_hash) = pk_hash { + if pk_hash != hash { + return Err(custom_error( + "DOMExceptionDataError", + "Hash mismatch".to_string(), + )); + } + } + + // 8-9. + let public_key = + rsa::pkcs1::RsaPublicKey::from_der(pk_info.subject_public_key) + .map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + let bytes_consumed = public_key.encoded_len().map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + if bytes_consumed + != spki::der::Length::new(pk_info.subject_public_key.len() as u16) + { + return Err(custom_error( + "DOMExceptionDataError", + "Some bytes were not consumed".to_string(), + )); + } + + Ok(ImportKeyResult { + data: pk_info.subject_public_key.to_vec().into(), + public_exponent: Some( + public_key.public_exponent.as_bytes().to_vec().into(), + ), + modulus_length: Some(public_key.modulus.as_bytes().len() * 8), + }) + } // TODO(@littledivy): jwk _ => Err(type_error("Unsupported format".to_string())), } @@ -1303,7 +1394,7 @@ pub async fn op_crypto_import_key( // 2-3. let pk_info = rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| { - custom_error("DOMExceptionOperationError", e.to_string()) + custom_error("DOMExceptionDataError", e.to_string()) })?; // 4-5. @@ -1355,6 +1446,12 @@ pub async fn op_crypto_import_key( )); } + // TODO(lucacasonato): + // If the parameters field of the maskGenAlgorithm field of params + // is not an instance of the HashAlgorithm ASN.1 type that is + // identical in content to the hashAlgorithm field of params, + // throw a NotSupportedError. + hash } _ => { @@ -1376,17 +1473,17 @@ pub async fn op_crypto_import_key( } // 8-9. - let private_key = - rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err( - |e| custom_error("DOMExceptionOperationError", e.to_string()), - )?; + let private_key = rsa::pkcs1::RsaPrivateKey::from_der( + pk_info.private_key, + ) + .map_err(|e| custom_error("DOMExceptionDataError", e.to_string()))?; - let bytes_consumed = private_key - .encoded_len() - .map_err(|e| custom_error("DataError", e.to_string()))?; + let bytes_consumed = private_key.encoded_len().map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; if bytes_consumed - != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) + != spki::der::Length::new(pk_info.private_key.len() as u16) { return Err(custom_error( "DOMExceptionDataError", @@ -1402,7 +1499,120 @@ pub async fn op_crypto_import_key( modulus_length: Some(private_key.modulus.as_bytes().len() * 8), }) } - // TODO(@littledivy): spki + KeyFormat::Spki => { + let hash = args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))?; + + // 2-3. + let pk_info = + spki::SubjectPublicKeyInfo::from_der(data).map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + // 4-5. + let alg = pk_info.algorithm.oid; + + // 6. + let pk_hash = match alg { + // rsaEncryption + RSA_ENCRYPTION_OID => None, + // id-RSASSA-PSS + RSASSA_PSS_OID => { + let params = PssPrivateKeyParameters::try_from( + pk_info.algorithm.parameters.ok_or_else(|| { + custom_error( + "DOMExceptionDataError", + "Malformed parameters".to_string(), + ) + })?, + ) + .map_err(|_| { + custom_error( + "DOMExceptionDataError", + "Malformed parameters".to_string(), + ) + })?; + + let hash_alg = params.hash_algorithm; + let hash = match hash_alg.oid { + // id-sha1 + ID_SHA1_OID => Some(CryptoHash::Sha1), + // id-sha256 + ID_SHA256_OID => Some(CryptoHash::Sha256), + // id-sha384 + ID_SHA384_OID => Some(CryptoHash::Sha384), + // id-sha256 + ID_SHA512_OID => Some(CryptoHash::Sha512), + _ => { + return Err(custom_error( + "DOMExceptionDataError", + "Unsupported hash algorithm".to_string(), + )) + } + }; + + if params.mask_gen_algorithm.oid != ID_MFG1 { + return Err(custom_error( + "DOMExceptionNotSupportedError", + "Unsupported hash algorithm".to_string(), + )); + } + + // TODO(lucacasonato): + // If the parameters field of the maskGenAlgorithm field of params + // is not an instance of the HashAlgorithm ASN.1 type that is + // identical in content to the hashAlgorithm field of params, + // throw a NotSupportedError. + + hash + } + _ => { + return Err(custom_error( + "DOMExceptionDataError", + "Unsupported algorithm".to_string(), + )) + } + }; + + // 7. + if let Some(pk_hash) = pk_hash { + if pk_hash != hash { + return Err(custom_error( + "DOMExceptionDataError", + "Hash mismatch".to_string(), + )); + } + } + + // 8-9. + let public_key = + rsa::pkcs1::RsaPublicKey::from_der(pk_info.subject_public_key) + .map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + let bytes_consumed = public_key.encoded_len().map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + if bytes_consumed + != spki::der::Length::new(pk_info.subject_public_key.len() as u16) + { + return Err(custom_error( + "DOMExceptionDataError", + "Some bytes were not consumed".to_string(), + )); + } + + Ok(ImportKeyResult { + data: pk_info.subject_public_key.to_vec().into(), + public_exponent: Some( + public_key.public_exponent.as_bytes().to_vec().into(), + ), + modulus_length: Some(public_key.modulus.as_bytes().len() * 8), + }) + } // TODO(@littledivy): jwk _ => Err(type_error("Unsupported format".to_string())), } @@ -1417,7 +1627,7 @@ pub async fn op_crypto_import_key( // 2-3. let pk_info = rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| { - custom_error("DOMExceptionOperationError", e.to_string()) + custom_error("DOMExceptionDataError", e.to_string()) })?; // 4-5. @@ -1469,6 +1679,12 @@ pub async fn op_crypto_import_key( )); } + // TODO(lucacasonato): + // If the parameters field of the maskGenAlgorithm field of params + // is not an instance of the HashAlgorithm ASN.1 type that is + // identical in content to the hashAlgorithm field of params, + // throw a NotSupportedError. + hash } _ => { @@ -1490,17 +1706,17 @@ pub async fn op_crypto_import_key( } // 8-9. - let private_key = - rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err( - |e| custom_error("DOMExceptionOperationError", e.to_string()), - )?; + let private_key = rsa::pkcs1::RsaPrivateKey::from_der( + pk_info.private_key, + ) + .map_err(|e| custom_error("DOMExceptionDataError", e.to_string()))?; let bytes_consumed = private_key.encoded_len().map_err(|e| { custom_error("DOMExceptionDataError", e.to_string()) })?; if bytes_consumed - != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) + != spki::der::Length::new(pk_info.private_key.len() as u16) { return Err(custom_error( "DOMExceptionDataError", @@ -1516,7 +1732,120 @@ pub async fn op_crypto_import_key( modulus_length: Some(private_key.modulus.as_bytes().len() * 8), }) } - // TODO(@littledivy): spki + KeyFormat::Spki => { + let hash = args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))?; + + // 2-3. + let pk_info = + spki::SubjectPublicKeyInfo::from_der(data).map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + // 4-5. + let alg = pk_info.algorithm.oid; + + // 6. + let pk_hash = match alg { + // rsaEncryption + RSA_ENCRYPTION_OID => None, + // id-RSAES-OAEP + RSAES_OAEP_OID => { + let params = OaepPrivateKeyParameters::try_from( + pk_info.algorithm.parameters.ok_or_else(|| { + custom_error( + "DOMExceptionDataError", + "Malformed parameters".to_string(), + ) + })?, + ) + .map_err(|_| { + custom_error( + "DOMExceptionDataError", + "Malformed parameters".to_string(), + ) + })?; + + let hash_alg = params.hash_algorithm; + let hash = match hash_alg.oid { + // id-sha1 + ID_SHA1_OID => Some(CryptoHash::Sha1), + // id-sha256 + ID_SHA256_OID => Some(CryptoHash::Sha256), + // id-sha384 + ID_SHA384_OID => Some(CryptoHash::Sha384), + // id-sha256 + ID_SHA512_OID => Some(CryptoHash::Sha512), + _ => { + return Err(custom_error( + "DOMExceptionDataError", + "Unsupported hash algorithm".to_string(), + )) + } + }; + + if params.mask_gen_algorithm.oid != ID_MFG1 { + return Err(custom_error( + "DOMExceptionDataError", + "Unsupported hash algorithm".to_string(), + )); + } + + // TODO(lucacasonato): + // If the parameters field of the maskGenAlgorithm field of params + // is not an instance of the HashAlgorithm ASN.1 type that is + // identical in content to the hashAlgorithm field of params, + // throw a NotSupportedError. + + hash + } + _ => { + return Err(custom_error( + "DOMExceptionDataError", + "Unsupported algorithm".to_string(), + )) + } + }; + + // 7. + if let Some(pk_hash) = pk_hash { + if pk_hash != hash { + return Err(custom_error( + "DOMExceptionDataError", + "Hash mismatch".to_string(), + )); + } + } + + // 8-9. + let public_key = + rsa::pkcs1::RsaPublicKey::from_der(pk_info.subject_public_key) + .map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + let bytes_consumed = public_key.encoded_len().map_err(|e| { + custom_error("DOMExceptionDataError", e.to_string()) + })?; + + if bytes_consumed + != spki::der::Length::new(pk_info.subject_public_key.len() as u16) + { + return Err(custom_error( + "DOMExceptionDataError", + "Some bytes were not consumed".to_string(), + )); + } + + Ok(ImportKeyResult { + data: pk_info.subject_public_key.to_vec().into(), + public_exponent: Some( + public_key.public_exponent.as_bytes().to_vec().into(), + ), + modulus_length: Some(public_key.modulus.as_bytes().len() * 8), + }) + } // TODO(@littledivy): jwk _ => Err(type_error("Unsupported format".to_string())), } |