0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 17:34:47 -05:00

fix(ext/node): sign with PEM private keys (#21287)

Add support for signing with a RSA PEM private key: `pkcs8` and `pkcs1`.

Fixes https://github.com/denoland/deno/issues/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 <dj.srivastava23@gmail.com>
This commit is contained in:
Divy Srivastava 2023-12-03 09:58:13 +05:30 committed by GitHub
parent 39c7d8dafe
commit 32438d25c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 106 additions and 42 deletions

View file

@ -1,6 +1,9 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // 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 { createSign, createVerify, sign, verify } from "node:crypto";
import { Buffer } from "node:buffer"; import { Buffer } from "node:buffer";
@ -9,6 +12,11 @@ const rsaPrivatePem = Buffer.from(
new URL("../testdata/rsa_private.pem", import.meta.url), 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( const rsaPublicPem = Buffer.from(
await Deno.readFile( await Deno.readFile(
new URL("../testdata/rsa_public.pem", import.meta.url), new URL("../testdata/rsa_public.pem", import.meta.url),
@ -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"),
);
}
}
},
});

View file

@ -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-----

View file

@ -19,6 +19,7 @@ use rand::distributions::Distribution;
use rand::distributions::Uniform; use rand::distributions::Uniform;
use rand::thread_rng; use rand::thread_rng;
use rand::Rng; use rand::Rng;
use rsa::pkcs1::DecodeRsaPrivateKey;
use rsa::pkcs8; use rsa::pkcs8;
use rsa::pkcs8::der::asn1; use rsa::pkcs8::der::asn1;
use rsa::pkcs8::der::Decode; use rsa::pkcs8::der::Decode;
@ -363,23 +364,32 @@ pub fn op_node_sign(
#[buffer] digest: &[u8], #[buffer] digest: &[u8],
#[string] digest_type: &str, #[string] digest_type: &str,
#[serde] key: StringOrBuffer, #[serde] key: StringOrBuffer,
#[string] key_type: &str, #[string] _type: &str,
#[string] key_format: &str, #[string] format: &str,
) -> Result<ToJsBuffer, AnyError> { ) -> Result<ToJsBuffer, AnyError> {
match key_type { let (label, doc) =
"rsa" => { pkcs8::SecretDocument::from_pem(std::str::from_utf8(&key).unwrap())?;
use rsa::pkcs1v15::SigningKey;
let key = match key_format { let oid;
"pem" => RsaPrivateKey::from_pkcs8_pem((&key).try_into()?) let pkey = match format {
.map_err(|_| type_error("Invalid RSA private key"))?, "pem" => {
// TODO(kt3k): Support der and jwk formats if label == "PRIVATE KEY" {
_ => { let pk_info = pkcs8::PrivateKeyInfo::try_from(doc.as_bytes())?;
return Err(type_error(format!( oid = pk_info.algorithm.oid;
"Unsupported key format: {}", pk_info.private_key
key_format } 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 = RsaPrivateKey::from_pkcs1_der(pkey)?;
Ok( Ok(
match digest_type { match digest_type {
"sha224" => { "sha224" => {
@ -408,10 +418,7 @@ pub fn op_node_sign(
.into(), .into(),
) )
} }
_ => Err(type_error(format!( _ => Err(type_error("Unsupported signing key")),
"Signing with {} keys is not supported yet",
key_type
))),
} }
} }
@ -1345,8 +1352,6 @@ fn parse_private_key(
format: &str, format: &str,
type_: &str, type_: &str,
) -> Result<pkcs8::SecretDocument, AnyError> { ) -> Result<pkcs8::SecretDocument, AnyError> {
use rsa::pkcs1::DecodeRsaPrivateKey;
match format { match format {
"pem" => { "pem" => {
let (label, doc) = let (label, doc) =

View file

@ -31,7 +31,8 @@ import {
export function isStringOrBuffer(val) { export function isStringOrBuffer(val) {
return typeof val === "string" || return typeof val === "string" ||
isArrayBufferView(val) || isArrayBufferView(val) ||
isAnyArrayBuffer(val); isAnyArrayBuffer(val) ||
Buffer.isBuffer(val);
} }
const { ops, encode } = globalThis.__bootstrap.core; const { ops, encode } = globalThis.__bootstrap.core;

View file

@ -210,7 +210,7 @@ export interface JsonWebKeyInput {
format: "jwk"; format: "jwk";
} }
function prepareAsymmetricKey(key) { export function prepareAsymmetricKey(key) {
if (isStringOrBuffer(key)) { if (isStringOrBuffer(key)) {
return { format: "pem", data: getArrayBufferOrView(key, "key") }; return { format: "pem", data: getArrayBufferOrView(key, "key") };
} else if (typeof key == "object") { } else if (typeof key == "object") {

View file

@ -20,8 +20,8 @@ import type {
PublicKeyInput, PublicKeyInput,
} from "ext:deno_node/internal/crypto/types.ts"; } from "ext:deno_node/internal/crypto/types.ts";
import { import {
getKeyMaterial,
KeyObject, KeyObject,
prepareAsymmetricKey,
} from "ext:deno_node/internal/crypto/keys.ts"; } from "ext:deno_node/internal/crypto/keys.ts";
import { createHash, Hash } from "ext:deno_node/internal/crypto/hash.ts"; import { createHash, Hash } from "ext:deno_node/internal/crypto/hash.ts";
import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts"; import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts";
@ -80,26 +80,13 @@ export class SignImpl extends Writable {
privateKey: BinaryLike | SignKeyObjectInput | SignPrivateKeyInput, privateKey: BinaryLike | SignKeyObjectInput | SignPrivateKeyInput,
encoding?: BinaryToTextEncoding, encoding?: BinaryToTextEncoding,
): Buffer | string { ): Buffer | string {
let keyData: Uint8Array; const { data, format, type } = prepareAsymmetricKey(privateKey);
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 ret = Buffer.from(ops.op_node_sign( const ret = Buffer.from(ops.op_node_sign(
this.hash.digest(), this.hash.digest(),
this.#digestType, this.#digestType,
keyData!, data!,
keyType, type,
keyFormat, format,
)); ));
return encoding ? ret.toString(encoding) : ret; return encoding ? ret.toString(encoding) : ret;
} }