@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
  const globalThis = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
3
3
 
4
4
  import { createRequire } from 'module';
@@ -1066,11 +1066,10 @@ var enums = {
1066
1066
  ed25519: 27,
1067
1067
  /** Ed448 (Sign only) */
1068
1068
  ed448: 28,
1069
- /** Post-quantum ML-KEM-768 + X25519 (Encrypt only) */
1070
- pqc_mlkem_x25519: 105,
1071
1069
  /** Post-quantum ML-DSA-64 + Ed25519 (Sign only) */
1072
- pqc_mldsa_ed25519: 107,
1073
-
1070
+ pqc_mldsa_ed25519: 30,
1071
+ /** Post-quantum ML-KEM-768 + X25519 (Encrypt only) */
1072
+ pqc_mlkem_x25519: 35,
1074
1073
  /** Persistent symmetric keys: encryption algorithm */
1075
1074
  aead: 100,
1076
1075
  /** Persistent symmetric keys: authentication algorithm */
@@ -1721,7 +1720,7 @@ var config = {
1721
1720
  * @memberof module:config
1722
1721
  * @property {String} versionString A version string to be included in armored messages
1723
1722
  */
1724
- versionString: 'OpenPGP.js 6.1.1-patch.2',
1723
+ versionString: 'OpenPGP.js 6.1.1-patch.3',
1725
1724
  /**
1726
1725
  * @memberof module:config
1727
1726
  * @property {String} commentString A comment string to be included in armored messages
@@ -6967,17 +6966,11 @@ async function generate$a(algo) {
6967
6966
  * @async
6968
6967
  */
6969
6968
  async function sign$9(algo, hashAlgo, message, publicKey, privateKey, hashed) {
6970
- if (getHashByteLength(hashAlgo) < getHashByteLength(getPreferredHashAlgo$2(algo))) {
6971
- // Enforce digest sizes:
6972
- // - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
6973
- // - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
6974
- throw new Error('Hash algorithm too weak for EdDSA.');
6975
- }
6976
6969
  switch (algo) {
6977
6970
  case enums.publicKey.ed25519:
6978
6971
  try {
6979
6972
  const webCrypto = util.getWebCrypto();
6980
- const jwk = privateKeyToJWK(algo, publicKey, privateKey);
6973
+ const jwk = privateKeyToJWK$1(algo, publicKey, privateKey);
6981
6974
  const key = await webCrypto.importKey('jwk', jwk, 'Ed25519', false, ['sign']);
6982
6975
 
6983
6976
  const signature = new Uint8Array(
@@ -7017,17 +7010,11 @@ async function sign$9(algo, hashAlgo, message, publicKey, privateKey, hashed) {
7017
7010
  * @async
7018
7011
  */
7019
7012
  async function verify$9(algo, hashAlgo, { RS }, m, publicKey, hashed) {
7020
- if (getHashByteLength(hashAlgo) < getHashByteLength(getPreferredHashAlgo$2(algo))) {
7021
- // Enforce digest sizes:
7022
- // - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
7023
- // - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
7024
- throw new Error('Hash algorithm too weak for EdDSA.');
7025
- }
7026
7013
  switch (algo) {
7027
7014
  case enums.publicKey.ed25519:
7028
7015
  try {
7029
7016
  const webCrypto = util.getWebCrypto();
7030
- const jwk = publicKeyToJWK(algo, publicKey);
7017
+ const jwk = publicKeyToJWK$1(algo, publicKey);
7031
7018
  const key = await webCrypto.importKey('jwk', jwk, 'Ed25519', false, ['verify']);
7032
7019
  const verified = await webCrypto.verify('Ed25519', key, RS, hashed);
7033
7020
  return verified;
@@ -7102,7 +7089,7 @@ function getPreferredHashAlgo$2(algo) {
7102
7089
  }
7103
7090
  }
7104
7091
 
7105
- const publicKeyToJWK = (algo, publicKey) => {
7092
+ const publicKeyToJWK$1 = (algo, publicKey) => {
7106
7093
  switch (algo) {
7107
7094
  case enums.publicKey.ed25519: {
7108
7095
  const jwk = {
@@ -7118,10 +7105,10 @@ const publicKeyToJWK = (algo, publicKey) => {
7118
7105
  }
7119
7106
  };
7120
7107
 
7121
- const privateKeyToJWK = (algo, publicKey, privateKey) => {
7108
+ const privateKeyToJWK$1 = (algo, publicKey, privateKey) => {
7122
7109
  switch (algo) {
7123
7110
  case enums.publicKey.ed25519: {
7124
- const jwk = publicKeyToJWK(algo, publicKey);
7111
+ const jwk = publicKeyToJWK$1(algo, publicKey);
7125
7112
  jwk.d = uint8ArrayToB64(privateKey);
7126
7113
  return jwk;
7127
7114
  }
@@ -8361,12 +8348,27 @@ const HKDF_INFO = {
8361
8348
  */
8362
8349
  async function generate$9(algo) {
8363
8350
  switch (algo) {
8364
- case enums.publicKey.x25519: {
8365
- // k stays in little-endian, unlike legacy ECDH over curve25519
8366
- const k = getRandomBytes(32);
8367
- const { publicKey: A } = nacl.box.keyPair.fromSecretKey(k);
8368
- return { A, k };
8369
- }
8351
+ case enums.publicKey.x25519:
8352
+ try {
8353
+ const webCrypto = util.getWebCrypto();
8354
+ const webCryptoKey = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
8355
+
8356
+ const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey);
8357
+ const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey);
8358
+
8359
+ return {
8360
+ A: new Uint8Array(b64ToUint8Array(publicKey.x)),
8361
+ k: b64ToUint8Array(privateKey.d)
8362
+ };
8363
+ } catch (err) {
8364
+ if (err.name !== 'NotSupportedError') {
8365
+ throw err;
8366
+ }
8367
+ // k stays in little-endian, unlike legacy ECDH over curve25519
8368
+ const k = getRandomBytes(32);
8369
+ const { publicKey: A } = nacl.box.keyPair.fromSecretKey(k);
8370
+ return { A, k };
8371
+ }
8370
8372
 
8371
8373
  case enums.publicKey.x448: {
8372
8374
  const x448 = await util.getNobleCurve(enums.publicKey.x448);
@@ -8508,13 +8510,32 @@ function getPayloadSize(algo) {
8508
8510
  */
8509
8511
  async function generateEphemeralEncryptionMaterial(algo, recipientA) {
8510
8512
  switch (algo) {
8511
- case enums.publicKey.x25519: {
8512
- const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
8513
- const sharedSecret = nacl.scalarMult(ephemeralSecretKey, recipientA);
8514
- assertNonZeroArray(sharedSecret);
8515
- const { publicKey: ephemeralPublicKey } = nacl.box.keyPair.fromSecretKey(ephemeralSecretKey);
8516
- return { ephemeralPublicKey, sharedSecret };
8517
- }
8513
+ case enums.publicKey.x25519:
8514
+ try {
8515
+ const webCrypto = util.getWebCrypto();
8516
+ const jwk = publicKeyToJWK(algo, recipientA);
8517
+ const ephemeralKeyPair = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
8518
+ const recipientPublicKey = await webCrypto.importKey('jwk', jwk, 'X25519', false, []);
8519
+ const sharedSecretBuffer = await webCrypto.deriveBits(
8520
+ { name: 'X25519', public: recipientPublicKey },
8521
+ ephemeralKeyPair.privateKey,
8522
+ getPayloadSize(algo) * 8 // in bits
8523
+ );
8524
+ const ephemeralPublicKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.publicKey);
8525
+ return {
8526
+ sharedSecret: new Uint8Array(sharedSecretBuffer),
8527
+ ephemeralPublicKey: new Uint8Array(b64ToUint8Array(ephemeralPublicKeyJwt.x))
8528
+ };
8529
+ } catch (err) {
8530
+ if (err.name !== 'NotSupportedError') {
8531
+ throw err;
8532
+ }
8533
+ const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
8534
+ const sharedSecret = nacl.scalarMult(ephemeralSecretKey, recipientA);
8535
+ assertNonZeroArray(sharedSecret);
8536
+ const { publicKey: ephemeralPublicKey } = nacl.box.keyPair.fromSecretKey(ephemeralSecretKey);
8537
+ return { ephemeralPublicKey, sharedSecret };
8538
+ }
8518
8539
  case enums.publicKey.x448: {
8519
8540
  const x448 = await util.getNobleCurve(enums.publicKey.x448);
8520
8541
  const ephemeralSecretKey = x448.utils.randomPrivateKey();
@@ -8530,11 +8551,27 @@ async function generateEphemeralEncryptionMaterial(algo, recipientA) {
8530
8551
 
8531
8552
  async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
8532
8553
  switch (algo) {
8533
- case enums.publicKey.x25519: {
8534
- const sharedSecret = nacl.scalarMult(k, ephemeralPublicKey);
8535
- assertNonZeroArray(sharedSecret);
8536
- return sharedSecret;
8537
- }
8554
+ case enums.publicKey.x25519:
8555
+ try {
8556
+ const webCrypto = util.getWebCrypto();
8557
+ const privateKeyJWK = privateKeyToJWK(algo, A, k);
8558
+ const ephemeralPublicKeyJWK = publicKeyToJWK(algo, ephemeralPublicKey);
8559
+ const privateKey = await webCrypto.importKey('jwk', privateKeyJWK, 'X25519', false, ['deriveKey', 'deriveBits']);
8560
+ const ephemeralPublicKeyReference = await webCrypto.importKey('jwk', ephemeralPublicKeyJWK, 'X25519', false, []);
8561
+ const sharedSecretBuffer = await webCrypto.deriveBits(
8562
+ { name: 'X25519', public: ephemeralPublicKeyReference },
8563
+ privateKey,
8564
+ getPayloadSize(algo) * 8 // in bits
8565
+ );
8566
+ return new Uint8Array(sharedSecretBuffer);
8567
+ } catch (err) {
8568
+ if (err.name !== 'NotSupportedError') {
8569
+ throw err;
8570
+ }
8571
+ const sharedSecret = nacl.scalarMult(k, ephemeralPublicKey);
8572
+ assertNonZeroArray(sharedSecret);
8573
+ return sharedSecret;
8574
+ }
8538
8575
  case enums.publicKey.x448: {
8539
8576
  const x448 = await util.getNobleCurve(enums.publicKey.x448);
8540
8577
  const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey);
@@ -8562,6 +8599,35 @@ function assertNonZeroArray(sharedSecret) {
8562
8599
  }
8563
8600
  }
8564
8601
 
8602
+
8603
+ function publicKeyToJWK(algo, publicKey) {
8604
+ switch (algo) {
8605
+ case enums.publicKey.x25519: {
8606
+ const jwk = {
8607
+ kty: 'OKP',
8608
+ crv: 'X25519',
8609
+ x: uint8ArrayToB64(publicKey),
8610
+ ext: true
8611
+ };
8612
+ return jwk;
8613
+ }
8614
+ default:
8615
+ throw new Error('Unsupported ECDH algorithm');
8616
+ }
8617
+ }
8618
+
8619
+ function privateKeyToJWK(algo, publicKey, privateKey) {
8620
+ switch (algo) {
8621
+ case enums.publicKey.x25519: {
8622
+ const jwk = publicKeyToJWK(algo, publicKey);
8623
+ jwk.d = uint8ArrayToB64(privateKey);
8624
+ return jwk;
8625
+ }
8626
+ default:
8627
+ throw new Error('Unsupported ECDH algorithm');
8628
+ }
8629
+ }
8630
+
8565
8631
  var ecdh_x = /*#__PURE__*/Object.freeze({
8566
8632
  __proto__: null,
8567
8633
  decrypt: decrypt$4,
@@ -9269,12 +9335,6 @@ var ecdsa = /*#__PURE__*/Object.freeze({
9269
9335
  async function sign$7(oid, hashAlgo, message, publicKey, privateKey, hashed) {
9270
9336
  const curve = new CurveWithOID(oid);
9271
9337
  checkPublicPointEnconding(curve, publicKey);
9272
- if (getHashByteLength(hashAlgo) < getHashByteLength(enums.hash.sha256)) {
9273
- // Enforce digest sizes, since the constraint was already present in RFC4880bis:
9274
- // see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
9275
- // and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
9276
- throw new Error('Hash algorithm too weak for EdDSA.');
9277
- }
9278
9338
  const { RS: signature } = await sign$9(enums.publicKey.ed25519, hashAlgo, message, publicKey.subarray(1), privateKey, hashed);
9279
9339
  // EdDSA signature params are returned in little-endian format
9280
9340
  return {
@@ -9298,12 +9358,6 @@ async function sign$7(oid, hashAlgo, message, publicKey, privateKey, hashed) {
9298
9358
  async function verify$7(oid, hashAlgo, { r, s }, m, publicKey, hashed) {
9299
9359
  const curve = new CurveWithOID(oid);
9300
9360
  checkPublicPointEnconding(curve, publicKey);
9301
- if (getHashByteLength(hashAlgo) < getHashByteLength(enums.hash.sha256)) {
9302
- // Enforce digest sizes, since the constraint was already present in RFC4880bis:
9303
- // see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
9304
- // and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
9305
- throw new Error('Hash algorithm too weak for EdDSA.');
9306
- }
9307
9361
  const RS = util.concatUint8Array([r, s]);
9308
9362
  return verify$9(enums.publicKey.ed25519, hashAlgo, { RS }, m, publicKey.subarray(1), hashed);
9309
9363
  }
@@ -10161,7 +10215,7 @@ async function generate$4(algo) {
10161
10215
  async function encrypt$2(algo, eccPublicKey, mlkemPublicKey, sessioneKeyData) {
10162
10216
  const { eccKeyShare, eccCipherText } = await encaps$1(algo, eccPublicKey);
10163
10217
  const { mlkemKeyShare, mlkemCipherText } = await encaps(algo, mlkemPublicKey);
10164
- const kek = await multiKeyCombine(algo, eccKeyShare, eccCipherText, eccPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey);
10218
+ const kek = await multiKeyCombine(algo, mlkemKeyShare, eccKeyShare, eccCipherText, eccPublicKey);
10165
10219
  const wrappedKey = await wrap(enums.symmetric.aes256, kek, sessioneKeyData); // C
10166
10220
  return { eccCipherText, mlkemCipherText, wrappedKey };
10167
10221
  }
@@ -10169,25 +10223,24 @@ async function encrypt$2(algo, eccPublicKey, mlkemPublicKey, sessioneKeyData) {
10169
10223
  async function decrypt$2(algo, eccCipherText, mlkemCipherText, eccSecretKey, eccPublicKey, mlkemSecretKey, mlkemPublicKey, encryptedSessionKeyData) {
10170
10224
  const eccKeyShare = await decaps$1(algo, eccCipherText, eccSecretKey, eccPublicKey);
10171
10225
  const mlkemKeyShare = await decaps(algo, mlkemCipherText, mlkemSecretKey);
10172
- const kek = await multiKeyCombine(algo, eccKeyShare, eccCipherText, eccPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey);
10226
+ const kek = await multiKeyCombine(algo, mlkemKeyShare, eccKeyShare, eccCipherText, eccPublicKey);
10173
10227
  const sessionKey = await unwrap(enums.symmetric.aes256, kek, encryptedSessionKeyData);
10174
10228
  return sessionKey;
10175
10229
  }
10176
10230
 
10177
- async function multiKeyCombine(algo, ecdhKeyShare, ecdhCipherText, ecdhPublicKey, mlkemKeyShare, mlkemCipherText, mlkemPublicKey) {
10178
- // LAMPS-aligned and NIST compatible combiner, proposed in: https://mailarchive.ietf.org/arch/msg/openpgp/NMTCy707LICtxIhP3Xt1U5C8MF0/
10179
- // 2a. KDF(mlkemSS || tradSS || tradCT || tradPK || Domain)
10180
- // where Domain is "Domain" for LAMPS, and "mlkemCT || mlkemPK || algId || const" for OpenPGP
10231
+ /**
10232
+ * KEM key combiner
10233
+ */
10234
+ async function multiKeyCombine(algo, mlkemKeyShare, ecdhKeyShare, ecdhCipherText, ecdhPublicKey) {
10235
+ const domSep = util.encodeUTF8('OpenPGPCompositeKDFv1');
10181
10236
  const encData = util.concatUint8Array([
10182
10237
  mlkemKeyShare,
10183
10238
  ecdhKeyShare,
10184
10239
  ecdhCipherText,
10185
10240
  ecdhPublicKey,
10186
- // domSep
10187
- mlkemCipherText,
10188
- mlkemPublicKey,
10189
10241
  new Uint8Array([algo]),
10190
- util.encodeUTF8('OpenPGPCompositeKDFv1')
10242
+ domSep,
10243
+ new Uint8Array([domSep.length])
10191
10244
  ]);
10192
10245
 
10193
10246
  const kek = await computeDigest(enums.hash.sha3_256, encData);
@@ -10324,12 +10377,6 @@ async function generate$1(algo) {
10324
10377
  }
10325
10378
 
10326
10379
  async function sign$2(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, mldsaSecretKey, dataDigest) {
10327
- if (hashAlgo !== getRequiredHashAlgo(signatureAlgo)) {
10328
- // The signature hash algo MUST be set to the specified algorithm, see
10329
- // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1.
10330
- throw new Error('Unexpected hash algorithm for PQC signature');
10331
- }
10332
-
10333
10380
  switch (signatureAlgo) {
10334
10381
  case enums.publicKey.pqc_mldsa_ed25519: {
10335
10382
  const { eccSignature } = await sign$3(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, dataDigest);
@@ -10343,12 +10390,6 @@ async function sign$2(signatureAlgo, hashAlgo, eccSecretKey, eccPublicKey, mldsa
10343
10390
  }
10344
10391
 
10345
10392
  async function verify$2(signatureAlgo, hashAlgo, eccPublicKey, mldsaPublicKey, dataDigest, { eccSignature, mldsaSignature }) {
10346
- if (hashAlgo !== getRequiredHashAlgo(signatureAlgo)) {
10347
- // The signature hash algo MUST be set to the specified algorithm, see
10348
- // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1.
10349
- throw new Error('Unexpected hash algorithm for PQC signature');
10350
- }
10351
-
10352
10393
  switch (signatureAlgo) {
10353
10394
  case enums.publicKey.pqc_mldsa_ed25519: {
10354
10395
  const eccVerifiedPromise = verify$3(signatureAlgo, hashAlgo, eccPublicKey, dataDigest, eccSignature);
@@ -10361,11 +10402,12 @@ async function verify$2(signatureAlgo, hashAlgo, eccPublicKey, mldsaPublicKey, d
10361
10402
  }
10362
10403
  }
10363
10404
 
10364
- function getRequiredHashAlgo(signatureAlgo) {
10365
- // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1.
10405
+ function isCompatibleHashAlgo(signatureAlgo, hashAlgo) {
10406
+ // The signature hash algo MUST have digest larger than 256 bits
10407
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-10.html#section-9.4
10366
10408
  switch (signatureAlgo) {
10367
10409
  case enums.publicKey.pqc_mldsa_ed25519:
10368
- return enums.hash.sha3_256;
10410
+ return getHashByteLength(hashAlgo) >= 32;
10369
10411
  default:
10370
10412
  throw new Error('Unsupported signature algorithm');
10371
10413
  }
@@ -12488,6 +12530,12 @@ async function verify$1(algo, hashAlgo, signature, publicParams, privateParams,
12488
12530
  return verify$8(oid, hashAlgo, { r, s }, data, Q, hashed);
12489
12531
  }
12490
12532
  case enums.publicKey.eddsaLegacy: {
12533
+ if (getHashByteLength(hashAlgo) < getHashByteLength(enums.hash.sha256)) {
12534
+ // Enforce digest sizes, since the constraint was already present in RFC4880bis:
12535
+ // see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
12536
+ // and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
12537
+ throw new Error('Hash algorithm too weak for EdDSALegacy.');
12538
+ }
12491
12539
  const { oid, Q } = publicParams;
12492
12540
  const curveSize = new CurveWithOID(oid).payloadSize;
12493
12541
  // When dealing little-endian MPI data, we always need to left-pad it, as done with big-endian values:
@@ -12498,6 +12546,13 @@ async function verify$1(algo, hashAlgo, signature, publicParams, privateParams,
12498
12546
  }
12499
12547
  case enums.publicKey.ed25519:
12500
12548
  case enums.publicKey.ed448: {
12549
+ if (getHashByteLength(hashAlgo) < getHashByteLength(getPreferredHashAlgo$2(algo))) {
12550
+ // Enforce digest sizes:
12551
+ // - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
12552
+ // - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
12553
+ throw new Error('Hash algorithm too weak for EdDSA.');
12554
+ }
12555
+
12501
12556
  const { A } = publicParams;
12502
12557
  return verify$9(algo, hashAlgo, signature, data, A, hashed);
12503
12558
  }
@@ -12510,6 +12565,11 @@ async function verify$1(algo, hashAlgo, signature, publicParams, privateParams,
12510
12565
  return verify$5(algo.getValue(), keyMaterial, signature.mac.data, hashed);
12511
12566
  }
12512
12567
  case enums.publicKey.pqc_mldsa_ed25519: {
12568
+ if (!isCompatibleHashAlgo(algo, hashAlgo)) {
12569
+ // The signature hash algo MUST have digest larger than 256 bits
12570
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-10.html#section-9.4
12571
+ throw new Error('Unexpected hash algorithm for PQC signature: digest size too short');
12572
+ }
12513
12573
  const { eccPublicKey, mldsaPublicKey } = publicParams;
12514
12574
  return verify$2(algo, hashAlgo, eccPublicKey, mldsaPublicKey, hashed, signature);
12515
12575
  }
@@ -12558,12 +12618,24 @@ async function sign$1(algo, hashAlgo, publicKeyParams, privateKeyParams, data, h
12558
12618
  return sign$8(oid, hashAlgo, data, Q, d, hashed);
12559
12619
  }
12560
12620
  case enums.publicKey.eddsaLegacy: {
12621
+ if (getHashByteLength(hashAlgo) < getHashByteLength(enums.hash.sha256)) {
12622
+ // Enforce digest sizes, since the constraint was already present in RFC4880bis:
12623
+ // see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
12624
+ // and https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.3-3
12625
+ throw new Error('Hash algorithm too weak for EdDSALegacy.');
12626
+ }
12561
12627
  const { oid, Q } = publicKeyParams;
12562
12628
  const { seed } = privateKeyParams;
12563
12629
  return sign$7(oid, hashAlgo, data, Q, seed, hashed);
12564
12630
  }
12565
12631
  case enums.publicKey.ed25519:
12566
12632
  case enums.publicKey.ed448: {
12633
+ if (getHashByteLength(hashAlgo) < getHashByteLength(getPreferredHashAlgo$2(algo))) {
12634
+ // Enforce digest sizes:
12635
+ // - Ed25519: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.4-4
12636
+ // - Ed448: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.5-4
12637
+ throw new Error('Hash algorithm too weak for EdDSA.');
12638
+ }
12567
12639
  const { A } = publicKeyParams;
12568
12640
  const { seed } = privateKeyParams;
12569
12641
  return sign$9(algo, hashAlgo, data, A, seed, hashed);
@@ -12575,6 +12647,11 @@ async function sign$1(algo, hashAlgo, publicKeyParams, privateKeyParams, data, h
12575
12647
  return { mac: new ShortByteString(mac) };
12576
12648
  }
12577
12649
  case enums.publicKey.pqc_mldsa_ed25519: {
12650
+ if (!isCompatibleHashAlgo(algo, hashAlgo)) {
12651
+ // The signature hash algo MUST have digest larger than 256 bits
12652
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-10.html#section-9.4
12653
+ throw new Error('Unexpected hash algorithm for PQC signature: digest size too short');
12654
+ }
12578
12655
  const { eccPublicKey } = publicKeyParams;
12579
12656
  const { eccSecretKey, mldsaSecretKey } = privateKeyParams;
12580
12657
  return sign$2(algo, hashAlgo, eccSecretKey, eccPublicKey, mldsaSecretKey, hashed);
@@ -16851,12 +16928,8 @@ class PublicKeyPacket {
16851
16928
  throw new Error('Legacy curve25519 cannot be used with v6 keys');
16852
16929
  }
16853
16930
  // The composite ML-DSA + EdDSA schemes MUST be used only with v6 keys.
16854
- // The composite ML-KEM + ECDH schemes MUST be used only with v6 keys.
16855
- if (this.version !== 6 && (
16856
- this.algorithm === enums.publicKey.pqc_mldsa_ed25519 ||
16857
- this.algorithm === enums.publicKey.pqc_mlkem_x25519
16858
- )) {
16859
- throw new Error('Unexpected key version: ML-DSA and ML-KEM algorithms can only be used with v6 keys');
16931
+ if (this.version !== 6 && this.algorithm === enums.publicKey.pqc_mldsa_ed25519) {
16932
+ throw new Error('Unexpected key version: ML-DSA algorithms can only be used with v6 keys');
16860
16933
  }
16861
16934
  this.publicParams = publicParams;
16862
16935
  pos += read;
@@ -17856,11 +17929,8 @@ class SecretKeyPacket extends PublicKeyPacket {
17856
17929
  )) {
17857
17930
  throw new Error(`Cannot generate v6 keys of type 'ecc' with curve ${curve}. Generate a key of type 'curve25519' instead`);
17858
17931
  }
17859
- if (this.version !== 6 && (
17860
- this.algorithm === enums.publicKey.pqc_mldsa_ed25519 ||
17861
- this.algorithm === enums.publicKey.pqc_mlkem_x25519
17862
- )) {
17863
- throw new Error(`Cannot generate v${this.version} keys of type 'pqc'. Generate a v6 key instead`);
17932
+ if (this.version !== 6 && this.algorithm === enums.publicKey.pqc_mldsa_ed25519) {
17933
+ throw new Error(`Cannot generate v${this.version} signing keys of type 'pqc'. Generate a v6 key instead`);
17864
17934
  }
17865
17935
  const { privateParams, publicParams } = await generateParams(this.algorithm, bits, curve, symmetric);
17866
17936
  this.privateParams = privateParams;
@@ -18364,12 +18434,6 @@ async function createBindingSignature(subkey, primaryKey, options, config) {
18364
18434
  * @async
18365
18435
  */
18366
18436
  async function getPreferredHashAlgo(targetKeys, signingKeyPacket, date = new Date(), targetUserIDs = [], config) {
18367
- if (signingKeyPacket.algorithm === enums.publicKey.pqc_mldsa_ed25519) {
18368
- // For PQC, the returned hash algo MUST be set to the specified algorithm, see
18369
- // https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-pqc#section-5.2.1.
18370
- return getRequiredHashAlgo(signingKeyPacket.algorithm);
18371
- }
18372
-
18373
18437
  /**
18374
18438
  * If `preferredSenderAlgo` appears in the prefs of all recipients, we pick it; otherwise, we use the
18375
18439
  * strongest supported algo (`defaultAlgo` is always implicitly supported by all keys).
@@ -18417,6 +18481,10 @@ async function getPreferredHashAlgo(targetKeys, signingKeyPacket, date = new Dat
18417
18481
  enums.publicKey.ed448
18418
18482
  ]);
18419
18483
 
18484
+ const pqcAlgos = new Set([
18485
+ enums.publicKey.pqc_mldsa_ed25519
18486
+ ]);
18487
+
18420
18488
  if (eccAlgos.has(signingKeyPacket.algorithm)) {
18421
18489
  // For ECC, the returned hash algo MUST be at least as strong as `preferredCurveHashAlgo`, see:
18422
18490
  // - ECDSA: https://www.rfc-editor.org/rfc/rfc9580.html#section-5.2.3.2-5
@@ -18439,6 +18507,21 @@ async function getPreferredHashAlgo(targetKeys, signingKeyPacket, date = new Dat
18439
18507
  strongestSupportedAlgo :
18440
18508
  preferredCurveAlgo;
18441
18509
  }
18510
+ } else if (pqcAlgos.has(signingKeyPacket.algorithm)) {
18511
+ // For PQC, the returned hash algo MUST be at least 256 bit long, see:
18512
+ // https://www.ietf.org/archive/id/draft-ietf-openpgp-pqc-10.html#section-9.4 .
18513
+ // Hence, we return the `preferredHashAlgo` as long as it's supported and long enough;
18514
+ // Otherwise, we look at the strongest supported algo, and ultimately fallback the default algo (SHA-256).
18515
+ const preferredSenderAlgoIsSupported = isSupportedHashAlgo(preferredSenderAlgo) && isCompatibleHashAlgo(signingKeyPacket.algorithm, preferredSenderAlgo);
18516
+
18517
+ if (preferredSenderAlgoIsSupported) {
18518
+ return preferredSenderAlgo;
18519
+ } else {
18520
+ const strongestSupportedAlgo = getStrongestSupportedHashAlgo();
18521
+ return isCompatibleHashAlgo(signingKeyPacket.algorithm, strongestSupportedAlgo) ?
18522
+ strongestSupportedAlgo :
18523
+ defaultAlgo;
18524
+ }
18442
18525
  }
18443
18526
 
18444
18527
  // `preferredSenderAlgo` may be weaker than the default, but we do not guard against this,