mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
780 lines
23 KiB
Rust
780 lines
23 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use aes_kw::KekAes128;
|
|
use aes_kw::KekAes192;
|
|
use aes_kw::KekAes256;
|
|
|
|
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
|
|
use base64::Engine;
|
|
use deno_core::error::custom_error;
|
|
use deno_core::error::not_supported;
|
|
use deno_core::error::type_error;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::op2;
|
|
use deno_core::ToJsBuffer;
|
|
|
|
use deno_core::unsync::spawn_blocking;
|
|
use deno_core::JsBuffer;
|
|
use deno_core::OpState;
|
|
use serde::Deserialize;
|
|
use shared::operation_error;
|
|
|
|
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
|
use p256::pkcs8::DecodePrivateKey;
|
|
use rand::rngs::OsRng;
|
|
use rand::rngs::StdRng;
|
|
use rand::thread_rng;
|
|
use rand::Rng;
|
|
use rand::SeedableRng;
|
|
use ring::digest;
|
|
use ring::hkdf;
|
|
use ring::hmac::Algorithm as HmacAlgorithm;
|
|
use ring::hmac::Key as HmacKey;
|
|
use ring::pbkdf2;
|
|
use ring::rand as RingRand;
|
|
use ring::signature::EcdsaKeyPair;
|
|
use ring::signature::EcdsaSigningAlgorithm;
|
|
use ring::signature::EcdsaVerificationAlgorithm;
|
|
use ring::signature::KeyPair;
|
|
use rsa::pkcs1::DecodeRsaPrivateKey;
|
|
use rsa::pkcs1::DecodeRsaPublicKey;
|
|
use rsa::signature::SignatureEncoding;
|
|
use rsa::signature::Signer;
|
|
use rsa::signature::Verifier;
|
|
use rsa::traits::SignatureScheme;
|
|
use rsa::Pss;
|
|
use rsa::RsaPrivateKey;
|
|
use rsa::RsaPublicKey;
|
|
use sha1::Sha1;
|
|
use sha2::Digest;
|
|
use sha2::Sha256;
|
|
use sha2::Sha384;
|
|
use sha2::Sha512;
|
|
use std::num::NonZeroU32;
|
|
use std::path::PathBuf;
|
|
|
|
pub use rand; // Re-export rand
|
|
|
|
mod decrypt;
|
|
mod ed25519;
|
|
mod encrypt;
|
|
mod export_key;
|
|
mod generate_key;
|
|
mod import_key;
|
|
mod key;
|
|
mod shared;
|
|
mod x25519;
|
|
|
|
pub use crate::decrypt::op_crypto_decrypt;
|
|
pub use crate::encrypt::op_crypto_encrypt;
|
|
pub use crate::export_key::op_crypto_export_key;
|
|
pub use crate::generate_key::op_crypto_generate_key;
|
|
pub use crate::import_key::op_crypto_import_key;
|
|
use crate::key::Algorithm;
|
|
use crate::key::CryptoHash;
|
|
use crate::key::CryptoNamedCurve;
|
|
use crate::key::HkdfOutput;
|
|
use crate::shared::V8RawKeyData;
|
|
|
|
deno_core::extension!(deno_crypto,
|
|
deps = [ deno_webidl, deno_web ],
|
|
ops = [
|
|
op_crypto_get_random_values,
|
|
op_crypto_generate_key,
|
|
op_crypto_sign_key,
|
|
op_crypto_verify_key,
|
|
op_crypto_derive_bits,
|
|
op_crypto_import_key,
|
|
op_crypto_export_key,
|
|
op_crypto_encrypt,
|
|
op_crypto_decrypt,
|
|
op_crypto_subtle_digest,
|
|
op_crypto_random_uuid,
|
|
op_crypto_wrap_key,
|
|
op_crypto_unwrap_key,
|
|
op_crypto_base64url_decode,
|
|
op_crypto_base64url_encode,
|
|
x25519::op_crypto_generate_x25519_keypair,
|
|
x25519::op_crypto_derive_bits_x25519,
|
|
x25519::op_crypto_import_spki_x25519,
|
|
x25519::op_crypto_import_pkcs8_x25519,
|
|
ed25519::op_crypto_generate_ed25519_keypair,
|
|
ed25519::op_crypto_import_spki_ed25519,
|
|
ed25519::op_crypto_import_pkcs8_ed25519,
|
|
ed25519::op_crypto_sign_ed25519,
|
|
ed25519::op_crypto_verify_ed25519,
|
|
ed25519::op_crypto_export_spki_ed25519,
|
|
ed25519::op_crypto_export_pkcs8_ed25519,
|
|
ed25519::op_crypto_jwk_x_ed25519,
|
|
x25519::op_crypto_export_spki_x25519,
|
|
x25519::op_crypto_export_pkcs8_x25519,
|
|
],
|
|
esm = [ "00_crypto.js" ],
|
|
options = {
|
|
maybe_seed: Option<u64>,
|
|
},
|
|
state = |state, options| {
|
|
if let Some(seed) = options.maybe_seed {
|
|
state.put(StdRng::seed_from_u64(seed));
|
|
}
|
|
},
|
|
);
|
|
|
|
#[op2]
|
|
#[serde]
|
|
pub fn op_crypto_base64url_decode(
|
|
#[string] data: String,
|
|
) -> Result<ToJsBuffer, AnyError> {
|
|
let data: Vec<u8> = BASE64_URL_SAFE_NO_PAD.decode(data)?;
|
|
Ok(data.into())
|
|
}
|
|
|
|
#[op2]
|
|
#[string]
|
|
pub fn op_crypto_base64url_encode(#[buffer] data: JsBuffer) -> String {
|
|
let data: String = BASE64_URL_SAFE_NO_PAD.encode(data);
|
|
data
|
|
}
|
|
|
|
#[op2(fast)]
|
|
pub fn op_crypto_get_random_values(
|
|
state: &mut OpState,
|
|
#[buffer] out: &mut [u8],
|
|
) -> Result<(), AnyError> {
|
|
if out.len() > 65536 {
|
|
return Err(
|
|
deno_web::DomExceptionQuotaExceededError::new(&format!("The ArrayBufferView's byte length ({}) exceeds the number of bytes of entropy available via this API (65536)", out.len()))
|
|
.into(),
|
|
);
|
|
}
|
|
|
|
let maybe_seeded_rng = state.try_borrow_mut::<StdRng>();
|
|
if let Some(seeded_rng) = maybe_seeded_rng {
|
|
seeded_rng.fill(out);
|
|
} else {
|
|
let mut rng = thread_rng();
|
|
rng.fill(out);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum KeyFormat {
|
|
Raw,
|
|
Pkcs8,
|
|
Spki,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum KeyType {
|
|
Secret,
|
|
Private,
|
|
Public,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub struct KeyData {
|
|
r#type: KeyType,
|
|
data: JsBuffer,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SignArg {
|
|
key: KeyData,
|
|
algorithm: Algorithm,
|
|
salt_length: Option<u32>,
|
|
hash: Option<CryptoHash>,
|
|
named_curve: Option<CryptoNamedCurve>,
|
|
}
|
|
|
|
#[op2(async)]
|
|
#[serde]
|
|
pub async fn op_crypto_sign_key(
|
|
#[serde] args: SignArg,
|
|
#[buffer] zero_copy: JsBuffer,
|
|
) -> Result<ToJsBuffer, AnyError> {
|
|
deno_core::unsync::spawn_blocking(move || {
|
|
let data = &*zero_copy;
|
|
let algorithm = args.algorithm;
|
|
|
|
let signature = match algorithm {
|
|
Algorithm::RsassaPkcs1v15 => {
|
|
use rsa::pkcs1v15::SigningKey;
|
|
let private_key = RsaPrivateKey::from_pkcs1_der(&args.key.data)?;
|
|
match args
|
|
.hash
|
|
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
|
|
{
|
|
CryptoHash::Sha1 => {
|
|
let signing_key = SigningKey::<Sha1>::new(private_key);
|
|
signing_key.sign(data)
|
|
}
|
|
CryptoHash::Sha256 => {
|
|
let signing_key = SigningKey::<Sha256>::new(private_key);
|
|
signing_key.sign(data)
|
|
}
|
|
CryptoHash::Sha384 => {
|
|
let signing_key = SigningKey::<Sha384>::new(private_key);
|
|
signing_key.sign(data)
|
|
}
|
|
CryptoHash::Sha512 => {
|
|
let signing_key = SigningKey::<Sha512>::new(private_key);
|
|
signing_key.sign(data)
|
|
}
|
|
}
|
|
.to_vec()
|
|
}
|
|
Algorithm::RsaPss => {
|
|
let private_key = RsaPrivateKey::from_pkcs1_der(&args.key.data)?;
|
|
|
|
let salt_len = args.salt_length.ok_or_else(|| {
|
|
type_error("Missing argument saltLength".to_string())
|
|
})? as usize;
|
|
|
|
let mut rng = OsRng;
|
|
match args
|
|
.hash
|
|
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
|
|
{
|
|
CryptoHash::Sha1 => {
|
|
let signing_key = Pss::new_with_salt::<Sha1>(salt_len);
|
|
let hashed = Sha1::digest(data);
|
|
signing_key.sign(Some(&mut rng), &private_key, &hashed)?
|
|
}
|
|
CryptoHash::Sha256 => {
|
|
let signing_key = Pss::new_with_salt::<Sha256>(salt_len);
|
|
let hashed = Sha256::digest(data);
|
|
signing_key.sign(Some(&mut rng), &private_key, &hashed)?
|
|
}
|
|
CryptoHash::Sha384 => {
|
|
let signing_key = Pss::new_with_salt::<Sha384>(salt_len);
|
|
let hashed = Sha384::digest(data);
|
|
signing_key.sign(Some(&mut rng), &private_key, &hashed)?
|
|
}
|
|
CryptoHash::Sha512 => {
|
|
let signing_key = Pss::new_with_salt::<Sha512>(salt_len);
|
|
let hashed = Sha512::digest(data);
|
|
signing_key.sign(Some(&mut rng), &private_key, &hashed)?
|
|
}
|
|
}
|
|
.to_vec()
|
|
}
|
|
Algorithm::Ecdsa => {
|
|
let curve: &EcdsaSigningAlgorithm =
|
|
args.named_curve.ok_or_else(not_supported)?.into();
|
|
|
|
let rng = RingRand::SystemRandom::new();
|
|
let key_pair = EcdsaKeyPair::from_pkcs8(curve, &args.key.data, &rng)?;
|
|
// We only support P256-SHA256 & P384-SHA384. These are recommended signature pairs.
|
|
// https://briansmith.org/rustdoc/ring/signature/index.html#statics
|
|
if let Some(hash) = args.hash {
|
|
match hash {
|
|
CryptoHash::Sha256 | CryptoHash::Sha384 => (),
|
|
_ => return Err(type_error("Unsupported algorithm")),
|
|
}
|
|
};
|
|
|
|
let signature = key_pair.sign(&rng, data)?;
|
|
|
|
// Signature data as buffer.
|
|
signature.as_ref().to_vec()
|
|
}
|
|
Algorithm::Hmac => {
|
|
let hash: HmacAlgorithm = args.hash.ok_or_else(not_supported)?.into();
|
|
|
|
let key = HmacKey::new(hash, &args.key.data);
|
|
|
|
let signature = ring::hmac::sign(&key, data);
|
|
signature.as_ref().to_vec()
|
|
}
|
|
_ => return Err(type_error("Unsupported algorithm".to_string())),
|
|
};
|
|
|
|
Ok(signature.into())
|
|
})
|
|
.await?
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct VerifyArg {
|
|
key: KeyData,
|
|
algorithm: Algorithm,
|
|
salt_length: Option<u32>,
|
|
hash: Option<CryptoHash>,
|
|
signature: JsBuffer,
|
|
named_curve: Option<CryptoNamedCurve>,
|
|
}
|
|
|
|
#[op2(async)]
|
|
pub async fn op_crypto_verify_key(
|
|
#[serde] args: VerifyArg,
|
|
#[buffer] zero_copy: JsBuffer,
|
|
) -> Result<bool, AnyError> {
|
|
deno_core::unsync::spawn_blocking(move || {
|
|
let data = &*zero_copy;
|
|
let algorithm = args.algorithm;
|
|
|
|
let verification = match algorithm {
|
|
Algorithm::RsassaPkcs1v15 => {
|
|
use rsa::pkcs1v15::Signature;
|
|
use rsa::pkcs1v15::VerifyingKey;
|
|
let public_key = read_rsa_public_key(args.key)?;
|
|
let signature: Signature = args.signature.as_ref().try_into()?;
|
|
match args
|
|
.hash
|
|
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
|
|
{
|
|
CryptoHash::Sha1 => {
|
|
let verifying_key = VerifyingKey::<Sha1>::new(public_key);
|
|
verifying_key.verify(data, &signature).is_ok()
|
|
}
|
|
CryptoHash::Sha256 => {
|
|
let verifying_key = VerifyingKey::<Sha256>::new(public_key);
|
|
verifying_key.verify(data, &signature).is_ok()
|
|
}
|
|
CryptoHash::Sha384 => {
|
|
let verifying_key = VerifyingKey::<Sha384>::new(public_key);
|
|
verifying_key.verify(data, &signature).is_ok()
|
|
}
|
|
CryptoHash::Sha512 => {
|
|
let verifying_key = VerifyingKey::<Sha512>::new(public_key);
|
|
verifying_key.verify(data, &signature).is_ok()
|
|
}
|
|
}
|
|
}
|
|
Algorithm::RsaPss => {
|
|
let public_key = read_rsa_public_key(args.key)?;
|
|
let signature = args.signature.as_ref();
|
|
|
|
let salt_len = args.salt_length.ok_or_else(|| {
|
|
type_error("Missing argument saltLength".to_string())
|
|
})? as usize;
|
|
|
|
match args
|
|
.hash
|
|
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
|
|
{
|
|
CryptoHash::Sha1 => {
|
|
let pss = Pss::new_with_salt::<Sha1>(salt_len);
|
|
let hashed = Sha1::digest(data);
|
|
pss.verify(&public_key, &hashed, signature).is_ok()
|
|
}
|
|
CryptoHash::Sha256 => {
|
|
let pss = Pss::new_with_salt::<Sha256>(salt_len);
|
|
let hashed = Sha256::digest(data);
|
|
pss.verify(&public_key, &hashed, signature).is_ok()
|
|
}
|
|
CryptoHash::Sha384 => {
|
|
let pss = Pss::new_with_salt::<Sha384>(salt_len);
|
|
let hashed = Sha384::digest(data);
|
|
pss.verify(&public_key, &hashed, signature).is_ok()
|
|
}
|
|
CryptoHash::Sha512 => {
|
|
let pss = Pss::new_with_salt::<Sha512>(salt_len);
|
|
let hashed = Sha512::digest(data);
|
|
pss.verify(&public_key, &hashed, signature).is_ok()
|
|
}
|
|
}
|
|
}
|
|
Algorithm::Hmac => {
|
|
let hash: HmacAlgorithm = args.hash.ok_or_else(not_supported)?.into();
|
|
let key = HmacKey::new(hash, &args.key.data);
|
|
ring::hmac::verify(&key, data, &args.signature).is_ok()
|
|
}
|
|
Algorithm::Ecdsa => {
|
|
let signing_alg: &EcdsaSigningAlgorithm =
|
|
args.named_curve.ok_or_else(not_supported)?.into();
|
|
let verify_alg: &EcdsaVerificationAlgorithm =
|
|
args.named_curve.ok_or_else(not_supported)?.into();
|
|
|
|
let private_key;
|
|
|
|
let public_key_bytes = match args.key.r#type {
|
|
KeyType::Private => {
|
|
let rng = RingRand::SystemRandom::new();
|
|
private_key =
|
|
EcdsaKeyPair::from_pkcs8(signing_alg, &args.key.data, &rng)?;
|
|
|
|
private_key.public_key().as_ref()
|
|
}
|
|
KeyType::Public => &*args.key.data,
|
|
_ => return Err(type_error("Invalid Key format".to_string())),
|
|
};
|
|
|
|
let public_key =
|
|
ring::signature::UnparsedPublicKey::new(verify_alg, public_key_bytes);
|
|
|
|
public_key.verify(data, &args.signature).is_ok()
|
|
}
|
|
_ => return Err(type_error("Unsupported algorithm".to_string())),
|
|
};
|
|
|
|
Ok(verification)
|
|
})
|
|
.await?
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct DeriveKeyArg {
|
|
key: KeyData,
|
|
algorithm: Algorithm,
|
|
hash: Option<CryptoHash>,
|
|
length: usize,
|
|
iterations: Option<u32>,
|
|
// ECDH
|
|
public_key: Option<KeyData>,
|
|
named_curve: Option<CryptoNamedCurve>,
|
|
// HKDF
|
|
info: Option<JsBuffer>,
|
|
}
|
|
|
|
#[op2(async)]
|
|
#[serde]
|
|
pub async fn op_crypto_derive_bits(
|
|
#[serde] args: DeriveKeyArg,
|
|
#[buffer] zero_copy: Option<JsBuffer>,
|
|
) -> Result<ToJsBuffer, AnyError> {
|
|
deno_core::unsync::spawn_blocking(move || {
|
|
let algorithm = args.algorithm;
|
|
match algorithm {
|
|
Algorithm::Pbkdf2 => {
|
|
let zero_copy = zero_copy.ok_or_else(not_supported)?;
|
|
let salt = &*zero_copy;
|
|
// The caller must validate these cases.
|
|
assert!(args.length > 0);
|
|
assert!(args.length % 8 == 0);
|
|
|
|
let algorithm = match args.hash.ok_or_else(not_supported)? {
|
|
CryptoHash::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1,
|
|
CryptoHash::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256,
|
|
CryptoHash::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384,
|
|
CryptoHash::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512,
|
|
};
|
|
|
|
// This will never panic. We have already checked length earlier.
|
|
let iterations =
|
|
NonZeroU32::new(args.iterations.ok_or_else(not_supported)?).unwrap();
|
|
let secret = args.key.data;
|
|
let mut out = vec![0; args.length / 8];
|
|
pbkdf2::derive(algorithm, iterations, salt, &secret, &mut out);
|
|
Ok(out.into())
|
|
}
|
|
Algorithm::Ecdh => {
|
|
let named_curve = args.named_curve.ok_or_else(|| {
|
|
type_error("Missing argument namedCurve".to_string())
|
|
})?;
|
|
|
|
let public_key = args
|
|
.public_key
|
|
.ok_or_else(|| type_error("Missing argument publicKey"))?;
|
|
|
|
match named_curve {
|
|
CryptoNamedCurve::P256 => {
|
|
let secret_key = p256::SecretKey::from_pkcs8_der(&args.key.data)
|
|
.map_err(|_| {
|
|
type_error("Unexpected error decoding private key")
|
|
})?;
|
|
|
|
let public_key = match public_key.r#type {
|
|
KeyType::Private => {
|
|
p256::SecretKey::from_pkcs8_der(&public_key.data)
|
|
.map_err(|_| {
|
|
type_error("Unexpected error decoding private key")
|
|
})?
|
|
.public_key()
|
|
}
|
|
KeyType::Public => {
|
|
let point = p256::EncodedPoint::from_bytes(public_key.data)
|
|
.map_err(|_| {
|
|
type_error("Unexpected error decoding private key")
|
|
})?;
|
|
|
|
let pk = p256::PublicKey::from_encoded_point(&point);
|
|
// pk is a constant time Option.
|
|
if pk.is_some().into() {
|
|
pk.unwrap()
|
|
} else {
|
|
return Err(type_error(
|
|
"Unexpected error decoding private key",
|
|
));
|
|
}
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let shared_secret = p256::elliptic_curve::ecdh::diffie_hellman(
|
|
secret_key.to_nonzero_scalar(),
|
|
public_key.as_affine(),
|
|
);
|
|
|
|
// raw serialized x-coordinate of the computed point
|
|
Ok(shared_secret.raw_secret_bytes().to_vec().into())
|
|
}
|
|
CryptoNamedCurve::P384 => {
|
|
let secret_key = p384::SecretKey::from_pkcs8_der(&args.key.data)
|
|
.map_err(|_| {
|
|
type_error("Unexpected error decoding private key")
|
|
})?;
|
|
|
|
let public_key = match public_key.r#type {
|
|
KeyType::Private => {
|
|
p384::SecretKey::from_pkcs8_der(&public_key.data)
|
|
.map_err(|_| {
|
|
type_error("Unexpected error decoding private key")
|
|
})?
|
|
.public_key()
|
|
}
|
|
KeyType::Public => {
|
|
let point = p384::EncodedPoint::from_bytes(public_key.data)
|
|
.map_err(|_| {
|
|
type_error("Unexpected error decoding private key")
|
|
})?;
|
|
|
|
let pk = p384::PublicKey::from_encoded_point(&point);
|
|
// pk is a constant time Option.
|
|
if pk.is_some().into() {
|
|
pk.unwrap()
|
|
} else {
|
|
return Err(type_error(
|
|
"Unexpected error decoding private key",
|
|
));
|
|
}
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let shared_secret = p384::elliptic_curve::ecdh::diffie_hellman(
|
|
secret_key.to_nonzero_scalar(),
|
|
public_key.as_affine(),
|
|
);
|
|
|
|
// raw serialized x-coordinate of the computed point
|
|
Ok(shared_secret.raw_secret_bytes().to_vec().into())
|
|
}
|
|
}
|
|
}
|
|
Algorithm::Hkdf => {
|
|
let zero_copy = zero_copy.ok_or_else(not_supported)?;
|
|
let salt = &*zero_copy;
|
|
let algorithm = match args.hash.ok_or_else(not_supported)? {
|
|
CryptoHash::Sha1 => hkdf::HKDF_SHA1_FOR_LEGACY_USE_ONLY,
|
|
CryptoHash::Sha256 => hkdf::HKDF_SHA256,
|
|
CryptoHash::Sha384 => hkdf::HKDF_SHA384,
|
|
CryptoHash::Sha512 => hkdf::HKDF_SHA512,
|
|
};
|
|
|
|
let info = args
|
|
.info
|
|
.ok_or_else(|| type_error("Missing argument info".to_string()))?;
|
|
// IKM
|
|
let secret = args.key.data;
|
|
// L
|
|
let length = args.length / 8;
|
|
|
|
let salt = hkdf::Salt::new(algorithm, salt);
|
|
let prk = salt.extract(&secret);
|
|
let info = &[&*info];
|
|
let okm = prk.expand(info, HkdfOutput(length)).map_err(|_e| {
|
|
custom_error(
|
|
"DOMExceptionOperationError",
|
|
"The length provided for HKDF is too large",
|
|
)
|
|
})?;
|
|
let mut r = vec![0u8; length];
|
|
okm.fill(&mut r)?;
|
|
Ok(r.into())
|
|
}
|
|
_ => Err(type_error("Unsupported algorithm".to_string())),
|
|
}
|
|
})
|
|
.await?
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
#[op2]
|
|
#[string]
|
|
pub fn op_crypto_random_uuid(state: &mut OpState) -> Result<String, AnyError> {
|
|
let maybe_seeded_rng = state.try_borrow_mut::<StdRng>();
|
|
let uuid = if let Some(seeded_rng) = maybe_seeded_rng {
|
|
let mut bytes = [0u8; 16];
|
|
seeded_rng.fill(&mut bytes);
|
|
fast_uuid_v4(&mut bytes)
|
|
} else {
|
|
let mut rng = thread_rng();
|
|
let mut bytes = [0u8; 16];
|
|
rng.fill(&mut bytes);
|
|
fast_uuid_v4(&mut bytes)
|
|
};
|
|
|
|
Ok(uuid)
|
|
}
|
|
|
|
#[op2(async)]
|
|
#[serde]
|
|
pub async fn op_crypto_subtle_digest(
|
|
#[serde] algorithm: CryptoHash,
|
|
#[buffer] data: JsBuffer,
|
|
) -> Result<ToJsBuffer, AnyError> {
|
|
let output = spawn_blocking(move || {
|
|
digest::digest(algorithm.into(), &data)
|
|
.as_ref()
|
|
.to_vec()
|
|
.into()
|
|
})
|
|
.await?;
|
|
|
|
Ok(output)
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct WrapUnwrapKeyArg {
|
|
key: V8RawKeyData,
|
|
algorithm: Algorithm,
|
|
}
|
|
|
|
#[op2]
|
|
#[serde]
|
|
pub fn op_crypto_wrap_key(
|
|
#[serde] args: WrapUnwrapKeyArg,
|
|
#[buffer] data: JsBuffer,
|
|
) -> Result<ToJsBuffer, AnyError> {
|
|
let algorithm = args.algorithm;
|
|
|
|
match algorithm {
|
|
Algorithm::AesKw => {
|
|
let key = args.key.as_secret_key()?;
|
|
|
|
if data.len() % 8 != 0 {
|
|
return Err(type_error("Data must be multiple of 8 bytes"));
|
|
}
|
|
|
|
let wrapped_key = match key.len() {
|
|
16 => KekAes128::new(key.into()).wrap_vec(&data),
|
|
24 => KekAes192::new(key.into()).wrap_vec(&data),
|
|
32 => KekAes256::new(key.into()).wrap_vec(&data),
|
|
_ => return Err(type_error("Invalid key length")),
|
|
}
|
|
.map_err(|_| operation_error("encryption error"))?;
|
|
|
|
Ok(wrapped_key.into())
|
|
}
|
|
_ => Err(type_error("Unsupported algorithm")),
|
|
}
|
|
}
|
|
|
|
#[op2]
|
|
#[serde]
|
|
pub fn op_crypto_unwrap_key(
|
|
#[serde] args: WrapUnwrapKeyArg,
|
|
#[buffer] data: JsBuffer,
|
|
) -> Result<ToJsBuffer, AnyError> {
|
|
let algorithm = args.algorithm;
|
|
match algorithm {
|
|
Algorithm::AesKw => {
|
|
let key = args.key.as_secret_key()?;
|
|
|
|
if data.len() % 8 != 0 {
|
|
return Err(type_error("Data must be multiple of 8 bytes"));
|
|
}
|
|
|
|
let unwrapped_key = match key.len() {
|
|
16 => KekAes128::new(key.into()).unwrap_vec(&data),
|
|
24 => KekAes192::new(key.into()).unwrap_vec(&data),
|
|
32 => KekAes256::new(key.into()).unwrap_vec(&data),
|
|
_ => return Err(type_error("Invalid key length")),
|
|
}
|
|
.map_err(|_| {
|
|
operation_error("decryption error - integrity check failed")
|
|
})?;
|
|
|
|
Ok(unwrapped_key.into())
|
|
}
|
|
_ => Err(type_error("Unsupported algorithm")),
|
|
}
|
|
}
|
|
|
|
pub fn get_declaration() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_crypto.d.ts")
|
|
}
|
|
|
|
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
|
|
|
|
fn fast_uuid_v4(bytes: &mut [u8; 16]) -> String {
|
|
// Set UUID version to 4 and variant to 1.
|
|
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
|
|
let buf = [
|
|
HEX_CHARS[(bytes[0] >> 4) as usize],
|
|
HEX_CHARS[(bytes[0] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[1] >> 4) as usize],
|
|
HEX_CHARS[(bytes[1] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[2] >> 4) as usize],
|
|
HEX_CHARS[(bytes[2] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[3] >> 4) as usize],
|
|
HEX_CHARS[(bytes[3] & 0x0f) as usize],
|
|
b'-',
|
|
HEX_CHARS[(bytes[4] >> 4) as usize],
|
|
HEX_CHARS[(bytes[4] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[5] >> 4) as usize],
|
|
HEX_CHARS[(bytes[5] & 0x0f) as usize],
|
|
b'-',
|
|
HEX_CHARS[(bytes[6] >> 4) as usize],
|
|
HEX_CHARS[(bytes[6] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[7] >> 4) as usize],
|
|
HEX_CHARS[(bytes[7] & 0x0f) as usize],
|
|
b'-',
|
|
HEX_CHARS[(bytes[8] >> 4) as usize],
|
|
HEX_CHARS[(bytes[8] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[9] >> 4) as usize],
|
|
HEX_CHARS[(bytes[9] & 0x0f) as usize],
|
|
b'-',
|
|
HEX_CHARS[(bytes[10] >> 4) as usize],
|
|
HEX_CHARS[(bytes[10] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[11] >> 4) as usize],
|
|
HEX_CHARS[(bytes[11] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[12] >> 4) as usize],
|
|
HEX_CHARS[(bytes[12] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[13] >> 4) as usize],
|
|
HEX_CHARS[(bytes[13] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[14] >> 4) as usize],
|
|
HEX_CHARS[(bytes[14] & 0x0f) as usize],
|
|
HEX_CHARS[(bytes[15] >> 4) as usize],
|
|
HEX_CHARS[(bytes[15] & 0x0f) as usize],
|
|
];
|
|
|
|
// Safety: the buffer is all valid UTF-8.
|
|
unsafe { String::from_utf8_unchecked(buf.to_vec()) }
|
|
}
|
|
|
|
#[test]
|
|
fn test_fast_uuid_v4_correctness() {
|
|
let mut rng = thread_rng();
|
|
let mut bytes = [0u8; 16];
|
|
rng.fill(&mut bytes);
|
|
let uuid = fast_uuid_v4(&mut bytes.clone());
|
|
let uuid_lib = uuid::Builder::from_bytes(bytes)
|
|
.set_variant(uuid::Variant::RFC4122)
|
|
.set_version(uuid::Version::Random)
|
|
.as_uuid()
|
|
.to_string();
|
|
assert_eq!(uuid, uuid_lib);
|
|
}
|