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:
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.
|
// 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"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
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::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())?;
|
||||||
|
|
||||||
|
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;
|
use rsa::pkcs1v15::SigningKey;
|
||||||
let key = match key_format {
|
let key = RsaPrivateKey::from_pkcs1_der(pkey)?;
|
||||||
"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
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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) =
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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") {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue