diff options
author | Divy Srivastava <dj.srivastava23@gmail.com> | 2021-09-14 18:51:20 +0530 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-14 15:21:20 +0200 |
commit | c41460ecc421ac7730cc5455542e5e05f7366c4a (patch) | |
tree | 18b7ac8683d6543b0610c25d42df089e2bf7d183 /ext/crypto/lib.rs | |
parent | d36b01ff6956930b51a80cd773f618b708a5f595 (diff) |
feat(ext/crypto): import RSA pkcs#8 keys (#11891)
Diffstat (limited to 'ext/crypto/lib.rs')
-rw-r--r-- | ext/crypto/lib.rs | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 47137b210..ef32499b9 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -12,8 +12,10 @@ use deno_core::Extension; use deno_core::OpState; use deno_core::ZeroCopyBuf; use serde::Deserialize; +use serde::Serialize; use std::cell::RefCell; +use std::convert::TryFrom; use std::convert::TryInto; use std::num::NonZeroU32; use std::rc::Rc; @@ -37,6 +39,8 @@ use ring::signature::EcdsaSigningAlgorithm; use ring::signature::EcdsaVerificationAlgorithm; use ring::signature::KeyPair; use rsa::padding::PaddingScheme; +use rsa::pkcs1::der::Decodable; +use rsa::pkcs1::der::Encodable; use rsa::pkcs1::FromRsaPrivateKey; use rsa::pkcs1::ToRsaPrivateKey; use rsa::pkcs8::der::asn1; @@ -66,6 +70,31 @@ lazy_static! { static ref PUB_EXPONENT_2: BigUint = BigUint::from_u64(65537).unwrap(); } +const RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1"); +const SHA1_RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.5"); +const SHA256_RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.11"); +const SHA384_RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.12"); +const SHA512_RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.13"); +const RSASSA_PSS_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.10"); +const ID_SHA1_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.3.14.3.2.26"); +const ID_SHA256_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("2.16.840.1.101.3.4.2.1"); +const ID_SHA384_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("2.16.840.1.101.3.4.2.2"); +const ID_SHA512_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("2.16.840.1.101.3.4.2.3"); +const ID_MFG1: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.8"); +const RSAES_OAEP_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.7"); + pub fn init(maybe_seed: Option<u64>) -> Extension { Extension::builder() .js(include_js_files!( @@ -82,6 +111,7 @@ pub fn init(maybe_seed: Option<u64>) -> Extension { ("op_crypto_sign_key", op_async(op_crypto_sign_key)), ("op_crypto_verify_key", op_async(op_crypto_verify_key)), ("op_crypto_derive_bits", op_async(op_crypto_derive_bits)), + ("op_crypto_import_key", op_async(op_crypto_import_key)), ("op_crypto_export_key", op_async(op_crypto_export_key)), ("op_crypto_encrypt_key", op_async(op_crypto_encrypt_key)), ("op_crypto_decrypt_key", op_async(op_crypto_decrypt_key)), @@ -817,6 +847,406 @@ pub async fn op_crypto_encrypt_key( } } +// The parameters field associated with OID id-RSASSA-PSS +// Defined in RFC 3447, section A.2.3 +// +// RSASSA-PSS-params ::= SEQUENCE { +// hashAlgorithm [0] HashAlgorithm DEFAULT sha1, +// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, +// saltLength [2] INTEGER DEFAULT 20, +// trailerField [3] TrailerField DEFAULT trailerFieldBC +// } +pub struct PssPrivateKeyParameters<'a> { + pub hash_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>, + pub mask_gen_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>, + pub salt_length: u32, +} + +// Context-specific tag number for hashAlgorithm. +const HASH_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber = + rsa::pkcs8::der::TagNumber::new(0); + +// Context-specific tag number for maskGenAlgorithm. +const MASK_GEN_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber = + rsa::pkcs8::der::TagNumber::new(1); + +// Context-specific tag number for saltLength. +const SALT_LENGTH_TAG: rsa::pkcs8::der::TagNumber = + rsa::pkcs8::der::TagNumber::new(2); + +impl<'a> TryFrom<rsa::pkcs8::der::asn1::Any<'a>> + for PssPrivateKeyParameters<'a> +{ + type Error = rsa::pkcs8::der::Error; + + fn try_from( + any: rsa::pkcs8::der::asn1::Any<'a>, + ) -> rsa::pkcs8::der::Result<PssPrivateKeyParameters> { + any.sequence(|decoder| { + let hash_algorithm = decoder + .context_specific(HASH_ALGORITHM_TAG)? + .map(TryInto::try_into) + .transpose()? + .unwrap(); + + let mask_gen_algorithm = decoder + .context_specific(MASK_GEN_ALGORITHM_TAG)? + .map(TryInto::try_into) + .transpose()? + .unwrap(); + + let salt_length = decoder + .context_specific(SALT_LENGTH_TAG)? + .map(TryInto::try_into) + .transpose()? + .unwrap_or(20); + + Ok(Self { + hash_algorithm, + mask_gen_algorithm, + salt_length, + }) + }) + } +} + +// The parameters field associated with OID id-RSAES-OAEP +// Defined in RFC 3447, section A.2.1 +// +// RSAES-OAEP-params ::= SEQUENCE { +// hashAlgorithm [0] HashAlgorithm DEFAULT sha1, +// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1, +// pSourceAlgorithm [2] PSourceAlgorithm DEFAULT pSpecifiedEmpty +// } +pub struct OaepPrivateKeyParameters<'a> { + pub hash_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>, + pub mask_gen_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>, + pub p_source_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>, +} + +impl<'a> TryFrom<rsa::pkcs8::der::asn1::Any<'a>> + for OaepPrivateKeyParameters<'a> +{ + type Error = rsa::pkcs8::der::Error; + + fn try_from( + any: rsa::pkcs8::der::asn1::Any<'a>, + ) -> rsa::pkcs8::der::Result<OaepPrivateKeyParameters> { + any.sequence(|decoder| { + let hash_algorithm = decoder.decode()?; + let mask_gen_algorithm = decoder.decode()?; + let p_source_algorithm = decoder.decode()?; + Ok(Self { + hash_algorithm, + mask_gen_algorithm, + p_source_algorithm, + }) + }) + } +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ImportKeyArg { + algorithm: Algorithm, + format: KeyFormat, + // RSASSA-PKCS1-v1_5 + hash: Option<CryptoHash>, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ImportKeyResult { + data: ZeroCopyBuf, + // RSASSA-PKCS1-v1_5 + public_exponent: Option<ZeroCopyBuf>, + modulus_length: Option<usize>, +} + +pub async fn op_crypto_import_key( + _state: Rc<RefCell<OpState>>, + args: ImportKeyArg, + zero_copy: Option<ZeroCopyBuf>, +) -> Result<ImportKeyResult, AnyError> { + let zero_copy = zero_copy.ok_or_else(null_opbuf)?; + let data = &*zero_copy; + let algorithm = args.algorithm; + + match algorithm { + Algorithm::RsassaPkcs1v15 => { + match args.format { + KeyFormat::Pkcs8 => { + let hash = args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))?; + + // 2-3. + let pk_info = + rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| { + custom_error("DOMExceptionOperationError", 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(type_error("Unsupported algorithm".to_string())), + }; + + // 7. + if let Some(pk_hash) = pk_hash { + if pk_hash != hash { + // TODO(@littledivy): DataError + return Err(type_error("Hash mismatch".to_string())); + } + } + + // 8-9. + let private_key = + rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err( + |e| custom_error("DOMExceptionOperationError", e.to_string()), + )?; + + let bytes_consumed = private_key.encoded_len().map_err(|e| { + // TODO(@littledivy): DataError + custom_error("DOMExceptionOperationError", e.to_string()) + })?; + + if bytes_consumed + != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) + { + // TODO(@littledivy): DataError + return Err(type_error("Some bytes were not consumed".to_string())); + } + + Ok(ImportKeyResult { + data: pk_info.private_key.to_vec().into(), + public_exponent: Some( + private_key.public_exponent.as_bytes().to_vec().into(), + ), + modulus_length: Some(private_key.modulus.as_bytes().len() * 8), + }) + } + // TODO(@littledivy): spki + // TODO(@littledivy): jwk + _ => Err(type_error("Unsupported format".to_string())), + } + } + Algorithm::RsaPss => { + match args.format { + KeyFormat::Pkcs8 => { + let hash = args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))?; + + // 2-3. + let pk_info = + rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| { + custom_error("DOMExceptionOperationError", 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 => { + // TODO(@littledivy): NotSupported error + let params = PssPrivateKeyParameters::try_from( + pk_info.algorithm.parameters.ok_or_else(|| { + type_error("Malformed parameters".to_string()) + })?, + ) + .map_err(|_| type_error("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), + // TODO(@littledivy): DataError + _ => { + return Err(type_error( + "Unsupported hash algorithm".to_string(), + )) + } + }; + + if params.mask_gen_algorithm.oid != ID_MFG1 { + // TODO(@littledivy): NotSupportedError + return Err(type_error( + "Unsupported hash algorithm".to_string(), + )); + } + + hash + } + // TODO(@littledivy): DataError + _ => return Err(type_error("Unsupported algorithm".to_string())), + }; + + // 7. + if let Some(pk_hash) = pk_hash { + if pk_hash != hash { + // TODO(@littledivy): DataError + return Err(type_error("Hash mismatch".to_string())); + } + } + + // 8-9. + let private_key = + rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err( + |e| custom_error("DOMExceptionOperationError", e.to_string()), + )?; + + let bytes_consumed = private_key.encoded_len().map_err(|e| { + // TODO(@littledivy): DataError + custom_error("DOMExceptionOperationError", e.to_string()) + })?; + + if bytes_consumed + != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) + { + // TODO(@littledivy): DataError + return Err(type_error("Some bytes were not consumed".to_string())); + } + + Ok(ImportKeyResult { + data: pk_info.private_key.to_vec().into(), + public_exponent: Some( + private_key.public_exponent.as_bytes().to_vec().into(), + ), + modulus_length: Some(private_key.modulus.as_bytes().len() * 8), + }) + } + // TODO(@littledivy): spki + // TODO(@littledivy): jwk + _ => Err(type_error("Unsupported format".to_string())), + } + } + Algorithm::RsaOaep => { + match args.format { + KeyFormat::Pkcs8 => { + let hash = args + .hash + .ok_or_else(|| type_error("Missing argument hash".to_string()))?; + + // 2-3. + let pk_info = + rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| { + custom_error("DOMExceptionOperationError", 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 => { + // TODO(@littledivy): NotSupported error + let params = OaepPrivateKeyParameters::try_from( + pk_info.algorithm.parameters.ok_or_else(|| { + type_error("Malformed parameters".to_string()) + })?, + ) + .map_err(|_| type_error("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), + // TODO(@littledivy): DataError + _ => { + return Err(type_error( + "Unsupported hash algorithm".to_string(), + )) + } + }; + + if params.mask_gen_algorithm.oid != ID_MFG1 { + // TODO(@littledivy): NotSupportedError + return Err(type_error( + "Unsupported hash algorithm".to_string(), + )); + } + + hash + } + // TODO(@littledivy): DataError + _ => return Err(type_error("Unsupported algorithm".to_string())), + }; + + // 7. + if let Some(pk_hash) = pk_hash { + if pk_hash != hash { + // TODO(@littledivy): DataError + return Err(type_error("Hash mismatch".to_string())); + } + } + + // 8-9. + let private_key = + rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err( + |e| custom_error("DOMExceptionOperationError", e.to_string()), + )?; + + let bytes_consumed = private_key.encoded_len().map_err(|e| { + // TODO(@littledivy): DataError + custom_error("DOMExceptionOperationError", e.to_string()) + })?; + + if bytes_consumed + != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16) + { + // TODO(@littledivy): DataError + return Err(type_error("Some bytes were not consumed".to_string())); + } + + Ok(ImportKeyResult { + data: pk_info.private_key.to_vec().into(), + public_exponent: Some( + private_key.public_exponent.as_bytes().to_vec().into(), + ), + modulus_length: Some(private_key.modulus.as_bytes().len() * 8), + }) + } + // TODO(@littledivy): spki + // TODO(@littledivy): jwk + _ => Err(type_error("Unsupported format".to_string())), + } + } + _ => Err(type_error("Unsupported algorithm".to_string())), + } +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct DecryptArg { |