Skip to content

Commit 2d4f2fd

Browse files
committed
8349732: Add support for JARs signed with ML-DSA
Reviewed-by: mullan
1 parent 1877ff9 commit 2d4f2fd

File tree

11 files changed

+718
-104
lines changed

11 files changed

+718
-104
lines changed

src/java.base/share/classes/sun/security/pkcs/PKCS7.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -530,8 +530,23 @@ public void encodeSignedData(DerOutputStream out)
530530
* @exception SignatureException on signature handling errors.
531531
*/
532532
public SignerInfo verify(SignerInfo info, byte[] bytes)
533-
throws NoSuchAlgorithmException, SignatureException {
534-
return info.verify(this, bytes);
533+
throws NoSuchAlgorithmException, SignatureException {
534+
return info.verify(this, bytes, null);
535+
}
536+
537+
/**
538+
* This verifies a given SignerInfo.
539+
*
540+
* @param info the signer information.
541+
* @param bytes the DER encoded content information.
542+
* @param cert certificate used to verify; find one inside the block if null
543+
*
544+
* @exception NoSuchAlgorithmException on unrecognized algorithms.
545+
* @exception SignatureException on signature handling errors.
546+
*/
547+
public SignerInfo verify(SignerInfo info, byte[] bytes, X509Certificate cert)
548+
throws NoSuchAlgorithmException, SignatureException {
549+
return info.verify(this, bytes, cert);
535550
}
536551

537552
/**
@@ -715,6 +730,19 @@ public boolean isOldStyle() {
715730
return this.oldStyle;
716731
}
717732

733+
// Generate signed data without a specified digAlgID.
734+
public static byte[] generateSignedData(
735+
String sigalg, Provider sigProvider,
736+
PrivateKey privateKey, X509Certificate[] signerChain,
737+
byte[] content, boolean internalsf, boolean directsign,
738+
Function<byte[], PKCS9Attributes> ts)
739+
throws SignatureException, InvalidKeyException, IOException,
740+
NoSuchAlgorithmException {
741+
return generateSignedData(sigalg, sigProvider, privateKey, signerChain,
742+
content, internalsf, directsign,
743+
null, ts);
744+
}
745+
718746
/**
719747
* Generate a PKCS7 data block.
720748
*
@@ -725,6 +753,7 @@ public boolean isOldStyle() {
725753
* @param content the content to sign
726754
* @param internalsf whether the content should be included in output
727755
* @param directsign if the content is signed directly or through authattrs
756+
* @param digAlgID digest alg to use; derive from other arguments if null
728757
* @param ts (optional) timestamper
729758
* @return the pkcs7 output in an array
730759
* @throws SignatureException if signing failed
@@ -736,23 +765,27 @@ public static byte[] generateSignedData(
736765
String sigalg, Provider sigProvider,
737766
PrivateKey privateKey, X509Certificate[] signerChain,
738767
byte[] content, boolean internalsf, boolean directsign,
768+
AlgorithmId digAlgID,
739769
Function<byte[], PKCS9Attributes> ts)
740770
throws SignatureException, InvalidKeyException, IOException,
741771
NoSuchAlgorithmException {
742772

743773
Signature signer = SignatureUtil.fromKey(sigalg, privateKey, sigProvider);
744774

745-
AlgorithmId digAlgID = SignatureUtil.getDigestAlgInPkcs7SignerInfo(
746-
signer, sigalg, privateKey, signerChain[0].getPublicKey(), directsign);
775+
if (digAlgID == null) {
776+
digAlgID = SignatureUtil.getDigestAlgInPkcs7SignerInfo(
777+
signer, sigalg, privateKey, signerChain[0].getPublicKey(), directsign);
778+
}
747779
AlgorithmId sigAlgID = SignatureUtil.fromSignature(signer, privateKey);
748780

749781
PKCS9Attributes authAttrs = null;
750782
if (!directsign) {
751783
// MessageDigest
752784
byte[] md;
753785
String digAlgName = digAlgID.getName();
754-
if (digAlgName.equals("SHAKE256") || digAlgName.equals("SHAKE256-LEN")) {
755-
// No MessageDigest impl for SHAKE256 yet
786+
if (digAlgName.equals("SHAKE256-LEN")) {
787+
// We don't check the LEN here. Usually it is returned
788+
// by SignatureUtil.getDigestAlgInPkcs7SignerInfo
756789
var shaker = new SHAKE256(64);
757790
shaker.update(content, 0, content.length);
758791
md = shaker.digest();

src/java.base/share/classes/sun/security/pkcs/SignerInfo.java

Lines changed: 84 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -303,15 +303,20 @@ public ArrayList<X509Certificate> getCertificateChain(PKCS7 block)
303303
return certList;
304304
}
305305

306-
/* Returns null if verify fails, this signerInfo if
307-
verify succeeds. */
308-
SignerInfo verify(PKCS7 block, byte[] data)
309-
throws NoSuchAlgorithmException, SignatureException {
306+
/**
307+
* Verify this signerInfo in a PKCS7 block.
308+
*
309+
* @param block the PKCS7 object
310+
* @param data the content to verify against; read from block if null
311+
* @param cert certificate to verify with; read from block if null
312+
* @return null if verify fails, this signerInfo if verify succeeds.
313+
*/
314+
SignerInfo verify(PKCS7 block, byte[] data, X509Certificate cert)
315+
throws NoSuchAlgorithmException, SignatureException {
310316

311317
try {
312-
Timestamp timestamp = null;
313318
try {
314-
timestamp = getTimestamp();
319+
getTimestamp();
315320
} catch (Exception e) {
316321
// Log exception and continue. This allows for the case
317322
// where, if there are no other errors, the code is
@@ -356,22 +361,19 @@ SignerInfo verify(PKCS7 block, byte[] data)
356361
return null;
357362

358363
byte[] computedMessageDigest;
359-
if (digestAlgName.equals("SHAKE256")
360-
|| digestAlgName.equals("SHAKE256-LEN")) {
361-
if (digestAlgName.equals("SHAKE256-LEN")) {
362-
// RFC8419: for EdDSA in CMS, the id-shake256-len
363-
// algorithm id must contain parameter value 512
364-
// encoded as a positive integer value
365-
byte[] params = digestAlgorithmId.getEncodedParams();
366-
if (params == null) {
367-
throw new SignatureException(
368-
"id-shake256-len oid missing length");
369-
}
370-
int v = new DerValue(params).getInteger();
371-
if (v != 512) {
372-
throw new SignatureException(
373-
"Unsupported id-shake256-" + v);
374-
}
364+
if (digestAlgName.equals("SHAKE256-LEN")) {
365+
// RFC8419: for EdDSA in CMS, the id-shake256-len
366+
// algorithm id must contain parameter value 512
367+
// encoded as a positive integer value
368+
byte[] params = digestAlgorithmId.getEncodedParams();
369+
if (params == null) {
370+
throw new SignatureException(
371+
"id-shake256-len oid missing length");
372+
}
373+
int v = new DerValue(params).getInteger();
374+
if (v != 512) {
375+
throw new SignatureException(
376+
"Unsupported id-shake256-" + v);
375377
}
376378
var md = new SHAKE256(64);
377379
md.update(data, 0, data.length);
@@ -410,9 +412,11 @@ SignerInfo verify(PKCS7 block, byte[] data)
410412
"SignerInfo digestEncryptionAlgorithm field", true));
411413
}
412414

413-
X509Certificate cert = getCertificate(block);
414415
if (cert == null) {
415-
return null;
416+
cert = getCertificate(block);
417+
if (cert == null) {
418+
return null;
419+
}
416420
}
417421
PublicKey key = cert.getPublicKey();
418422

@@ -503,29 +507,59 @@ private static void algorithmsConformanceCheck(
503507
}
504508

505509
if (!AlgorithmId.get(spec.getDigestAlgorithm()).equals(digAlgId)) {
506-
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
510+
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
507511
}
508512
break;
509513
case "Ed25519":
510-
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.sha512)) {
511-
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
514+
if (!digAlgId.equalsOID(AlgorithmId.SHA512_oid)) {
515+
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
512516
}
513517
break;
514518
case "Ed448":
515519
if (directSign) {
516-
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256)) {
517-
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
520+
if (!digAlgId.equalsOID(AlgorithmId.SHAKE256_512_oid)) {
521+
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
518522
}
519523
} else {
520-
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256$512)) {
521-
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
524+
if (!digAlgId.equals(SignatureUtil.DigestAlgHolder.shake256lenWith512)) {
525+
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
522526
}
523527
}
524528
break;
525529
case "HSS/LMS":
526530
// RFC 8708 requires the same hash algorithm used as in the HSS/LMS algorithm
527-
if (!digAlgId.equals(AlgorithmId.get(KeyUtil.hashAlgFromHSS(key)))) {
528-
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
531+
if (!digAlgId.equalsOID(KeyUtil.hashAlgFromHSS(key))) {
532+
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
533+
}
534+
break;
535+
case "ML-DSA-44":
536+
// Following 3 from Table 1 inside
537+
// https://datatracker.ietf.org/doc/html/rfc9882#name-signerinfo-content
538+
if (!digAlgId.equalsOID(AlgorithmId.SHA256_oid)
539+
&& !digAlgId.equalsOID(AlgorithmId.SHA384_oid)
540+
&& !digAlgId.equalsOID(AlgorithmId.SHA512_oid)
541+
&& !digAlgId.equalsOID(AlgorithmId.SHA3_256_oid)
542+
&& !digAlgId.equalsOID(AlgorithmId.SHA3_384_oid)
543+
&& !digAlgId.equalsOID(AlgorithmId.SHA3_512_oid)
544+
&& !digAlgId.equalsOID(AlgorithmId.SHAKE128_256_oid)
545+
&& !digAlgId.equalsOID(AlgorithmId.SHAKE256_512_oid)) {
546+
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
547+
}
548+
break;
549+
case "ML-DSA-65":
550+
if (!digAlgId.equalsOID(AlgorithmId.SHA384_oid)
551+
&& !digAlgId.equalsOID(AlgorithmId.SHA512_oid)
552+
&& !digAlgId.equalsOID(AlgorithmId.SHA3_384_oid)
553+
&& !digAlgId.equalsOID(AlgorithmId.SHA3_512_oid)
554+
&& !digAlgId.equalsOID(AlgorithmId.SHAKE256_512_oid)) {
555+
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
556+
}
557+
break;
558+
case "ML-DSA-87":
559+
if (!digAlgId.equalsOID(AlgorithmId.SHA512_oid)
560+
&& !digAlgId.equalsOID(AlgorithmId.SHA3_512_oid)
561+
&& !digAlgId.equalsOID(AlgorithmId.SHAKE256_512_oid)) {
562+
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
529563
}
530564
break;
531565
}
@@ -538,9 +572,9 @@ private static void algorithmsConformanceCheck(
538572
* The digest algorithm is in the form "DIG", and the encryption
539573
* algorithm can be in any of the 3 forms:
540574
*
541-
* 1. Old style key algorithm like RSA, DSA, EC, this method returns
575+
* 1. Simple key algorithm like RSA, DSA, EC, this method returns
542576
* DIGwithKEY.
543-
* 2. New style signature algorithm in the form of HASHwithKEY, this
577+
* 2. Traditional signature algorithm in the form of HASHwithKEY, this
544578
* method returns DIGwithKEY. Please note this is not HASHwithKEY.
545579
* 3. Modern signature algorithm like RSASSA-PSS and EdDSA, this method
546580
* returns the signature algorithm itself.
@@ -550,40 +584,26 @@ private static void algorithmsConformanceCheck(
550584
*/
551585
public static String makeSigAlg(AlgorithmId digAlgId, AlgorithmId encAlgId) {
552586
String encAlg = encAlgId.getName();
553-
switch (encAlg) {
554-
case "RSASSA-PSS":
555-
case "Ed25519":
556-
case "Ed448":
557-
case "HSS/LMS":
558-
return encAlg;
559-
default:
560-
String digAlg = digAlgId.getName();
561-
String keyAlg = SignatureUtil.extractKeyAlgFromDwithE(encAlg);
562-
if (keyAlg == null) {
563-
// The encAlg used to be only the key alg
564-
keyAlg = encAlg;
565-
}
566-
if (digAlg.startsWith("SHA-")) {
567-
digAlg = "SHA" + digAlg.substring(4);
568-
}
569-
if (keyAlg.equals("EC")) keyAlg = "ECDSA";
570-
String sigAlg = digAlg + "with" + keyAlg;
571-
try {
572-
Signature.getInstance(sigAlg);
573-
return sigAlg;
574-
} catch (NoSuchAlgorithmException e) {
575-
// Possibly an unknown modern signature algorithm,
576-
// in this case, encAlg should already be a signature
577-
// algorithm.
578-
return encAlg;
579-
}
587+
String keyAlg = SignatureUtil.extractKeyAlgFromDwithE(encAlg);
588+
if (keyAlg == null) { // No "WITH" inside
589+
if (encAlg.equals("RSA") || encAlg.equals("DSA") || encAlg.equals("EC")) {
590+
keyAlg = encAlg; // Sometimes encAlgId is just the enc alg
591+
} else {
592+
return encAlg; // Must be a modern algorithm like EdDSA or ML-DSA
593+
}
594+
}
595+
String digAlg = digAlgId.getName();
596+
if (digAlg.startsWith("SHA-")) {
597+
digAlg = "SHA" + digAlg.substring(4);
580598
}
599+
if (keyAlg.equals("EC")) keyAlg = "ECDSA";
600+
return digAlg + "with" + keyAlg;
581601
}
582602

583603
/* Verify the content of the pkcs7 block. */
584604
SignerInfo verify(PKCS7 block)
585605
throws NoSuchAlgorithmException, SignatureException {
586-
return verify(block, null);
606+
return verify(block, null, null);
587607
}
588608

589609
public BigInteger getVersion() {

src/java.base/share/classes/sun/security/util/KeyUtil.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ public static byte[] trimZeroes(byte[] b) {
431431
* @return the hash algorithm
432432
* @throws NoSuchAlgorithmException if key is from an unknown configuration
433433
*/
434-
public static String hashAlgFromHSS(PublicKey publicKey)
434+
public static ObjectIdentifier hashAlgFromHSS(PublicKey publicKey)
435435
throws NoSuchAlgorithmException {
436436
try {
437437
DerValue val = new DerValue(publicKey.getEncoded());
@@ -450,7 +450,7 @@ public static String hashAlgFromHSS(PublicKey publicKey)
450450
+ ((rawKey[6] & 0xff) << 8) + (rawKey[7] & 0xff);
451451
return switch (num) {
452452
// RFC 8554 only supports SHA_256 hash algorithm
453-
case 5, 6, 7, 8, 9 -> "SHA-256";
453+
case 5, 6, 7, 8, 9 -> AlgorithmId.SHA256_oid;
454454
default -> throw new NoSuchAlgorithmException("Unknown LMS type: " + num);
455455
};
456456
} catch (IOException e) {

src/java.base/share/classes/sun/security/util/SignatureUtil.java

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -190,16 +190,16 @@ public static void initSignWithParam(Signature s, PrivateKey key,
190190
SharedSecrets.getJavaSecuritySignatureAccess().initSign(s, key, params, sr);
191191
}
192192

193-
public static class EdDSADigestAlgHolder {
193+
public static class DigestAlgHolder {
194194
public static final AlgorithmId sha512;
195-
public static final AlgorithmId shake256;
196-
public static final AlgorithmId shake256$512;
195+
public static final AlgorithmId shake256_512;
196+
public static final AlgorithmId shake256lenWith512;
197197

198198
static {
199199
try {
200-
sha512 = new AlgorithmId(ObjectIdentifier.of(KnownOIDs.SHA_512));
201-
shake256 = new AlgorithmId(ObjectIdentifier.of(KnownOIDs.SHAKE256_512));
202-
shake256$512 = new AlgorithmId(
200+
sha512 = new AlgorithmId(AlgorithmId.SHA512_oid);
201+
shake256_512 = new AlgorithmId(AlgorithmId.SHAKE256_512_oid);
202+
shake256lenWith512 = new AlgorithmId(
203203
ObjectIdentifier.of(KnownOIDs.SHAKE256_LEN),
204204
new DerValue((byte) 2, new byte[]{2, 0})); // int 512
205205
} catch (IOException e) {
@@ -233,18 +233,22 @@ public static AlgorithmId getDigestAlgInPkcs7SignerInfo(
233233
// https://www.rfc-editor.org/rfc/rfc8419.html#section-3
234234
switch (kAlg.toUpperCase(Locale.ENGLISH)) {
235235
case "ED25519":
236-
digAlgID = EdDSADigestAlgHolder.sha512;
236+
digAlgID = DigestAlgHolder.sha512;
237237
break;
238238
case "ED448":
239239
if (directsign) {
240-
digAlgID = EdDSADigestAlgHolder.shake256;
240+
digAlgID = DigestAlgHolder.shake256_512;
241241
} else {
242-
digAlgID = EdDSADigestAlgHolder.shake256$512;
242+
digAlgID = DigestAlgHolder.shake256lenWith512;
243243
}
244244
break;
245245
default:
246246
throw new AssertionError("Unknown curve name: " + kAlg);
247247
}
248+
} else if (kAlg.toUpperCase(Locale.ENGLISH).startsWith("ML-DSA")) {
249+
// https://datatracker.ietf.org/doc/html/rfc9882#name-signerinfo-content
250+
// Just use SHA-512
251+
digAlgID = DigestAlgHolder.sha512;
248252
} else if (sigalg.equalsIgnoreCase("RSASSA-PSS")) {
249253
try {
250254
digAlgID = AlgorithmId.get(signer.getParameters()
@@ -254,7 +258,7 @@ public static AlgorithmId getDigestAlgInPkcs7SignerInfo(
254258
throw new AssertionError("Should not happen", e);
255259
}
256260
} else if (sigalg.equalsIgnoreCase("HSS/LMS")) {
257-
digAlgID = AlgorithmId.get(KeyUtil.hashAlgFromHSS(publicKey));
261+
digAlgID = new AlgorithmId(KeyUtil.hashAlgFromHSS(publicKey));
258262
} else {
259263
digAlgID = AlgorithmId.get(extractDigestAlgFromDwithE(sigalg));
260264
}

0 commit comments

Comments
 (0)