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:
parent
cd03de3c7b
commit
5afb2cca65
4 changed files with 156 additions and 212 deletions
|
@ -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
113
ext/crypto/export_key.rs
Normal 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()),
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue