Skip to content

Commit 0d042d8

Browse files
authored
fix(ext/crypto): interoperable import/export (#16153)
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
1 parent aa710aa commit 0d042d8

File tree

4 files changed

+75
-446
lines changed

4 files changed

+75
-446
lines changed

cli/tests/unit/webcrypto_test.ts

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -383,21 +383,6 @@ Deno.test(async function generateImportHmacJwk() {
383383
const pkcs8TestVectors = [
384384
// rsaEncryption
385385
{ pem: "cli/tests/testdata/webcrypto/id_rsaEncryption.pem", hash: "SHA-256" },
386-
// id-RSASSA-PSS (sha256)
387-
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_md:sha256 -out id_rsassaPss.pem`
388-
{ pem: "cli/tests/testdata/webcrypto/id_rsassaPss.pem", hash: "SHA-256" },
389-
// id-RSASSA-PSS (default parameters)
390-
// `openssl genpkey -algorithm rsa-pss -out id_rsassaPss.pem`
391-
{
392-
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_default.pem",
393-
hash: "SHA-1",
394-
},
395-
// id-RSASSA-PSS (default hash)
396-
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_saltlen:30 -out rsaPss_saltLen_30.pem`
397-
{
398-
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_saltLen_30.pem",
399-
hash: "SHA-1",
400-
},
401386
];
402387

403388
Deno.test({ permissions: { read: true } }, async function importRsaPkcs8() {
@@ -435,6 +420,57 @@ Deno.test({ permissions: { read: true } }, async function importRsaPkcs8() {
435420
}
436421
});
437422

423+
const nonInteroperableVectors = [
424+
// id-RSASSA-PSS (sha256)
425+
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_md:sha256 -out id_rsassaPss.pem`
426+
{ pem: "cli/tests/testdata/webcrypto/id_rsassaPss.pem", hash: "SHA-256" },
427+
// id-RSASSA-PSS (default parameters)
428+
// `openssl genpkey -algorithm rsa-pss -out id_rsassaPss.pem`
429+
{
430+
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_default.pem",
431+
hash: "SHA-1",
432+
},
433+
// id-RSASSA-PSS (default hash)
434+
// `openssl genpkey -algorithm rsa-pss -pkeyopt rsa_pss_keygen_saltlen:30 -out rsaPss_saltLen_30.pem`
435+
{
436+
pem: "cli/tests/testdata/webcrypto/id_rsassaPss_saltLen_30.pem",
437+
hash: "SHA-1",
438+
},
439+
];
440+
441+
Deno.test(
442+
{ permissions: { read: true } },
443+
async function importNonInteroperableRsaPkcs8() {
444+
const pemHeader = "-----BEGIN PRIVATE KEY-----";
445+
const pemFooter = "-----END PRIVATE KEY-----";
446+
for (const { pem, hash } of nonInteroperableVectors) {
447+
const keyFile = await Deno.readTextFile(pem);
448+
const pemContents = keyFile.substring(
449+
pemHeader.length,
450+
keyFile.length - pemFooter.length,
451+
);
452+
const binaryDerString = atob(pemContents);
453+
const binaryDer = new Uint8Array(binaryDerString.length);
454+
for (let i = 0; i < binaryDerString.length; i++) {
455+
binaryDer[i] = binaryDerString.charCodeAt(i);
456+
}
457+
458+
await assertRejects(
459+
() =>
460+
crypto.subtle.importKey(
461+
"pkcs8",
462+
binaryDer,
463+
{ name: "RSA-PSS", hash },
464+
true,
465+
["sign"],
466+
),
467+
DOMException,
468+
"unsupported algorithm",
469+
);
470+
}
471+
},
472+
);
473+
438474
// deno-fmt-ignore
439475
const asn1AlgorithmIdentifier = new Uint8Array([
440476
0x02, 0x01, 0x00, // INTEGER

ext/crypto/import_key.rs

Lines changed: 24 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use crate::key::CryptoNamedCurve;
22
use crate::shared::*;
3-
use crate::OaepPrivateKeyParameters;
4-
use crate::PssPrivateKeyParameters;
53
use deno_core::error::AnyError;
64
use deno_core::op;
75
use deno_core::ZeroCopyBuf;
@@ -52,11 +50,11 @@ pub enum KeyData {
5250
#[serde(rename_all = "camelCase", tag = "algorithm")]
5351
pub enum ImportKeyOptions {
5452
#[serde(rename = "RSASSA-PKCS1-v1_5")]
55-
RsassaPkcs1v15 { hash: ShaHash },
53+
RsassaPkcs1v15 {},
5654
#[serde(rename = "RSA-PSS")]
57-
RsaPss { hash: ShaHash },
55+
RsaPss {},
5856
#[serde(rename = "RSA-OAEP")]
59-
RsaOaep { hash: ShaHash },
57+
RsaOaep {},
6058
#[serde(rename = "ECDSA", rename_all = "camelCase")]
6159
Ecdsa { named_curve: EcNamedCurve },
6260
#[serde(rename = "ECDH", rename_all = "camelCase")]
@@ -91,11 +89,9 @@ pub fn op_crypto_import_key(
9189
key_data: KeyData,
9290
) -> Result<ImportKeyResult, AnyError> {
9391
match opts {
94-
ImportKeyOptions::RsassaPkcs1v15 { hash } => {
95-
import_key_rsassa(key_data, hash)
96-
}
97-
ImportKeyOptions::RsaPss { hash } => import_key_rsapss(key_data, hash),
98-
ImportKeyOptions::RsaOaep { hash } => import_key_rsaoaep(key_data, hash),
92+
ImportKeyOptions::RsassaPkcs1v15 {} => import_key_rsassa(key_data),
93+
ImportKeyOptions::RsaPss {} => import_key_rsapss(key_data),
94+
ImportKeyOptions::RsaOaep {} => import_key_rsaoaep(key_data),
9995
ImportKeyOptions::Ecdsa { named_curve }
10096
| ImportKeyOptions::Ecdh { named_curve } => {
10197
import_key_ec(key_data, named_curve)
@@ -193,7 +189,6 @@ fn import_key_rsa_jwk(
193189

194190
fn import_key_rsassa(
195191
key_data: KeyData,
196-
hash: ShaHash,
197192
) -> Result<ImportKeyResult, deno_core::anyhow::Error> {
198193
match key_data {
199194
KeyData::Spki(data) => {
@@ -204,26 +199,9 @@ fn import_key_rsassa(
204199
// 4-5.
205200
let alg = pk_info.algorithm.oid;
206201

207-
// 6.
208-
let pk_hash = match alg {
209-
// rsaEncryption
210-
RSA_ENCRYPTION_OID => None,
211-
// sha1WithRSAEncryption
212-
SHA1_RSA_ENCRYPTION_OID => Some(ShaHash::Sha1),
213-
// sha256WithRSAEncryption
214-
SHA256_RSA_ENCRYPTION_OID => Some(ShaHash::Sha256),
215-
// sha384WithRSAEncryption
216-
SHA384_RSA_ENCRYPTION_OID => Some(ShaHash::Sha384),
217-
// sha512WithRSAEncryption
218-
SHA512_RSA_ENCRYPTION_OID => Some(ShaHash::Sha512),
219-
_ => return Err(data_error("unsupported algorithm")),
220-
};
221-
222-
// 7.
223-
if let Some(pk_hash) = pk_hash {
224-
if pk_hash != hash {
225-
return Err(data_error("hash mismatch"));
226-
}
202+
// 6-7. (skipped, only support rsaEncryption for interoperability)
203+
if alg != RSA_ENCRYPTION_OID {
204+
return Err(data_error("unsupported algorithm"));
227205
}
228206

229207
// 8-9.
@@ -260,26 +238,9 @@ fn import_key_rsassa(
260238
// 4-5.
261239
let alg = pk_info.algorithm.oid;
262240

263-
// 6.
264-
let pk_hash = match alg {
265-
// rsaEncryption
266-
RSA_ENCRYPTION_OID => None,
267-
// sha1WithRSAEncryption
268-
SHA1_RSA_ENCRYPTION_OID => Some(ShaHash::Sha1),
269-
// sha256WithRSAEncryption
270-
SHA256_RSA_ENCRYPTION_OID => Some(ShaHash::Sha256),
271-
// sha384WithRSAEncryption
272-
SHA384_RSA_ENCRYPTION_OID => Some(ShaHash::Sha384),
273-
// sha512WithRSAEncryption
274-
SHA512_RSA_ENCRYPTION_OID => Some(ShaHash::Sha512),
275-
_ => return Err(data_error("unsupported algorithm")),
276-
};
277-
278-
// 7.
279-
if let Some(pk_hash) = pk_hash {
280-
if pk_hash != hash {
281-
return Err(data_error("hash mismatch"));
282-
}
241+
// 6-7. (skipped, only support rsaEncryption for interoperability)
242+
if alg != RSA_ENCRYPTION_OID {
243+
return Err(data_error("unsupported algorithm"));
283244
}
284245

285246
// 8-9.
@@ -317,7 +278,6 @@ fn import_key_rsassa(
317278

318279
fn import_key_rsapss(
319280
key_data: KeyData,
320-
hash: ShaHash,
321281
) -> Result<ImportKeyResult, deno_core::anyhow::Error> {
322282
match key_data {
323283
KeyData::Spki(data) => {
@@ -328,47 +288,9 @@ fn import_key_rsapss(
328288
// 4-5.
329289
let alg = pk_info.algorithm.oid;
330290

331-
// 6.
332-
let pk_hash = match alg {
333-
// rsaEncryption
334-
RSA_ENCRYPTION_OID => None,
335-
// id-RSASSA-PSS
336-
RSASSA_PSS_OID => {
337-
let params = PssPrivateKeyParameters::try_from(
338-
pk_info
339-
.algorithm
340-
.parameters
341-
.ok_or_else(|| data_error("malformed parameters"))?,
342-
)
343-
.map_err(|_| data_error("malformed parameters"))?;
344-
345-
let hash_alg = params.hash_algorithm;
346-
let hash = match hash_alg.oid {
347-
// id-sha1
348-
ID_SHA1_OID => Some(ShaHash::Sha1),
349-
// id-sha256
350-
ID_SHA256_OID => Some(ShaHash::Sha256),
351-
// id-sha384
352-
ID_SHA384_OID => Some(ShaHash::Sha384),
353-
// id-sha256
354-
ID_SHA512_OID => Some(ShaHash::Sha512),
355-
_ => return Err(data_error("unsupported hash algorithm")),
356-
};
357-
358-
if params.mask_gen_algorithm.oid != ID_MFG1 {
359-
return Err(not_supported_error("unsupported hash algorithm"));
360-
}
361-
362-
hash
363-
}
364-
_ => return Err(data_error("unsupported algorithm")),
365-
};
366-
367-
// 7.
368-
if let Some(pk_hash) = pk_hash {
369-
if pk_hash != hash {
370-
return Err(data_error("hash mismatch"));
371-
}
291+
// 6-7. (skipped, only support rsaEncryption for interoperability)
292+
if alg != RSA_ENCRYPTION_OID {
293+
return Err(data_error("unsupported algorithm"));
372294
}
373295

374296
// 8-9.
@@ -405,42 +327,9 @@ fn import_key_rsapss(
405327
// 4-5.
406328
let alg = pk_info.algorithm.oid;
407329

408-
// 6.
409-
// 6.
410-
let pk_hash = match alg {
411-
// rsaEncryption
412-
RSA_ENCRYPTION_OID => None,
413-
// id-RSASSA-PSS
414-
RSASSA_PSS_OID => {
415-
let params = PssPrivateKeyParameters::try_from(
416-
pk_info
417-
.algorithm
418-
.parameters
419-
.ok_or_else(|| not_supported_error("malformed parameters"))?,
420-
)
421-
.map_err(|_| not_supported_error("malformed parameters"))?;
422-
423-
let hash_alg = params.hash_algorithm;
424-
match hash_alg.oid {
425-
// id-sha1
426-
ID_SHA1_OID => Some(ShaHash::Sha1),
427-
// id-sha256
428-
ID_SHA256_OID => Some(ShaHash::Sha256),
429-
// id-sha384
430-
ID_SHA384_OID => Some(ShaHash::Sha384),
431-
// id-sha256
432-
ID_SHA512_OID => Some(ShaHash::Sha512),
433-
_ => return Err(data_error("unsupported hash algorithm")),
434-
}
435-
}
436-
_ => return Err(data_error("unsupported algorithm")),
437-
};
438-
439-
// 7.
440-
if let Some(pk_hash) = pk_hash {
441-
if pk_hash != hash {
442-
return Err(data_error("hash mismatch"));
443-
}
330+
// 6-7. (skipped, only support rsaEncryption for interoperability)
331+
if alg != RSA_ENCRYPTION_OID {
332+
return Err(data_error("unsupported algorithm"));
444333
}
445334

446335
// 8-9.
@@ -478,7 +367,6 @@ fn import_key_rsapss(
478367

479368
fn import_key_rsaoaep(
480369
key_data: KeyData,
481-
hash: ShaHash,
482370
) -> Result<ImportKeyResult, deno_core::anyhow::Error> {
483371
match key_data {
484372
KeyData::Spki(data) => {
@@ -489,41 +377,9 @@ fn import_key_rsaoaep(
489377
// 4-5.
490378
let alg = pk_info.algorithm.oid;
491379

492-
// 6.
493-
let pk_hash = match alg {
494-
// rsaEncryption
495-
RSA_ENCRYPTION_OID => None,
496-
// id-RSAES-OAEP
497-
RSAES_OAEP_OID => {
498-
let params = OaepPrivateKeyParameters::try_from(
499-
pk_info
500-
.algorithm
501-
.parameters
502-
.ok_or_else(|| data_error("malformed parameters"))?,
503-
)
504-
.map_err(|_| data_error("malformed parameters"))?;
505-
506-
let hash_alg = params.hash_algorithm;
507-
match hash_alg.oid {
508-
// id-sha1
509-
ID_SHA1_OID => Some(ShaHash::Sha1),
510-
// id-sha256
511-
ID_SHA256_OID => Some(ShaHash::Sha256),
512-
// id-sha384
513-
ID_SHA384_OID => Some(ShaHash::Sha384),
514-
// id-sha256
515-
ID_SHA512_OID => Some(ShaHash::Sha512),
516-
_ => return Err(data_error("unsupported hash algorithm")),
517-
}
518-
}
519-
_ => return Err(data_error("unsupported algorithm")),
520-
};
521-
522-
// 7.
523-
if let Some(pk_hash) = pk_hash {
524-
if pk_hash != hash {
525-
return Err(data_error("hash mismatch"));
526-
}
380+
// 6-7. (skipped, only support rsaEncryption for interoperability)
381+
if alg != RSA_ENCRYPTION_OID {
382+
return Err(data_error("unsupported algorithm"));
527383
}
528384

529385
// 8-9.
@@ -560,42 +416,9 @@ fn import_key_rsaoaep(
560416
// 4-5.
561417
let alg = pk_info.algorithm.oid;
562418

563-
// 6.
564-
// 6.
565-
let pk_hash = match alg {
566-
// rsaEncryption
567-
RSA_ENCRYPTION_OID => None,
568-
// id-RSAES-OAEP
569-
RSAES_OAEP_OID => {
570-
let params = OaepPrivateKeyParameters::try_from(
571-
pk_info
572-
.algorithm
573-
.parameters
574-
.ok_or_else(|| not_supported_error("malformed parameters"))?,
575-
)
576-
.map_err(|_| not_supported_error("malformed parameters"))?;
577-
578-
let hash_alg = params.hash_algorithm;
579-
match hash_alg.oid {
580-
// id-sha1
581-
ID_SHA1_OID => Some(ShaHash::Sha1),
582-
// id-sha256
583-
ID_SHA256_OID => Some(ShaHash::Sha256),
584-
// id-sha384
585-
ID_SHA384_OID => Some(ShaHash::Sha384),
586-
// id-sha256
587-
ID_SHA512_OID => Some(ShaHash::Sha512),
588-
_ => return Err(data_error("unsupported hash algorithm")),
589-
}
590-
}
591-
_ => return Err(data_error("unsupported algorithm")),
592-
};
593-
594-
// 7.
595-
if let Some(pk_hash) = pk_hash {
596-
if pk_hash != hash {
597-
return Err(data_error("hash mismatch"));
598-
}
419+
// 6-7. (skipped, only support rsaEncryption for interoperability)
420+
if alg != RSA_ENCRYPTION_OID {
421+
return Err(data_error("unsupported algorithm"));
599422
}
600423

601424
// 8-9.

0 commit comments

Comments
 (0)