Skip to content

Commit 32438d2

Browse files
authored
fix(ext/node): sign with PEM private keys (#21287)
Add support for signing with a RSA PEM private key: `pkcs8` and `pkcs1`. Fixes #18972 Ref #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]>
1 parent 39c7d8d commit 32438d2

File tree

6 files changed

+106
-42
lines changed

6 files changed

+106
-42
lines changed

cli/tests/unit_node/crypto/crypto_sign_test.ts

+45-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
22

3-
import { assert, assertEquals } from "../../../../test_util/std/assert/mod.ts";
3+
import {
4+
assert,
5+
assertEquals,
6+
} from "../../../../test_util/std/testing/asserts.ts";
47
import { createSign, createVerify, sign, verify } from "node:crypto";
58
import { Buffer } from "node:buffer";
69

@@ -9,6 +12,11 @@ const rsaPrivatePem = Buffer.from(
912
new URL("../testdata/rsa_private.pem", import.meta.url),
1013
),
1114
);
15+
const rsaPrivatePkcs1Pem = Buffer.from(
16+
await Deno.readFile(
17+
new URL("../testdata/rsa_private_pkcs1.pem", import.meta.url),
18+
),
19+
);
1220
const rsaPublicPem = Buffer.from(
1321
await Deno.readFile(
1422
new URL("../testdata/rsa_public.pem", import.meta.url),
@@ -86,3 +94,39 @@ Deno.test({
8694
}
8795
},
8896
});
97+
98+
Deno.test({
99+
name: "crypto.createPrivateKey|sign - RSA PEM",
100+
fn() {
101+
for (const testCase of table) {
102+
for (const algorithm of testCase.algorithms) {
103+
assertEquals(
104+
createSign(algorithm).update(data).sign(rsaPrivatePem, "hex"),
105+
testCase.signature,
106+
);
107+
assertEquals(
108+
sign(algorithm, data, rsaPrivatePem),
109+
Buffer.from(testCase.signature, "hex"),
110+
);
111+
}
112+
}
113+
},
114+
});
115+
116+
Deno.test({
117+
name: "crypto.createPrivateKey|sign - RSA PKCS1 PEM",
118+
fn() {
119+
for (const testCase of table) {
120+
for (const algorithm of testCase.algorithms) {
121+
assertEquals(
122+
createSign(algorithm).update(data).sign(rsaPrivatePkcs1Pem, "hex"),
123+
testCase.signature,
124+
);
125+
assertEquals(
126+
sign(algorithm, data, rsaPrivatePkcs1Pem),
127+
Buffer.from(testCase.signature, "hex"),
128+
);
129+
}
130+
}
131+
},
132+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEpQIBAAKCAQEAt9xYiIonscC3vz/A2ceR7KhZZlDu/5bye53nCVTcKnWd2seY
3+
6UAdKersX6njr83Dd5OVe1BW/wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq+/8i
4+
BkrTi8mGN4YCytivE24YI0D4XZMPfkLSpab2y/Hy4DjQKBq1ThZ0UBnK+9IhX37J
5+
u/ZoGYSlTIGIhzyaiYBh7wrZBoPczIEu6et/kN2VnnbRUtkYTF97ggcv5h+hDpUQ
6+
jQW0ZgOMcTc8n+RkGpIt0/iM/bTjI3Tz/gsFdi6hHcpZgbopPL630296iByyigQC
7+
PJVzdusFrQN5DeC+zT/nGypQkZanLb4ZspSx9QIDAQABAoIBAQCS2erYu8gyoGPi
8+
3E/zYgQ6ishFAZWzDWSFubwD5wSm4SSAzvViL/RbO6kqS25xR569DmLRiHzD17VI
9+
mJMsNECUnPrqR2TL256OJZaXrNHh3I1lUwVhEzjeKMsL4/ys+d70XPXoiocVblVs
10+
moDXEIGEqa48ywPvVE3Fngeuxrsq3/GCVBNiwtt0YjAOZxmKEh31UZdHO+YI+wNF
11+
/Z8KQCPscN5HGlR0SIQOlqMANz49aKStrevdvjS1UcpabzDEkuK84g3saJhcpAhb
12+
pGFmAf5GTjkkhE0rE1qDF15dSqrKGfCFtOjUeK17SIEN7E322ChmTReZ1hYGfoSV
13+
cdFntUINAoGBAPFKL5QeJ6wZu8R/ru11wTG6sQA0Jub2hGccPXpbnPrT+3CACOLI
14+
JTCLy/xTKW3dqRHj/wZEe+jUw88w7jwGb1BkWr4BI8tDvY9jQLP1jyuLWRfrxXbp
15+
4Z0oeBBwBeCI/ZG7FIvdDTqWxn1aj3Tmh6s4ByqEdtwrrrJPcBUNl01fAoGBAMMR
16+
3RGE/ca6X6xz6kgUD6TtHVhiiRJK1jm/u+q0n7i/MBkeDgTZkHYS7lPc0yIdtqaI
17+
Plz5yzwHnAvuMrv8LSdkjwioig2yQa3tAij8kXxqs7wN5418DMV2s1OJBrPthYPs
18+
bv4im2iI8V63JQS4ZMYQbckq8ABYccTpOnxXDy0rAoGBAKkvzHa+QjERhjB9GyoT
19+
1FhLQIsVBmYSWrp1+cGO9V6HPxoeHJzvm+wTSf/uS/FmaINL6+j4Ii4a6gWgmJts
20+
I6cqBtqNsAx5vjQJczf8KdxthBYa0sXTrsfktXNJKUXMqIgDtp9vazQ2vozs8AQX
21+
FPAAhD3SzgkJdCBBRSTt97ZfAoGAWAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCD
22+
dCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP/Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNm
23+
bDXFPdG0G3hzQovx/9fajiRV4DWghLHeT9wzJfZabRRiI0VQR472300AVEeX4vgb
24+
rDBn600CgYEAk7czBCT9rHn/PNwCa17hlTy88C4vXkwbz83Oa+aX5L4e5gw5lhcR
25+
2ZuZHLb2r6oMt9rlD7EIDItSs+u21LOXWPTAlazdnpYUyw/CzogM/PN+qNwMRXn5
26+
uXFFhmlP2mVg2EdELTahXch8kWqHaCSX53yvqCtRKu/j76V31TfQZGM=
27+
-----END RSA PRIVATE KEY-----

ext/node/ops/crypto/mod.rs

+26-21
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use rand::distributions::Distribution;
1919
use rand::distributions::Uniform;
2020
use rand::thread_rng;
2121
use rand::Rng;
22+
use rsa::pkcs1::DecodeRsaPrivateKey;
2223
use rsa::pkcs8;
2324
use rsa::pkcs8::der::asn1;
2425
use rsa::pkcs8::der::Decode;
@@ -363,23 +364,32 @@ pub fn op_node_sign(
363364
#[buffer] digest: &[u8],
364365
#[string] digest_type: &str,
365366
#[serde] key: StringOrBuffer,
366-
#[string] key_type: &str,
367-
#[string] key_format: &str,
367+
#[string] _type: &str,
368+
#[string] format: &str,
368369
) -> Result<ToJsBuffer, AnyError> {
369-
match key_type {
370-
"rsa" => {
370+
let (label, doc) =
371+
pkcs8::SecretDocument::from_pem(std::str::from_utf8(&key).unwrap())?;
372+
373+
let oid;
374+
let pkey = match format {
375+
"pem" => {
376+
if label == "PRIVATE KEY" {
377+
let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
378+
oid = pk_info.algorithm.oid;
379+
pk_info.private_key
380+
} else if label == "RSA PRIVATE KEY" {
381+
oid = RSA_ENCRYPTION_OID;
382+
doc.as_bytes()
383+
} else {
384+
return Err(type_error("Invalid PEM label"));
385+
}
386+
}
387+
_ => return Err(type_error("Unsupported key format")),
388+
};
389+
match oid {
390+
RSA_ENCRYPTION_OID => {
371391
use rsa::pkcs1v15::SigningKey;
372-
let key = match key_format {
373-
"pem" => RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)
374-
.map_err(|_| type_error("Invalid RSA private key"))?,
375-
// TODO(kt3k): Support der and jwk formats
376-
_ => {
377-
return Err(type_error(format!(
378-
"Unsupported key format: {}",
379-
key_format
380-
)))
381-
}
382-
};
392+
let key = RsaPrivateKey::from_pkcs1_der(pkey)?;
383393
Ok(
384394
match digest_type {
385395
"sha224" => {
@@ -408,10 +418,7 @@ pub fn op_node_sign(
408418
.into(),
409419
)
410420
}
411-
_ => Err(type_error(format!(
412-
"Signing with {} keys is not supported yet",
413-
key_type
414-
))),
421+
_ => Err(type_error("Unsupported signing key")),
415422
}
416423
}
417424

@@ -1345,8 +1352,6 @@ fn parse_private_key(
13451352
format: &str,
13461353
type_: &str,
13471354
) -> Result<pkcs8::SecretDocument, AnyError> {
1348-
use rsa::pkcs1::DecodeRsaPrivateKey;
1349-
13501355
match format {
13511356
"pem" => {
13521357
let (label, doc) =

ext/node/polyfills/internal/crypto/cipher.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ import {
3131
export function isStringOrBuffer(val) {
3232
return typeof val === "string" ||
3333
isArrayBufferView(val) ||
34-
isAnyArrayBuffer(val);
34+
isAnyArrayBuffer(val) ||
35+
Buffer.isBuffer(val);
3536
}
3637

3738
const { ops, encode } = globalThis.__bootstrap.core;

ext/node/polyfills/internal/crypto/keys.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export interface JsonWebKeyInput {
210210
format: "jwk";
211211
}
212212

213-
function prepareAsymmetricKey(key) {
213+
export function prepareAsymmetricKey(key) {
214214
if (isStringOrBuffer(key)) {
215215
return { format: "pem", data: getArrayBufferOrView(key, "key") };
216216
} else if (typeof key == "object") {

ext/node/polyfills/internal/crypto/sig.ts

+5-18
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import type {
2020
PublicKeyInput,
2121
} from "ext:deno_node/internal/crypto/types.ts";
2222
import {
23-
getKeyMaterial,
2423
KeyObject,
24+
prepareAsymmetricKey,
2525
} from "ext:deno_node/internal/crypto/keys.ts";
2626
import { createHash, Hash } from "ext:deno_node/internal/crypto/hash.ts";
2727
import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts";
@@ -80,26 +80,13 @@ export class SignImpl extends Writable {
8080
privateKey: BinaryLike | SignKeyObjectInput | SignPrivateKeyInput,
8181
encoding?: BinaryToTextEncoding,
8282
): Buffer | string {
83-
let keyData: Uint8Array;
84-
let keyType: KeyType;
85-
let keyFormat: KeyFormat;
86-
if (typeof privateKey === "string" || isArrayBufferView(privateKey)) {
87-
// if the key is BinaryLike, interpret it as a PEM encoded RSA key
88-
// deno-lint-ignore no-explicit-any
89-
keyData = privateKey as any;
90-
keyType = "rsa";
91-
keyFormat = "pem";
92-
} else {
93-
keyData = getKeyMaterial(privateKey);
94-
keyType = "rsa";
95-
keyFormat = "pem";
96-
}
83+
const { data, format, type } = prepareAsymmetricKey(privateKey);
9784
const ret = Buffer.from(ops.op_node_sign(
9885
this.hash.digest(),
9986
this.#digestType,
100-
keyData!,
101-
keyType,
102-
keyFormat,
87+
data!,
88+
type,
89+
format,
10390
));
10491
return encoding ? ret.toString(encoding) : ret;
10592
}

0 commit comments

Comments
 (0)