1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-08 15:19:40 -05:00

feat(crypto): support importKey in SPKI format (#12921)

This commit adds support for `spki` key format for `crypto.subtle.importKey` for the RSA* algorithms.
This commit is contained in:
Yacine Hmito 2021-12-09 20:32:55 +01:00 committed by GitHub
parent e70dc53460
commit a3d024ac2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 458 additions and 795 deletions

View file

@ -270,7 +270,7 @@
/**
* @param {ArrayBufferView | ArrayBuffer} input
* @returns
* @returns {Uint8Array}
*/
function copyBuffer(input) {
return TypedArrayPrototypeSlice(
@ -826,7 +826,6 @@
keyData,
extractable,
keyUsages,
["sign"],
);
}
case "RSA-OAEP": {
@ -836,7 +835,6 @@
keyData,
extractable,
keyUsages,
["decrypt", "unwrapKey"],
);
}
case "HKDF": {
@ -1495,8 +1493,7 @@
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
// PKCS#1 for RSA
type: "raw",
type: "private",
data: keyData,
});
@ -1556,8 +1553,7 @@
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
// PKCS#1 for RSA
type: "raw",
type: "private",
data: keyData,
});
@ -1614,7 +1610,7 @@
namedCurve: normalizedAlgorithm.namedCurve,
});
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "pkcs8",
type: "private",
data: keyData,
});
} else {
@ -1672,7 +1668,7 @@
namedCurve: normalizedAlgorithm.namedCurve,
});
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "pkcs8",
type: "private",
data: keyData,
});
} else {
@ -1768,7 +1764,10 @@
length,
});
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, { type: "raw", data: keyData });
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "secret",
data: keyData,
});
// 6-10.
const algorithm = {
@ -1980,7 +1979,7 @@
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "raw",
type: "secret",
data,
});
@ -2165,13 +2164,9 @@
length = normalizedAlgorithm.length;
}
if (keyUsages.length == 0) {
throw new DOMException("Key usage is empty", "SyntaxError");
}
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "raw",
type: "secret",
data,
});
@ -2232,7 +2227,7 @@
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "raw",
type: "public",
data,
});
@ -2258,13 +2253,27 @@
}
}
const SUPPORTED_RSA_KEY_USAGES = {
"RSASSA-PKCS1-v1_5": {
spki: ["verify"],
pkcs8: ["sign"],
},
"RSA-PSS": {
spki: ["verify"],
pkcs8: ["sign"],
},
"RSA-OAEP": {
spki: ["encrypt", "wrapKey"],
pkcs8: ["decrypt", "unwrapKey"],
},
};
async function importKeyRSA(
format,
normalizedAlgorithm,
keyData,
extractable,
keyUsages,
supportedKeyUsages,
) {
switch (format) {
case "pkcs8": {
@ -2272,16 +2281,16 @@
if (
ArrayPrototypeFind(
keyUsages,
(u) => !ArrayPrototypeIncludes(supportedKeyUsages, u),
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].pkcs8,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
if (keyUsages.length == 0) {
throw new DOMException("Key usage is empty", "SyntaxError");
}
// 2-9.
const { modulusLength, publicExponent, data } = await core
.opAsync(
@ -2297,8 +2306,7 @@
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
// PKCS#1 for RSA
type: "raw",
type: "private",
data,
});
@ -2319,6 +2327,57 @@
return key;
}
case "spki": {
// 1.
if (
ArrayPrototypeFind(
keyUsages,
(u) =>
!ArrayPrototypeIncludes(
SUPPORTED_RSA_KEY_USAGES[normalizedAlgorithm.name].spki,
u,
),
) !== undefined
) {
throw new DOMException("Invalid key usages", "SyntaxError");
}
// 2-9.
const { modulusLength, publicExponent, data } = await core
.opAsync(
"op_crypto_import_key",
{
algorithm: normalizedAlgorithm.name,
format: "spki",
// Needed to perform step 7 without normalization.
hash: normalizedAlgorithm.hash.name,
},
keyData,
);
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "public",
data,
});
const algorithm = {
name: normalizedAlgorithm.name,
modulusLength,
publicExponent,
hash: normalizedAlgorithm.hash,
};
const key = constructKey(
"public",
extractable,
usageIntersection(keyUsages, recognisedUsages),
algorithm,
handle,
);
return key;
}
default:
throw new DOMException("Not implemented", "NotSupportedError");
}
@ -2355,7 +2414,7 @@
// 3.
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "raw",
type: "secret",
data: keyData,
});
@ -2407,7 +2466,7 @@
// 4.
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "raw",
type: "secret",
data: keyData,
});
@ -2545,7 +2604,7 @@
});
const handle = {};
WeakMapPrototypeSet(KEY_STORE, handle, {
type: "raw",
type: "secret",
data: keyData,
});
@ -2634,7 +2693,7 @@
) {
const baseKeyhandle = baseKey[_handle];
const baseKeyData = WeakMapPrototypeGet(KEY_STORE, baseKeyhandle);
const publicKeyhandle = baseKey[_handle];
const publicKeyhandle = publicKey[_handle];
const publicKeyData = WeakMapPrototypeGet(KEY_STORE, publicKeyhandle);
const buf = await core.opAsync("op_crypto_derive_bits", {
@ -2705,6 +2764,7 @@
key: keyData,
algorithm: "RSA-OAEP",
hash: hashAlgorithm,
label: normalizedAlgorithm.label,
}, data);
// 6.

View file

@ -40,6 +40,7 @@ use rsa::padding::PaddingScheme;
use rsa::pkcs1::der::Decodable;
use rsa::pkcs1::der::Encodable;
use rsa::pkcs1::FromRsaPrivateKey;
use rsa::pkcs1::FromRsaPublicKey;
use rsa::pkcs1::ToRsaPrivateKey;
use rsa::pkcs8::der::asn1;
use rsa::pkcs8::FromPrivateKey;
@ -275,12 +276,18 @@ pub enum KeyFormat {
Spki,
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum KeyType {
Secret,
Private,
Public,
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
pub struct KeyData {
// TODO(littledivy): Kept here to be used to importKey() in future.
#[allow(dead_code)]
r#type: KeyFormat,
r#type: KeyType,
data: ZeroCopyBuf,
}
@ -458,8 +465,7 @@ pub async fn op_crypto_verify_key(
let verification = match algorithm {
Algorithm::RsassaPkcs1v15 => {
let public_key: RsaPublicKey =
RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key();
let public_key = read_rsa_public_key(args.key)?;
let (padding, hashed) = match args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
@ -515,8 +521,7 @@ pub async fn op_crypto_verify_key(
.salt_length
.ok_or_else(|| type_error("Missing argument saltLength".to_string()))?
as usize;
let public_key: RsaPublicKey =
RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key();
let public_key = read_rsa_public_key(args.key)?;
let rng = OsRng;
let (padding, hashed) = match args
@ -903,6 +908,17 @@ pub struct EncryptArg {
length: Option<usize>,
}
fn read_rsa_public_key(key_data: KeyData) -> Result<RsaPublicKey, AnyError> {
let public_key = match key_data.r#type {
KeyType::Private => {
RsaPrivateKey::from_pkcs1_der(&*key_data.data)?.to_public_key()
}
KeyType::Public => RsaPublicKey::from_pkcs1_der(&*key_data.data)?,
KeyType::Secret => unreachable!("unexpected KeyType::Secret"),
};
Ok(public_key)
}
pub async fn op_crypto_encrypt_key(
_state: Rc<RefCell<OpState>>,
args: EncryptArg,
@ -913,8 +929,7 @@ pub async fn op_crypto_encrypt_key(
match algorithm {
Algorithm::RsaOaep => {
let public_key: RsaPublicKey =
RsaPrivateKey::from_pkcs1_der(&*args.key.data)?.to_public_key();
let public_key = read_rsa_public_key(args.key)?;
let label = args.label.map(|l| String::from_utf8_lossy(&*l).to_string());
let mut rng = OsRng;
let padding = match args
@ -1230,7 +1245,7 @@ pub async fn op_crypto_import_key(
// 2-3.
let pk_info =
rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| {
custom_error("DOMExceptionOperationError", e.to_string())
custom_error("DOMExceptionDataError", e.to_string())
})?;
// 4-5.
@ -1248,7 +1263,12 @@ pub async fn op_crypto_import_key(
SHA384_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha384),
// sha512WithRSAEncryption
SHA512_RSA_ENCRYPTION_OID => Some(CryptoHash::Sha512),
_ => return Err(type_error("Unsupported algorithm".to_string())),
_ => {
return Err(custom_error(
"DOMExceptionDataError",
"Unsupported algorithm".to_string(),
))
}
};
// 7.
@ -1262,17 +1282,17 @@ pub async fn op_crypto_import_key(
}
// 8-9.
let private_key =
rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err(
|e| custom_error("DOMExceptionOperationError", e.to_string()),
)?;
let private_key = rsa::pkcs1::RsaPrivateKey::from_der(
pk_info.private_key,
)
.map_err(|e| custom_error("DOMExceptionDataError", e.to_string()))?;
let bytes_consumed = private_key.encoded_len().map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
if bytes_consumed
!= rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16)
!= spki::der::Length::new(pk_info.private_key.len() as u16)
{
return Err(custom_error(
"DOMExceptionDataError",
@ -1288,7 +1308,78 @@ pub async fn op_crypto_import_key(
modulus_length: Some(private_key.modulus.as_bytes().len() * 8),
})
}
// TODO(@littledivy): spki
KeyFormat::Spki => {
let hash = args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?;
// 2-3.
let pk_info =
spki::SubjectPublicKeyInfo::from_der(data).map_err(|e| {
custom_error("DOMExceptionDataError", 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(custom_error(
"DOMExceptionDataError",
"Unsupported algorithm".to_string(),
))
}
};
// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(custom_error(
"DOMExceptionDataError",
"Hash mismatch".to_string(),
));
}
}
// 8-9.
let public_key =
rsa::pkcs1::RsaPublicKey::from_der(pk_info.subject_public_key)
.map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
let bytes_consumed = public_key.encoded_len().map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
if bytes_consumed
!= spki::der::Length::new(pk_info.subject_public_key.len() as u16)
{
return Err(custom_error(
"DOMExceptionDataError",
"Some bytes were not consumed".to_string(),
));
}
Ok(ImportKeyResult {
data: pk_info.subject_public_key.to_vec().into(),
public_exponent: Some(
public_key.public_exponent.as_bytes().to_vec().into(),
),
modulus_length: Some(public_key.modulus.as_bytes().len() * 8),
})
}
// TODO(@littledivy): jwk
_ => Err(type_error("Unsupported format".to_string())),
}
@ -1303,7 +1394,7 @@ pub async fn op_crypto_import_key(
// 2-3.
let pk_info =
rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| {
custom_error("DOMExceptionOperationError", e.to_string())
custom_error("DOMExceptionDataError", e.to_string())
})?;
// 4-5.
@ -1355,6 +1446,12 @@ pub async fn op_crypto_import_key(
));
}
// TODO(lucacasonato):
// If the parameters field of the maskGenAlgorithm field of params
// is not an instance of the HashAlgorithm ASN.1 type that is
// identical in content to the hashAlgorithm field of params,
// throw a NotSupportedError.
hash
}
_ => {
@ -1376,17 +1473,17 @@ pub async fn op_crypto_import_key(
}
// 8-9.
let private_key =
rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err(
|e| custom_error("DOMExceptionOperationError", e.to_string()),
)?;
let private_key = rsa::pkcs1::RsaPrivateKey::from_der(
pk_info.private_key,
)
.map_err(|e| custom_error("DOMExceptionDataError", e.to_string()))?;
let bytes_consumed = private_key
.encoded_len()
.map_err(|e| custom_error("DataError", e.to_string()))?;
let bytes_consumed = private_key.encoded_len().map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
if bytes_consumed
!= rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16)
!= spki::der::Length::new(pk_info.private_key.len() as u16)
{
return Err(custom_error(
"DOMExceptionDataError",
@ -1402,7 +1499,120 @@ pub async fn op_crypto_import_key(
modulus_length: Some(private_key.modulus.as_bytes().len() * 8),
})
}
// TODO(@littledivy): spki
KeyFormat::Spki => {
let hash = args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?;
// 2-3.
let pk_info =
spki::SubjectPublicKeyInfo::from_der(data).map_err(|e| {
custom_error("DOMExceptionDataError", 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 => {
let params = PssPrivateKeyParameters::try_from(
pk_info.algorithm.parameters.ok_or_else(|| {
custom_error(
"DOMExceptionDataError",
"Malformed parameters".to_string(),
)
})?,
)
.map_err(|_| {
custom_error(
"DOMExceptionDataError",
"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),
_ => {
return Err(custom_error(
"DOMExceptionDataError",
"Unsupported hash algorithm".to_string(),
))
}
};
if params.mask_gen_algorithm.oid != ID_MFG1 {
return Err(custom_error(
"DOMExceptionNotSupportedError",
"Unsupported hash algorithm".to_string(),
));
}
// TODO(lucacasonato):
// If the parameters field of the maskGenAlgorithm field of params
// is not an instance of the HashAlgorithm ASN.1 type that is
// identical in content to the hashAlgorithm field of params,
// throw a NotSupportedError.
hash
}
_ => {
return Err(custom_error(
"DOMExceptionDataError",
"Unsupported algorithm".to_string(),
))
}
};
// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(custom_error(
"DOMExceptionDataError",
"Hash mismatch".to_string(),
));
}
}
// 8-9.
let public_key =
rsa::pkcs1::RsaPublicKey::from_der(pk_info.subject_public_key)
.map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
let bytes_consumed = public_key.encoded_len().map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
if bytes_consumed
!= spki::der::Length::new(pk_info.subject_public_key.len() as u16)
{
return Err(custom_error(
"DOMExceptionDataError",
"Some bytes were not consumed".to_string(),
));
}
Ok(ImportKeyResult {
data: pk_info.subject_public_key.to_vec().into(),
public_exponent: Some(
public_key.public_exponent.as_bytes().to_vec().into(),
),
modulus_length: Some(public_key.modulus.as_bytes().len() * 8),
})
}
// TODO(@littledivy): jwk
_ => Err(type_error("Unsupported format".to_string())),
}
@ -1417,7 +1627,7 @@ pub async fn op_crypto_import_key(
// 2-3.
let pk_info =
rsa::pkcs8::PrivateKeyInfo::from_der(data).map_err(|e| {
custom_error("DOMExceptionOperationError", e.to_string())
custom_error("DOMExceptionDataError", e.to_string())
})?;
// 4-5.
@ -1469,6 +1679,12 @@ pub async fn op_crypto_import_key(
));
}
// TODO(lucacasonato):
// If the parameters field of the maskGenAlgorithm field of params
// is not an instance of the HashAlgorithm ASN.1 type that is
// identical in content to the hashAlgorithm field of params,
// throw a NotSupportedError.
hash
}
_ => {
@ -1490,17 +1706,17 @@ pub async fn op_crypto_import_key(
}
// 8-9.
let private_key =
rsa::pkcs1::RsaPrivateKey::from_der(pk_info.private_key).map_err(
|e| custom_error("DOMExceptionOperationError", e.to_string()),
)?;
let private_key = rsa::pkcs1::RsaPrivateKey::from_der(
pk_info.private_key,
)
.map_err(|e| custom_error("DOMExceptionDataError", e.to_string()))?;
let bytes_consumed = private_key.encoded_len().map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
if bytes_consumed
!= rsa::pkcs1::der::Length::new(pk_info.private_key.len() as u16)
!= spki::der::Length::new(pk_info.private_key.len() as u16)
{
return Err(custom_error(
"DOMExceptionDataError",
@ -1516,7 +1732,120 @@ pub async fn op_crypto_import_key(
modulus_length: Some(private_key.modulus.as_bytes().len() * 8),
})
}
// TODO(@littledivy): spki
KeyFormat::Spki => {
let hash = args
.hash
.ok_or_else(|| type_error("Missing argument hash".to_string()))?;
// 2-3.
let pk_info =
spki::SubjectPublicKeyInfo::from_der(data).map_err(|e| {
custom_error("DOMExceptionDataError", 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 => {
let params = OaepPrivateKeyParameters::try_from(
pk_info.algorithm.parameters.ok_or_else(|| {
custom_error(
"DOMExceptionDataError",
"Malformed parameters".to_string(),
)
})?,
)
.map_err(|_| {
custom_error(
"DOMExceptionDataError",
"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),
_ => {
return Err(custom_error(
"DOMExceptionDataError",
"Unsupported hash algorithm".to_string(),
))
}
};
if params.mask_gen_algorithm.oid != ID_MFG1 {
return Err(custom_error(
"DOMExceptionDataError",
"Unsupported hash algorithm".to_string(),
));
}
// TODO(lucacasonato):
// If the parameters field of the maskGenAlgorithm field of params
// is not an instance of the HashAlgorithm ASN.1 type that is
// identical in content to the hashAlgorithm field of params,
// throw a NotSupportedError.
hash
}
_ => {
return Err(custom_error(
"DOMExceptionDataError",
"Unsupported algorithm".to_string(),
))
}
};
// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(custom_error(
"DOMExceptionDataError",
"Hash mismatch".to_string(),
));
}
}
// 8-9.
let public_key =
rsa::pkcs1::RsaPublicKey::from_der(pk_info.subject_public_key)
.map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
let bytes_consumed = public_key.encoded_len().map_err(|e| {
custom_error("DOMExceptionDataError", e.to_string())
})?;
if bytes_consumed
!= spki::der::Length::new(pk_info.subject_public_key.len() as u16)
{
return Err(custom_error(
"DOMExceptionDataError",
"Some bytes were not consumed".to_string(),
));
}
Ok(ImportKeyResult {
data: pk_info.subject_public_key.to_vec().into(),
public_exponent: Some(
public_key.public_exponent.as_bytes().to_vec().into(),
),
modulus_length: Some(public_key.modulus.as_bytes().len() * 8),
})
}
// TODO(@littledivy): jwk
_ => Err(type_error("Unsupported format".to_string())),
}

File diff suppressed because it is too large Load diff