Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ext/node): ed25519 signing and cipheriv autopadding fixes #24957

Merged
merged 6 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ext/node/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_cipheriv_encrypt,
ops::crypto::op_node_cipheriv_final,
ops::crypto::op_node_cipheriv_set_aad,
ops::crypto::op_node_cipheriv_take,
ops::crypto::op_node_create_cipheriv,
ops::crypto::op_node_create_decipheriv,
ops::crypto::op_node_create_hash,
Expand Down Expand Up @@ -260,7 +261,9 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_scrypt_async,
ops::crypto::op_node_scrypt_sync,
ops::crypto::op_node_sign,
ops::crypto::op_node_sign_ed25519,
ops::crypto::op_node_verify,
ops::crypto::op_node_verify_ed25519,
ops::crypto::keys::op_node_create_private_key,
ops::crypto::keys::op_node_create_public_key,
ops::crypto::keys::op_node_create_secret_key,
Expand Down
13 changes: 13 additions & 0 deletions ext/node/ops/crypto/cipher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ impl CipherContext {
self.cipher.borrow_mut().encrypt(input, output);
}

pub fn take_tag(self) -> Tag {
Rc::try_unwrap(self.cipher).ok()?.into_inner().take_tag()
}

pub fn r#final(
self,
auto_pad: bool,
Expand Down Expand Up @@ -290,6 +294,15 @@ impl Cipher {
}
}
}

fn take_tag(self) -> Tag {
use Cipher::*;
match self {
Aes128Gcm(cipher) => Some(cipher.finish().to_vec()),
Aes256Gcm(cipher) => Some(cipher.finish().to_vec()),
_ => None,
}
}
}

impl Decipher {
Expand Down
21 changes: 4 additions & 17 deletions ext/node/ops/crypto/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,14 +496,9 @@ impl KeyObjectHandle {
AsymmetricPrivateKey::X25519(x25519_dalek::StaticSecret::from(bytes))
}
ED25519_OID => {
let string_ref = OctetStringRef::from_der(pk_info.private_key)
let signing_key = ed25519_dalek::SigningKey::try_from(pk_info)
.map_err(|_| type_error("invalid Ed25519 private key"))?;
if string_ref.as_bytes().len() != 32 {
return Err(type_error("Ed25519 private key is the wrong length"));
}
let mut bytes = [0; 32];
bytes.copy_from_slice(string_ref.as_bytes());
AsymmetricPrivateKey::Ed25519(ed25519_dalek::SigningKey::from(bytes))
AsymmetricPrivateKey::Ed25519(signing_key)
}
DH_KEY_AGREEMENT_OID => {
let params = pk_info
Expand Down Expand Up @@ -643,16 +638,8 @@ impl KeyObjectHandle {
AsymmetricPublicKey::X25519(x25519_dalek::PublicKey::from(bytes))
}
ED25519_OID => {
let mut bytes = [0; 32];
let data = spki.subject_public_key.as_bytes().ok_or_else(|| {
type_error("malformed or missing public key in ed25519 spki")
})?;
if data.len() < 32 {
return Err(type_error("ed25519 public key is too short"));
}
bytes.copy_from_slice(&data[0..32]);
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&bytes)
.map_err(|_| type_error("ed25519 public key is malformed"))?;
let verifying_key = ed25519_dalek::VerifyingKey::try_from(spki)
.map_err(|_| type_error("invalid Ed25519 private key"))?;
AsymmetricPublicKey::Ed25519(verifying_key)
}
DH_KEY_AGREEMENT_OID => {
Expand Down
60 changes: 60 additions & 0 deletions ext/node/ops/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use num_bigint_dig::BigUint;
use rand::distributions::Distribution;
use rand::distributions::Uniform;
use rand::Rng;
use ring::signature::Ed25519KeyPair;
use std::future::Future;
use std::rc::Rc;

Expand Down Expand Up @@ -272,6 +273,18 @@ pub fn op_node_cipheriv_final(
context.r#final(auto_pad, input, output)
}

#[op2]
#[buffer]
pub fn op_node_cipheriv_take(
state: &mut OpState,
#[smi] rid: u32,
) -> Result<Option<Vec<u8>>, AnyError> {
let context = state.resource_table.take::<cipher::CipherContext>(rid)?;
let context = Rc::try_unwrap(context)
.map_err(|_| type_error("Cipher context is already in use"))?;
Ok(context.take_tag())
}

#[op2(fast)]
#[smi]
pub fn op_node_create_decipheriv(
Expand Down Expand Up @@ -938,3 +951,50 @@ pub fn op_node_diffie_hellman(

Ok(res)
}

#[op2(fast)]
pub fn op_node_sign_ed25519(
#[cppgc] key: &KeyObjectHandle,
#[buffer] data: &[u8],
#[buffer] signature: &mut [u8],
) -> Result<(), AnyError> {
let private = key
.as_private_key()
.ok_or_else(|| type_error("Expected private key"))?;

let ed25519 = match private {
AsymmetricPrivateKey::Ed25519(private) => private,
_ => return Err(type_error("Expected Ed25519 private key")),
};

let pair = Ed25519KeyPair::from_seed_unchecked(ed25519.as_bytes().as_slice())
.map_err(|_| type_error("Invalid Ed25519 private key"))?;
signature.copy_from_slice(pair.sign(data).as_ref());

Ok(())
}

#[op2(fast)]
pub fn op_node_verify_ed25519(
#[cppgc] key: &KeyObjectHandle,
#[buffer] data: &[u8],
#[buffer] signature: &[u8],
) -> Result<bool, AnyError> {
let public = key
.as_public_key()
.ok_or_else(|| type_error("Expected public key"))?;

let ed25519 = match &*public {
AsymmetricPublicKey::Ed25519(public) => public,
_ => return Err(type_error("Expected Ed25519 public key")),
};

let verified = ring::signature::UnparsedPublicKey::new(
&ring::signature::ED25519,
ed25519.as_bytes().as_slice(),
)
.verify(data, signature)
.is_ok();

Ok(verified)
}
148 changes: 6 additions & 142 deletions ext/node/ops/crypto/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@
use deno_core::error::generic_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use digest::Digest;
use digest::FixedOutput;
use digest::FixedOutputReset;
use digest::OutputSizeUser;
use digest::Reset;
use digest::Update;
use rand::rngs::OsRng;
use rsa::signature::hazmat::PrehashSigner as _;
use rsa::signature::hazmat::PrehashVerifier as _;
Expand Down Expand Up @@ -146,29 +140,9 @@ impl KeyObjectHandle {
AsymmetricPrivateKey::X25519(_) => {
Err(type_error("x25519 key cannot be used for signing"))
}
AsymmetricPrivateKey::Ed25519(key) => {
if !matches!(
digest_type,
"rsa-sha512" | "sha512" | "sha512withrsaencryption"
) {
return Err(type_error(format!(
"digest not allowed for Ed25519 signature: {}",
digest_type
)));
}

let mut precomputed_digest = PrecomputedDigest([0; 64]);
if digest.len() != precomputed_digest.0.len() {
return Err(type_error("Invalid sha512 digest"));
}
precomputed_digest.0.copy_from_slice(digest);

let signature = key
.sign_prehashed(precomputed_digest, None)
.map_err(|_| generic_error("failed to sign digest with Ed25519"))?;

Ok(signature.to_bytes().into())
}
AsymmetricPrivateKey::Ed25519(_) => Err(type_error(
"Ed25519 key cannot be used for prehashed signing",
)),
AsymmetricPrivateKey::Dh(_) => {
Err(type_error("DH key cannot be used for signing"))
}
Expand Down Expand Up @@ -275,122 +249,12 @@ impl KeyObjectHandle {
AsymmetricPublicKey::X25519(_) => {
Err(type_error("x25519 key cannot be used for verification"))
}
AsymmetricPublicKey::Ed25519(key) => {
if !matches!(
digest_type,
"rsa-sha512" | "sha512" | "sha512withrsaencryption"
) {
return Err(type_error(format!(
"digest not allowed for Ed25519 signature: {}",
digest_type
)));
}

let mut signature_fixed = [0u8; 64];
if signature.len() != signature_fixed.len() {
return Err(type_error("Invalid Ed25519 signature"));
}
signature_fixed.copy_from_slice(signature);

let signature = ed25519_dalek::Signature::from_bytes(&signature_fixed);

let mut precomputed_digest = PrecomputedDigest([0; 64]);
precomputed_digest.0.copy_from_slice(digest);

Ok(
key
.verify_prehashed_strict(precomputed_digest, None, &signature)
.is_ok(),
)
}
AsymmetricPublicKey::Ed25519(_) => Err(type_error(
"Ed25519 key cannot be used for prehashed verification",
)),
AsymmetricPublicKey::Dh(_) => {
Err(type_error("DH key cannot be used for verification"))
}
}
}
}

struct PrecomputedDigest([u8; 64]);

impl OutputSizeUser for PrecomputedDigest {
type OutputSize = <sha2::Sha512 as OutputSizeUser>::OutputSize;
}

impl Digest for PrecomputedDigest {
fn new() -> Self {
unreachable!()
}

fn new_with_prefix(_data: impl AsRef<[u8]>) -> Self {
unreachable!()
}

fn update(&mut self, _data: impl AsRef<[u8]>) {
unreachable!()
}

fn chain_update(self, _data: impl AsRef<[u8]>) -> Self {
unreachable!()
}

fn finalize(self) -> digest::Output<Self> {
self.0.into()
}

fn finalize_into(self, _out: &mut digest::Output<Self>) {
unreachable!()
}

fn finalize_reset(&mut self) -> digest::Output<Self>
where
Self: digest::FixedOutputReset,
{
unreachable!()
}

fn finalize_into_reset(&mut self, _out: &mut digest::Output<Self>)
where
Self: digest::FixedOutputReset,
{
unreachable!()
}

fn reset(&mut self)
where
Self: digest::Reset,
{
unreachable!()
}

fn output_size() -> usize {
unreachable!()
}

fn digest(_data: impl AsRef<[u8]>) -> digest::Output<Self> {
unreachable!()
}
}

impl Reset for PrecomputedDigest {
fn reset(&mut self) {
unreachable!()
}
}

impl FixedOutputReset for PrecomputedDigest {
fn finalize_into_reset(&mut self, _out: &mut digest::Output<Self>) {
unreachable!()
}
}

impl FixedOutput for PrecomputedDigest {
fn finalize_into(self, _out: &mut digest::Output<Self>) {
unreachable!()
}
}

impl Update for PrecomputedDigest {
fn update(&mut self, _data: &[u8]) {
unreachable!()
}
}
7 changes: 6 additions & 1 deletion ext/node/polyfills/internal/crypto/cipher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
op_node_cipheriv_encrypt,
op_node_cipheriv_final,
op_node_cipheriv_set_aad,
op_node_cipheriv_take,
op_node_create_cipheriv,
op_node_create_decipheriv,
op_node_decipheriv_decrypt,
Expand Down Expand Up @@ -194,7 +195,11 @@ export class Cipheriv extends Transform implements Cipher {

final(encoding: string = getDefaultEncoding()): Buffer | string {
const buf = new Buffer(16);

if (this.#cache.cache.byteLength == 0) {
const maybeTag = op_node_cipheriv_take(this.#context);
if (maybeTag) this.#authTag = Buffer.from(maybeTag);
return encoding === "buffer" ? Buffer.from([]) : "";
}
if (!this.#autoPadding && this.#cache.cache.byteLength != 16) {
throw new Error("Invalid final block size");
}
Expand Down
Loading