summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuca Casonato <hello@lcas.dev>2021-12-13 13:22:03 +0100
committerGitHub <noreply@github.com>2021-12-13 13:22:03 +0100
commit5afb2cca65f10b8d0baaff4989a102d24d629087 (patch)
treeb390f1e3dccdff3bc9b58fd79473ba967ee62783
parentcd03de3c7b8a99aeee3953e69392c26d3667c41d (diff)
refactor(ext/crypto): clean up exportKey rust code (#13052)
-rw-r--r--ext/crypto/00_crypto.js21
-rw-r--r--ext/crypto/export_key.rs113
-rw-r--r--ext/crypto/lib.rs203
-rw-r--r--ext/crypto/shared.rs31
4 files changed, 156 insertions, 212 deletions
diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js
index 4e2a90f3b..818061387 100644
--- a/ext/crypto/00_crypto.js
+++ b/ext/crypto/00_crypto.js
@@ -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;
diff --git a/ext/crypto/export_key.rs b/ext/crypto/export_key.rs
new file mode 100644
index 000000000..22a4b55ca
--- /dev/null
+++ b/ext/crypto/export_key.rs
@@ -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()),
+ }
+}
diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs
index 379b101a2..971b32bbb 100644
--- a/ext/crypto/lib.rs
+++ b/ext/crypto/lib.rs
@@ -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)),
@@ -571,205 +573,6 @@ pub async fn op_crypto_verify_key(
#[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 {
key: KeyData,
algorithm: Algorithm,
diff --git a/ext/crypto/shared.rs b/ext/crypto/shared.rs
index 696b1c087..1af0169ef 100644
--- a/ext/crypto/shared.rs
+++ b/ext/crypto/shared.rs
@@ -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)
}