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:
parent
84f8747157
commit
2199bdaf64
3 changed files with 276 additions and 14 deletions
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue