summaryrefslogtreecommitdiff
path: root/ext/crypto/lib.rs
diff options
context:
space:
mode:
authorDivy Srivastava <dj.srivastava23@gmail.com>2021-09-14 18:51:20 +0530
committerGitHub <noreply@github.com>2021-09-14 15:21:20 +0200
commitc41460ecc421ac7730cc5455542e5e05f7366c4a (patch)
tree18b7ac8683d6543b0610c25d42df089e2bf7d183 /ext/crypto/lib.rs
parentd36b01ff6956930b51a80cd773f618b708a5f595 (diff)
feat(ext/crypto): import RSA pkcs#8 keys (#11891)
Diffstat (limited to 'ext/crypto/lib.rs')
-rw-r--r--ext/crypto/lib.rs430
1 files changed, 430 insertions, 0 deletions
diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs
index 47137b210..ef32499b9 100644
--- a/ext/crypto/lib.rs
+++ b/ext/crypto/lib.rs
@@ -12,8 +12,10 @@ use deno_core::Extension;
use deno_core::OpState;
use deno_core::ZeroCopyBuf;
use serde::Deserialize;
+use serde::Serialize;
use std::cell::RefCell;
+use std::convert::TryFrom;
use std::convert::TryInto;
use std::num::NonZeroU32;
use std::rc::Rc;
@@ -37,6 +39,8 @@ use ring::signature::EcdsaSigningAlgorithm;
use ring::signature::EcdsaVerificationAlgorithm;
use ring::signature::KeyPair;
use rsa::padding::PaddingScheme;
+use rsa::pkcs1::der::Decodable;
+use rsa::pkcs1::der::Encodable;
use rsa::pkcs1::FromRsaPrivateKey;
use rsa::pkcs1::ToRsaPrivateKey;
use rsa::pkcs8::der::asn1;
@@ -66,6 +70,31 @@ lazy_static! {
static ref PUB_EXPONENT_2: BigUint = BigUint::from_u64(65537).unwrap();
}
+const RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.1");
+const SHA1_RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.5");
+const SHA256_RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.11");
+const SHA384_RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.12");
+const SHA512_RSA_ENCRYPTION_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.13");
+const RSASSA_PSS_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.10");
+const ID_SHA1_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.3.14.3.2.26");
+const ID_SHA256_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("2.16.840.1.101.3.4.2.1");
+const ID_SHA384_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("2.16.840.1.101.3.4.2.2");
+const ID_SHA512_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("2.16.840.1.101.3.4.2.3");
+const ID_MFG1: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.8");
+const RSAES_OAEP_OID: rsa::pkcs8::ObjectIdentifier =
+ rsa::pkcs8::ObjectIdentifier::new("1.2.840.113549.1.1.7");
+
pub fn init(maybe_seed: Option<u64>) -> Extension {
Extension::builder()
.js(include_js_files!(
@@ -82,6 +111,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_import_key", op_async(op_crypto_import_key)),
("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)),
@@ -817,6 +847,406 @@ pub async fn op_crypto_encrypt_key(
}
}
+// The parameters field associated with OID id-RSASSA-PSS
+// Defined in RFC 3447, section A.2.3
+//
+// RSASSA-PSS-params ::= SEQUENCE {
+// hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
+// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
+// saltLength [2] INTEGER DEFAULT 20,
+// trailerField [3] TrailerField DEFAULT trailerFieldBC
+// }
+pub struct PssPrivateKeyParameters<'a> {
+ pub hash_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>,
+ pub mask_gen_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>,
+ pub salt_length: u32,
+}
+
+// Context-specific tag number for hashAlgorithm.
+const HASH_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber =
+ rsa::pkcs8::der::TagNumber::new(0);
+
+// Context-specific tag number for maskGenAlgorithm.
+const MASK_GEN_ALGORITHM_TAG: rsa::pkcs8::der::TagNumber =
+ rsa::pkcs8::der::TagNumber::new(1);
+
+// Context-specific tag number for saltLength.
+const SALT_LENGTH_TAG: rsa::pkcs8::der::TagNumber =
+ rsa::pkcs8::der::TagNumber::new(2);
+
+impl<'a> TryFrom<rsa::pkcs8::der::asn1::Any<'a>>
+ for PssPrivateKeyParameters<'a>
+{
+ type Error = rsa::pkcs8::der::Error;
+
+ fn try_from(
+ any: rsa::pkcs8::der::asn1::Any<'a>,
+ ) -> rsa::pkcs8::der::Result<PssPrivateKeyParameters> {
+ any.sequence(|decoder| {
+ let hash_algorithm = decoder
+ .context_specific(HASH_ALGORITHM_TAG)?
+ .map(TryInto::try_into)
+ .transpose()?
+ .unwrap();
+
+ let mask_gen_algorithm = decoder
+ .context_specific(MASK_GEN_ALGORITHM_TAG)?
+ .map(TryInto::try_into)
+ .transpose()?
+ .unwrap();
+
+ let salt_length = decoder
+ .context_specific(SALT_LENGTH_TAG)?
+ .map(TryInto::try_into)
+ .transpose()?
+ .unwrap_or(20);
+
+ Ok(Self {
+ hash_algorithm,
+ mask_gen_algorithm,
+ salt_length,
+ })
+ })
+ }
+}
+
+// The parameters field associated with OID id-RSAES-OAEP
+// Defined in RFC 3447, section A.2.1
+//
+// RSAES-OAEP-params ::= SEQUENCE {
+// hashAlgorithm [0] HashAlgorithm DEFAULT sha1,
+// maskGenAlgorithm [1] MaskGenAlgorithm DEFAULT mgf1SHA1,
+// pSourceAlgorithm [2] PSourceAlgorithm DEFAULT pSpecifiedEmpty
+// }
+pub struct OaepPrivateKeyParameters<'a> {
+ pub hash_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>,
+ pub mask_gen_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>,
+ pub p_source_algorithm: rsa::pkcs8::AlgorithmIdentifier<'a>,
+}
+
+impl<'a> TryFrom<rsa::pkcs8::der::asn1::Any<'a>>
+ for OaepPrivateKeyParameters<'a>
+{
+ type Error = rsa::pkcs8::der::Error;
+
+ fn try_from(
+ any: rsa::pkcs8::der::asn1::Any<'a>,
+ ) -> rsa::pkcs8::der::Result<OaepPrivateKeyParameters> {
+ any.sequence(|decoder| {
+ let hash_algorithm = decoder.decode()?;
+ let mask_gen_algorithm = decoder.decode()?;
+ let p_source_algorithm = decoder.decode()?;
+ Ok(Self {
+ hash_algorithm,
+ mask_gen_algorithm,
+ p_source_algorithm,
+ })
+ })
+ }
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ImportKeyArg {
+ algorithm: Algorithm,
+ format: KeyFormat,
+ // RSASSA-PKCS1-v1_5
+ hash: Option<CryptoHash>,
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ImportKeyResult {
+ data: ZeroCopyBuf,
+ // RSASSA-PKCS1-v1_5
+ public_exponent: Option<ZeroCopyBuf>,
+ modulus_length: Option<usize>,
+}
+
+pub async fn op_crypto_import_key(
+ _state: Rc<RefCell<OpState>>,
+ args: ImportKeyArg,
+ zero_copy: Option<ZeroCopyBuf>,
+) -> Result<ImportKeyResult, AnyError> {
+ let zero_copy = zero_copy.ok_or_else(null_opbuf)?;
+ let data = &*zero_copy;
+ let algorithm = args.algorithm;
+
+ match algorithm {
+ Algorithm::RsassaPkcs1v15 => {
+ match args.format {
+ KeyFormat::Pkcs8 => {
+ let hash = args
+ .hash
+ .ok_or_else(|| type_error("Missing argument hash".to_string()))?;
+
+ // 2-3.
+ let pk_info =
+ rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| {
+ custom_error("DOMExceptionOperationError", e.to_string())
+ })?;
+
+ // 4-5.
+ let alg = pk_info.algorithm.oid;
+
+ // 6.
+ let pk_hash = match alg {
+ // rsaEncryption
+ RSA_ENCRYPTION_OID => None,
+ // sha1WithRSAEncryption
+ SHA1_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha1),
+ // sha256WithRSAEncryption
+ SHA256_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha256),
+ // sha384WithRSAEncryption
+ SHA384_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha384),
+ // sha512WithRSAEncryption
+ SHA512_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha512),
+ _ => return Err(type_error("Unsupported algorithm".to_string())),
+ };
+
+ // 7.
+ if let Some(pk_hash) = pk_hash {
+ if pk_hash != hash {
+ // TODO(@littledivy): DataError
+ return Err(type_error("Hash mismatch".to_string()));
+ }
+ }
+
+ // 8-9.
+ let private_key =
+ rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err(
+ |e| custom_error("DOMExceptionOperationError", e.to_string()),
+ )?;
+
+ let bytes_consumed = private_key.encoded_len().map_err(|e| {
+ // TODO(@littledivy): DataError
+ custom_error("DOMExceptionOperationError", e.to_string())
+ })?;
+
+ if bytes_consumed
+ != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16)
+ {
+ // TODO(@littledivy): DataError
+ return Err(type_error("Some bytes were not consumed".to_string()));
+ }
+
+ Ok(ImportKeyResult {
+ data: pk_info.private_key.to_vec().into(),
+ public_exponent: Some(
+ private_key.public_exponent.as_bytes().to_vec().into(),
+ ),
+ modulus_length: Some(private_key.modulus.as_bytes().len() * 8),
+ })
+ }
+ // TODO(@littledivy): spki
+ // TODO(@littledivy): jwk
+ _ => Err(type_error("Unsupported format".to_string())),
+ }
+ }
+ Algorithm::RsaPss => {
+ match args.format {
+ KeyFormat::Pkcs8 => {
+ let hash = args
+ .hash
+ .ok_or_else(|| type_error("Missing argument hash".to_string()))?;
+
+ // 2-3.
+ let pk_info =
+ rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| {
+ custom_error("DOMExceptionOperationError", e.to_string())
+ })?;
+
+ // 4-5.
+ let alg = pk_info.algorithm.oid;
+
+ // 6.
+ let pk_hash = match alg {
+ // rsaEncryption
+ RSA_ENCRYPTION_OID => None,
+ // id-RSASSA-PSS
+ RSASSA_PSS_OID => {
+ // TODO(@littledivy): NotSupported error
+ let params = PssPrivateKeyParameters::try_from(
+ pk_info.algorithm.parameters.ok_or_else(|| {
+ type_error("Malformed parameters".to_string())
+ })?,
+ )
+ .map_err(|_| type_error("Malformed parameters".to_string()))?;
+
+ let hash_alg = params.hash_algorithm;
+ let hash = match hash_alg.oid {
+ // id-sha1
+ ID_SHA1_OID => Some(CryptoHash::Sha1),
+ // id-sha256
+ ID_SHA256_OID => Some(CryptoHash::Sha256),
+ // id-sha384
+ ID_SHA384_OID => Some(CryptoHash::Sha384),
+ // id-sha256
+ ID_SHA512_OID => Some(CryptoHash::Sha512),
+ // TODO(@littledivy): DataError
+ _ => {
+ return Err(type_error(
+ "Unsupported hash algorithm".to_string(),
+ ))
+ }
+ };
+
+ if params.mask_gen_algorithm.oid != ID_MFG1 {
+ // TODO(@littledivy): NotSupportedError
+ return Err(type_error(
+ "Unsupported hash algorithm".to_string(),
+ ));
+ }
+
+ hash
+ }
+ // TODO(@littledivy): DataError
+ _ => return Err(type_error("Unsupported algorithm".to_string())),
+ };
+
+ // 7.
+ if let Some(pk_hash) = pk_hash {
+ if pk_hash != hash {
+ // TODO(@littledivy): DataError
+ return Err(type_error("Hash mismatch".to_string()));
+ }
+ }
+
+ // 8-9.
+ let private_key =
+ rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err(
+ |e| custom_error("DOMExceptionOperationError", e.to_string()),
+ )?;
+
+ let bytes_consumed = private_key.encoded_len().map_err(|e| {
+ // TODO(@littledivy): DataError
+ custom_error("DOMExceptionOperationError", e.to_string())
+ })?;
+
+ if bytes_consumed
+ != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16)
+ {
+ // TODO(@littledivy): DataError
+ return Err(type_error("Some bytes were not consumed".to_string()));
+ }
+
+ Ok(ImportKeyResult {
+ data: pk_info.private_key.to_vec().into(),
+ public_exponent: Some(
+ private_key.public_exponent.as_bytes().to_vec().into(),
+ ),
+ modulus_length: Some(private_key.modulus.as_bytes().len() * 8),
+ })
+ }
+ // TODO(@littledivy): spki
+ // TODO(@littledivy): jwk
+ _ => Err(type_error("Unsupported format".to_string())),
+ }
+ }
+ Algorithm::RsaOaep => {
+ match args.format {
+ KeyFormat::Pkcs8 => {
+ let hash = args
+ .hash
+ .ok_or_else(|| type_error("Missing argument hash".to_string()))?;
+
+ // 2-3.
+ let pk_info =
+ rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| {
+ custom_error("DOMExceptionOperationError", e.to_string())
+ })?;
+
+ // 4-5.
+ let alg = pk_info.algorithm.oid;
+
+ // 6.
+ let pk_hash = match alg {
+ // rsaEncryption
+ RSA_ENCRYPTION_OID => None,
+ // id-RSAES-OAEP
+ RSAES_OAEP_OID => {
+ // TODO(@littledivy): NotSupported error
+ let params = OaepPrivateKeyParameters::try_from(
+ pk_info.algorithm.parameters.ok_or_else(|| {
+ type_error("Malformed parameters".to_string())
+ })?,
+ )
+ .map_err(|_| type_error("Malformed parameters".to_string()))?;
+
+ let hash_alg = params.hash_algorithm;
+ let hash = match hash_alg.oid {
+ // id-sha1
+ ID_SHA1_OID => Some(CryptoHash::Sha1),
+ // id-sha256
+ ID_SHA256_OID => Some(CryptoHash::Sha256),
+ // id-sha384
+ ID_SHA384_OID => Some(CryptoHash::Sha384),
+ // id-sha256
+ ID_SHA512_OID => Some(CryptoHash::Sha512),
+ // TODO(@littledivy): DataError
+ _ => {
+ return Err(type_error(
+ "Unsupported hash algorithm".to_string(),
+ ))
+ }
+ };
+
+ if params.mask_gen_algorithm.oid != ID_MFG1 {
+ // TODO(@littledivy): NotSupportedError
+ return Err(type_error(
+ "Unsupported hash algorithm".to_string(),
+ ));
+ }
+
+ hash
+ }
+ // TODO(@littledivy): DataError
+ _ => return Err(type_error("Unsupported algorithm".to_string())),
+ };
+
+ // 7.
+ if let Some(pk_hash) = pk_hash {
+ if pk_hash != hash {
+ // TODO(@littledivy): DataError
+ return Err(type_error("Hash mismatch".to_string()));
+ }
+ }
+
+ // 8-9.
+ let private_key =
+ rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err(
+ |e| custom_error("DOMExceptionOperationError", e.to_string()),
+ )?;
+
+ let bytes_consumed = private_key.encoded_len().map_err(|e| {
+ // TODO(@littledivy): DataError
+ custom_error("DOMExceptionOperationError", e.to_string())
+ })?;
+
+ if bytes_consumed
+ != rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16)
+ {
+ // TODO(@littledivy): DataError
+ return Err(type_error("Some bytes were not consumed".to_string()));
+ }
+
+ Ok(ImportKeyResult {
+ data: pk_info.private_key.to_vec().into(),
+ public_exponent: Some(
+ private_key.public_exponent.as_bytes().to_vec().into(),
+ ),
+ modulus_length: Some(private_key.modulus.as_bytes().len() * 8),
+ })
+ }
+ // TODO(@littledivy): spki
+ // TODO(@littledivy): jwk
+ _ => Err(type_error("Unsupported format".to_string())),
+ }
+ }
+ _ => Err(type_error("Unsupported algorithm".to_string())),
+ }
+}
+
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DecryptArg {