Skip to content

Commit

Permalink
fix(ext/node): sign with PEM private keys (denoland#21287)
Browse files Browse the repository at this point in the history
Add support for signing with a RSA PEM private key: `pkcs8` and `pkcs1`.

Fixes denoland#18972
Ref denoland#21124 

Verified fix with `npm:sshpk`. Unverfied but fixes
`npm:google-auth-library`, `npm:web-push` & `oracle/oci-typescript-sdk`

---------

Signed-off-by: Divy Srivastava <[email protected]>
  • Loading branch information
littledivy committed Dec 3, 2023
1 parent 39c7d8d commit 32438d2
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 42 deletions.
46 changes: 45 additions & 1 deletion cli/tests/unit_node/crypto/crypto_sign_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

import { assert, assertEquals } from "../../../../test_util/std/assert/mod.ts";
import {
assert,
assertEquals,
} from "../../../../test_util/std/testing/asserts.ts";
import { createSign, createVerify, sign, verify } from "node:crypto";
import { Buffer } from "node:buffer";

Expand All @@ -9,6 +12,11 @@ const rsaPrivatePem = Buffer.from(
new URL("../testdata/rsa_private.pem", import.meta.url),
),
);
const rsaPrivatePkcs1Pem = Buffer.from(
await Deno.readFile(
new URL("../testdata/rsa_private_pkcs1.pem", import.meta.url),
),
);
const rsaPublicPem = Buffer.from(
await Deno.readFile(
new URL("../testdata/rsa_public.pem", import.meta.url),
Expand Down Expand Up @@ -86,3 +94,39 @@ Deno.test({
}
},
});

Deno.test({
name: "crypto.createPrivateKey|sign - RSA PEM",
fn() {
for (const testCase of table) {
for (const algorithm of testCase.algorithms) {
assertEquals(
createSign(algorithm).update(data).sign(rsaPrivatePem, "hex"),
testCase.signature,
);
assertEquals(
sign(algorithm, data, rsaPrivatePem),
Buffer.from(testCase.signature, "hex"),
);
}
}
},
});

