1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-24 15:19:26 -05:00

refactor(ext/crypto): clean up exportKey rust code (#13052)

This commit is contained in:
Luca Casonato 2021-12-13 13:22:03 +01:00 committed by GitHub
parent cd03de3c7b
commit 5afb2cca65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 156 additions and 212 deletions

View file

@ -852,6 +852,7 @@
* @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'";
@ -878,7 +879,7 @@
case "RSASSA-PKCS1-v1_5":
case "RSA-PSS":
case "RSA-OAEP": {
return await exportKeyRSA(format, key, innerKey);
return exportKeyRSA(format, key, innerKey);
}
case "AES-CTR":
case "AES-CBC":
@ -2532,7 +2533,7 @@
}
}
async function exportKeyRSA(format, key, innerKey) {
function exportKeyRSA(format, key, innerKey) {
switch (format) {
case "pkcs8": {
// 1.
@ -2544,12 +2545,10 @@
}
// 2.
const data = await core.opAsync("op_crypto_export_key", {
key: innerKey,
format: "pkcs8",
const data = core.opSync("op_crypto_export_key", {
algorithm: key[_algorithm].name,
hash: key[_algorithm].hash.name,
});
format: "pkcs8",
}, innerKey);
// 3.
return data.buffer;
@ -2564,12 +2563,10 @@
}
// 2.
const data = await core.opAsync("op_crypto_export_key", {
key: innerKey,
format: "spki",
const data = core.opSync("op_crypto_export_key", {
algorithm: key[_algorithm].name,
hash: key[_algorithm].hash.name,
});
format: "spki",
}, innerKey);
// 3.
return data.buffer;

113
ext/crypto/export_key.rs Normal file
View file

@ -0,0 +1,113 @@
use deno_core::error::AnyError;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use serde::Deserialize;
use serde::Serialize;
use spki::der::asn1;
use spki::der::Encodable;
use crate::shared::*;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExportKeyOptions {
format: ExportKeyFormat,
#[serde(flatten)]
algorithm: ExportKeyAlgorithm,
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ExportKeyFormat {
Pkcs8,
Spki,
Jwk,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase", tag = "algorithm")]
pub enum ExportKeyAlgorithm {
#[serde(rename = "RSASSA-PKCS1-v1_5")]
RsassaPkcs1v15 {},
#[serde(rename = "RSA-PSS")]
RsaPss {},
#[serde(rename = "RSA-OAEP")]
RsaOaep {},
}
#[derive(Serialize)]
#[serde(untagged)]
pub enum ExportKeyResult {
Pkcs8(ZeroCopyBuf),
Spki(ZeroCopyBuf),
}
pub fn op_crypto_export_key(
_state: &mut OpState,
opts: ExportKeyOptions,
key_data: RawKeyData,
) -> Result<ExportKeyResult, AnyError> {
match opts.algorithm {
ExportKeyAlgorithm::RsassaPkcs1v15 {}
| ExportKeyAlgorithm::RsaPss {}
| ExportKeyAlgorithm::RsaOaep {} => export_key_rsa(opts.format, key_data),
}
}
fn export_key_rsa(
format: ExportKeyFormat,
key_data: RawKeyData,
) -> Result<ExportKeyResult, deno_core::anyhow::Error> {
match format {
ExportKeyFormat::Spki => {
let subject_public_key = &key_data.as_rsa_public_key()?;
// the SPKI structure
let key_info = spki::SubjectPublicKeyInfo {
algorithm: spki::AlgorithmIdentifier {
// rsaEncryption(1)
oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"),
// parameters field should not be ommited (None).
// It MUST have ASN.1 type NULL.
parameters: Some(asn1::Any::from(asn1::Null)),
},
subject_public_key,
};
// Infallible because we know the public key is valid.
let spki_der = key_info.to_vec().unwrap();
Ok(ExportKeyResult::Spki(spki_der.into()))
}
ExportKeyFormat::Pkcs8 => {
let private_key = key_data.as_rsa_private_key()?;
// 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,
};
// Infallible because we know the private key is valid.
let pkcs8_der = pk_info.to_vec().unwrap();
Ok(ExportKeyResult::Pkcs8(pkcs8_der.into()))
}
_ => Err(unsupported_format()),
}
}

View file

@ -10,6 +10,7 @@ use deno_core::op_sync;
use deno_core::Extension;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use export_key::op_crypto_export_key;
use serde::Deserialize;
use std::cell::RefCell;
@ -56,6 +57,7 @@ use std::path::PathBuf;
pub use rand; // Re-export rand
mod export_key;
mod import_key;
mod key;
mod shared;
@ -93,7 +95,7 @@ pub fn init(maybe_seed: Option<u64>) -> Extension {
("op_crypto_verify_key", op_async(op_crypto_verify_key)),
("op_crypto_derive_bits", op_async(op_crypto_derive_bits)),
("op_crypto_import_key", op_sync(op_crypto_import_key)),
("op_crypto_export_key", op_async(op_crypto_export_key)),
("op_crypto_export_key", op_sync(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)),
@ -569,205 +571,6 @@ 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,
_: (),
) -> 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())
}
KeyFormat::Spki => {
// public_key is a PKCS#1 DER-encoded public key
let subject_public_key = &args.key.data;
// the SPKI structure
let key_info = spki::SubjectPublicKeyInfo {
algorithm: spki::AlgorithmIdentifier {
// rsaEncryption(1)
oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"),
// parameters field should not be ommited (None).
// It MUST have ASN.1 type NULL.
parameters: Some(asn1::Any::from(asn1::Null)),
},
subject_public_key,
};
// Infallible based on spec because of the way we import and generate keys.
let spki_der = key_info.to_vec().unwrap();
Ok(spki_der.into())
}
// 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())
}
KeyFormat::Spki => {
// Intentionally unused but required. Not encoded into SPKI (see below).
let _hash = args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?;
// public_key is a PKCS#1 DER-encoded public key
let subject_public_key = &args.key.data;
// the SPKI structure
let key_info = spki::SubjectPublicKeyInfo {
algorithm: spki::AlgorithmIdentifier {
// rsaEncryption(1)
oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"),
// parameters field should not be ommited (None).
// It MUST have ASN.1 type NULL.
parameters: Some(asn1::Any::from(asn1::Null)),
},
subject_public_key,
};
// Infallible based on spec because of the way we import and generate keys.
let spki_der = key_info.to_vec().unwrap();
Ok(spki_der.into())
}
// 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())
}
KeyFormat::Spki => {
// Intentionally unused but required. Not encoded into SPKI (see below).
let _hash = args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?;
// public_key is a PKCS#1 DER-encoded public key
let subject_public_key = &args.key.data;
// the SPKI structure
let key_info = spki::SubjectPublicKeyInfo {
algorithm: spki::AlgorithmIdentifier {
// rsaEncryption(1)
oid: spki::ObjectIdentifier::new("1.2.840.113549.1.1.1"),
// parameters field should not be ommited (None).
// It MUST have ASN.1 type NULL.
parameters: Some(asn1::Any::from(asn1::Null)),
},
subject_public_key,
};
// Infallible based on spec because of the way we import and generate keys.
let spki_der = key_info.to_vec().unwrap();
Ok(spki_der.into())
}
// TODO(@littledivy): jwk
_ => unreachable!(),
}
}
_ => Err(type_error("Unsupported algorithm".to_string())),
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeriveKeyArg {

View file

@ -1,8 +1,12 @@
use std::borrow::Cow;
use deno_core::error::custom_error;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::ZeroCopyBuf;
use rsa::pkcs1::FromRsaPrivateKey;
use rsa::pkcs1::ToRsaPublicKey;
use rsa::RsaPrivateKey;
use serde::Deserialize;
use serde::Serialize;
@ -61,6 +65,33 @@ pub enum RawKeyData {
Public(ZeroCopyBuf),
}
impl RawKeyData {
pub fn as_rsa_public_key(&self) -> Result<Cow<'_, [u8]>, AnyError> {
match self {
RawKeyData::Public(data) => Ok(Cow::Borrowed(data)),
RawKeyData::Private(data) => {
let private_key = RsaPrivateKey::from_pkcs1_der(data)
.map_err(|_| type_error("expected valid private key"))?;
let public_key_doc = private_key
.to_public_key()
.to_pkcs1_der()
.map_err(|_| type_error("expected valid public key"))?;
Ok(Cow::Owned(public_key_doc.as_der().into()))
}
_ => Err(type_error("expected public key")),
}
}
pub fn as_rsa_private_key(&self) -> Result<&[u8], AnyError> {
match self {
RawKeyData::Private(data) => Ok(data),
_ => Err(type_error("expected private key")),
}
}
}
pub fn data_error(msg: impl Into<Cow<'static, str>>) -> AnyError {
custom_error("DOMExceptionDataError", msg)
}