summaryrefslogtreecommitdiff
path: root/ext/node/ops/crypto/mod.rs
diff options
context:
space:
mode:
authorLuca Casonato <hello@lcas.dev>2024-08-07 08:43:58 +0200
committerGitHub <noreply@github.com>2024-08-07 08:43:58 +0200
commit4fa8869f2487749a9f190cb3047f4f3e6d571f27 (patch)
tree640c13e45e0bf1c63340c15f64b08b614ddcf120 /ext/node/ops/crypto/mod.rs
parent9a83efa04b6e733ca0fdbf9e780c4b77f0d9f4be (diff)
feat(ext/node): rewrite crypto keys (#24463)
This completely rewrites how we handle key material in ext/node. Changes in this PR: - **Signing** - RSA - RSA-PSS 🆕 - DSA 🆕 - EC - ED25519 🆕 - **Verifying** - RSA - RSA-PSS 🆕 - DSA 🆕 - EC 🆕 - ED25519 🆕 - **Private key import** - Passphrase encrypted private keys 🆕 - RSA - PEM - DER (PKCS#1) 🆕 - DER (PKCS#8) 🆕 - RSA-PSS - PEM - DER (PKCS#1) 🆕 - DER (PKCS#8) 🆕 - DSA 🆕 - EC - PEM - DER (SEC1) 🆕 - DER (PKCS#8) 🆕 - X25519 🆕 - ED25519 🆕 - DH - **Public key import** - RSA - PEM - DER (PKCS#1) 🆕 - DER (PKCS#8) 🆕 - RSA-PSS 🆕 - DSA 🆕 - EC 🆕 - X25519 🆕 - ED25519 🆕 - DH 🆕 - **Private key export** - RSA 🆕 - DSA 🆕 - EC 🆕 - X25519 🆕 - ED25519 🆕 - DH 🆕 - **Public key export** - RSA - DSA 🆕 - EC 🆕 - X25519 🆕 - ED25519 🆕 - DH 🆕 - **Key pair generation** - Overhauled, but supported APIs unchanged This PR adds a lot of new individual functionality. But most importantly because of the new key material representation, it is now trivial to add new algorithms (as shown by this PR). Now, when adding a new algorithm, it is also widely supported - for example previously we supported ED25519 key pair generation, but we could not import, export, sign or verify with ED25519. We can now do all of those things.
Diffstat (limited to 'ext/node/ops/crypto/mod.rs')
-rw-r--r--ext/node/ops/crypto/mod.rs882
1 files changed, 22 insertions, 860 deletions
diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs
index 1b545e024..07d901cbc 100644
--- a/ext/node/ops/crypto/mod.rs
+++ b/ext/node/ops/crypto/mod.rs
@@ -3,7 +3,6 @@ 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;
@@ -11,21 +10,12 @@ use deno_core::StringOrBuffer;
use deno_core::ToJsBuffer;
use elliptic_curve::sec1::ToEncodedPoint;
use hkdf::Hkdf;
+use keys::KeyObjectHandle;
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::pkcs1::DecodeRsaPrivateKey;
-use rsa::pkcs1::DecodeRsaPublicKey;
-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;
@@ -34,24 +24,21 @@ use p256::NistP256;
use p384::NistP384;
use rsa::pkcs8::DecodePrivateKey;
use rsa::pkcs8::DecodePublicKey;
-use rsa::signature::hazmat::PrehashSigner;
-use rsa::signature::hazmat::PrehashVerifier;
-use rsa::signature::SignatureEncoding;
use rsa::Oaep;
use rsa::Pkcs1v15Encrypt;
use rsa::RsaPrivateKey;
use rsa::RsaPublicKey;
-use spki::EncodePublicKey;
mod cipher;
mod dh;
mod digest;
+pub mod keys;
mod md5_sha1;
mod primes;
+mod sign;
pub mod x509;
use self::digest::match_fixed_digest_with_eager_block_buffer;
-use self::digest::match_fixed_digest_with_oid;
#[op2(fast)]
pub fn op_node_check_prime(
@@ -339,156 +326,23 @@ pub fn op_node_decipheriv_final(
}
#[op2]
-#[serde]
+#[buffer]
pub fn op_node_sign(
+ #[cppgc] handle: &KeyObjectHandle,
#[buffer] digest: &[u8],
#[string] digest_type: &str,
- #[serde] key: StringOrBuffer,
- #[string] _type: &str,
- #[string] format: &str,
-) -> Result<ToJsBuffer, AnyError> {
- let (label, doc) =
- pkcs8::SecretDocument::from_pem(std::str::from_utf8(&key).unwrap())?;
-
- let oid;
- let pkey = match format {
- "pem" => match label {
- "PRIVATE KEY" => {
- let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
- oid = pk_info.algorithm.oid;
- pk_info.private_key
- }
- "RSA PRIVATE KEY" => {
- oid = RSA_ENCRYPTION_OID;
- doc.as_bytes()
- }
- "EC PRIVATE KEY" => {
- let ec_pk = sec1::EcPrivateKey::from_der(doc.as_bytes())?;
- match ec_pk.parameters {
- Some(sec1::EcParameters::NamedCurve(o)) => {
- oid = o;
- ec_pk.private_key
- }
- // https://datatracker.ietf.org/doc/html/rfc5915#section-3
- //
- // Though the ASN.1 indicates that
- // the parameters field is OPTIONAL, implementations that conform to
- // this document MUST always include the parameters field.
- _ => return Err(type_error("invalid ECPrivateKey params")),
- }
- }
- _ => return Err(type_error("Invalid PEM label")),
- },
- _ => return Err(type_error("Unsupported key format")),
- };
-
- match oid {
- RSA_ENCRYPTION_OID => {
- use rsa::pkcs1v15::SigningKey;
- let key = RsaPrivateKey::from_pkcs1_der(pkey)?;
-
- // md5-sha1 is special, because it's not wrapped in an ASN.1 DigestInfo
- // (so has no prefix).
- // See https://github.com/openssl/openssl/blob/af82623d32962b3eff5b0f0b0dedec5eb730b231/crypto/rsa/rsa_sign.c#L285
- if digest_type == "md5-sha1" {
- let signing_key = SigningKey::<md5_sha1::Md5Sha1>::new_unprefixed(key);
- let signature = signing_key.sign_prehash(digest)?.to_vec();
- return Ok(signature.into());
- }
-
- let signature = match_fixed_digest_with_oid!(
- digest_type,
- fn <D>() {
- let signing_key = SigningKey::<D>::new(key);
- signing_key.sign_prehash(digest)?.to_vec()
- },
- _ => {
- return Err(type_error(format!(
- "digest not allowed for RSA signature: {}",
- digest_type
- )))
- }
- );
- Ok(signature.into())
- }
- // signature structure encoding is DER by default for DSA and ECDSA.
- //
- // TODO(@littledivy): Validate public_key if present
- ID_SECP256R1_OID => {
- let key = p256::ecdsa::SigningKey::from_slice(pkey)?;
- Ok(
- key
- .sign_prehash(digest)
- .map(|sig: p256::ecdsa::Signature| sig.to_der().to_vec().into())?,
- )
- }
- ID_SECP384R1_OID => {
- let key = p384::ecdsa::SigningKey::from_slice(pkey)?;
- Ok(
- key
- .sign_prehash(digest)
- .map(|sig: p384::ecdsa::Signature| sig.to_der().to_vec().into())?,
- )
- }
- _ => Err(type_error("Unsupported signing key")),
- }
+) -> Result<Box<[u8]>, AnyError> {
+ handle.sign_prehashed(digest_type, digest)
}
-#[op2]
+#[op2(fast)]
pub fn op_node_verify(
+ #[cppgc] handle: &KeyObjectHandle,
#[buffer] digest: &[u8],
#[string] digest_type: &str,
- #[serde] key: StringOrBuffer,
- #[string] key_type: &str,
- #[string] key_format: &str,
#[buffer] signature: &[u8],
) -> Result<bool, AnyError> {
- match key_type {
- "rsa" => {
- use rsa::pkcs1v15::VerifyingKey;
- let key = match key_format {
- "pem" => RsaPublicKey::from_public_key_pem((&key).try_into()?)
- .map_err(|_| type_error("Invalid RSA public key"))?,
- // TODO(kt3k): Support der and jwk formats
- _ => {
- return Err(type_error(format!(
- "Unsupported key format: {}",
- key_format
- )))
- }
- };
-
- // md5-sha1 is special, because it's not wrapped in an ASN.1 DigestInfo
- // (so has no prefix).
- // See https://github.com/openssl/openssl/blob/af82623d32962b3eff5b0f0b0dedec5eb730b231/crypto/rsa/rsa_sign.c#L285
- if digest_type == "md5-sha1" {
- let verifying_key =
- VerifyingKey::<md5_sha1::Md5Sha1>::new_unprefixed(key);
- let verified = verifying_key
- .verify_prehash(digest, &signature.try_into()?)
- .is_ok();
- return Ok(verified);
- }
-
- Ok(match_fixed_digest_with_oid!(
- digest_type,
- fn <D>() {
- let verifying_key = VerifyingKey::<D>::new(key);
- verifying_key.verify_prehash(digest, &signature.try_into()?).is_ok()
- },
- _ => {
- return Err(type_error(format!(
- "digest not allowed for RSA signature: {}",
- digest_type
- )))
- }
- ))
- }
- _ => Err(type_error(format!(
- "Verifying with {} keys is not supported",
- key_type
- ))),
- }
+ handle.verify_prehashed(digest_type, digest, signature)
}
fn pbkdf2_sync(
@@ -542,13 +396,13 @@ pub async fn op_node_pbkdf2_async(
}
#[op2(fast)]
-pub fn op_node_generate_secret(#[buffer] buf: &mut [u8]) {
+pub fn op_node_fill_random(#[buffer] buf: &mut [u8]) {
rand::thread_rng().fill(buf);
}
#[op2(async)]
#[serde]
-pub async fn op_node_generate_secret_async(#[smi] len: i32) -> ToJsBuffer {
+pub async fn op_node_fill_random_async(#[smi] len: i32) -> ToJsBuffer {
spawn_blocking(move || {
let mut buf = vec![0u8; len as usize];
rand::thread_rng().fill(&mut buf[..]);
@@ -560,11 +414,15 @@ pub async fn op_node_generate_secret_async(#[smi] len: i32) -> ToJsBuffer {
fn hkdf_sync(
digest_algorithm: &str,
- ikm: &[u8],
+ handle: &KeyObjectHandle,
salt: &[u8],
info: &[u8],
okm: &mut [u8],
) -> Result<(), AnyError> {
+ let Some(ikm) = handle.as_secret_key() else {
+ return Err(type_error("expected secret key"));
+ };
+
match_fixed_digest_with_eager_block_buffer!(
digest_algorithm,
fn <D>() {
@@ -581,328 +439,32 @@ fn hkdf_sync(
#[op2(fast)]
pub fn op_node_hkdf(
#[string] digest_algorithm: &str,
- #[buffer] ikm: &[u8],
+ #[cppgc] handle: &KeyObjectHandle,
#[buffer] salt: &[u8],
#[buffer] info: &[u8],
#[buffer] okm: &mut [u8],
) -> Result<(), AnyError> {
- hkdf_sync(digest_algorithm, ikm, salt, info, okm)
+ hkdf_sync(digest_algorithm, handle, salt, info, okm)
}
#[op2(async)]
#[serde]
pub async fn op_node_hkdf_async(
#[string] digest_algorithm: String,
- #[buffer] ikm: JsBuffer,
+ #[cppgc] handle: &KeyObjectHandle,
#[buffer] salt: JsBuffer,
#[buffer] info: JsBuffer,
#[number] okm_len: usize,
) -> Result<ToJsBuffer, AnyError> {
+ let handle = handle.clone();
spawn_blocking(move || {
let mut okm = vec![0u8; okm_len];
- hkdf_sync(&digest_algorithm, &ikm, &salt, &info, &mut okm)?;
+ hkdf_sync(&digest_algorithm, &handle, &salt, &info, &mut okm)?;
Ok(okm.into())
})
.await?
}
-use rsa::pkcs1::EncodeRsaPrivateKey;
-use rsa::pkcs1::EncodeRsaPublicKey;
-
-use self::primes::Prime;
-
-fn generate_rsa(
- modulus_length: usize,
- public_exponent: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- let mut rng = rand::thread_rng();
- let private_key = RsaPrivateKey::new_with_exp(
- &mut rng,
- modulus_length,
- &rsa::BigUint::from_usize(public_exponent).unwrap(),
- )?;
- let public_key = private_key.to_public_key();
- let private_key_der = private_key.to_pkcs1_der()?.as_bytes().to_vec();
- let public_key_der = public_key.to_pkcs1_der()?.to_vec();
-
- Ok((private_key_der.into(), public_key_der.into()))
-}
-
-#[op2]
-#[serde]
-pub fn op_node_generate_rsa(
- #[number] modulus_length: usize,
- #[number] public_exponent: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- generate_rsa(modulus_length, public_exponent)
-}
-
-#[op2(async)]
-#[serde]
-pub async fn op_node_generate_rsa_async(
- #[number] modulus_length: usize,
- #[number] public_exponent: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- spawn_blocking(move || generate_rsa(modulus_length, public_exponent)).await?
-}
-
-#[op2]
-#[string]
-pub fn op_node_export_rsa_public_pem(
- #[buffer] pkcs1_der: &[u8],
-) -> Result<String, AnyError> {
- let public_key = RsaPublicKey::from_pkcs1_der(pkcs1_der)?;
- let export = public_key.to_public_key_pem(Default::default())?;
- Ok(export)
-}
-
-#[op2]
-#[serde]
-pub fn op_node_export_rsa_spki_der(
- #[buffer] pkcs1_der: &[u8],
-) -> Result<ToJsBuffer, AnyError> {
- let public_key = RsaPublicKey::from_pkcs1_der(pkcs1_der)?;
- let export = public_key.to_public_key_der()?.to_vec();
- Ok(export.into())
-}
-
-fn dsa_generate(
- modulus_length: usize,
- divisor_length: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- let mut rng = rand::thread_rng();
- use dsa::pkcs8::EncodePrivateKey;
- use dsa::Components;
- use dsa::KeySize;
- use dsa::SigningKey;
-
- let key_size = match (modulus_length, divisor_length) {
- #[allow(deprecated)]
- (1024, 160) => KeySize::DSA_1024_160,
- (2048, 224) => KeySize::DSA_2048_224,
- (2048, 256) => KeySize::DSA_2048_256,
- (3072, 256) => KeySize::DSA_3072_256,
- _ => return Err(type_error("Invalid modulus_length or divisor_length")),
- };
- let components = Components::generate(&mut rng, key_size);
- let signing_key = SigningKey::generate(&mut rng, components);
- let verifying_key = signing_key.verifying_key();
-
- Ok((
- signing_key
- .to_pkcs8_der()
- .map_err(|_| type_error("Not valid pkcs8"))?
- .as_bytes()
- .to_vec()
- .into(),
- verifying_key
- .to_public_key_der()
- .map_err(|_| type_error("Not valid spki"))?
- .to_vec()
- .into(),
- ))
-}
-
-#[op2]
-#[serde]
-pub fn op_node_dsa_generate(
- #[number] modulus_length: usize,
- #[number] divisor_length: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- dsa_generate(modulus_length, divisor_length)
-}
-
-#[op2(async)]
-#[serde]
-pub async fn op_node_dsa_generate_async(
- #[number] modulus_length: usize,
- #[number] divisor_length: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- spawn_blocking(move || dsa_generate(modulus_length, divisor_length)).await?
-}
-
-fn ec_generate(
- named_curve: &str,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- let mut rng = rand::thread_rng();
- // TODO(@littledivy): Support public key point encoding.
- // Default is uncompressed.
- match named_curve {
- "P-256" | "prime256v1" | "secp256r1" => {
- let key = p256::SecretKey::random(&mut rng);
- let public_key = key.public_key();
-
- Ok((
- key.to_bytes().to_vec().into(),
- public_key.to_encoded_point(false).as_ref().to_vec().into(),
- ))
- }
- "P-384" | "prime384v1" | "secp384r1" => {
- let key = p384::SecretKey::random(&mut rng);
- let public_key = key.public_key();
-
- Ok((
- key.to_bytes().to_vec().into(),
- public_key.to_encoded_point(false).as_ref().to_vec().into(),
- ))
- }
- _ => Err(type_error("Unsupported named curve")),
- }
-}
-
-#[op2]
-#[serde]
-pub fn op_node_ec_generate(
- #[string] named_curve: &str,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- ec_generate(named_curve)
-}
-
-#[op2(async)]
-#[serde]
-pub async fn op_node_ec_generate_async(
- #[string] named_curve: String,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- spawn_blocking(move || ec_generate(&named_curve)).await?
-}
-
-fn ed25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- use ring::signature::Ed25519KeyPair;
- use ring::signature::KeyPair;
-
- let mut rng = thread_rng();
- let mut seed = vec![0u8; 32];
- rng.fill(seed.as_mut_slice());
-
- let pair = Ed25519KeyPair::from_seed_unchecked(&seed)
- .map_err(|_| type_error("Failed to generate Ed25519 key"))?;
-
- let public_key = pair.public_key().as_ref().to_vec();
- Ok((seed.into(), public_key.into()))
-}
-
-#[op2]
-#[serde]
-pub fn op_node_ed25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError>
-{
- ed25519_generate()
-}
-
-#[op2(async)]
-#[serde]
-pub async fn op_node_ed25519_generate_async(
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- spawn_blocking(ed25519_generate).await?
-}
-
-fn x25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- // u-coordinate of the base point.
- const X25519_BASEPOINT_BYTES: [u8; 32] = [
- 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0,
- ];
-
- let mut pkey = [0; 32];
-
- let mut rng = thread_rng();
- rng.fill(pkey.as_mut_slice());
-
- let pkey_copy = pkey.to_vec();
- // https://www.rfc-editor.org/rfc/rfc7748#section-6.1
- // pubkey = x25519(a, 9) which is constant-time Montgomery ladder.
- // https://eprint.iacr.org/2014/140.pdf page 4
- // https://eprint.iacr.org/2017/212.pdf algorithm 8
- // pubkey is in LE order.
- let pubkey = x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES);
-
- Ok((pkey_copy.into(), pubkey.to_vec().into()))
-}
-
-#[op2]
-#[serde]
-pub fn op_node_x25519_generate() -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- x25519_generate()
-}
-
-#[op2(async)]
-#[serde]
-pub async fn op_node_x25519_generate_async(
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- spawn_blocking(x25519_generate).await?
-}
-
-fn dh_generate_group(
- group_name: &str,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- let dh = match group_name {
- "modp5" => dh::DiffieHellman::group::<dh::Modp1536>(),
- "modp14" => dh::DiffieHellman::group::<dh::Modp2048>(),
- "modp15" => dh::DiffieHellman::group::<dh::Modp3072>(),
- "modp16" => dh::DiffieHellman::group::<dh::Modp4096>(),
- "modp17" => dh::DiffieHellman::group::<dh::Modp6144>(),
- "modp18" => dh::DiffieHellman::group::<dh::Modp8192>(),
- _ => return Err(type_error("Unsupported group name")),
- };
-
- Ok((
- dh.private_key.into_vec().into(),
- dh.public_key.into_vec().into(),
- ))
-}
-
-#[op2]
-#[serde]
-pub fn op_node_dh_generate_group(
- #[string] group_name: &str,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- dh_generate_group(group_name)
-}
-
-#[op2(async)]
-#[serde]
-pub async fn op_node_dh_generate_group_async(
- #[string] group_name: String,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- spawn_blocking(move || dh_generate_group(&group_name)).await?
-}
-
-fn dh_generate(
- prime: Option<&[u8]>,
- prime_len: usize,
- generator: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- let prime = prime
- .map(|p| p.into())
- .unwrap_or_else(|| Prime::generate(prime_len));
- let dh = dh::DiffieHellman::new(prime, generator);
-
- Ok((
- dh.private_key.into_vec().into(),
- dh.public_key.into_vec().into(),
- ))
-}
-
-#[op2]
-#[serde]
-pub fn op_node_dh_generate(
- #[serde] prime: Option<&[u8]>,
- #[number] prime_len: usize,
- #[number] generator: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- dh_generate(prime, prime_len, generator)
-}
-
-// TODO(lev): This duplication should be avoided.
-#[op2]
-#[serde]
-pub fn op_node_dh_generate2(
- #[buffer] prime: JsBuffer,
- #[number] prime_len: usize,
- #[number] generator: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- dh_generate(Some(prime).as_deref(), prime_len, generator)
-}
-
#[op2]
#[serde]
pub fn op_node_dh_compute_secret(
@@ -918,17 +480,6 @@ pub fn op_node_dh_compute_secret(
Ok(shared_secret.to_bytes_be().into())
}
-#[op2(async)]
-#[serde]
-pub async fn op_node_dh_generate_async(
- #[buffer] prime: Option<JsBuffer>,
- #[number] prime_len: usize,
- #[number] generator: usize,
-) -> Result<(ToJsBuffer, ToJsBuffer), AnyError> {
- spawn_blocking(move || dh_generate(prime.as_deref(), prime_len, generator))
- .await?
-}
-
#[op2(fast)]
#[smi]
pub fn op_node_random_int(
@@ -1288,392 +839,3 @@ 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 },
- #[serde(rename = "dh")]
- Dh,
-}
-
-// 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 DH_KEY_AGREEMENT_OID: const_oid::ObjectIdentifier =
- const_oid::ObjectIdentifier::new_unwrap("1.2.840.113549.1.3.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>,
- #[allow(dead_code)]
- 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> {
- match format {
- "pem" => {
- let pem = std::str::from_utf8(key).map_err(|err| {
- type_error(format!(
- "Invalid PEM private key: not valid utf8 starting at byte {}",
- err.valid_up_to()
- ))
- })?;
- let (_, doc) = pkcs8::SecretDocument::from_pem(pem)?;
- 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(),
- })
- }
- DH_KEY_AGREEMENT_OID => Ok(AsymmetricKeyDetails::Dh),
- 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")),
- }
-}
-
-fn parse_public_key(
- key: &[u8],
- format: &str,
- type_: &str,
-) -> Result<pkcs8::Document, AnyError> {
- match format {
- "pem" => {
- let pem = std::str::from_utf8(key).map_err(|err| {
- type_error(format!(
- "Invalid PEM private key: not valid utf8 starting at byte {}",
- err.valid_up_to()
- ))
- })?;
- let (label, doc) = pkcs8::Document::from_pem(pem)?;
- if label != "PUBLIC KEY" {
- return Err(type_error("Invalid PEM label"));
- }
- Ok(doc)
- }
- "der" => match type_ {
- "pkcs1" => pkcs8::Document::from_pkcs1_der(key)
- .map_err(|_| type_error("Invalid PKCS1 public key")),
- _ => Err(type_error(format!("Unsupported key type: {}", type_))),
- },
- _ => Err(type_error(format!("Unsupported key format: {}", format))),
- }
-}
-
-#[op2]
-#[serde]
-pub fn op_node_create_public_key(
- #[buffer] key: &[u8],
- #[string] format: &str,
- #[string] type_: &str,
-) -> Result<AsymmetricKeyDetails, AnyError> {
- let mut doc = None;
-
- let pk_info = if type_ != "spki" {
- doc.replace(parse_public_key(key, format, type_)?);
- spki::SubjectPublicKeyInfoRef::try_from(doc.as_ref().unwrap().as_bytes())?
- } else {
- spki::SubjectPublicKeyInfoRef::try_from(key)?
- };
-
- let alg = pk_info.algorithm.oid;
-
- match alg {
- RSA_ENCRYPTION_OID => {
- let public_key = rsa::pkcs1::RsaPublicKey::from_der(
- pk_info.subject_public_key.raw_bytes(),
- )?;
- let modulus_length = public_key.modulus.as_bytes().len() * 8;
-
- Ok(AsymmetricKeyDetails::Rsa {
- modulus_length,
- public_exponent: BigInt::from_bytes_be(
- num_bigint::Sign::Plus,
- public_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 public_key = rsa::pkcs1::RsaPublicKey::from_der(
- pk_info.subject_public_key.raw_bytes(),
- )?;
- let modulus_length = public_key.modulus.as_bytes().len() * 8;
- Ok(AsymmetricKeyDetails::RsaPss {
- modulus_length,
- public_exponent: BigInt::from_bytes_be(
- num_bigint::Sign::Plus,
- public_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(),
- })
- }
- DH_KEY_AGREEMENT_OID => Ok(AsymmetricKeyDetails::Dh),
- _ => Err(type_error("Unsupported algorithm")),
- }
-}