summaryrefslogtreecommitdiff
path: root/ext/node/ops/crypto/mod.rs
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2023-11-09 09:56:59 -0800
committerGitHub <noreply@github.com>2023-11-09 23:26:59 +0530
commitc4029f6af22b373bf22383453cb4e2159f3b5b72 (patch)
tree588594d5d5eb98689101565fb76c6a546cc9e78a /ext/node/ops/crypto/mod.rs
parentee7fd0a2127e6a435023bf9a43de60130117697d (diff)
fix(node): implement createPrivateKey (#20981)
Towards #18455
Diffstat (limited to 'ext/node/ops/crypto/mod.rs')
-rw-r--r--ext/node/ops/crypto/mod.rs274
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")),
+ }
+}