Deno.test({
name: "crypto.createPrivateKey|sign - RSA PKCS1 PEM",
fn() {
for (const testCase of table) {
for (const algorithm of testCase.algorithms) {
assertEquals(
createSign(algorithm).update(data).sign(rsaPrivatePkcs1Pem, "hex"),
testCase.signature,
);
assertEquals(
sign(algorithm, data, rsaPrivatePkcs1Pem),
Buffer.from(testCase.signature, "hex"),
);
}
}
},
});
27 changes: 27 additions & 0 deletions cli/tests/unit_node/testdata/rsa_private_pkcs1.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAt9xYiIonscC3vz/A2ceR7KhZZlDu/5bye53nCVTcKnWd2seY
6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq+/8i
BkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2y/Hy4DjQKBq1ThZ0UBnK+9IhX37J
u/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/kN2VnnbRUtkYTF97ggcv5h+hDpUQ
jQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsFdi6hHcpZgbopPL630296iByyigQC
PJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx9QIDAQABAoIBAQCS2erYu8gyoGPi
3E/zYgQ6ishFAZWzDWSFubwD5wSm4SSAzvViL/RbO6kqS25xR569DmLRiHzD17VI
mJMsNECUnPrqR2TL256OJZaXrNHh3I1lUwVhEzjeKMsL4/ys+d70XPXoiocVblVs
moDXEIGEqa48ywPvVE3Fngeuxrsq3/GCVBNiwtt0YjAOZxmKEh31UZdHO+YI+wNF
/Z8KQCPscN5HGlR0SIQOlqMANz49aKStrevdvjS1UcpabzDEkuK84g3saJhcpAhb
pGFmAf5GTjkkhE0rE1qDF15dSqrKGfCFtOjUeK17SIEN7E322ChmTReZ1hYGfoSV
cdFntUINAoGBAPFKL5QeJ6wZu8R/ru11wTG6sQA0Jub2hGccPXpbnPrT+3CACOLI
JTCLy/xTKW3dqRHj/wZEe+jUw88w7jwGb1BkWr4BI8tDvY9jQLP1jyuLWRfrxXbp
4Z0oeBBwBeCI/ZG7FIvdDTqWxn1aj3Tmh6s4ByqEdtwrrrJPcBUNl01fAoGBAMMR
3RGE/ca6X6xz6kgUD6TtHVhiiRJK1jm/u+q0n7i/MBkeDgTZkHYS7lPc0yIdtqaI
Plz5yzwHnAvuMrv8LSdkjwioig2yQa3tAij8kXxqs7wN5418DMV2s1OJBrPthYPs
bv4im2iI8V63JQS4ZMYQbckq8ABYccTpOnxXDy0rAoGBAKkvzHa+QjERhjB9GyoT
1FhLQIsVBmYSWrp1+cGO9V6HPxoeHJzvm+wTSf/uS/FmaINL6+j4Ii4a6gWgmJts
I6cqBtqNsAx5vjQJczf8KdxthBYa0sXTrsfktXNJKUXMqIgDtp9vazQ2vozs8AQX
FPAAhD3SzgkJdCBBRSTt97ZfAoGAWAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCD
dCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP/Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNm
bDXFPdG0G3hzQovx/9fajiRV4DWghLHeT9wzJfZabRRiI0VQR472300AVEeX4vgb
rDBn600CgYEAk7czBCT9rHn/PNwCa17hlTy88C4vXkwbz83Oa+aX5L4e5gw5lhcR
2ZuZHLb2r6oMt9rlD7EIDItSs+u21LOXWPTAlazdnpYUyw/CzogM/PN+qNwMRXn5
uXFFhmlP2mVg2EdELTahXch8kWqHaCSX53yvqCtRKu/j76V31TfQZGM=
-----END RSA PRIVATE KEY-----
47 changes: 26 additions & 21 deletions ext/node/ops/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use rand::distributions::Distribution;
use rand::distributions::Uniform;
use rand::thread_rng;
use rand::Rng;
use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::pkcs8;
use rsa::pkcs8::der::asn1;
use rsa::pkcs8::der::Decode;
Expand Down Expand Up @@ -363,23 +364,32 @@ pub fn op_node_sign(
#[buffer] digest: &[u8],
#[string] digest_type: &str,
#[serde] key: StringOrBuffer,
#[string] key_type: &str,
#[string] key_format: &str,
#[string] _type: &str,
#[string] format: &str,
) -> Result<ToJsBuffer, AnyError> {
match key_type {
"rsa" => {
let (label, doc) =
pkcs8::SecretDocument::from_pem(std::str::from_utf8(&key).unwrap())?;

let oid;
let pkey = match format {
"pem" => {
if label == "PRIVATE KEY" {
let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
oid = pk_info.algorithm.oid;
pk_info.private_key
} else if label == "RSA PRIVATE KEY" {
oid = RSA_ENCRYPTION_OID;
doc.as_bytes()
} else {
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 = match key_format {
"pem" => RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)
.map_err(|_| type_error("Invalid RSA private key"))?,
// TODO(kt3k): Support der and jwk formats
_ => {
return Err(type_error(format!(
"Unsupported key format: {}",
key_format
)))
}
};
let key = RsaPrivateKey::from_pkcs1_der(pkey)?;
Ok(
match digest_type {
"sha224" => {
Expand Down Expand Up @@ -408,10 +418,7 @@ pub fn op_node_sign(
.into(),
)
}
_ => Err(type_error(format!(
"Signing with {} keys is not supported yet",
key_type
))),
_ => Err(type_error("Unsupported signing key")),
}
}

Expand Down Expand Up @@ -1345,8 +1352,6 @@ fn parse_private_key(
format: &str,
type_: &str,
) -> Result<pkcs8::SecretDocument, AnyError> {
use rsa::pkcs1::DecodeRsaPrivateKey;

match format {
"pem" => {
let (label, doc) =
Expand Down
3 changes: 2 additions & 1 deletion ext/node/polyfills/internal/crypto/cipher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import {
export function isStringOrBuffer(val) {
return typeof val === "string" ||
isArrayBufferView(val) ||
isAnyArrayBuffer(val);
isAnyArrayBuffer(val) ||
Buffer.isBuffer(val);
}

const { ops, encode } = globalThis.__bootstrap.core;
Expand Down
2 changes: 1 addition & 1 deletion ext/node/polyfills/internal/crypto/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export interface JsonWebKeyInput {
format: "jwk";
}

function prepareAsymmetricKey(key) {
export function prepareAsymmetricKey(key) {
if (isStringOrBuffer(key)) {
return { format: "pem", data: getArrayBufferOrView(key, "key") };
} else if (typeof key == "object") {
Expand Down
23 changes: 5 additions & 18 deletions ext/node/polyfills/internal/crypto/sig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import type {
PublicKeyInput,
} from "ext:deno_node/internal/crypto/types.ts";
import {
getKeyMaterial,
KeyObject,
prepareAsymmetricKey,
} from "ext:deno_node/internal/crypto/keys.ts";
import { createHash, Hash } from "ext:deno_node/internal/crypto/hash.ts";
import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts";
Expand Down Expand Up @@ -80,26 +80,13 @@ export class SignImpl extends Writable {
privateKey: BinaryLike | SignKeyObjectInput | SignPrivateKeyInput,
encoding?: BinaryToTextEncoding,
): Buffer | string {
let keyData: Uint8Array;
let keyType: KeyType;
let keyFormat: KeyFormat;
if (typeof privateKey === "string" || isArrayBufferView(privateKey)) {
// if the key is BinaryLike, interpret it as a PEM encoded RSA key
// deno-lint-ignore no-explicit-any
keyData = privateKey as any;
keyType = "rsa";
keyFormat = "pem";
} else {
keyData = getKeyMaterial(privateKey);
keyType = "rsa";
keyFormat = "pem";
}
const { data, format, type } = prepareAsymmetricKey(privateKey);
const ret = Buffer.from(ops.op_node_sign(
this.hash.digest(),
this.#digestType,
keyData!,
keyType,
keyFormat,
data!,
type,
format,
));
return encoding ? ret.toString(encoding) : ret;
}
Expand Down

0 comments on commit 32438d2

Please sign in to comment.