fix(ext/node): implement Certificate API (#29828)

This commit is contained in:
Divy Srivastava 2025-06-23 02:34:44 -07:00 committed by GitHub
parent 0f08eb076b
commit d15581fb19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 570 additions and 13 deletions

9
Cargo.lock generated
View file

@ -1900,6 +1900,14 @@ dependencies = [
"x25519-dalek", "x25519-dalek",
] ]
[[package]]
name = "deno_crypto_provider"
version = "0.1.0"
dependencies = [
"aws-lc-rs",
"aws-lc-sys",
]
[[package]] [[package]]
name = "deno_doc" name = "deno_doc"
version = "0.178.0" version = "0.178.0"
@ -2345,6 +2353,7 @@ dependencies = [
"ctr", "ctr",
"data-encoding", "data-encoding",
"deno_core", "deno_core",
"deno_crypto_provider",
"deno_error", "deno_error",
"deno_fetch", "deno_fetch",
"deno_fs", "deno_fs",

View file

@ -33,6 +33,7 @@ members = [
"ext/websocket", "ext/websocket",
"ext/webstorage", "ext/webstorage",
"libs/config", "libs/config",
"libs/crypto",
"libs/node_resolver", "libs/node_resolver",
"libs/npm_cache", "libs/npm_cache",
"libs/npm_installer", "libs/npm_installer",
@ -113,6 +114,7 @@ denort_helper = { version = "0.5.0", path = "./ext/rt_helper" }
# workspace libraries # workspace libraries
deno_bench_util = { version = "0.201.0", path = "./bench_util" } deno_bench_util = { version = "0.201.0", path = "./bench_util" }
deno_config = { version = "=0.57.0", features = ["workspace"], path = "./libs/config" } deno_config = { version = "=0.57.0", features = ["workspace"], path = "./libs/config" }
deno_crypto_provider = { version = "0.1.0", path = "./libs/crypto" }
deno_features = { version = "0.4.0", path = "./runtime/features" } deno_features = { version = "0.4.0", path = "./runtime/features" }
deno_lib = { version = "0.25.0", path = "./cli/lib" } deno_lib = { version = "0.25.0", path = "./cli/lib" }
deno_npm_cache = { version = "0.26.0", path = "./libs/npm_cache" } deno_npm_cache = { version = "0.26.0", path = "./libs/npm_cache" }
@ -134,6 +136,7 @@ async-once-cell = "0.5.4"
async-stream = "0.3" async-stream = "0.3"
async-trait = "0.1.73" async-trait = "0.1.73"
aws-lc-rs = { version = "1.0.0" } aws-lc-rs = { version = "1.0.0" }
aws-lc-sys = { version = "0.26.0" }
base32 = "=0.5.1" base32 = "=0.5.1"
base64 = "0.22.1" base64 = "0.22.1"
base64-simd = "0.8" base64-simd = "0.8"

View file

@ -30,6 +30,7 @@ const-oid.workspace = true
ctr.workspace = true ctr.workspace = true
data-encoding.workspace = true data-encoding.workspace = true
deno_core.workspace = true deno_core.workspace = true
deno_crypto_provider.workspace = true
deno_error.workspace = true deno_error.workspace = true
deno_fetch.workspace = true deno_fetch.workspace = true
deno_fs.workspace = true deno_fs.workspace = true

View file

@ -314,6 +314,9 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_sign_ed25519, ops::crypto::op_node_sign_ed25519,
ops::crypto::op_node_verify, ops::crypto::op_node_verify,
ops::crypto::op_node_verify_ed25519, ops::crypto::op_node_verify_ed25519,
ops::crypto::op_node_verify_spkac,
ops::crypto::op_node_cert_export_public_key,
ops::crypto::op_node_cert_export_challenge,
ops::crypto::keys::op_node_create_private_key, ops::crypto::keys::op_node_create_private_key,
ops::crypto::keys::op_node_create_ed_raw, ops::crypto::keys::op_node_create_ed_raw,
ops::crypto::keys::op_node_create_rsa_jwk, ops::crypto::keys::op_node_create_rsa_jwk,

View file

@ -1140,3 +1140,46 @@ pub fn op_node_verify_ed25519(
Ok(verified) Ok(verified)
} }
#[derive(Debug, thiserror::Error, deno_error::JsError)]
pub enum SpkacError {
#[error("spkac is too large")]
#[property("code" = "ERR_OUT_OF_RANGE")]
#[class(range)]
BufferOutOfRange,
}
#[op2(fast)]
pub fn op_node_verify_spkac(
#[buffer] spkac: &[u8],
) -> Result<bool, SpkacError> {
if spkac.len() > i32::MAX as usize {
return Err(SpkacError::BufferOutOfRange);
}
Ok(deno_crypto_provider::spki::verify_spkac(spkac))
}
#[op2]
#[buffer]
pub fn op_node_cert_export_public_key(
#[buffer] spkac: &[u8],
) -> Result<Option<Vec<u8>>, SpkacError> {
if spkac.len() > i32::MAX as usize {
return Err(SpkacError::BufferOutOfRange);
}
Ok(deno_crypto_provider::spki::export_public_key(spkac))
}
#[op2]
#[buffer]
pub fn op_node_cert_export_challenge(
#[buffer] spkac: &[u8],
) -> Result<Option<Vec<u8>>, SpkacError> {
if spkac.len() > i32::MAX as usize {
return Err(SpkacError::BufferOutOfRange);
}
Ok(deno_crypto_provider::spki::export_challenge(spkac))
}

View file

@ -1,23 +1,59 @@
// Copyright 2018-2025 the Deno authors. MIT license. // Copyright 2018-2025 the Deno authors. MIT license.
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. // Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
import { notImplemented } from "ext:deno_node/_utils.ts"; import {
op_node_cert_export_challenge,
op_node_cert_export_public_key,
op_node_verify_spkac,
} from "ext:core/ops";
import { Buffer } from "node:buffer"; import { Buffer } from "node:buffer";
import { BinaryLike } from "ext:deno_node/internal/crypto/types.ts"; import { getArrayBufferOrView } from "ext:deno_node/internal/crypto/keys.ts";
export class Certificate { // The functions contained in this file cover the SPKAC format
static Certificate = Certificate; // (also referred to as Netscape SPKI). A general description of
static exportChallenge(_spkac: BinaryLike, _encoding?: string): Buffer { // the format can be found at https://en.wikipedia.org/wiki/SPKAC
notImplemented("crypto.Certificate.exportChallenge");
}
static exportPublicKey(_spkac: BinaryLike, _encoding?: string): Buffer { function verifySpkac(spkac, encoding) {
notImplemented("crypto.Certificate.exportPublicKey"); return op_node_verify_spkac(
} getArrayBufferOrView(spkac, "spkac", encoding),
);
}
static verifySpkac(_spkac: BinaryLike, _encoding?: string): boolean { function exportPublicKey(spkac, encoding) {
notImplemented("crypto.Certificate.verifySpkac"); const publicKey = op_node_cert_export_public_key(
getArrayBufferOrView(spkac, "spkac", encoding),
);
return publicKey ? Buffer.from(publicKey) : "";
}
function exportChallenge(spkac, encoding) {
const challenge = op_node_cert_export_challenge(
getArrayBufferOrView(spkac, "spkac", encoding),
);
return challenge ? Buffer.from(challenge) : "";
}
// The legacy implementation of this exposed the Certificate
// object and required that users create an instance before
// calling the member methods. This API pattern has been
// deprecated, however, as the method implementations do not
// rely on any object state.
// For backwards compatibility reasons, this cannot be converted into a
// ES6 Class.
export function Certificate() {
// deno-lint-ignore prefer-primordials
if (!(this instanceof Certificate)) {
return new Certificate();
} }
} }
Certificate.prototype.verifySpkac = verifySpkac;
Certificate.prototype.exportPublicKey = exportPublicKey;
Certificate.prototype.exportChallenge = exportChallenge;
Certificate.exportChallenge = exportChallenge;
Certificate.exportPublicKey = exportPublicKey;
Certificate.verifySpkac = verifySpkac;
export default Certificate; export default Certificate;

View file

@ -96,6 +96,13 @@ export const getArrayBufferOrView = hideStackFrames(
} }
return Buffer.from(buffer, encoding); return Buffer.from(buffer, encoding);
} }
if (buffer instanceof DataView) {
return new Uint8Array(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength,
);
}
if (!isArrayBufferView(buffer)) { if (!isArrayBufferView(buffer)) {
throw new ERR_INVALID_ARG_TYPE( throw new ERR_INVALID_ARG_TYPE(
name, name,

16
libs/crypto/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
# Copyright 2018-2025 the Deno authors. MIT license.
[package]
name = "deno_crypto_provider"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
description = "Cryptography provider for Deno"
[lib]
path = "lib.rs"
[dependencies]
aws-lc-rs.workspace = true
aws-lc-sys.workspace = true

3
libs/crypto/README.md Normal file
View file

@ -0,0 +1,3 @@
# `deno_crypto`
Rust cryptography provider for Deno WebCrypto and `node:crypto`.

97
libs/crypto/ffi.rs Normal file
View file

@ -0,0 +1,97 @@
// Copyright 2018-2025 the Deno authors. MIT license.
pub struct PKey(pub *mut aws_lc_sys::EVP_PKEY);
impl PKey {
pub fn from_ptr(ptr: *mut aws_lc_sys::EVP_PKEY) -> Option<Self> {
if ptr.is_null() {
None
} else {
Some(Self(ptr))
}
}
pub fn as_ptr(&self) -> *mut aws_lc_sys::EVP_PKEY {
self.0
}
}
impl Drop for PKey {
fn drop(&mut self) {
// SAFETY: We need to free the underlying EVP_PKEY when the PKey wrapper is dropped.
// The null check ensures we don't try to free a null pointer.
unsafe {
if self.0.is_null() {
return;
}
aws_lc_sys::EVP_PKEY_free(self.0);
}
}
}
impl std::ops::Deref for PKey {
type Target = *mut aws_lc_sys::EVP_PKEY;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct Bio(pub *mut aws_lc_sys::BIO);
impl Drop for Bio {
fn drop(&mut self) {
// SAFETY: We need to free the underlying BIO when the Bio wrapper is dropped.
// The null check ensures we don't try to free a null pointer.
unsafe {
if self.0.is_null() {
return;
}
aws_lc_sys::BIO_free(self.0);
}
}
}
impl Bio {
pub fn new_memory() -> Result<Self, &'static str> {
// SAFETY: Creating a new memory BIO requires FFI calls to the OpenSSL API.
// We check for null pointer returns to ensure safety.
unsafe {
let bio = aws_lc_sys::BIO_new(aws_lc_sys::BIO_s_mem());
if bio.is_null() {
return Err("Failed to create memory BIO");
}
Ok(Bio(bio))
}
}
pub fn get_contents(&self) -> Result<Vec<u8>, &'static str> {
// SAFETY: Retrieving content from a BIO requires FFI calls and raw pointer manipulation.
// We verify the pointer is not null and create a slice with the correct length.
// The data is copied into a Vec to ensure memory safety after this function returns.
unsafe {
let mut len = 0;
let mut content_ptr = std::ptr::null();
aws_lc_sys::BIO_mem_contents(self.0, &mut content_ptr, &mut len);
if content_ptr.is_null() || len == 0 {
return Err("No content in BIO");
}
let data = std::slice::from_raw_parts(content_ptr, len);
Ok(data.to_vec())
}
}
pub fn as_ptr(&self) -> *mut aws_lc_sys::BIO {
self.0
}
}
impl std::ops::Deref for Bio {
type Target = *mut aws_lc_sys::BIO;
fn deref(&self) -> &Self::Target {
&self.0
}
}

9
libs/crypto/lib.rs Normal file
View file

@ -0,0 +1,9 @@
// Copyright 2018-2025 the Deno authors. MIT license.
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![deny(clippy::unused_async)]
#![deny(clippy::unnecessary_wraps)]
mod ffi;
pub mod spki;

188
libs/crypto/spki.rs Normal file
View file

@ -0,0 +1,188 @@
// Copyright 2018-2025 the Deno authors. MIT license.
use std::ptr::NonNull;
use crate::ffi::Bio;
use crate::ffi::PKey;
#[derive(Debug)]
pub struct NetscapeSpki(*mut aws_lc_sys::NETSCAPE_SPKI);
impl NetscapeSpki {
/// Decodes a base64-encoded SPKI certificate.
fn from_base64(data: &[u8]) -> Result<Self, &'static str> {
// Trim trailing characters for compatibility with OpenSSL.
let end = data
.iter()
.rposition(|&b| !b" \n\r\t".contains(&b))
.map_or(0, |i| i + 1);
// SAFETY: Cast data pointer to convert base64 to NETSCAPE_SPKI
unsafe {
let spki = aws_lc_sys::NETSCAPE_SPKI_b64_decode(
data.as_ptr() as *const _,
end as isize,
);
if spki.is_null() {
return Err("Failed to decode base64 SPKI data");
}
Ok(NetscapeSpki(spki))
}
}
fn verify(&self, pkey: &PKey) -> bool {
// SAFETY: Use public key to verify SPKI certificate
unsafe {
let result = aws_lc_sys::NETSCAPE_SPKI_verify(self.0, pkey.as_ptr());
result > 0
}
}
fn spkac(&self) -> Result<&aws_lc_sys::NETSCAPE_SPKAC, &'static str> {
// SAFETY: Access spkac field via raw pointer with null checks
unsafe {
if self.0.is_null() || (*self.0).spkac.is_null() {
return Err("Invalid SPKAC structure");
}
Ok(&*(*self.0).spkac)
}
}
fn get_public_key(&self) -> Result<PKey, &'static str> {
// SAFETY: Extract public key, null checked by PKey::from_ptr
unsafe {
let pkey = aws_lc_sys::NETSCAPE_SPKI_get_pubkey(self.0);
PKey::from_ptr(pkey).ok_or("Failed to extract public key")
}
}
fn get_challenge(&self) -> Result<Vec<u8>, &'static str> {
// SAFETY: Extract challenge with null checks and BufferGuard for cleanup
unsafe {
let spkac = self.spkac()?;
let challenge = spkac.challenge;
if challenge.is_null() {
return Err("No challenge found in SPKI certificate");
}
let mut buf = std::ptr::null_mut();
let buf_len = aws_lc_sys::ASN1_STRING_to_UTF8(&mut buf, challenge);
if buf_len <= 0 || buf.is_null() {
return Err("Failed to extract challenge string");
}
let _guard = BufferGuard(NonNull::new(buf).unwrap());
let challenge_slice =
std::slice::from_raw_parts(buf as *const u8, buf_len as usize);
Ok(challenge_slice.to_vec())
}
}
pub fn as_ptr(&self) -> *mut aws_lc_sys::NETSCAPE_SPKI {
self.0
}
}
impl Drop for NetscapeSpki {
fn drop(&mut self) {
// SAFETY: Free NETSCAPE_SPKI with null check
unsafe {
if !self.0.is_null() {
aws_lc_sys::NETSCAPE_SPKI_free(self.0);
}
}
}
}
// RAII guard for automatically freeing ASN1 string buffers
struct BufferGuard(NonNull<u8>);
impl Drop for BufferGuard {
fn drop(&mut self) {
// SAFETY: Free ASN1_STRING buffer (NonNull guarantees non-null)
unsafe {
aws_lc_sys::OPENSSL_free(self.0.as_ptr() as *mut std::ffi::c_void);
}
}
}
/// Validates the SPKAC data structure.
///
/// Returns true if the signature in the SPKAC data is valid.
pub fn verify_spkac(data: &[u8]) -> bool {
let spki = match NetscapeSpki::from_base64(data) {
Ok(spki) => spki,
Err(_) => return false,
};
let pkey = match extract_public_key_from_spkac(&spki) {
Ok(pkey) => pkey,
Err(_) => return false,
};
spki.verify(&pkey)
}
/// Extracts the public key from the SPKAC structure.
fn extract_public_key_from_spkac(
spki: &NetscapeSpki,
) -> Result<PKey, &'static str> {
// SAFETY: Extract public key with null checks and proper ownership
unsafe {
let spkac = spki.spkac()?;
let pubkey = spkac.pubkey;
if pubkey.is_null() {
return Err("No public key in SPKAC structure");
}
let pkey = aws_lc_sys::X509_PUBKEY_get(pubkey);
PKey::from_ptr(pkey).ok_or("Failed to extract public key from X509_PUBKEY")
}
}
/// Exports the public key from the SPKAC data in PEM format.
pub fn export_public_key(data: &[u8]) -> Option<Vec<u8>> {
let spki = NetscapeSpki::from_base64(data).ok()?;
let pkey = spki.get_public_key().ok()?;
let bio = Bio::new_memory().ok()?;
// SAFETY: Write public key to BIO in PEM format, check result
unsafe {
let result = aws_lc_sys::PEM_write_bio_PUBKEY(bio.as_ptr(), pkey.as_ptr());
if result <= 0 {
return None;
}
}
bio.get_contents().ok()
}
/// Exports the challenge string from the SPKAC data.
pub fn export_challenge(data: &[u8]) -> Option<Vec<u8>> {
let spki = NetscapeSpki::from_base64(data).ok()?;
spki.get_challenge().ok()
}
#[cfg(test)]
mod tests {
use crate::spki::verify_spkac;
#[test]
fn test_md_spkac() {
// md4 and md5 based signatures are not supported.
// https://github.com/aws/aws-lc/commit/7e28b9ee89d85fbc80b69bc0eeb0070de81ac563
let spkac_data = br#"MIICUzCCATswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC33FiIiiexwLe/P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+nkSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zMgS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMjdPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqctvhmylLH1AgMBAAEWE3RoaXMtaXMtYS1jaGFsbGVuZ2UwDQYJKoZIhvcNAQEEBQADggEBAIozmeW1kfDfAVwRQKileZGLRGCD7AjdHLYEe16xTBPve8Af1bDOyuWsAm4qQLYA4FAFROiKeGqxCtIErEvm87/09tCfF1My/1Uj+INjAk39DK9J9alLlTsrwSgd1lb3YlXY7TyitCmh7iXLo4pVhA2chNA3njiMq3CUpSvGbpzrESL2dv97lv590gUD988wkTDVyYsf0T8+X0Kww3AgPWGji+2f2i5/jTfD/s1lK1nqi7ZxFm0pGZoy1MJ51SCEy7Y82ajroI+5786nC02mo9ak7samca4YDZOoxN4d3tax4B/HDF5dqJSm1/31xYLDTfujCM5FkSjRc4m6hnriEkc="#;
assert!(!verify_spkac(spkac_data));
}
#[test]
fn test_spkac_verify() {
let spkac = b"MIICUzCCATswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXzfKgGnkkOF7+VwMzGpiWy5nna/VGJOfPBsCVg5WooJHN9nAFyqLxoV0WyhwvIdHhIgcTX2L4BHRa+4B0zb4stRHK02ZknJvionK4kBfa+k7Q4DzasW3ulLCTXPLVBKzW9QSzE4Wult17BX6uSUy3Bpr/Nuk6B4Ja3JnFpdSYmJbWP55kRONFBZYPCXr7T8k6hzEHcevFE/PUi6IU+LKiwyGH5KXAUzRbMtqbZLn/rEAmEBxmv/z/+shAwiRE8s9RqBi+pVdwqWdw6ibNkbM7G3j4CMyfAk7EOpGf5loRIrVWB4XrVYWb2EQ6sd9LfiQ9GwqlFYw006MUo6nxoEtNAgMBAAEWE3RoaXMtaXMtYS1jaGFsbGVuZ2UwDQYJKoZIhvcNAQELBQADggEBAHUw1UoZjG7TCb/JhFo5p8XIFeizGEwYoqttBoVTQ+MeCfnNoLLeAyId0atb2jPnYsI25Z/PHHV1N9t0L/NelY3rZC/Z00Wx8IGeslnGXXbqwnp36Umb0r2VmxTr8z1QaToGyOQXp4Xor9qbQFoANIivyVUYsuqJ1FnDJCC/jBPo4IWiQbTst331v2fiVdV+/XUh9AIjcm4085b65HjFwLxDeWhbgAZ+UfhqBbTVA1K8uUqS8e3gbeaNstZvnclxZ3PlHSk8v1RdIG4e5ThTOwPH5u/7KKeafn9SwgY/Q8KqaVfHHCv1IeVlijamjnyFhWc35kGlBUNgLOnWAOE3GsM=";
assert!(verify_spkac(spkac));
}
}

