@protontech/openpgp 6.1.1-patch.2 → 6.1.1-patch.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- /*! OpenPGP.js v6.1.1-patch.2 - 2025-05-14 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */
1
+ /*! OpenPGP.js v6.1.1-patch.3 - 2025-06-18 - this is LGPL licensed code, see LICENSE/our website https://openpgpjs.org/ for more information. */
2
2
  'use strict';
3
3
 
4
4
  const globalThis = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -1089,11 +1089,10 @@ var enums = {
1089
1089
  ed25519: 27,
1090
1090
  /** Ed448 (Sign only) */
1091
1091
  ed448: 28,
1092
- /** Post-quantum ML-KEM-768 + X25519 (Encrypt only) */
1093
- pqc_mlkem_x25519: 105,
1094
1092
  /** Post-quantum ML-DSA-64 + Ed25519 (Sign only) */
1095
- pqc_mldsa_ed25519: 107,
1096
-
1093
+ pqc_mldsa_ed25519: 30,
1094
+ /** Post-quantum ML-KEM-768 + X25519 (Encrypt only) */
1095
+ pqc_mlkem_x25519: 35,
1097
1096
  /** Persistent symmetric keys: encryption algorithm */
1098
1097
  aead: 100,
1099
1098
  /** Persistent symmetric keys: authentication algorithm */
@@ -1744,7 +1743,7 @@ var config = {
1744
1743
  * @memberof module:config
1745
1744
  * @property {String} versionString A version string to be included in armored messages
1746
1745
  */
1747
- versionString: 'OpenPGP.js 6.1.1-patch.2',
1746
+ versionString: 'OpenPGP.js 6.1.1-patch.3',
1748
1747
  /**
1749
1748
  * @memberof module:config
1750
1749
  * @property {String} commentString A comment string to be included in armored messages
@@ -6990,17 +6989,11 @@ async function generate$a(algo) {
6990
6989
  * @async
6991
6990
  */
6992
6991
  async function sign$9(algo, hashAlgo, message, publicKey, privateKey, hashed) {
6993
- if (getHashByteLength(hashAlgo) < getHashByteLength(getPreferredHashAlgo$2(algo))) {
6994
- // Enforce digest sizes:
6995
- // - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
6996
- // - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
6997
- throw new Error('Hash algorithm too weak for EdDSA.');
6998
- }
6999
6992
  switch (algo) {
7000
6993
  case enums.publicKey.ed25519:
7001
6994
  try {
7002
6995
  const webCrypto = util.getWebCrypto();
7003
- const jwk = privateKeyToJWK(algo, publicKey, privateKey);
6996
+ const jwk = privateKeyToJWK$1(algo, publicKey, privateKey);
7004
6997
  const key = await webCrypto.importKey('jwk', jwk, 'Ed25519', false, ['sign']);
7005
6998
 
7006
6999
  const signature = new Uint8Array(
@@ -7040,17 +7033,11 @@ async function sign$9(algo, hashAlgo, message, publicKey, privateKey, hashed) {
7040
7033
  * @async
7041
7034
  */
7042
7035
  async function verify$9(algo, hashAlgo, { RS }, m, publicKey, hashed) {
7043
- if (getHashByteLength(hashAlgo) < getHashByteLength(getPreferredHashAlgo$2(algo))) {
7044
- // Enforce digest sizes:
7045
- // - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
7046
- // - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
7047
- throw new Error('Hash algorithm too weak for EdDSA.');
7048
- }
7049
7036
  switch (algo) {
7050
7037
  case enums.publicKey.ed25519:
7051
7038
  try {
7052
7039
  const webCrypto = util.getWebCrypto();
7053
- const jwk = publicKeyToJWK(algo, publicKey);
7040
+ const jwk = publicKeyToJWK$1(algo, publicKey);
7054
7041
  const key = await webCrypto.importKey('jwk', jwk, 'Ed25519', false, ['verify']);
7055
7042
  const verified = await webCrypto.verify('Ed25519', key, RS, hashed);
7056
7043
  return verified;
@@ -7125,7 +7112,7 @@ function getPreferredHashAlgo$2(algo) {
7125
7112
  }
7126
7113
  }
7127
7114
 
7128
- const publicKeyToJWK = (algo, publicKey) => {
7115
+ const publicKeyToJWK$1 = (algo, publicKey) => {
7129
7116
  switch (algo) {
7130
7117
  case enums.publicKey.ed25519: {
7131
7118
  const jwk = {
@@ -7141,10 +7128,10 @@ const publicKeyToJWK = (algo, publicKey) => {
7141
7128
  }
7142
7129
  };
7143
7130
 
7144
- const privateKeyToJWK = (algo, publicKey, privateKey) => {
7131
+ const privateKeyToJWK$1 = (algo, publicKey, privateKey) => {
7145
7132
  switch (algo) {
7146
7133
  case enums.publicKey.ed25519: {
7147
- const jwk = publicKeyToJWK(algo, publicKey);
7134
+ const jwk = publicKeyToJWK$1(algo, publicKey);
7148
7135
  jwk.d = uint8ArrayToB64(privateKey);
7149
7136
  return jwk;
7150
7137
  }
@@ -8384,12 +8371,27 @@ const HKDF_INFO = {
8384
8371
  */
8385
8372
  async function generate$9(algo) {
8386
8373
  switch (algo) {
8387
- case enums.publicKey.x25519: {
8388
- // k stays in little-endian, unlike legacy ECDH over curve25519
8389
- const k = getRandomBytes(32);
8390
- const { publicKey: A } = nacl.box.keyPair.fromSecretKey(k);
8391
- return { A, k };
8392
- }
8374
+ case enums.publicKey.x25519:
8375
+ try {
8376
+ const webCrypto = util.getWebCrypto();
8377
+ const webCryptoKey = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
8378
+
8379
+ const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey);
8380
+ const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey);
8381
+
8382
+ return {
8383
+ A: new Uint8Array(b64ToUint8Array(publicKey.x)),
8384
+ k: b64ToUint8Array(privateKey.d)
8385
+ };
8386
+ } catch (err) {
8387
+ if (err.name !== 'NotSupportedError') {
8388
+ throw err;
8389
+ }
8390
+ // k stays in little-endian, unlike legacy ECDH over curve25519
8391
+ const k = getRandomBytes(32);
8392
+ const { publicKey: A } = nacl.box.keyPair.fromSecretKey(k);
8393
+ return { A, k };
8394
+ }
8393
8395
 
8394
8396
  case enums.publicKey.x448: {
8395
8397
  const x448 = await util.getNobleCurve(enums.publicKey.x448);
@@ -8531,13 +8533,32 @@ function getPayloadSize(algo) {
8531
8533
  */
8532
8534
  async function generateEphemeralEncryptionMaterial(algo, recipientA) {
8533
8535
  switch (algo) {
8534
- case enums.publicKey.x25519: {
8535
- const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
8536
- const sharedSecret = nacl.scalarMult(ephemeralSecretKey, recipientA);
8537
- assertNonZeroArray(sharedSecret);
8538
- const { publicKey: ephemeralPublicKey } = nacl.box.keyPair.fromSecretKey(ephemeralSecretKey);
8539
- return { ephemeralPublicKey, sharedSecret };
8540
- }
8536
+ case enums.publicKey.x25519:
8537
+ try {
8538
+ const webCrypto = util.getWebCrypto();
8539
+ const jwk = publicKeyToJWK(algo, recipientA);
8540
+ const ephemeralKeyPair = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
8541
+ const recipientPublicKey = await webCrypto.importKey('jwk', jwk, 'X25519', false, []);
8542
+ const sharedSecretBuffer = await webCrypto.deriveBits(
8543
+ { name: 'X25519', public: recipientPublicKey },
8544
+ ephemeralKeyPair.privateKey,
8545
+ getPayloadSize(algo) * 8 // in bits
8546
+ );
8547
+ const ephemeralPublicKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.publicKey);
8548
+ return {
8549
+ sharedSecret: new Uint8Array(sharedSecretBuffer),
8550
+ ephemeralPublicKey: new Uint8Array(b64ToUint8Array(ephemeralPublicKeyJwt.x))
8551
+ };
8552
+ } catch (err) {
8553
+ if (err.name !== 'NotSupportedError') {
8554
+ throw err;
8555
+ }
8556
+ const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
8557
+ const sharedSecret = nacl.scalarMult(ephemeralSecretKey, recipientA);
8558
+ assertNonZeroArray(sharedSecret);
8559
+ const { publicKey: ephemeralPublicKey } = nacl.box.keyPair.fromSecretKey(ephemeralSecretKey);
8560
+ return { ephemeralPublicKey, sharedSecret };
8561
+ }
8541
8562
  case enums.publicKey.x448: {
8542
8563
  const x448 = await util.getNobleCurve(enums.publicKey.x448);
8543
8564
  const ephemeralSecretKey = x448.utils.randomPrivateKey();
@@ -8553,11 +8574,27 @@ async function generateEphemeralEncryptionMaterial(algo, recipientA) {
8553
8574
 
8554
8575
  async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
8555
8576
  switch (algo) {
8556
- case enums.publicKey.x25519: {
8557
- const sharedSecret = nacl.scalarMult(k, ephemeralPublicKey);
8558
- assertNonZeroArray(sharedSecret);
8559
- return sharedSecret;
8560
- }
8577
+ case enums.publicKey.x25519:
8578
+ try {
8579
+ const webCrypto = util.getWebCrypto();
8580
+ const privateKeyJWK = privateKeyToJWK(algo, A, k);
8581
+ const ephemeralPublicKeyJWK = publicKeyToJWK(algo, ephemeralPublicKey);
8582
+ const privateKey = await webCrypto.importKey('jwk', privateKeyJWK, 'X25519', false, ['deriveKey', 'deriveBits']);
8583
+ const ephemeralPublicKeyReference = await webCrypto.importKey('jwk', ephemeralPublicKeyJWK, 'X25519', false, []);
8584
+ const sharedSecretBuffer = await webCrypto.deriveBits(
8585
+ { name: 'X25519', public: ephemeralPublicKeyReference },
8586
+ privateKey,
8587
+ getPayloadSize(algo) * 8 // in bits
8588
+ );
8589
+ return new Uint8Array(sharedSecretBuffer);
8590
+ } catch (err) {
8591
+ if (err.name !== 'NotSupportedError') {
8592
+ throw err;
8593
+ }
8594
+ const sharedSecret = nacl.scalarMult(k, ephemeralPublicKey);
8595
+ assertNonZeroArray(sharedSecret);
8596
+ return sharedSecret;
8597
+ }
8561
8598
  case enums.publicKey.x448: {
8562
8599
  const x448 = await util.getNobleCurve(enums.publicKey.x448);
8563
8600
  const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey);
@@ -8585,6 +8622,35 @@ function assertNonZeroArray(sharedSecret) {
8585
8622
  }
8586
8623
  }
8587
8624
 
8625
+
8626
+ function publicKeyToJWK(algo, publicKey) {
8627
+ switch (algo) {
8628
+ case enums.publicKey.x25519: {
8629
+ const jwk = {
8630
+ kty: 'OKP',
8631
+ crv: 'X25519',
8632
+ x: uint8ArrayToB64(publicKey),
8633
+ ext: true
8634
+ };
8635
+ return jwk;
8636
+ }
8637
+ default:
8638
+ throw new Error('Unsupported ECDH algorithm');
8639
+ }
8640
+ }
8641
+
8642
+ function privateKeyToJWK(algo, publicKey, privateKey) {
8643
+ switch (algo) {
8644
+ case enums.publicKey.x25519: {
8645
+ const jwk = publicKeyToJWK(algo, publicKey);
8646
+ jwk.d = uint8ArrayToB64(privateKey);
8647
+ return jwk;
8648
+ }
8649
+ default:
8650
+ throw new Error('Unsupported ECDH algorithm');
8651
+ }
8652
+ }
8653
+
8588
8654
  var ecdh_x = /*#__PURE__*/Object.freeze({
8589
8655
  __proto__: null,
8590
8656
  decrypt: decrypt$4,
@@ -9292,12 +9358,6 @@ var ecdsa = /*#__PURE__*/Object.freeze({
9292
9358
  async function sign$7(oid, hashAlgo, message, publicKey, privateKey, hashed) {
9293
9359
  const curve = new CurveWithOID(oid);
9294
9360
  checkPublicPointEnconding(curve, publicKey);
9295
- if (getHashByteLength(hashAlgo) < getHashByteLength(enums.hash.sha256)) {
9296
- // Enforce digest sizes, since the constraint was already present in RFC4880bis:
9297
- // see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
9298
- // and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
9299
- throw new Error('Hash algorithm too weak for EdDSA.');
9300
- }
9301
9361
  const { RS: signature } = await sign$9(enums.publicKey.ed25519, hashAlgo, message, publicKey.subarray(1), privateKey, hashed);
9302
9362
  // EdDSA signature params are returned in little-endian format
9303
9363
  return {
@@ -9321,12 +9381,6 @@ async function sign$7(oid, hashAlgo, message, publicKey, privateKey, hashed) {
9321
9381
  async function verify$7(oid, hashAlgo, { r, s }, m, publicKey, hashed) {
9322
9382
  const curve = new CurveWithOID(oid);
9323
9383
  checkPublicPointEnconding(curve, publicKey);
9324
- if (getHashByteLength(hashAlgo) < getHashByteLength(enums.hash.sha256)) {
9325
- // Enforce digest sizes, since the constraint was already present in RFC4880bis:
9326
- // see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
9327
- // and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
9328
- throw new Error('Hash algorithm too weak for EdDSA.');
9329
- }
9330
9384
  const RS = util.concatUint8Array([r, s]);
9331
9385
  return verify$9(enums.publicKey.ed25519, hashAlgo, { RS }, m, publicKey.subarray(1), hashed);
9332
9386
  }
@@ -10184,7 +10238,7 @@ async function generate$4(algo) {
10184
10238
  async function encrypt$2(algo, eccPublicKey, mlkemPublicKey, sessioneKeyData) {
10185
10239
  const { eccKeyShare, eccCipherText } = await encaps$1(algo, eccPublicKey);
10186
10240
  const { mlkemKeyShare, mlkemCipherText } = await encaps(algo, mlkemPublicKey);
10187
- const kek = await multiKeyCombine(algo, eccKeyShare, eccCipherText, eccPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey);
10241
+ const kek = await multiKeyCombine(algo, mlkemKeyShare, eccKeyShare, eccCipherText, eccPublicKey);
10188
10242
  const wrappedKey = await wrap(enums.symmetric.aes256, kek, sessioneKeyData); // C
10189
10243
  return { eccCipherText, mlkemCipherText, wrappedKey };
10190
10244
  }
@@ -10192,25 +10246,24 @@ async function encrypt$2(algo, eccPublicKey, mlkemPublicKey, sessioneKeyData) {
10192
10246
  async function decrypt$2(algo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, mlkemPublicKey, encryptedSessionKeyData) {
10193
10247
  const eccKeyShare = await decaps$1(algo, eccCipherText, eccSecretKey, eccPublicKey);
10194
10248
  const mlkemKeyShare = await decaps(algo, mlkemCipherText, mlkemSecretKey);
10195
- const kek = await multiKeyCombine(algo, eccKeyShare, eccCipherText, eccPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey);
10249
+ const kek = await multiKeyCombine(algo, mlkemKeyShare, eccKeyShare, eccCipherText, eccPublicKey);
10196
10250
  const sessionKey = await unwrap(enums.symmetric.aes256, kek, encryptedSessionKeyData);
10197
10251
  return sessionKey;
10198
10252
  }
10199
10253
 
10200
- async function multiKeyCombine(algo, ecdhKeyShare, ecdhCipherText, ecdhPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey) {
10201
- // LAMPS-aligned and NIST compatible combiner, proposed in: https://mailarchive.ietf.org/arch/msg/openpgp/NMTCy707LICtxIhP3Xt1U5C8MF0/
10202
- // 2a. KDF(mlkemSS || tradSS || tradCT || tradPK || Domain)
10203
- // where Domain is "Domain" for LAMPS, and "mlkemCT || mlkemPK || algId || const" for OpenPGP
10254
+ /**
10255
+ * KEM key combiner
10256
+ */
10257
+ async function multiKeyCombine(algo, mlkemKeyShare, ecdhKeyShare, ecdhCipherText, ecdhPublicKey) {
10258
+ const domSep = util.encodeUTF8('OpenPGPCompositeKDFv1');
10204
10259
  const encData = util.concatUint8Array([
10205
10260
  mlkemKeyShare,
10206
10261
  ecdhKeyShare,
10207
10262
  ecdhCipherText,
10208
10263
  ecdhPublicKey,
10209
- // domSep
10210
- mlkemCipherText,
10211
- mlkemPublicKey,
10212
10264
  new Uint8Array([algo]),
10213
- util.encodeUTF8('OpenPGPCompositeKDFv1')
10265
+ domSep,
10266
+ new Uint8Array([domSep.length])
10214
10267
  ]);
10215
10268
 
10216
10269
  const kek = await computeDigest(enums.hash.sha3_256, encData);
@@ -10347,12 +10400,6 @@ async function generate$1(algo) {
10347
10400
  }
10348
10401
 
10349
10402
  async function sign$2(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, mldsaSecretKey, dataDigest) {
10350
- if (hashAlgo !== getRequiredHashAlgo(signatureAlgo)) {
10351
- // The signature hash algo MUST be set to the specified algorithm, see
10352
- // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1.
10353
- throw new Error('Unexpected hash algorithm for PQC signature');
10354
- }
10355
-
10356
10403
  switch (signatureAlgo) {
10357
10404
  case enums.publicKey.pqc_mldsa_ed25519: {
10358
10405
  const { eccSignature } = await sign$3(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, dataDigest);
@@ -10366,12 +10413,6 @@ async function sign$2(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, mldsa
10366
10413
  }
10367
10414
 
10368
10415
  async function verify$2(signatureAlgo, hashAlgo, eccPublicKey, mldsaPublicKey, dataDigest, { eccSignature, mldsaSignature }) {
10369
- if (hashAlgo !== getRequiredHashAlgo(signatureAlgo)) {
10370
- // The signature hash algo MUST be set to the specified algorithm, see
10371
- // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1.
10372
- throw new Error('Unexpected hash algorithm for PQC signature');
10373
- }
10374
-
10375
10416
  switch (signatureAlgo) {
10376
10417
  case enums.publicKey.pqc_mldsa_ed25519: {
10377
10418
  const eccVerifiedPromise = verify$3(signatureAlgo, hashAlgo, eccPublicKey, dataDigest, eccSignature);
@@ -10384,11 +10425,12 @@ async function verify$2(signatureAlgo, hashAlgo, eccPublicKey, mldsaPublicKey, d
10384
10425
  }
10385
10426
  }
10386
10427
 
10387
- function getRequiredHashAlgo(signatureAlgo) {
10388
- // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1.
10428
+ function isCompatibleHashAlgo(signatureAlgo, hashAlgo) {
10429
+ // The signature hash algo MUST have digest larger than 256 bits
10430
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-10.html#section-9.4
10389
10431
  switch (signatureAlgo) {
10390
10432
  case enums.publicKey.pqc_mldsa_ed25519:
10391
- return enums.hash.sha3_256;
10433
+ return getHashByteLength(hashAlgo) >= 32;
10392
10434
  default:
10393
10435
  throw new Error('Unsupported signature algorithm');
10394
10436
  }
@@ -12511,6 +12553,12 @@ async function verify$1(algo, hashAlgo, signature, publicParams, privateParams,
12511
12553
  return verify$8(oid, hashAlgo, { r, s }, data, Q, hashed);
12512
12554
  }
12513
12555
  case enums.publicKey.eddsaLegacy: {
12556
+ if (getHashByteLength(hashAlgo) < getHashByteLength(enums.hash.sha256)) {
12557
+ // Enforce digest sizes, since the constraint was already present in RFC4880bis:
12558
+ // see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
12559
+ // and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
12560
+ throw new Error('Hash algorithm too weak for EdDSALegacy.');
12561
+ }
12514
12562
  const { oid, Q } = publicParams;
12515
12563
  const curveSize = new CurveWithOID(oid).payloadSize;
12516
12564
  // When dealing little-endian MPI data, we always need to left-pad it, as done with big-endian values:
@@ -12521,6 +12569,13 @@ async function verify$1(algo, hashAlgo, signature, publicParams, privateParams,
12521
12569
  }
12522
12570
  case enums.publicKey.ed25519:
12523
12571
  case enums.publicKey.ed448: {
12572
+ if (getHashByteLength(hashAlgo) < getHashByteLength(getPreferredHashAlgo$2(algo))) {
12573
+ // Enforce digest sizes:
12574
+ // - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
12575
+ // - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
12576
+ throw new Error('Hash algorithm too weak for EdDSA.');
12577
+ }
12578
+
12524
12579
  const { A } = publicParams;
12525
12580
  return verify$9(algo, hashAlgo, signature, data, A, hashed);
12526
12581
  }
@@ -12533,6 +12588,11 @@ async function verify$1(algo, hashAlgo, signature, publicParams, privateParams,
12533
12588
  return verify$5(algo.getValue(), keyMaterial, signature.mac.data, hashed);
12534
12589
  }
12535
12590
  case enums.publicKey.pqc_mldsa_ed25519: {
12591
+ if (!isCompatibleHashAlgo(algo, hashAlgo)) {
12592
+ // The signature hash algo MUST have digest larger than 256 bits
12593
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-10.html#section-9.4
12594
+ throw new Error('Unexpected hash algorithm for PQC signature: digest size too short');
12595
+ }
12536
12596
  const { eccPublicKey, mldsaPublicKey } = publicParams;
12537
12597
  return verify$2(algo, hashAlgo, eccPublicKey, mldsaPublicKey, hashed, signature);
12538
12598
  }
@@ -12581,12 +12641,24 @@ async function sign$1(algo, hashAlgo, publicKeyParams, privateKeyParams, data, h
12581
12641
  return sign$8(oid, hashAlgo, data, Q, d, hashed);
12582
12642
  }
12583
12643
  case enums.publicKey.eddsaLegacy: {
12644
+ if (getHashByteLength(hashAlgo) < getHashByteLength(enums.hash.sha256)) {
12645
+ // Enforce digest sizes, since the constraint was already present in RFC4880bis:
12646
+ // see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
12647
+ // and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
12648
+ throw new Error('Hash algorithm too weak for EdDSALegacy.');
12649
+ }
12584
12650
  const { oid, Q } = publicKeyParams;
12585
12651
  const { seed } = privateKeyParams;
12586
12652
  return sign$7(oid, hashAlgo, data, Q, seed, hashed);
12587
12653
  }
12588
12654
  case enums.publicKey.ed25519:
12589
12655
  case enums.publicKey.ed448: {
12656
+ if (getHashByteLength(hashAlgo) < getHashByteLength(getPreferredHashAlgo$2(algo))) {
12657
+ // Enforce digest sizes:
12658
+ // - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
12659
+ // - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
12660
+ throw new Error('Hash algorithm too weak for EdDSA.');
12661
+ }
12590
12662
  const { A } = publicKeyParams;
12591
12663
  const { seed } = privateKeyParams;
12592
12664
  return sign$9(algo, hashAlgo, data, A, seed, hashed);
@@ -12598,6 +12670,11 @@ async function sign$1(algo, hashAlgo, publicKeyParams, privateKeyParams, data, h
12598
12670
  return { mac: new ShortByteString(mac) };
12599
12671
  }
12600
12672
  case enums.publicKey.pqc_mldsa_ed25519: {
12673
+ if (!isCompatibleHashAlgo(algo, hashAlgo)) {
12674
+ // The signature hash algo MUST have digest larger than 256 bits
12675
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-10.html#section-9.4
12676
+ throw new Error('Unexpected hash algorithm for PQC signature: digest size too short');
12677
+ }
12601
12678
  const { eccPublicKey } = publicKeyParams;
12602
12679
  const { eccSecretKey, mldsaSecretKey } = privateKeyParams;
12603
12680
  return sign$2(algo, hashAlgo, eccSecretKey, eccPublicKey, mldsaSecretKey, hashed);
@@ -16874,12 +16951,8 @@ class PublicKeyPacket {
16874
16951
  throw new Error('Legacy curve25519 cannot be used with v6 keys');
16875
16952
  }
16876
16953
  // The composite ML-DSA + EdDSA schemes MUST be used only with v6 keys.
16877
- // The composite ML-KEM + ECDH schemes MUST be used only with v6 keys.
16878
- if (this.version !== 6 && (
16879
- this.algorithm === enums.publicKey.pqc_mldsa_ed25519 ||
16880
- this.algorithm === enums.publicKey.pqc_mlkem_x25519
16881
- )) {
16882
- throw new Error('Unexpected key version: ML-DSA and ML-KEM algorithms can only be used with v6 keys');
16954
+ if (this.version !== 6 && this.algorithm === enums.publicKey.pqc_mldsa_ed25519) {
16955
+ throw new Error('Unexpected key version: ML-DSA algorithms can only be used with v6 keys');
16883
16956
  }
16884
16957
  this.publicParams = publicParams;
16885
16958
  pos += read;
@@ -17879,11 +17952,8 @@ class SecretKeyPacket extends PublicKeyPacket {
17879
17952
  )) {
17880
17953
  throw new Error(`Cannot generate v6 keys of type 'ecc' with curve ${curve}. Generate a key of type 'curve25519' instead`);
17881
17954
  }
17882
- if (this.version !== 6 && (
17883
- this.algorithm === enums.publicKey.pqc_mldsa_ed25519 ||
17884
- this.algorithm === enums.publicKey.pqc_mlkem_x25519
17885
- )) {
17886
- throw new Error(`Cannot generate v${this.version} keys of type 'pqc'. Generate a v6 key instead`);
17955
+ if (this.version !== 6 && this.algorithm === enums.publicKey.pqc_mldsa_ed25519) {
17956
+ throw new Error(`Cannot generate v${this.version} signing keys of type 'pqc'. Generate a v6 key instead`);
17887
17957
  }
17888
17958
  const { privateParams, publicParams } = await generateParams(this.algorithm, bits, curve, symmetric);
17889
17959
  this.privateParams = privateParams;
@@ -18387,12 +18457,6 @@ async function createBindingSignature(subkey, primaryKey, options, config) {
18387
18457
  * @async
18388
18458
  */
18389
18459
  async function getPreferredHashAlgo(targetKeys, signingKeyPacket, date = new Date(), targetUserIDs = [], config) {
18390
- if (signingKeyPacket.algorithm === enums.publicKey.pqc_mldsa_ed25519) {
18391
- // For PQC, the returned hash algo MUST be set to the specified algorithm, see
18392
- // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1.
18393
- return getRequiredHashAlgo(signingKeyPacket.algorithm);
18394
- }
18395
-
18396
18460
  /**
18397
18461
  * If `preferredSenderAlgo` appears in the prefs of all recipients, we pick it; otherwise, we use the
18398
18462
  * strongest supported algo (`defaultAlgo` is always implicitly supported by all keys).
@@ -18440,6 +18504,10 @@ async function getPreferredHashAlgo(targetKeys, signingKeyPacket, date = new Dat
18440
18504
  enums.publicKey.ed448
18441
18505
  ]);
18442
18506
 
18507
+ const pqcAlgos = new Set([
18508
+ enums.publicKey.pqc_mldsa_ed25519
18509
+ ]);
18510
+
18443
18511
  if (eccAlgos.has(signingKeyPacket.algorithm)) {
18444
18512
  // For ECC, the returned hash algo MUST be at least as strong as `preferredCurveHashAlgo`, see:
18445
18513
  // - ECDSA: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.2-5
@@ -18462,6 +18530,21 @@ async function getPreferredHashAlgo(targetKeys, signingKeyPacket, date = new Dat
18462
18530
  strongestSupportedAlgo :
18463
18531
  preferredCurveAlgo;
18464
18532
  }
18533
+ } else if (pqcAlgos.has(signingKeyPacket.algorithm)) {
18534
+ // For PQC, the returned hash algo MUST be at least 256 bit long, see:
18535
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-10.html#section-9.4 .
18536
+ // Hence, we return the `preferredHashAlgo` as long as it's supported and long enough;
18537
+ // Otherwise, we look at the strongest supported algo, and ultimately fallback the default algo (SHA-256).
18538
+ const preferredSenderAlgoIsSupported = isSupportedHashAlgo(preferredSenderAlgo) && isCompatibleHashAlgo(signingKeyPacket.algorithm, preferredSenderAlgo);
18539
+
18540
+ if (preferredSenderAlgoIsSupported) {
18541
+ return preferredSenderAlgo;
18542
+ } else {
18543
+ const strongestSupportedAlgo = getStrongestSupportedHashAlgo();
18544
+ return isCompatibleHashAlgo(signingKeyPacket.algorithm, strongestSupportedAlgo) ?
18545
+ strongestSupportedAlgo :
18546
+ defaultAlgo;
18547
+ }
18465
18548
  }
18466
18549
 
18467
18550
  // `preferredSenderAlgo` may be weaker than the default, but we do not guard against this,