mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 12:19:12 +00:00
fix(ext/node): implement Certificate API (#29828)
This commit is contained in:
parent
0f08eb076b
commit
d15581fb19
18 changed files with 570 additions and 13 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1900,6 +1900,14 @@ dependencies = [
|
|||
"x25519-dalek",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_crypto_provider"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aws-lc-rs",
|
||||
"aws-lc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_doc"
|
||||
version = "0.178.0"
|
||||
|
@ -2345,6 +2353,7 @@ dependencies = [
|
|||
"ctr",
|
||||
"data-encoding",
|
||||
"deno_core",
|
||||
"deno_crypto_provider",
|
||||
"deno_error",
|
||||
"deno_fetch",
|
||||
"deno_fs",
|
||||
|
|
|
@ -33,6 +33,7 @@ members = [
|
|||
"ext/websocket",
|
||||
"ext/webstorage",
|
||||
"libs/config",
|
||||
"libs/crypto",
|
||||
"libs/node_resolver",
|
||||
"libs/npm_cache",
|
||||
"libs/npm_installer",
|
||||
|
@ -113,6 +114,7 @@ denort_helper = { version = "0.5.0", path = "./ext/rt_helper" }
|
|||
# workspace libraries
|
||||
deno_bench_util = { version = "0.201.0", path = "./bench_util" }
|
||||
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_lib = { version = "0.25.0", path = "./cli/lib" }
|
||||
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-trait = "0.1.73"
|
||||
aws-lc-rs = { version = "1.0.0" }
|
||||
aws-lc-sys = { version = "0.26.0" }
|
||||
base32 = "=0.5.1"
|
||||
base64 = "0.22.1"
|
||||
base64-simd = "0.8"
|
||||
|
|
|
@ -30,6 +30,7 @@ const-oid.workspace = true
|
|||
ctr.workspace = true
|
||||
data-encoding.workspace = true
|
||||
deno_core.workspace = true
|
||||
deno_crypto_provider.workspace = true
|
||||
deno_error.workspace = true
|
||||
deno_fetch.workspace = true
|
||||
deno_fs.workspace = true
|
||||
|
|
|
@ -314,6 +314,9 @@ deno_core::extension!(deno_node,
|
|||
ops::crypto::op_node_sign_ed25519,
|
||||
ops::crypto::op_node_verify,
|
||||
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_ed_raw,
|
||||
ops::crypto::keys::op_node_create_rsa_jwk,
|
||||
|
|
|
@ -1140,3 +1140,46 @@ pub fn op_node_verify_ed25519(
|
|||
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -1,23 +1,59 @@
|
|||
// Copyright 2018-2025 the Deno authors. 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 { BinaryLike } from "ext:deno_node/internal/crypto/types.ts";
|
||||
import { getArrayBufferOrView } from "ext:deno_node/internal/crypto/keys.ts";
|
||||
|
||||
export class Certificate {
|
||||
static Certificate = Certificate;
|
||||
static exportChallenge(_spkac: BinaryLike, _encoding?: string): Buffer {
|
||||
notImplemented("crypto.Certificate.exportChallenge");
|
||||
}
|
||||
// The functions contained in this file cover the SPKAC format
|
||||
// (also referred to as Netscape SPKI). A general description of
|
||||
// the format can be found at https://en.wikipedia.org/wiki/SPKAC
|
||||
|
||||
static exportPublicKey(_spkac: BinaryLike, _encoding?: string): Buffer {
|
||||
notImplemented("crypto.Certificate.exportPublicKey");
|
||||
}
|
||||
function verifySpkac(spkac, encoding) {
|
||||
return op_node_verify_spkac(
|
||||
getArrayBufferOrView(spkac, "spkac", encoding),
|
||||
);
|
||||
}
|
||||
|
||||
static verifySpkac(_spkac: BinaryLike, _encoding?: string): boolean {
|
||||
notImplemented("crypto.Certificate.verifySpkac");
|
||||
function exportPublicKey(spkac, encoding) {
|
||||
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;
|
||||
|
|
|
@ -96,6 +96,13 @@ export const getArrayBufferOrView = hideStackFrames(
|
|||
}
|
||||
return Buffer.from(buffer, encoding);
|
||||
}
|
||||
if (buffer instanceof DataView) {
|
||||
return new Uint8Array(
|
||||
buffer.buffer,
|
||||
buffer.byteOffset,
|
||||
buffer.byteLength,
|
||||
);
|
||||
}
|
||||
if (!isArrayBufferView(buffer)) {
|
||||
throw new ERR_INVALID_ARG_TYPE(
|
||||
name,
|
||||
|
|
16
libs/crypto/Cargo.toml
Normal file
16
libs/crypto/Cargo.toml
Normal 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
3
libs/crypto/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# `deno_crypto`
|
||||
|
||||
Rust cryptography provider for Deno WebCrypto and `node:crypto`.
|
97
libs/crypto/ffi.rs
Normal file
97
libs/crypto/ffi.rs
Normal 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
9
libs/crypto/lib.rs
Normal 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
188
libs/crypto/spki.rs
Normal 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));
|
||||
}
|
||||
}
|
|
@ -7,6 +7,9 @@
|
|||
"echo.js",
|
||||
"elipses.txt",
|
||||
"empty.txt",
|
||||
"keys/rsa_public.pem",
|
||||
"keys/rsa_spkac_invalid.spkac",
|
||||
"keys/rsa_spkac.spkac",
|
||||
"x.txt"
|
||||
],
|
||||
"internet": [
|
||||
|
@ -380,6 +383,7 @@
|
|||
"test-console-sync-write-error.js",
|
||||
"test-console-table.js",
|
||||
"test-console-tty-colors.js",
|
||||
"test-crypto-certificate.js",
|
||||
"test-crypto-dh-constructor.js",
|
||||
"test-crypto-dh-errors.js",
|
||||
"test-crypto-dh-odd-key.js",
|
||||
|
|
|
@ -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-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-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-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)
|
||||
|
|
9
tests/node_compat/test/fixtures/keys/rsa_public.pem
vendored
Normal file
9
tests/node_compat/test/fixtures/keys/rsa_public.pem
vendored
Normal 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-----
|
1
tests/node_compat/test/fixtures/keys/rsa_spkac.spkac
vendored
Normal file
1
tests/node_compat/test/fixtures/keys/rsa_spkac.spkac
vendored
Normal 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=
|
1
tests/node_compat/test/fixtures/keys/rsa_spkac_invalid.spkac
vendored
Normal file
1
tests/node_compat/test/fixtures/keys/rsa_spkac_invalid.spkac
vendored
Normal 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=
|
128
tests/node_compat/test/parallel/test-crypto-certificate.js
Normal file
128
tests/node_compat/test/parallel/test-crypto-certificate.js
Normal 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);
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue