Skip to content

Commit

Permalink
fix(ext/crypto): interoperable import/export (#16153)
Browse files Browse the repository at this point in the history
This PR updates RSA key import/export to a state which is interoperable
with other implementations.

For RSA the only OID in and out is `rsaEncryption`.
For EC the only OID in and out is `id-ecpublickey` (fixed in #16152).

see w3c/webcrypto#307 (comment)
see w3c/webcrypto#307
see w3c/webcrypto#305
see nodejs/node#42816
  • Loading branch information
panva committed Oct 4, 2022
1 parent aa710aa commit 0d042d8
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 446 deletions.
66 changes: 51 additions & 15 deletions cli/tests/unit/webcrypto_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,21 +383,6 @@ Deno.test(async function generateImportHmacJwk() {
const pkcs8TestVectors = [
// rsaEncryption
{ pem: "cli/tests/testdata/webcrypto/id_rsaEncryption.pem", hash: "SHA-256" },
// id-RSASSA-PSS (sha256)
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_md:sha256 -out id_rsassaPss.pem`
{ pem: "cli/tests/testdata/webcrypto/id_rsassaPss.pem", hash: "SHA-256" },
// id-RSASSA-PSS (default parameters)
// `openssl genpkey -algorithm rsa-pss -out id_rsassaPss.pem`
{
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_default.pem",
hash: "SHA-1",
},
// id-RSASSA-PSS (default hash)
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_saltlen:30 -out rsaPss_saltLen_30.pem`
{
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_saltLen_30.pem",
hash: "SHA-1",
},
];

Deno.test({ permissions: { read: true } }, async function importRsaPkcs8() {
Expand Down Expand Up @@ -435,6 +420,57 @@ Deno.test({ permissions: { read: true } }, async function importRsaPkcs8() {
}
});

const nonInteroperableVectors = [
// id-RSASSA-PSS (sha256)
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_md:sha256 -out id_rsassaPss.pem`
{ pem: "cli/tests/testdata/webcrypto/id_rsassaPss.pem", hash: "SHA-256" },
// id-RSASSA-PSS (default parameters)
// `openssl genpkey -algorithm rsa-pss -out id_rsassaPss.pem`
{
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_default.pem",
hash: "SHA-1",
},
// id-RSASSA-PSS (default hash)
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_saltlen:30 -out rsaPss_saltLen_30.pem`
{
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_saltLen_30.pem",
hash: "SHA-1",
},
];

Deno.test(
{ permissions: { read: true } },
async function importNonInteroperableRsaPkcs8() {
const pemHeader = "-----BEGIN PRIVATE KEY-----";
const pemFooter = "-----END PRIVATE KEY-----";
for (const { pem, hash } of nonInteroperableVectors) {
const keyFile = await Deno.readTextFile(pem);
const pemContents = keyFile.substring(
pemHeader.length,
keyFile.length - pemFooter.length,
);
const binaryDerString = atob(pemContents);
const binaryDer = new Uint8Array(binaryDerString.length);
for (let i = 0; i < binaryDerString.length; i++) {
binaryDer[i] = binaryDerString.charCodeAt(i);
}

await assertRejects(
() =>
crypto.subtle.importKey(
"pkcs8",
binaryDer,
{ name: "RSA-PSS", hash },
true,
["sign"],
),
DOMException,
"unsupported algorithm",
);
}
},
);

// deno-fmt-ignore
const asn1AlgorithmIdentifier = new Uint8Array([
0x02, 0x01, 0x00, // INTEGER
Expand Down
225 changes: 24 additions & 201 deletions ext/crypto/import_key.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::key::CryptoNamedCurve;
use crate::shared::*;
use crate::OaepPrivateKeyParameters;
use crate::PssPrivateKeyParameters;
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::ZeroCopyBuf;
Expand Down Expand Up @@ -52,11 +50,11 @@ pub enum KeyData {
#[serde(rename_all = "camelCase", tag = "algorithm")]
pub enum ImportKeyOptions {
#[serde(rename = "RSASSA-PKCS1-v1_5")]
RsassaPkcs1v15 { hash: ShaHash },
RsassaPkcs1v15 {},
#[serde(rename = "RSA-PSS")]
RsaPss { hash: ShaHash },
RsaPss {},
#[serde(rename = "RSA-OAEP")]
RsaOaep { hash: ShaHash },
RsaOaep {},
#[serde(rename = "ECDSA", rename_all = "camelCase")]
Ecdsa { named_curve: EcNamedCurve },
#[serde(rename = "ECDH", rename_all = "camelCase")]
Expand Down Expand Up @@ -91,11 +89,9 @@ pub fn op_crypto_import_key(
key_data: KeyData,
) -> Result<ImportKeyResult, AnyError> {
match opts {
ImportKeyOptions::RsassaPkcs1v15 { hash } => {
import_key_rsassa(key_data, hash)
}
ImportKeyOptions::RsaPss { hash } => import_key_rsapss(key_data, hash),
ImportKeyOptions::RsaOaep { hash } => import_key_rsaoaep(key_data, hash),
ImportKeyOptions::RsassaPkcs1v15 {} => import_key_rsassa(key_data),
ImportKeyOptions::RsaPss {} => import_key_rsapss(key_data),
ImportKeyOptions::RsaOaep {} => import_key_rsaoaep(key_data),
ImportKeyOptions::Ecdsa { named_curve }
| ImportKeyOptions::Ecdh { named_curve } => {
import_key_ec(key_data, named_curve)
Expand Down Expand Up @@ -193,7 +189,6 @@ fn import_key_rsa_jwk(

fn import_key_rsassa(
key_data: KeyData,
hash: ShaHash,
) -> Result<ImportKeyResult, deno_core::anyhow::Error> {
match key_data {
KeyData::Spki(data) => {
Expand All @@ -204,26 +199,9 @@ fn import_key_rsassa(
// 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(ShaHash::Sha1),
// sha256WithRSAEncryption
SHA256_RSA_ENCRYPTION_OID => Some(ShaHash::Sha256),
// sha384WithRSAEncryption
SHA384_RSA_ENCRYPTION_OID => Some(ShaHash::Sha384),
// sha512WithRSAEncryption
SHA512_RSA_ENCRYPTION_OID => Some(ShaHash::Sha512),
_ => return Err(data_error("unsupported algorithm")),
};

// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(data_error("hash mismatch"));
}
// 6-7. (skipped, only support rsaEncryption for interoperability)
if alg != RSA_ENCRYPTION_OID {
return Err(data_error("unsupported algorithm"));
}

// 8-9.
Expand Down Expand Up @@ -260,26 +238,9 @@ fn import_key_rsassa(
// 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(ShaHash::Sha1),
// sha256WithRSAEncryption
SHA256_RSA_ENCRYPTION_OID => Some(ShaHash::Sha256),
// sha384WithRSAEncryption
SHA384_RSA_ENCRYPTION_OID => Some(ShaHash::Sha384),
// sha512WithRSAEncryption
SHA512_RSA_ENCRYPTION_OID => Some(ShaHash::Sha512),
_ => return Err(data_error("unsupported algorithm")),
};

// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(data_error("hash mismatch"));
}
// 6-7. (skipped, only support rsaEncryption for interoperability)
if alg != RSA_ENCRYPTION_OID {
return Err(data_error("unsupported algorithm"));
}

// 8-9.
Expand Down Expand Up @@ -317,7 +278,6 @@ fn import_key_rsassa(

fn import_key_rsapss(
key_data: KeyData,
hash: ShaHash,
) -> Result<ImportKeyResult, deno_core::anyhow::Error> {
match key_data {
KeyData::Spki(data) => {
Expand All @@ -328,47 +288,9 @@ fn import_key_rsapss(
// 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(|| data_error("malformed parameters"))?,
)
.map_err(|_| data_error("malformed parameters"))?;

let hash_alg = params.hash_algorithm;
let hash = match hash_alg.oid {
// id-sha1
ID_SHA1_OID => Some(ShaHash::Sha1),
// id-sha256
ID_SHA256_OID => Some(ShaHash::Sha256),
// id-sha384
ID_SHA384_OID => Some(ShaHash::Sha384),
// id-sha256
ID_SHA512_OID => Some(ShaHash::Sha512),
_ => return Err(data_error("unsupported hash algorithm")),
};

if params.mask_gen_algorithm.oid != ID_MFG1 {
return Err(not_supported_error("unsupported hash algorithm"));
}

hash
}
_ => return Err(data_error("unsupported algorithm")),
};

// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(data_error("hash mismatch"));
}
// 6-7. (skipped, only support rsaEncryption for interoperability)
if alg != RSA_ENCRYPTION_OID {
return Err(data_error("unsupported algorithm"));
}

// 8-9.
Expand Down Expand Up @@ -405,42 +327,9 @@ fn import_key_rsapss(
// 4-5.
let alg = pk_info.algorithm.oid;

// 6.
// 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(|| not_supported_error("malformed parameters"))?,
)
.map_err(|_| not_supported_error("malformed parameters"))?;

let hash_alg = params.hash_algorithm;
match hash_alg.oid {
// id-sha1
ID_SHA1_OID => Some(ShaHash::Sha1),
// id-sha256
ID_SHA256_OID => Some(ShaHash::Sha256),
// id-sha384
ID_SHA384_OID => Some(ShaHash::Sha384),
// id-sha256
ID_SHA512_OID => Some(ShaHash::Sha512),
_ => return Err(data_error("unsupported hash algorithm")),
}
}
_ => return Err(data_error("unsupported algorithm")),
};

// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(data_error("hash mismatch"));
}
// 6-7. (skipped, only support rsaEncryption for interoperability)
if alg != RSA_ENCRYPTION_OID {
return Err(data_error("unsupported algorithm"));
}

// 8-9.
Expand Down Expand Up @@ -478,7 +367,6 @@ fn import_key_rsapss(

fn import_key_rsaoaep(
key_data: KeyData,
hash: ShaHash,
) -> Result<ImportKeyResult, deno_core::anyhow::Error> {
match key_data {
KeyData::Spki(data) => {
Expand All @@ -489,41 +377,9 @@ fn import_key_rsaoaep(
// 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(|| data_error("malformed parameters"))?,
)
.map_err(|_| data_error("malformed parameters"))?;

let hash_alg = params.hash_algorithm;
match hash_alg.oid {
// id-sha1
ID_SHA1_OID => Some(ShaHash::Sha1),
// id-sha256
ID_SHA256_OID => Some(ShaHash::Sha256),
// id-sha384
ID_SHA384_OID => Some(ShaHash::Sha384),
// id-sha256
ID_SHA512_OID => Some(ShaHash::Sha512),
_ => return Err(data_error("unsupported hash algorithm")),
}
}
_ => return Err(data_error("unsupported algorithm")),
};

// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(data_error("hash mismatch"));
}
// 6-7. (skipped, only support rsaEncryption for interoperability)
if alg != RSA_ENCRYPTION_OID {
return Err(data_error("unsupported algorithm"));
}

// 8-9.
Expand Down Expand Up @@ -560,42 +416,9 @@ fn import_key_rsaoaep(
// 4-5.
let alg = pk_info.algorithm.oid;

// 6.
// 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(|| not_supported_error("malformed parameters"))?,
)
.map_err(|_| not_supported_error("malformed parameters"))?;

let hash_alg = params.hash_algorithm;
match hash_alg.oid {
// id-sha1
ID_SHA1_OID => Some(ShaHash::Sha1),
// id-sha256
ID_SHA256_OID => Some(ShaHash::Sha256),
// id-sha384
ID_SHA384_OID => Some(ShaHash::Sha384),
// id-sha256
ID_SHA512_OID => Some(ShaHash::Sha512),
_ => return Err(data_error("unsupported hash algorithm")),
}
}
_ => return Err(data_error("unsupported algorithm")),
};

// 7.
if let Some(pk_hash) = pk_hash {
if pk_hash != hash {
return Err(data_error("hash mismatch"));
}
// 6-7. (skipped, only support rsaEncryption for interoperability)
if alg != RSA_ENCRYPTION_OID {
return Err(data_error("unsupported algorithm"));
}

// 8-9.
Expand Down
Loading

0 comments on commit 0d042d8

Please sign in to comment.