diff options
Diffstat (limited to 'ext/node/ops/crypto/mod.rs')
-rw-r--r-- | ext/node/ops/crypto/mod.rs | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs index b81eb97f1..35940da77 100644 --- a/ext/node/ops/crypto/mod.rs +++ b/ext/node/ops/crypto/mod.rs @@ -3,6 +3,7 @@ use deno_core::error::generic_error; use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::op2; +use deno_core::serde_v8::BigInt as V8BigInt; use deno_core::unsync::spawn_blocking; use deno_core::JsBuffer; use deno_core::OpState; @@ -13,10 +14,16 @@ use hkdf::Hkdf; use num_bigint::BigInt; use num_bigint_dig::BigUint; use num_traits::FromPrimitive; +use once_cell::sync::Lazy; use rand::distributions::Distribution; use rand::distributions::Uniform; use rand::thread_rng; use rand::Rng; +use rsa::pkcs8; +use rsa::pkcs8::der::asn1; +use rsa::pkcs8::der::Decode; +use rsa::pkcs8::der::Encode; +use rsa::pkcs8::der::Reader; use std::future::Future; use std::rc::Rc; @@ -1173,3 +1180,270 @@ pub async fn op_node_gen_prime_async( ) -> Result<ToJsBuffer, AnyError> { Ok(spawn_blocking(move || gen_prime(size)).await?) } + +#[derive(serde::Serialize)] +#[serde(tag = "type")] +pub enum AsymmetricKeyDetails { + #[serde(rename = "rsa")] + #[serde(rename_all = "camelCase")] + Rsa { + modulus_length: usize, + public_exponent: V8BigInt, + }, + #[serde(rename = "rsa-pss")] + #[serde(rename_all = "camelCase")] + RsaPss { + modulus_length: usize, + public_exponent: V8BigInt, + hash_algorithm: String, + salt_length: u32, + }, + #[serde(rename = "ec")] + #[serde(rename_all = "camelCase")] + Ec { named_curve: String }, +} + +// https://oidref.com/ +const ID_SHA1_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("1.3.14.3.2.26"); +const ID_SHA256_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1"); +const ID_SHA384_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.2"); +const ID_SHA512_OID: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.3"); +const ID_MFG1: rsa::pkcs8::ObjectIdentifier = + rsa::pkcs8::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.8"); +pub const ID_SECP256R1_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); +pub const ID_SECP384R1_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.34"); +pub const ID_SECP521R1_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.3.132.0.35"); + +// Default HashAlgorithm for RSASSA-PSS-params (sha1) +// +// sha1 HashAlgorithm ::= { +// algorithm id-sha1, +// parameters SHA1Parameters : NULL +// } +// +// SHA1Parameters ::= NULL +static SHA1_HASH_ALGORITHM: Lazy<rsa::pkcs8::AlgorithmIdentifierRef<'static>> = + Lazy::new(|| rsa::pkcs8::AlgorithmIdentifierRef { + // id-sha1 + oid: ID_SHA1_OID, + // NULL + parameters: Some(asn1::AnyRef::from(asn1::Null)), + }); + +// TODO(@littledivy): `pkcs8` should provide AlgorithmIdentifier to Any conversion. +static ENCODED_SHA1_HASH_ALGORITHM: Lazy<Vec<u8>> = + Lazy::new(|| SHA1_HASH_ALGORITHM.to_der().unwrap()); + +// Default MaskGenAlgrithm for RSASSA-PSS-params (mgf1SHA1) +// +// mgf1SHA1 MaskGenAlgorithm ::= { +// algorithm id-mgf1, +// parameters HashAlgorithm : sha1 +// } +static MGF1_SHA1_MASK_ALGORITHM: Lazy< + rsa::pkcs8::AlgorithmIdentifierRef<'static>, +> = Lazy::new(|| rsa::pkcs8::AlgorithmIdentifierRef { + // id-mgf1 + oid: ID_MFG1, + // sha1 + parameters: Some( + asn1::AnyRef::from_der(&ENCODED_SHA1_HASH_ALGORITHM).unwrap(), + ), +}); + +pub const RSA_ENCRYPTION_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1"); +pub const RSASSA_PSS_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10"); +pub const EC_OID: const_oid::ObjectIdentifier = + const_oid::ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); + +// 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::AlgorithmIdentifierRef<'a>, + pub mask_gen_algorithm: rsa::pkcs8::AlgorithmIdentifierRef<'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::AnyRef<'a>> + for PssPrivateKeyParameters<'a> +{ + type Error = rsa::pkcs8::der::Error; + + fn try_from( + any: rsa::pkcs8::der::asn1::AnyRef<'a>, + ) -> rsa::pkcs8::der::Result<PssPrivateKeyParameters> { + any.sequence(|decoder| { + let hash_algorithm = decoder + .context_specific::<rsa::pkcs8::AlgorithmIdentifierRef>( + HASH_ALGORITHM_TAG, + pkcs8::der::TagMode::Explicit, + )? + .map(TryInto::try_into) + .transpose()? + .unwrap_or(*SHA1_HASH_ALGORITHM); + + let mask_gen_algorithm = decoder + .context_specific::<rsa::pkcs8::AlgorithmIdentifierRef>( + MASK_GEN_ALGORITHM_TAG, + pkcs8::der::TagMode::Explicit, + )? + .map(TryInto::try_into) + .transpose()? + .unwrap_or(*MGF1_SHA1_MASK_ALGORITHM); + + let salt_length = decoder + .context_specific::<u32>( + SALT_LENGTH_TAG, + pkcs8::der::TagMode::Explicit, + )? + .map(TryInto::try_into) + .transpose()? + .unwrap_or(20); + + Ok(Self { + hash_algorithm, + mask_gen_algorithm, + salt_length, + }) + }) + } +} + +fn parse_private_key( + key: &[u8], + format: &str, + type_: &str, +) -> Result<pkcs8::SecretDocument, AnyError> { + use rsa::pkcs1::DecodeRsaPrivateKey; + + match format { + "pem" => { + let (label, doc) = + pkcs8::SecretDocument::from_pem(std::str::from_utf8(key).unwrap())?; + if label != "PRIVATE KEY" { + return Err(type_error("Invalid PEM label")); + } + Ok(doc) + } + "der" => { + match type_ { + "pkcs8" => pkcs8::SecretDocument::from_pkcs8_der(key) + .map_err(|_| type_error("Invalid PKCS8 private key")), + "pkcs1" => pkcs8::SecretDocument::from_pkcs1_der(key) + .map_err(|_| type_error("Invalid PKCS1 private key")), + // TODO(@littledivy): sec1 type + _ => Err(type_error(format!("Unsupported key type: {}", type_))), + } + } + _ => Err(type_error(format!("Unsupported key format: {}", format))), + } +} + +#[op2] +#[serde] +pub fn op_node_create_private_key( + #[buffer] key: &[u8], + #[string] format: &str, + #[string] type_: &str, +) -> Result<AsymmetricKeyDetails, AnyError> { + use rsa::pkcs1::der::Decode; + + let doc = parse_private_key(key, format, type_)?; + let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?; + + let alg = pk_info.algorithm.oid; + + match alg { + RSA_ENCRYPTION_OID => { + let private_key = + rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?; + let modulus_length = private_key.modulus.as_bytes().len() * 8; + + Ok(AsymmetricKeyDetails::Rsa { + modulus_length, + public_exponent: BigInt::from_bytes_be( + num_bigint::Sign::Plus, + private_key.public_exponent.as_bytes(), + ) + .into(), + }) + } + RSASSA_PSS_OID => { + 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_algorithm = match hash_alg.oid { + ID_SHA1_OID => "sha1", + ID_SHA256_OID => "sha256", + ID_SHA384_OID => "sha384", + ID_SHA512_OID => "sha512", + _ => return Err(type_error("Unsupported hash algorithm")), + }; + + let private_key = + rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key)?; + let modulus_length = private_key.modulus.as_bytes().len() * 8; + Ok(AsymmetricKeyDetails::RsaPss { + modulus_length, + public_exponent: BigInt::from_bytes_be( + num_bigint::Sign::Plus, + private_key.public_exponent.as_bytes(), + ) + .into(), + hash_algorithm: hash_algorithm.to_string(), + salt_length: params.salt_length, + }) + } + EC_OID => { + let named_curve = pk_info + .algorithm + .parameters_oid() + .map_err(|_| type_error("malformed parameters"))?; + let named_curve = match named_curve { + ID_SECP256R1_OID => "p256", + ID_SECP384R1_OID => "p384", + ID_SECP521R1_OID => "p521", + _ => return Err(type_error("Unsupported named curve")), + }; + + Ok(AsymmetricKeyDetails::Ec { + named_curve: named_curve.to_string(), + }) + } + _ => Err(type_error("Unsupported algorithm")), + } +} |