mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8349732: Add support for JARs signed with ML-DSA
Reviewed-by: mullan
This commit is contained in:
parent
1877ff996b
commit
2d4f2fde22
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -530,8 +530,23 @@ public class PKCS7 {
|
||||
* @exception SignatureException on signature handling errors.
|
||||
*/
|
||||
public SignerInfo verify(SignerInfo info, byte[] bytes)
|
||||
throws NoSuchAlgorithmException, SignatureException {
|
||||
return info.verify(this, bytes);
|
||||
throws NoSuchAlgorithmException, SignatureException {
|
||||
return info.verify(this, bytes, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This verifies a given SignerInfo.
|
||||
*
|
||||
* @param info the signer information.
|
||||
* @param bytes the DER encoded content information.
|
||||
* @param cert certificate used to verify; find one inside the block if null
|
||||
*
|
||||
* @exception NoSuchAlgorithmException on unrecognized algorithms.
|
||||
* @exception SignatureException on signature handling errors.
|
||||
*/
|
||||
public SignerInfo verify(SignerInfo info, byte[] bytes, X509Certificate cert)
|
||||
throws NoSuchAlgorithmException, SignatureException {
|
||||
return info.verify(this, bytes, cert);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -715,6 +730,19 @@ public class PKCS7 {
|
||||
return this.oldStyle;
|
||||
}
|
||||
|
||||
// Generate signed data without a specified digAlgID.
|
||||
public static byte[] generateSignedData(
|
||||
String sigalg, Provider sigProvider,
|
||||
PrivateKey privateKey, X509Certificate[] signerChain,
|
||||
byte[] content, boolean internalsf, boolean directsign,
|
||||
Function<byte[], PKCS9Attributes> ts)
|
||||
throws SignatureException, InvalidKeyException, IOException,
|
||||
NoSuchAlgorithmException {
|
||||
return generateSignedData(sigalg, sigProvider, privateKey, signerChain,
|
||||
content, internalsf, directsign,
|
||||
null, ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a PKCS7 data block.
|
||||
*
|
||||
@ -725,6 +753,7 @@ public class PKCS7 {
|
||||
* @param content the content to sign
|
||||
* @param internalsf whether the content should be included in output
|
||||
* @param directsign if the content is signed directly or through authattrs
|
||||
* @param digAlgID digest alg to use; derive from other arguments if null
|
||||
* @param ts (optional) timestamper
|
||||
* @return the pkcs7 output in an array
|
||||
* @throws SignatureException if signing failed
|
||||
@ -736,14 +765,17 @@ public class PKCS7 {
|
||||
String sigalg, Provider sigProvider,
|
||||
PrivateKey privateKey, X509Certificate[] signerChain,
|
||||
byte[] content, boolean internalsf, boolean directsign,
|
||||
AlgorithmId digAlgID,
|
||||
Function<byte[], PKCS9Attributes> ts)
|
||||
throws SignatureException, InvalidKeyException, IOException,
|
||||
NoSuchAlgorithmException {
|
||||
|
||||
Signature signer = SignatureUtil.fromKey(sigalg, privateKey, sigProvider);
|
||||
|
||||
AlgorithmId digAlgID = SignatureUtil.getDigestAlgInPkcs7SignerInfo(
|
||||
signer, sigalg, privateKey, signerChain[0].getPublicKey(), directsign);
|
||||
if (digAlgID == null) {
|
||||
digAlgID = SignatureUtil.getDigestAlgInPkcs7SignerInfo(
|
||||
signer, sigalg, privateKey, signerChain[0].getPublicKey(), directsign);
|
||||
}
|
||||
AlgorithmId sigAlgID = SignatureUtil.fromSignature(signer, privateKey);
|
||||
|
||||
PKCS9Attributes authAttrs = null;
|
||||
@ -751,8 +783,9 @@ public class PKCS7 {
|
||||
// MessageDigest
|
||||
byte[] md;
|
||||
String digAlgName = digAlgID.getName();
|
||||
if (digAlgName.equals("SHAKE256") || digAlgName.equals("SHAKE256-LEN")) {
|
||||
// No MessageDigest impl for SHAKE256 yet
|
||||
if (digAlgName.equals("SHAKE256-LEN")) {
|
||||
// We don't check the LEN here. Usually it is returned
|
||||
// by SignatureUtil.getDigestAlgInPkcs7SignerInfo
|
||||
var shaker = new SHAKE256(64);
|
||||
shaker.update(content, 0, content.length);
|
||||
md = shaker.digest();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -303,15 +303,20 @@ public class SignerInfo implements DerEncoder {
|
||||
return certList;
|
||||
}
|
||||
|
||||
/* Returns null if verify fails, this signerInfo if
|
||||
verify succeeds. */
|
||||
SignerInfo verify(PKCS7 block, byte[] data)
|
||||
throws NoSuchAlgorithmException, SignatureException {
|
||||
/**
|
||||
* Verify this signerInfo in a PKCS7 block.
|
||||
*
|
||||
* @param block the PKCS7 object
|
||||
* @param data the content to verify against; read from block if null
|
||||
* @param cert certificate to verify with; read from block if null
|
||||
* @return null if verify fails, this signerInfo if verify succeeds.
|
||||
*/
|
||||
SignerInfo verify(PKCS7 block, byte[] data, X509Certificate cert)
|
||||
throws NoSuchAlgorithmException, SignatureException {
|
||||
|
||||
try {
|
||||
Timestamp timestamp = null;
|
||||
try {
|
||||
timestamp = getTimestamp();
|
||||
getTimestamp();
|
||||
} catch (Exception e) {
|
||||
// Log exception and continue. This allows for the case
|
||||
// where, if there are no other errors, the code is
|
||||
@ -356,22 +361,19 @@ public class SignerInfo implements DerEncoder {
|
||||
return null;
|
||||
|
||||
byte[] computedMessageDigest;
|
||||
if (digestAlgName.equals("SHAKE256")
|
||||
|| digestAlgName.equals("SHAKE256-LEN")) {
|
||||
if (digestAlgName.equals("SHAKE256-LEN")) {
|
||||
// RFC8419: for EdDSA in CMS, the id-shake256-len
|
||||
// algorithm id must contain parameter value 512
|
||||
// encoded as a positive integer value
|
||||
byte[] params = digestAlgorithmId.getEncodedParams();
|
||||
if (params == null) {
|
||||
throw new SignatureException(
|
||||
"id-shake256-len oid missing length");
|
||||
}
|
||||
int v = new DerValue(params).getInteger();
|
||||
if (v != 512) {
|
||||
throw new SignatureException(
|
||||
"Unsupported id-shake256-" + v);
|
||||
}
|
||||
if (digestAlgName.equals("SHAKE256-LEN")) {
|
||||
// RFC8419: for EdDSA in CMS, the id-shake256-len
|
||||
// algorithm id must contain parameter value 512
|
||||
// encoded as a positive integer value
|
||||
byte[] params = digestAlgorithmId.getEncodedParams();
|
||||
if (params == null) {
|
||||
throw new SignatureException(
|
||||
"id-shake256-len oid missing length");
|
||||
}
|
||||
int v = new DerValue(params).getInteger();
|
||||
if (v != 512) {
|
||||
throw new SignatureException(
|
||||
"Unsupported id-shake256-" + v);
|
||||
}
|
||||
var md = new SHAKE256(64);
|
||||
md.update(data, 0, data.length);
|
||||
@ -410,9 +412,11 @@ public class SignerInfo implements DerEncoder {
|
||||
"SignerInfo digestEncryptionAlgorithm field", true));
|
||||
}
|
||||
|
||||
X509Certificate cert = getCertificate(block);
|
||||
if (cert == null) {
|
||||
return null;
|
||||
cert = getCertificate(block);
|
||||
if (cert == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
PublicKey key = cert.getPublicKey();
|
||||
|
||||
@ -503,29 +507,59 @@ public class SignerInfo implements DerEncoder {
|
||||
}
|
||||
|
||||
if (!AlgorithmId.get(spec.getDigestAlgorithm()).equals(digAlgId)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
|
||||
}
|
||||
break;
|
||||
case "Ed25519":
|
||||
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.sha512)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
||||
if (!digAlgId.equalsOID(AlgorithmId.SHA512_oid)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
|
||||
}
|
||||
break;
|
||||
case "Ed448":
|
||||
if (directSign) {
|
||||
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
||||
if (!digAlgId.equalsOID(AlgorithmId.SHAKE256_512_oid)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
|
||||
}
|
||||
} else {
|
||||
if (!digAlgId.equals(SignatureUtil.EdDSADigestAlgHolder.shake256$512)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
||||
if (!digAlgId.equals(SignatureUtil.DigestAlgHolder.shake256lenWith512)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "HSS/LMS":
|
||||
// RFC 8708 requires the same hash algorithm used as in the HSS/LMS algorithm
|
||||
if (!digAlgId.equals(AlgorithmId.get(KeyUtil.hashAlgFromHSS(key)))) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm");
|
||||
if (!digAlgId.equalsOID(KeyUtil.hashAlgFromHSS(key))) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
|
||||
}
|
||||
break;
|
||||
case "ML-DSA-44":
|
||||
// Following 3 from Table 1 inside
|
||||
// https://datatracker.ietf.org/doc/html/rfc9882#name-signerinfo-content
|
||||
if (!digAlgId.equalsOID(AlgorithmId.SHA256_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA384_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA512_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA3_256_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA3_384_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA3_512_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHAKE128_256_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHAKE256_512_oid)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
|
||||
}
|
||||
break;
|
||||
case "ML-DSA-65":
|
||||
if (!digAlgId.equalsOID(AlgorithmId.SHA384_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA512_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA3_384_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA3_512_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHAKE256_512_oid)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
|
||||
}
|
||||
break;
|
||||
case "ML-DSA-87":
|
||||
if (!digAlgId.equalsOID(AlgorithmId.SHA512_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHA3_512_oid)
|
||||
&& !digAlgId.equalsOID(AlgorithmId.SHAKE256_512_oid)) {
|
||||
throw new NoSuchAlgorithmException("Incompatible digest algorithm " + digAlgId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -538,9 +572,9 @@ public class SignerInfo implements DerEncoder {
|
||||
* The digest algorithm is in the form "DIG", and the encryption
|
||||
* algorithm can be in any of the 3 forms:
|
||||
*
|
||||
* 1. Old style key algorithm like RSA, DSA, EC, this method returns
|
||||
* 1. Simple key algorithm like RSA, DSA, EC, this method returns
|
||||
* DIGwithKEY.
|
||||
* 2. New style signature algorithm in the form of HASHwithKEY, this
|
||||
* 2. Traditional signature algorithm in the form of HASHwithKEY, this
|
||||
* method returns DIGwithKEY. Please note this is not HASHwithKEY.
|
||||
* 3. Modern signature algorithm like RSASSA-PSS and EdDSA, this method
|
||||
* returns the signature algorithm itself.
|
||||
@ -550,40 +584,26 @@ public class SignerInfo implements DerEncoder {
|
||||
*/
|
||||
public static String makeSigAlg(AlgorithmId digAlgId, AlgorithmId encAlgId) {
|
||||
String encAlg = encAlgId.getName();
|
||||
switch (encAlg) {
|
||||
case "RSASSA-PSS":
|
||||
case "Ed25519":
|
||||
case "Ed448":
|
||||
case "HSS/LMS":
|
||||
return encAlg;
|
||||
default:
|
||||
String digAlg = digAlgId.getName();
|
||||
String keyAlg = SignatureUtil.extractKeyAlgFromDwithE(encAlg);
|
||||
if (keyAlg == null) {
|
||||
// The encAlg used to be only the key alg
|
||||
keyAlg = encAlg;
|
||||
}
|
||||
if (digAlg.startsWith("SHA-")) {
|
||||
digAlg = "SHA" + digAlg.substring(4);
|
||||
}
|
||||
if (keyAlg.equals("EC")) keyAlg = "ECDSA";
|
||||
String sigAlg = digAlg + "with" + keyAlg;
|
||||
try {
|
||||
Signature.getInstance(sigAlg);
|
||||
return sigAlg;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Possibly an unknown modern signature algorithm,
|
||||
// in this case, encAlg should already be a signature
|
||||
// algorithm.
|
||||
return encAlg;
|
||||
}
|
||||
String keyAlg = SignatureUtil.extractKeyAlgFromDwithE(encAlg);
|
||||
if (keyAlg == null) { // No "WITH" inside
|
||||
if (encAlg.equals("RSA") || encAlg.equals("DSA") || encAlg.equals("EC")) {
|
||||
keyAlg = encAlg; // Sometimes encAlgId is just the enc alg
|
||||
} else {
|
||||
return encAlg; // Must be a modern algorithm like EdDSA or ML-DSA
|
||||
}
|
||||
}
|
||||
String digAlg = digAlgId.getName();
|
||||
if (digAlg.startsWith("SHA-")) {
|
||||
digAlg = "SHA" + digAlg.substring(4);
|
||||
}
|
||||
if (keyAlg.equals("EC")) keyAlg = "ECDSA";
|
||||
return digAlg + "with" + keyAlg;
|
||||
}
|
||||
|
||||
/* Verify the content of the pkcs7 block. */
|
||||
SignerInfo verify(PKCS7 block)
|
||||
throws NoSuchAlgorithmException, SignatureException {
|
||||
return verify(block, null);
|
||||
return verify(block, null, null);
|
||||
}
|
||||
|
||||
public BigInteger getVersion() {
|
||||
|
||||
@ -431,7 +431,7 @@ public final class KeyUtil {
|
||||
* @return the hash algorithm
|
||||
* @throws NoSuchAlgorithmException if key is from an unknown configuration
|
||||
*/
|
||||
public static String hashAlgFromHSS(PublicKey publicKey)
|
||||
public static ObjectIdentifier hashAlgFromHSS(PublicKey publicKey)
|
||||
throws NoSuchAlgorithmException {
|
||||
try {
|
||||
DerValue val = new DerValue(publicKey.getEncoded());
|
||||
@ -450,7 +450,7 @@ public final class KeyUtil {
|
||||
+ ((rawKey[6] & 0xff) << 8) + (rawKey[7] & 0xff);
|
||||
return switch (num) {
|
||||
// RFC 8554 only supports SHA_256 hash algorithm
|
||||
case 5, 6, 7, 8, 9 -> "SHA-256";
|
||||
case 5, 6, 7, 8, 9 -> AlgorithmId.SHA256_oid;
|
||||
default -> throw new NoSuchAlgorithmException("Unknown LMS type: " + num);
|
||||
};
|
||||
} catch (IOException e) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -190,16 +190,16 @@ public class SignatureUtil {
|
||||
SharedSecrets.getJavaSecuritySignatureAccess().initSign(s, key, params, sr);
|
||||
}
|
||||
|
||||
public static class EdDSADigestAlgHolder {
|
||||
public static class DigestAlgHolder {
|
||||
public static final AlgorithmId sha512;
|
||||
public static final AlgorithmId shake256;
|
||||
public static final AlgorithmId shake256$512;
|
||||
public static final AlgorithmId shake256_512;
|
||||
public static final AlgorithmId shake256lenWith512;
|
||||
|
||||
static {
|
||||
try {
|
||||
sha512 = new AlgorithmId(ObjectIdentifier.of(KnownOIDs.SHA_512));
|
||||
shake256 = new AlgorithmId(ObjectIdentifier.of(KnownOIDs.SHAKE256_512));
|
||||
shake256$512 = new AlgorithmId(
|
||||
sha512 = new AlgorithmId(AlgorithmId.SHA512_oid);
|
||||
shake256_512 = new AlgorithmId(AlgorithmId.SHAKE256_512_oid);
|
||||
shake256lenWith512 = new AlgorithmId(
|
||||
ObjectIdentifier.of(KnownOIDs.SHAKE256_LEN),
|
||||
new DerValue((byte) 2, new byte[]{2, 0})); // int 512
|
||||
} catch (IOException e) {
|
||||
@ -233,18 +233,22 @@ public class SignatureUtil {
|
||||
// https://www.rfc-editor.org/rfc/rfc8419.html#section-3
|
||||
switch (kAlg.toUpperCase(Locale.ENGLISH)) {
|
||||
case "ED25519":
|
||||
digAlgID = EdDSADigestAlgHolder.sha512;
|
||||
digAlgID = DigestAlgHolder.sha512;
|
||||
break;
|
||||
case "ED448":
|
||||
if (directsign) {
|
||||
digAlgID = EdDSADigestAlgHolder.shake256;
|
||||
digAlgID = DigestAlgHolder.shake256_512;
|
||||
} else {
|
||||
digAlgID = EdDSADigestAlgHolder.shake256$512;
|
||||
digAlgID = DigestAlgHolder.shake256lenWith512;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unknown curve name: " + kAlg);
|
||||
}
|
||||
} else if (kAlg.toUpperCase(Locale.ENGLISH).startsWith("ML-DSA")) {
|
||||
// https://datatracker.ietf.org/doc/html/rfc9882#name-signerinfo-content
|
||||
// Just use SHA-512
|
||||
digAlgID = DigestAlgHolder.sha512;
|
||||
} else if (sigalg.equalsIgnoreCase("RSASSA-PSS")) {
|
||||
try {
|
||||
digAlgID = AlgorithmId.get(signer.getParameters()
|
||||
@ -254,7 +258,7 @@ public class SignatureUtil {
|
||||
throw new AssertionError("Should not happen", e);
|
||||
}
|
||||
} else if (sigalg.equalsIgnoreCase("HSS/LMS")) {
|
||||
digAlgID = AlgorithmId.get(KeyUtil.hashAlgFromHSS(publicKey));
|
||||
digAlgID = new AlgorithmId(KeyUtil.hashAlgFromHSS(publicKey));
|
||||
} else {
|
||||
digAlgID = AlgorithmId.get(extractDigestAlgFromDwithE(sigalg));
|
||||
}
|
||||
|
||||
@ -307,6 +307,14 @@ public class AlgorithmId implements Serializable, DerEncoder {
|
||||
Arrays.equals(encodedParams, other.encodedParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this AlgorithmID only has the specified ObjectIdentifier
|
||||
* and a NULL params. Note the encoded params might be ASN.1 NULL or absent.
|
||||
*/
|
||||
public boolean equalsOID(ObjectIdentifier oid) {
|
||||
return algid.equals(oid) && encodedParams == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this AlgorithmID to another. If algorithm parameters are
|
||||
* available, they are compared. Otherwise, just the object IDs
|
||||
@ -628,6 +636,12 @@ public class AlgorithmId implements Serializable, DerEncoder {
|
||||
public static final ObjectIdentifier SHA3_512_oid =
|
||||
ObjectIdentifier.of(KnownOIDs.SHA3_512);
|
||||
|
||||
public static final ObjectIdentifier SHAKE128_256_oid =
|
||||
ObjectIdentifier.of(KnownOIDs.SHAKE128_256);
|
||||
|
||||
public static final ObjectIdentifier SHAKE256_512_oid =
|
||||
ObjectIdentifier.of(KnownOIDs.SHAKE256_512);
|
||||
|
||||
public static final ObjectIdentifier DSA_oid =
|
||||
ObjectIdentifier.of(KnownOIDs.DSA);
|
||||
|
||||
|
||||
@ -255,29 +255,35 @@ the private key:
|
||||
|
||||
Table: Default Signature Algorithms and Block File Extensions
|
||||
|
||||
keyalg key size default sigalg block file extension
|
||||
------- -------- -------------- --------------------
|
||||
DSA any size SHA256withDSA .DSA
|
||||
RSA \< 624 SHA256withRSA .RSA
|
||||
\<= 7680 SHA384withRSA
|
||||
\> 7680 SHA512withRSA
|
||||
EC \< 512 SHA384withECDSA .EC
|
||||
\>= 512 SHA512withECDSA
|
||||
RSASSA-PSS \< 624 RSASSA-PSS (with SHA-256) .RSA
|
||||
\<= 7680 RSASSA-PSS (with SHA-384)
|
||||
\> 7680 RSASSA-PSS (with SHA-512)
|
||||
EdDSA 255 Ed25519 .EC
|
||||
448 Ed448
|
||||
------- -------- -------------- ------
|
||||
keyalg key size default sigalg block file extension
|
||||
------- -------- -------------- --------------------
|
||||
DSA any size SHA256withDSA .DSA
|
||||
RSA \< 624 SHA256withRSA .RSA
|
||||
\<= 7680 SHA384withRSA
|
||||
\> 7680 SHA512withRSA
|
||||
EC \< 512 SHA384withECDSA .EC
|
||||
\>= 512 SHA512withECDSA
|
||||
RSASSA-PSS^1^ \< 624 RSASSA-PSS (with SHA-256) .RSA
|
||||
\<= 7680 RSASSA-PSS (with SHA-384)
|
||||
\> 7680 RSASSA-PSS (with SHA-512)
|
||||
EdDSA^2^ EdDSA .EC
|
||||
ML-DSA^2^ ML-DSA .DSA
|
||||
------- -------- -------------- ------
|
||||
|
||||
* If an RSASSA-PSS key is encoded with parameters, then jarsigner will use the
|
||||
1. If an RSASSA-PSS key is encoded with parameters, then jarsigner will use the
|
||||
same parameters in the signature. Otherwise, jarsigner will use parameters that
|
||||
are determined by the size of the key as specified in the table above.
|
||||
For example, an 3072-bit RSASSA-PSS key will use RSASSA-PSS as the signature
|
||||
algorithm and SHA-384 as the hash and MGF1 algorithms.
|
||||
|
||||
* If a key algorithm is not listed in this table, the `.DSA` extension
|
||||
is used when signing a JAR file.
|
||||
2. Modern digital signature algorithms such as EdDSA and ML-DSA use the same
|
||||
name for both the key and signature algorithms. Only the signature algorithm
|
||||
with the same name can be used with a given key algorithm. The specific
|
||||
signature parameter set (for example, Ed25519 or Ed448 for EdDSA) is the
|
||||
same as that of the key.
|
||||
|
||||
If a key algorithm is not listed in this table, the `.DSA` block file extension
|
||||
is always used.
|
||||
|
||||
These default signature algorithms can be overridden by using the `-sigalg`
|
||||
option.
|
||||
|
||||
105
test/jdk/sun/security/pkcs/pkcs7/MLDSADigestConformance.java
Normal file
105
test/jdk/sun/security/pkcs/pkcs7/MLDSADigestConformance.java
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8349732
|
||||
* @summary ML-DSA digest alg conformance check
|
||||
* @modules java.base/sun.security.pkcs
|
||||
* java.base/sun.security.tools.keytool
|
||||
* java.base/sun.security.x509
|
||||
* @library /test/lib
|
||||
*/
|
||||
|
||||
import jdk.test.lib.Asserts;
|
||||
import sun.security.pkcs.PKCS7;
|
||||
import sun.security.tools.keytool.CertAndKeyGen;
|
||||
import sun.security.x509.AlgorithmId;
|
||||
import sun.security.x509.X500Name;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class MLDSADigestConformance {
|
||||
|
||||
static String[] ALL_KEY_ALGS = {"ML-DSA-44", "ML-DSA-65", "ML-DSA-87"};
|
||||
static String[] ALL_DIGEST_ALGS = {
|
||||
"SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512",
|
||||
"SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512",
|
||||
"SHAKE128-256", "SHAKE256-512"};
|
||||
static Map<String, List<String>> SUPPORTED = Map.of(
|
||||
"ML-DSA-44", List.of("SHA-256", "SHA-384", "SHA-512",
|
||||
"SHA3-256", "SHA3-384", "SHA3-512",
|
||||
"SHAKE128-256", "SHAKE256-512"),
|
||||
"ML-DSA-65", List.of("SHA-384", "SHA-512",
|
||||
"SHA3-384", "SHA3-512", "SHAKE256-512"),
|
||||
"ML-DSA-87", List.of("SHA-512", "SHA3-512", "SHAKE256-512")
|
||||
);
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
testSig("ML-DSA-44");
|
||||
testSig("ML-DSA-65");
|
||||
testSig("ML-DSA-87");
|
||||
}
|
||||
|
||||
static void testSig(String keyAlg) throws Exception {
|
||||
System.out.println("Testing " + keyAlg);
|
||||
var cag = new CertAndKeyGen(keyAlg, keyAlg);
|
||||
cag.generate(keyAlg);
|
||||
var sk = cag.getPrivateKey();
|
||||
var certs = new X509Certificate[] {
|
||||
cag.getSelfCertificate(new X500Name("CN=Me"), 1000)
|
||||
};
|
||||
var count = testDigest(keyAlg, sk, certs, null);
|
||||
System.out.println(" digestAlg default: " + count);
|
||||
Asserts.assertEQ(count, 1);
|
||||
for (var da : ALL_DIGEST_ALGS) {
|
||||
count = testDigest(keyAlg, sk, certs, da);
|
||||
System.out.println(" digestAlg " + da + ": " + count);
|
||||
if (SUPPORTED.get(keyAlg).contains(da)) {
|
||||
Asserts.assertEQ(count, 1);
|
||||
} else {
|
||||
Asserts.assertEQ(count, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int testDigest(String keyAlg, PrivateKey sk,
|
||||
X509Certificate[] certs, String digestAlg) throws Exception {
|
||||
var content = "hello".getBytes(StandardCharsets.UTF_8);
|
||||
var p7 = PKCS7.generateSignedData(keyAlg, null,
|
||||
sk, certs,
|
||||
content, true, false,
|
||||
digestAlg == null ? null : AlgorithmId.get(digestAlg),
|
||||
null);
|
||||
try {
|
||||
return new PKCS7(p7).verify(null).length;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,15 +37,17 @@ import java.util.zip.ZipFile;
|
||||
/*
|
||||
* @test
|
||||
* @bug 8342442 8345057
|
||||
* @summary Test default implementation. Use othervm because
|
||||
* ML_DSA_Impls.version might be modified
|
||||
* @library /test/lib
|
||||
* @modules java.base/sun.security.provider
|
||||
* @run main/timeout=480 Launcher
|
||||
* @run main/othervm/timeout=480 Launcher
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @summary Test verifying the intrinsic implementation.
|
||||
* @bug 8342442 8345057
|
||||
* @summary Test verifying the intrinsic implementation.
|
||||
* @library /test/lib
|
||||
* @modules java.base/sun.security.provider
|
||||
* @run main/othervm/timeout=480 -Xcomp Launcher
|
||||
|
||||
98
test/jdk/sun/security/provider/pqc/ML_DSA_CMS.java
Normal file
98
test/jdk/sun/security/provider/pqc/ML_DSA_CMS.java
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8349732
|
||||
* @library /test/lib
|
||||
* @summary Add support for JARs signed with ML-DSA
|
||||
* @modules java.base/sun.security.pkcs
|
||||
*/
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.security.RepositoryFileReader;
|
||||
import sun.security.pkcs.PKCS7;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Base64;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static jdk.test.lib.security.RepositoryFileReader.*;
|
||||
|
||||
public class ML_DSA_CMS {
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Example signed-data encodings from RFC 9882, Appendix B
|
||||
// (https://datatracker.ietf.org/doc/html/rfc9882#name-examples), which
|
||||
// can be verified by example certificates from RFC 9881, Appendix C.3
|
||||
// (https://datatracker.ietf.org/doc/html/rfc9881#name-example-certificates)
|
||||
//
|
||||
// These data can be retrieved from the following GitHub releases:
|
||||
// https://github.com/lamps-wg/cms-ml-dsa/releases/tag/draft-ietf-lamps-cms-ml-dsa-07
|
||||
// https://github.com/lamps-wg/dilithium-certificates/releases/tag/draft-ietf-lamps-dilithium-certificates-13
|
||||
//
|
||||
// Although the release tags include "draft", these values are the
|
||||
// same as those in the final RFCs 9881 and 9882.
|
||||
try (var cmsReader = RepositoryFileReader.of(CMS_ML_DSA.class,
|
||||
"cms-ml-dsa-draft-ietf-lamps-cms-ml-dsa-07/");
|
||||
var dsaReader = RepositoryFileReader.of(DILITHIUM_CERTIFICATES.class,
|
||||
"dilithium-certificates-draft-ietf-lamps-dilithium-certificates-13/")) {
|
||||
test(readCMS(cmsReader, "mldsa44-signed-attrs.pem"),
|
||||
readCert(dsaReader, "ML-DSA-44.crt"));
|
||||
test(readCMS(cmsReader, "mldsa65-signed-attrs.pem"),
|
||||
readCert(dsaReader, "ML-DSA-65.crt"));
|
||||
test(readCMS(cmsReader, "mldsa87-signed-attrs.pem"),
|
||||
readCert(dsaReader, "ML-DSA-87.crt"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies a signed file.
|
||||
/// @param data the signed data in PKCS #7 format
|
||||
/// @param cert the certificate used to verify
|
||||
static void test(byte[] data, X509Certificate cert) throws Exception {
|
||||
var p7 = new PKCS7(data);
|
||||
for (var si : p7.getSignerInfos()) {
|
||||
Asserts.assertTrue(p7.verify(si, null, cert).getIssuerName() != null);
|
||||
}
|
||||
}
|
||||
|
||||
// Read data in https://datatracker.ietf.org/doc/html/rfc9882#name-examples
|
||||
static byte[] readCMS(RepositoryFileReader f, String entry) throws IOException {
|
||||
var data = f.read("examples/" + entry);
|
||||
var pem = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(data)))
|
||||
.lines()
|
||||
.filter(s -> !s.contains("-----"))
|
||||
.collect(Collectors.joining());
|
||||
return Base64.getMimeDecoder().decode(pem);
|
||||
}
|
||||
|
||||
// Read data in https://datatracker.ietf.org/doc/html/rfc9881#name-example-certificates
|
||||
static X509Certificate readCert(RepositoryFileReader f, String entry) throws Exception {
|
||||
var data = f.read("examples/" + entry);
|
||||
var cf = CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(data));
|
||||
}
|
||||
}
|
||||
181
test/jdk/sun/security/tools/jarsigner/ML_DSA.java
Normal file
181
test/jdk/sun/security/tools/jarsigner/ML_DSA.java
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/*
|
||||
* @test
|
||||
* @bug 8298387
|
||||
* @summary signing with ML-DSA
|
||||
* @library /test/lib
|
||||
* @modules java.base/sun.security.util
|
||||
*/
|
||||
|
||||
import jdk.security.jarsigner.JarSigner;
|
||||
import jdk.test.lib.Asserts;
|
||||
import jdk.test.lib.SecurityTools;
|
||||
import jdk.test.lib.process.Proc;
|
||||
import jdk.test.lib.security.DerUtils;
|
||||
import jdk.test.lib.util.JarUtils;
|
||||
import sun.security.util.KnownOIDs;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyStore;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
public class ML_DSA {
|
||||
|
||||
static List<String> SIGNERS = List.of("44", "65", "87");
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length > 0) {
|
||||
// Launched by testDisabled() in a sub-process to count signers
|
||||
var expectedCount = Integer.parseInt(args[0]);
|
||||
try (var jf = new JarFile("a.jar")) {
|
||||
var je = jf.getJarEntry("a");
|
||||
jf.getInputStream(je).readAllBytes();
|
||||
var signers = je.getCodeSigners();
|
||||
var count = signers == null ? 0 : signers.length;
|
||||
if (expectedCount != count) {
|
||||
throw new RuntimeException("Expected: " + expectedCount
|
||||
+ ", actual " + count);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
prepare();
|
||||
testAPI();
|
||||
testTool(); // call this last, as it modifies a.jar.
|
||||
testDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
private static void prepare() throws Exception {
|
||||
for (var signer : SIGNERS) {
|
||||
SecurityTools.keytool("-keystore ks -storepass changeit -genkeypair -alias "
|
||||
+ signer + " -keyalg ML-DSA-" + signer + " -dname CN=" + signer)
|
||||
.shouldHaveExitValue(0);
|
||||
}
|
||||
JarUtils.createJarFile(Path.of("a.jar"), Path.of("."),
|
||||
Files.write(Path.of("a"), new byte[10]));
|
||||
}
|
||||
|
||||
static void testAPI() throws Exception {
|
||||
var pass = "changeit".toCharArray();
|
||||
var ks = KeyStore.getInstance(new File("ks"), pass);
|
||||
for (var signer : SIGNERS) {
|
||||
var jsb = new JarSigner.Builder((KeyStore.PrivateKeyEntry)
|
||||
ks.getEntry(signer, new KeyStore.PasswordProtection(pass)));
|
||||
try (var zf = new ZipFile("a.jar");
|
||||
var of = Files.newOutputStream(Path.of(signer + ".jar"))) {
|
||||
jsb.signerName(signer).build().sign(zf, of);
|
||||
}
|
||||
try (var jf = new JarFile(signer + ".jar")) {
|
||||
var je = jf.getJarEntry("a");
|
||||
jf.getInputStream(je).readAllBytes();
|
||||
Asserts.assertEquals(1, je.getCodeSigners().length);
|
||||
checkDigestAlgorithm(jf, signer, KnownOIDs.SHA_512);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void testTool() throws Exception {
|
||||
for (var signer : SIGNERS) {
|
||||
SecurityTools.jarsigner("-keystore ks -storepass changeit a.jar " + signer)
|
||||
.shouldHaveExitValue(0);
|
||||
}
|
||||
SecurityTools.jarsigner("-verify a.jar -verbose -certs")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldContain("jar verified");
|
||||
|
||||
try (var jf = new JarFile("a.jar")) {
|
||||
var je = jf.getJarEntry("a");
|
||||
jf.getInputStream(je).readAllBytes();
|
||||
Asserts.assertEquals(3, je.getCodeSigners().length);
|
||||
for (var signer : SIGNERS) {
|
||||
checkDigestAlgorithm(jf, signer, KnownOIDs.SHA_512);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void testDisabled() throws Exception {
|
||||
|
||||
// All disabled
|
||||
Files.writeString(Paths.get("my.security"),
|
||||
"jdk.jar.disabledAlgorithms=ML-DSA");
|
||||
SecurityTools.jarsigner("-J-Djava.security.properties=my.security" +
|
||||
" -keystore ks -storepass changeit" +
|
||||
" -verify a.jar -verbose -certs -strict")
|
||||
.shouldHaveExitValue(16)
|
||||
.shouldContain("ML-DSA-44 key (disabled)")
|
||||
.shouldContain("ML-DSA-65 key (disabled)")
|
||||
.shouldContain("ML-DSA-87 key (disabled)")
|
||||
.shouldContain("The jar will be treated as unsigned");
|
||||
// Need to launch in a new process because security property is
|
||||
// read at the beginning
|
||||
Proc.create("ML_DSA")
|
||||
.secprop("jdk.jar.disabledAlgorithms", "ML-DSA")
|
||||
.args("0")
|
||||
.start()
|
||||
.waitFor(0);
|
||||
|
||||
// One disabled, one made weak
|
||||
Files.writeString(Paths.get("my.security"), """
|
||||
jdk.jar.disabledAlgorithms=ML-DSA-44
|
||||
jdk.security.legacyAlgorithms=ML-DSA-65
|
||||
""");
|
||||
SecurityTools.jarsigner("-J-Djava.security.properties=my.security" +
|
||||
" -keystore ks -storepass changeit" +
|
||||
" -verify a.jar -verbose -certs -strict")
|
||||
.shouldHaveExitValue(0)
|
||||
.shouldContain("ML-DSA-44 key (disabled)")
|
||||
.shouldContain("ML-DSA-65 key (weak)")
|
||||
.shouldContain("ML-DSA-87 key")
|
||||
.shouldNotContain("ML-DSA-87 key (disabled)")
|
||||
.shouldContain("jar verified.");
|
||||
Proc.create("ML_DSA")
|
||||
.secprop("jdk.jar.disabledAlgorithms", "ML-DSA-44")
|
||||
.secprop("jdk.security.legacyAlgorithms", "ML-DSA-65")
|
||||
.args("2") // weak still considered signer, disabled not
|
||||
.start()
|
||||
.waitFor(0);
|
||||
}
|
||||
|
||||
static void checkDigestAlgorithm(JarFile jf, String alias, KnownOIDs digAlg)
|
||||
throws Exception {
|
||||
var p7 = jf.getInputStream(jf.getEntry("META-INF/" + alias + ".DSA"))
|
||||
.readAllBytes();
|
||||
// SignedData - digestAlgorithms
|
||||
DerUtils.checkAlg(p7, "10100", digAlg);
|
||||
// SignedData - signerInfos - digestAlgorithm
|
||||
DerUtils.checkAlg(p7, "104020", digAlg);
|
||||
// SignedData - signerInfos - signatureAlgorithm
|
||||
DerUtils.checkAlg(p7, "104040", KnownOIDs.valueOf("ML_DSA_" + alias));
|
||||
// SignedData - signerInfos - signedAttrs - CMSAlgorithmProtection - digestAlgorithm
|
||||
DerUtils.checkAlg(p7, "1040321000", digAlg);
|
||||
// SignedData - signerInfos - signedAttrs - CMSAlgorithmProtection - signatureAlgorithm
|
||||
DerUtils.checkAlg(p7, "1040321010", KnownOIDs.valueOf("ML_DSA_" + alias));
|
||||
}
|
||||
}
|
||||
151
test/lib/jdk/test/lib/security/RepositoryFileReader.java
Normal file
151
test/lib/jdk/test/lib/security/RepositoryFileReader.java
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.test.lib.security;
|
||||
|
||||
import jdk.test.lib.artifacts.Artifact;
|
||||
import jdk.test.lib.artifacts.ArtifactResolver;
|
||||
import jdk.test.lib.artifacts.ArtifactResolverException;
|
||||
import jtreg.SkippedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/// A helper class to read files from a code repository.
|
||||
///
|
||||
/// By default, the code repository is stored on the artifact server
|
||||
/// as a ZIP file.
|
||||
///
|
||||
/// Users can specify the "jdk.tests.repos.pattern" system property to read
|
||||
/// the files from an alternative location. The value of this system property
|
||||
/// represents the URL for each file entry where:
|
||||
/// - "%o" maps to the last part of [organization name][Artifact#organization()],
|
||||
/// - "%n" maps to [name][Artifact#name()],
|
||||
/// - "%r" maps to [version][Artifact#revision()],
|
||||
/// - "%e" maps to the name of the file entry to read.
|
||||
///
|
||||
/// For example, with the [CMS_ML_DSA] class inside this test:
|
||||
/// - The pattern `file:///Users/tester/repos/external/%o/%n/%e` resolves to
|
||||
/// a local file like `/Users/tester/repos/external/lamps-wg/cms-ml-dsa/entry`.
|
||||
/// - The pattern `https://raw.repos.com/%o/%n/%r/%e` resolves to
|
||||
/// `https://raw.repos.com/lamps-wg/cms-ml-dsa/c8f0cf7/entry`.
|
||||
///
|
||||
public sealed interface RepositoryFileReader extends AutoCloseable {
|
||||
|
||||
/// Reads the content of `entry` as a byte array.
|
||||
byte[] read(String entry) throws IOException;
|
||||
|
||||
/// Overrides the method with a different exception type
|
||||
/// to avoid compiler warnings about `InterruptedException`.
|
||||
@Override
|
||||
void close() throws IOException;
|
||||
|
||||
/// Returns a `RepositoryFileReader`.
|
||||
/// @param klass the `Artifact` class
|
||||
/// @param zipPrefix the prefix used in the ZIP file. See
|
||||
/// [ZipReader#ZipReader(ZipFile, String)].
|
||||
static RepositoryFileReader of(Class<?> klass, String zipPrefix) {
|
||||
Artifact artifact = klass.getAnnotation(Artifact.class);
|
||||
var org = artifact.organization();
|
||||
var prop = System.getProperty("jdk.tests.repos.pattern");
|
||||
if (prop != null && org.startsWith("jpg.tests.jdk.repos.")) {
|
||||
prop = prop.replace("%o", org.substring(org.lastIndexOf('.') + 1));
|
||||
prop = prop.replace("%n", artifact.name());
|
||||
prop = prop.replace("%r", artifact.revision());
|
||||
System.out.println("Creating URLReader on " + prop);
|
||||
return new URLReader(prop);
|
||||
} else {
|
||||
try {
|
||||
Path p = ArtifactResolver.resolve(klass).entrySet().stream()
|
||||
.findAny().get().getValue();
|
||||
System.out.println("Creating ZipReader on " + p);
|
||||
return new ZipReader(new ZipFile(p.toFile()), zipPrefix);
|
||||
} catch (ArtifactResolverException | IOException e) {
|
||||
throw new SkippedException("Cannot find " + artifact.name(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `RepositoryFileReader` to read file from a URL.
|
||||
/// @param base the base URL string, contains "%e" mapping to entry name
|
||||
record URLReader(String base) implements RepositoryFileReader {
|
||||
@Override
|
||||
public void close() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(String entry) throws IOException {
|
||||
System.out.println("Reading " + entry + "...");
|
||||
try (var is = new URI(base.replace("%e", entry)).toURL().openStream()) {
|
||||
return is.readAllBytes();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IOException("Cannot create URI", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A `RepositoryFileReader` to read file from a ZIP file.
|
||||
/// @param zf the `ZipFile`
|
||||
/// @param zipPrefix optional prefix string inside the ZIP file. For example,
|
||||
/// if an entry "folder/file" is represented as "archive/folder/file"
|
||||
/// inside the ZIP, "archive/" should be provided as `zipPrefix`.
|
||||
record ZipReader(ZipFile zf, String zipPrefix) implements RepositoryFileReader {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
zf.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(String entry) throws IOException {
|
||||
System.out.println("Reading " + entry + "...");
|
||||
ZipEntry ze = zf.getEntry(zipPrefix + entry);
|
||||
if (ze != null) {
|
||||
return zf.getInputStream(ze).readAllBytes();
|
||||
} else {
|
||||
throw new RuntimeException("Entry not found: " + entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Artifact(
|
||||
organization = "jpg.tests.jdk.repos.lamps-wg",
|
||||
name = "dilithium-certificates",
|
||||
revision = "785a549",
|
||||
extension = "zip",
|
||||
unpack = false)
|
||||
public static class DILITHIUM_CERTIFICATES {
|
||||
}
|
||||
|
||||
@Artifact(
|
||||
organization = "jpg.tests.jdk.repos.lamps-wg",
|
||||
name = "cms-ml-dsa",
|
||||
revision = "c8f0cf7",
|
||||
extension = "zip",
|
||||
unpack = false)
|
||||
public static class CMS_ML_DSA {
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user