mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -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:
parent
39c7d8dafe
commit
32438d25c3
6 changed files with 106 additions and 42 deletions
|
@ -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";
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
cli/tests/unit_node/testdata/rsa_private_pkcs1.pem
vendored
Normal file
27
cli/tests/unit_node/testdata/rsa_private_pkcs1.pem
vendored
Normal 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-----
|
|
@ -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;
|
||||
|
@ -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" => {
|
||||
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 (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 = RsaPrivateKey::from_pkcs1_der(pkey)?;
|
||||
Ok(
|
||||
match digest_type {
|
||||
"sha224" => {
|
||||
|
@ -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")),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) =
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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";
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue