0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-03-03 09:31:22 -05:00

feat(ext/crypto): export RSA keys as pkcs#8 (#11880)

This commit is contained in:
Divy Srivastava 2021-09-13 15:03:28 +05:30 committed by GitHub
parent 84f8747157
commit 2199bdaf64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 276 additions and 14 deletions

View file

@ -357,6 +357,50 @@ unitTest(async function subtleCryptoHmacImportExport() {
assertEquals(exportedKey2, jwk);
});
// deno-fmt-ignore
const asn1AlgorithmIdentifier = new Uint8Array([
0x02, 0x01, 0x00, // INTEGER
0x30, 0x0d, // SEQUENCE (2 elements)
0x06, 0x09, // OBJECT IDENTIFIER
0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, // 1.2.840.113549.1.1.1 (rsaEncryption)
0x05, 0x00, // NULL
]);
unitTest(async function rsaExportPkcs8() {
for (const algorithm of ["RSASSA-PKCS1-v1_5", "RSA-PSS", "RSA-OAEP"]) {
const keyPair = await crypto.subtle.generateKey(
{
name: algorithm,
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
algorithm !== "RSA-OAEP" ? ["sign", "verify"] : ["encrypt", "decrypt"],
);
assert(keyPair.privateKey);
assert(keyPair.publicKey);
assertEquals(keyPair.privateKey.extractable, true);
const exportedKey = await crypto.subtle.exportKey(
"pkcs8",
keyPair.privateKey,
);
assert(exportedKey);
assert(exportedKey instanceof ArrayBuffer);
const pkcs8 = new Uint8Array(exportedKey);
assert(pkcs8.length > 0);
assertEquals(
pkcs8.slice(4, asn1AlgorithmIdentifier.byteLength + 4),
asn1AlgorithmIdentifier,
);
}
});
unitTest(async function testHkdfDeriveBits() {
const rawKey = await crypto.getRandomValues(new Uint8Array(16));
const key = await crypto.subtle.importKey(

View file

@ -1001,7 +1001,6 @@
* @param {CryptoKey} key
* @returns {Promise<any>}
*/
// deno-lint-ignore require-await
async exportKey(format, key) {
webidl.assertBranded(this, SubtleCrypto);
const prefix = "Failed to execute 'exportKey' on 'SubtleCrypto'";
@ -1077,8 +1076,92 @@
// TODO(@littledivy): Redundant break but deno_lint complains without it
break;
}
// TODO(@littledivy): RSASSA-PKCS1-v1_5
// TODO(@littledivy): RSA-PSS
case "RSASSA-PKCS1-v1_5": {
switch (format) {
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a private key",
"InvalidAccessError",
);
}
// 2.
const data = await core.opAsync(
"op_crypto_export_key",
{
key: innerKey,
format: "pkcs8",
algorithm: "RSASSA-PKCS1-v1_5",
},
);
// 3.
return data.buffer;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
case "RSA-PSS": {
switch (format) {
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a private key",
"InvalidAccessError",
);
}
// 2.
const data = await core.opAsync(
"op_crypto_export_key",
{
key: innerKey,
format: "pkcs8",
algorithm: "RSA-PSS",
hash: key[_algorithm].hash.name,
},
);
// 3.
return data.buffer;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
case "RSA-OAEP": {
switch (format) {
case "pkcs8": {
// 1.
if (key[_type] !== "private") {
throw new DOMException(
"Key is not a private key",
"InvalidAccessError",
);
}
// 2.
const data = await core.opAsync(
"op_crypto_export_key",
{
key: innerKey,
format: "pkcs8",
algorithm: "RSA-PSS",
hash: key[_algorithm].hash.name,
},
);
// 3.
return data.buffer;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
}
// TODO(@littledivy): ECDSA
default:
throw new DOMException("Not implemented", "NotSupportedError");
@ -1339,7 +1422,8 @@
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "pkcs8",
// PKCS#1 for RSA
type: "raw",
data: keyData,
});
@ -1399,7 +1483,8 @@
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "pkcs8",
// PKCS#1 for RSA
type: "raw",
data: keyData,
});

View file

@ -37,8 +37,9 @@ use ring::signature::EcdsaSigningAlgorithm;
use ring::signature::EcdsaVerificationAlgorithm;
use ring::signature::KeyPair;
use rsa::padding::PaddingScheme;
use rsa::pkcs8::FromPrivateKey;
use rsa::pkcs8::ToPrivateKey;
use rsa::pkcs1::FromRsaPrivateKey;
use rsa::pkcs1::ToRsaPrivateKey;
use rsa::pkcs8::der::asn1;
use rsa::BigUint;
use rsa::PublicKey;
use rsa::RsaPrivateKey;
@ -81,6 +82,7 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
("op_crypto_sign_key", op_async(op_crypto_sign_key)),
("op_crypto_verify_key", op_async(op_crypto_verify_key)),
("op_crypto_derive_bits", op_async(op_crypto_derive_bits)),
("op_crypto_export_key", op_async(op_crypto_export_key)),
("op_crypto_encrypt_key", op_async(op_crypto_encrypt_key)),
("op_crypto_decrypt_key", op_async(op_crypto_decrypt_key)),
("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)),
@ -164,7 +166,7 @@ pub async fn op_crypto_generate_key(
.unwrap()
.map_err(|e| custom_error("DOMExceptionOperationError", e.to_string()))?;
private_key.to_pkcs8_der()?.as_ref().to_vec()
private_key.to_pkcs1_der()?.as_ref().to_vec()
}
Algorithm::Ecdsa => {
let curve: &EcdsaSigningAlgorithm =
@ -270,7 +272,7 @@ pub async fn op_crypto_sign_key(
let signature = match algorithm {
Algorithm::RsassaPkcs1v15 => {
let private_key = RsaPrivateKey::from_pkcs8_der(&*args.key.data)?;
let private_key = RsaPrivateKey::from_pkcs1_der(&*args.key.data)?;
let (padding, hashed) = match args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
@ -320,7 +322,7 @@ pub async fn op_crypto_sign_key(
private_key.sign(padding, &hashed)?
}
Algorithm::RsaPss => {
let private_key = RsaPrivateKey::from_pkcs8_der(&*args.key.data)?;
let private_key = RsaPrivateKey::from_pkcs1_der(&*args.key.data)?;
let salt_len = args
.salt_length
@ -426,7 +428,7 @@ pub async fn op_crypto_verify_key(
let verification = match algorithm {
Algorithm::RsassaPkcs1v15 => {
let public_key: RsaPublicKey =
RsaPrivateKey::from_pkcs8_der(&*args.key.data)?.to_public_key();
RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key();
let (padding, hashed) = match args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
@ -483,7 +485,7 @@ pub async fn op_crypto_verify_key(
.ok_or_else(|| type_error("Missing argument saltLength".to_string()))?
as usize;
let public_key: RsaPublicKey =
RsaPrivateKey::from_pkcs8_der(&*args.key.data)?.to_public_key();
RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key();
let rng = OsRng;
let (padding, hashed) = match args
@ -552,6 +554,137 @@ pub async fn op_crypto_verify_key(
Ok(verification)
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExportKeyArg {
key: KeyData,
algorithm: Algorithm,
format: KeyFormat,
// RSA-PSS
hash: Option<CryptoHash>,
}
pub async fn op_crypto_export_key(
_state: Rc<RefCell<OpState>>,
args: ExportKeyArg,
_zero_copy: Option<ZeroCopyBuf>,
) -> Result<ZeroCopyBuf, AnyError> {
let algorithm = args.algorithm;
match algorithm {
Algorithm::RsassaPkcs1v15 => {
match args.format {
KeyFormat::Pkcs8 => {
// private_key is a PKCS#1 DER-encoded private key
let private_key = &args.key.data;
// the PKCS#8 v1 structure
// PrivateKeyInfo ::= SEQUENCE {
// version Version,
// privateKeyAlgorithm PrivateKeyAlgorithmIdentifier,
// privateKey PrivateKey,
// attributes [0] IMPLICIT Attributes OPTIONAL }
// version is 0 when publickey is None
let pk_info = rsa::pkcs8::PrivateKeyInfo {
attributes: None,
public_key: None,
algorithm: rsa::pkcs8::AlgorithmIdentifier {
// rsaEncryption(1)
oid: rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1"),
// parameters field should not be ommited (None).
// It MUST have ASN.1 type NULL as per defined in RFC 3279 Section 2.3.1
parameters: Some(asn1::Any::from(asn1::Null)),
},
private_key,
};
Ok(pk_info.to_der().as_ref().to_vec().into())
}
// TODO(@littledivy): spki
// TODO(@littledivy): jwk
_ => unreachable!(),
}
}
Algorithm::RsaPss => {
match args.format {
KeyFormat::Pkcs8 => {
// Intentionally unused but required. Not encoded into PKCS#8 (see below).
let _hash = args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?;
// private_key is a PKCS#1 DER-encoded private key
let private_key = &args.key.data;
// version is 0 when publickey is None
let pk_info = rsa::pkcs8::PrivateKeyInfo {
attributes: None,
public_key: None,
algorithm: rsa::pkcs8::AlgorithmIdentifier {
// Spec wants the OID to be id-RSASSA-PSS (1.2.840.113549.1.1.10) but ring and RSA do not support it.
// Instead, we use rsaEncryption (1.2.840.113549.1.1.1) as specified in RFC 3447.
// Node, Chromium and Firefox also use rsaEncryption (1.2.840.113549.1.1.1) and do not support id-RSASSA-PSS.
// parameters are set to NULL opposed to what spec wants (see above)
oid: rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1"),
// parameters field should not be ommited (None).
// It MUST have ASN.1 type NULL as per defined in RFC 3279 Section 2.3.1
parameters: Some(asn1::Any::from(asn1::Null)),
},
private_key,
};
Ok(pk_info.to_der().as_ref().to_vec().into())
}
// TODO(@littledivy): spki
// TODO(@littledivy): jwk
_ => unreachable!(),
}
}
Algorithm::RsaOaep => {
match args.format {
KeyFormat::Pkcs8 => {
// Intentionally unused but required. Not encoded into PKCS#8 (see below).
let _hash = args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?;
// private_key is a PKCS#1 DER-encoded private key
let private_key = &args.key.data;
// version is 0 when publickey is None
let pk_info = rsa::pkcs8::PrivateKeyInfo {
attributes: None,
public_key: None,
algorithm: rsa::pkcs8::AlgorithmIdentifier {
// Spec wants the OID to be id-RSAES-OAEP (1.2.840.113549.1.1.10) but ring and RSA crate do not support it.
// Instead, we use rsaEncryption (1.2.840.113549.1.1.1) as specified in RFC 3447.
// Chromium and Firefox also use rsaEncryption (1.2.840.113549.1.1.1) and do not support id-RSAES-OAEP.
// parameters are set to NULL opposed to what spec wants (see above)
oid: rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1"),
// parameters field should not be ommited (None).
// It MUST have ASN.1 type NULL as per defined in RFC 3279 Section 2.3.1
parameters: Some(asn1::Any::from(asn1::Null)),
},
private_key,
};
Ok(pk_info.to_der().as_ref().to_vec().into())
}
// TODO(@littledivy): spki
// TODO(@littledivy): jwk
_ => unreachable!(),
}
}
_ => Err(type_error("Unsupported algorithm".to_string())),
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeriveKeyArg {
@ -642,7 +775,7 @@ pub async fn op_crypto_encrypt_key(
match algorithm {
Algorithm::RsaOaep => {
let public_key: RsaPublicKey =
RsaPrivateKey::from_pkcs8_der(&*args.key.data)?.to_public_key();
RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key();
let label = args.label.map(|l| String::from_utf8_lossy(&*l).to_string());
let mut rng = OsRng;
let padding = match args
@ -705,7 +838,7 @@ pub async fn op_crypto_decrypt_key(
match algorithm {
Algorithm::RsaOaep => {
let private_key: RsaPrivateKey =
RsaPrivateKey::from_pkcs8_der(&*args.key.data)?;
RsaPrivateKey::from_pkcs1_der(&*args.key.data)?;
let label = args.label.map(|l| String::from_utf8_lossy(&*l).to_string());
let padding = match args
.hash