View file

@ -7,6 +7,9 @@
"echo.js", "echo.js",
"elipses.txt", "elipses.txt",
"empty.txt", "empty.txt",
"keys/rsa_public.pem",
"keys/rsa_spkac_invalid.spkac",
"keys/rsa_spkac.spkac",
"x.txt" "x.txt"
], ],
"internet": [ "internet": [
@ -380,6 +383,7 @@
"test-console-sync-write-error.js", "test-console-sync-write-error.js",
"test-console-table.js", "test-console-table.js",
"test-console-tty-colors.js", "test-console-tty-colors.js",
"test-crypto-certificate.js",
"test-crypto-dh-constructor.js", "test-crypto-dh-constructor.js",
"test-crypto-dh-errors.js", "test-crypto-dh-errors.js",
"test-crypto-dh-odd-key.js", "test-crypto-dh-odd-key.js",

View file

@ -488,7 +488,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
- [parallel/test-crypto-async-sign-verify.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-async-sign-verify.js) - [parallel/test-crypto-async-sign-verify.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-async-sign-verify.js)
- [parallel/test-crypto-authenticated-stream.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-authenticated-stream.js) - [parallel/test-crypto-authenticated-stream.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-authenticated-stream.js)
- [parallel/test-crypto-authenticated.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-authenticated.js) - [parallel/test-crypto-authenticated.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-authenticated.js)
- [parallel/test-crypto-certificate.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-certificate.js)
- [parallel/test-crypto-cipheriv-decipheriv.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-cipheriv-decipheriv.js) - [parallel/test-crypto-cipheriv-decipheriv.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-cipheriv-decipheriv.js)
- [parallel/test-crypto-classes.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-classes.js) - [parallel/test-crypto-classes.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-classes.js)
- [parallel/test-crypto-des3-wrap.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-des3-wrap.js) - [parallel/test-crypto-des3-wrap.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-crypto-des3-wrap.js)

View file

@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl83yoBp5JDhe/lcDMxqY
lsuZ52v1RiTnzwbAlYOVqKCRzfZwBcqi8aFdFsocLyHR4SIHE19i+AR0WvuAdM2+
LLURytNmZJyb4qJyuJAX2vpO0OA82rFt7pSwk1zy1QSs1vUEsxOFrpbdewV+rklM
twaa/zbpOgeCWtyZxaXUmJiW1j+eZETjRQWWDwl6+0/JOocxB3HrxRPz1IuiFPiy
osMhh+SlwFM0WzLam2S5/6xAJhAcZr/8//rIQMIkRPLPUagYvqVXcKlncOomzZGz
Oxt4+AjMnwJOxDqRn+ZaESK1VgeF61WFm9hEOrHfS34kPRsKpRWMNNOjFKOp8aBL
TQIDAQAB
-----END PUBLIC KEY-----

View file

@ -0,0 +1 @@
MIICUzCCATswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCXzfKgGnkkOF7+VwMzGpiWy5nna/VGJOfPBsCVg5WooJHN9nAFyqLxoV0WyhwvIdHhIgcTX2L4BHRa+4B0zb4stRHK02ZknJvionK4kBfa+k7Q4DzasW3ulLCTXPLVBKzW9QSzE4Wult17BX6uSUy3Bpr/Nuk6B4Ja3JnFpdSYmJbWP55kRONFBZYPCXr7T8k6hzEHcevFE/PUi6IU+LKiwyGH5KXAUzRbMtqbZLn/rEAmEBxmv/z/+shAwiRE8s9RqBi+pVdwqWdw6ibNkbM7G3j4CMyfAk7EOpGf5loRIrVWB4XrVYWb2EQ6sd9LfiQ9GwqlFYw006MUo6nxoEtNAgMBAAEWE3RoaXMtaXMtYS1jaGFsbGVuZ2UwDQYJKoZIhvcNAQELBQADggEBAHUw1UoZjG7TCb/JhFo5p8XIFeizGEwYoqttBoVTQ+MeCfnNoLLeAyId0atb2jPnYsI25Z/PHHV1N9t0L/NelY3rZC/Z00Wx8IGeslnGXXbqwnp36Umb0r2VmxTr8z1QaToGyOQXp4Xor9qbQFoANIivyVUYsuqJ1FnDJCC/jBPo4IWiQbTst331v2fiVdV+/XUh9AIjcm4085b65HjFwLxDeWhbgAZ+UfhqBbTVA1K8uUqS8e3gbeaNstZvnclxZ3PlHSk8v1RdIG4e5ThTOwPH5u/7KKeafn9SwgY/Q8KqaVfHHCv1IeVlijamjnyFhWc35kGlBUNgLOnWAOE3GsM=

View file

@ -0,0 +1 @@
UzCCATswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC33FiIiiexwLe/P8DZx5HsqFlmUO7/lvJ7necJVNwqdZ3ax5jpQB0p6uxfqeOvzcN3k5V7UFb/Am+nkSNZMAZhsWzCU2Z4Pjh50QYz3f0Hour7/yIGStOLyYY3hgLK2K8TbhgjQPhdkw9+QtKlpvbL8fLgONAoGrVOFnRQGcr70iFffsm79mgZhKVMgYiHPJqJgGHvCtkGg9zMgS7p63+Q3ZWedtFS2RhMX3uCBy/mH6EOlRCNBbRmA4xxNzyf5GQaki3T+Iz9tOMjdPP+CwV2LqEdylmBuik8vrfTb3qIHLKKBAI8lXN26wWtA3kN4L7NP+cbKlCRlqctvhmylLH1AgMBAAEWE3RoaXMtaXMtYS1jaGFsbGVuZ2UwDQYJKoZIhvcNAQEEBQADggEBAIozmeW1kfDfAVwRQKileZGLRGCD7AjdHLYEe16xTBPve8Af1bDOyuWsAm4qQLYA4FAFROiKeGqxCtIErEvm87/09tCfF1My/1Uj+INjAk39DK9J9alLlTsrwSgd1lb3YlXY7TyitCmh7iXLo4pVhA2chNA3njiMq3CUpSvGbpzrESL2dv97lv590gUD988wkTDVyYsf0T8+X0Kww3AgPWGji+2f2i5/jTfD/s1lK1nqi7ZxFm0pGZoy1MJ51SCEy7Y82ajroI+5786nC02mo9ak7samca4YDZOoxN4d3tax4B/HDF5dqJSm1/31xYLDTfujCM5FkSjRc4m6hnriEkc=

View file

@ -0,0 +1,128 @@
// deno-fmt-ignore-file
// deno-lint-ignore-file
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
// Taken from Node 23.9.0
// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const crypto = require('crypto');
const { Certificate } = crypto;
const fixtures = require('../common/fixtures');
// Test Certificates
const spkacValid = fixtures.readKey('rsa_spkac.spkac');
const spkacChallenge = 'this-is-a-challenge';
const spkacFail = fixtures.readKey('rsa_spkac_invalid.spkac');
const spkacPublicPem = fixtures.readKey('rsa_public.pem');
function copyArrayBuffer(buf) {
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}
function checkMethods(certificate) {
assert.strictEqual(certificate.verifySpkac(spkacValid), true);
assert.strictEqual(certificate.verifySpkac(spkacFail), false);
assert.strictEqual(
stripLineEndings(certificate.exportPublicKey(spkacValid).toString('utf8')),
stripLineEndings(spkacPublicPem.toString('utf8'))
);
assert.strictEqual(certificate.exportPublicKey(spkacFail), '');
assert.strictEqual(
certificate.exportChallenge(spkacValid).toString('utf8'),
spkacChallenge
);
assert.strictEqual(certificate.exportChallenge(spkacFail), '');
const ab = copyArrayBuffer(spkacValid);
assert.strictEqual(certificate.verifySpkac(ab), true);
assert.strictEqual(certificate.verifySpkac(new Uint8Array(ab)), true);
assert.strictEqual(certificate.verifySpkac(new DataView(ab)), true);
}
{
// Test maximum size of input buffer
let buf;
let skip = false;
try {
buf = Buffer.alloc(2 ** 31);
} catch {
// The allocation may fail on some systems. That is expected due
// to architecture and memory constraints. If it does, go ahead
// and skip this test.
skip = true;
}
if (!skip) {
assert.throws(
() => Certificate.verifySpkac(buf), {
code: 'ERR_OUT_OF_RANGE'
});
assert.throws(
() => Certificate.exportChallenge(buf), {
code: 'ERR_OUT_OF_RANGE'
});
assert.throws(
() => Certificate.exportPublicKey(buf), {
code: 'ERR_OUT_OF_RANGE'
});
}
}
{
// Test instance methods
checkMethods(new Certificate());
}
{
// Test static methods
checkMethods(Certificate);
}
function stripLineEndings(obj) {
return obj.replace(/\n/g, '');
}
// Direct call Certificate() should return instance
assert(Certificate() instanceof Certificate);
[1, {}, [], Infinity, true, undefined, null].forEach((val) => {
assert.throws(
() => Certificate.verifySpkac(val),
{ code: 'ERR_INVALID_ARG_TYPE' }
);
});
[1, {}, [], Infinity, true, undefined, null].forEach((val) => {
const errObj = { code: 'ERR_INVALID_ARG_TYPE' };
assert.throws(() => Certificate.exportPublicKey(val), errObj);
assert.throws(() => Certificate.exportChallenge(val), errObj);
});