From 4fcb17bb9309b0283ea7c90a48f7431cde40e144 Mon Sep 17 00:00:00 2001 From: Saint Wesonga Date: Fri, 15 Dec 2023 04:12:49 +0000 Subject: [PATCH 1/8] 8302017: Allocate BadPaddingException only if it will be thrown Reviewed-by: bstafford, mbalao Backport-of: 334b977259930368160db705c1f2feda0b0e8707 --- .../com/sun/crypto/provider/RSACipher.java | 27 ++++++-- .../classes/sun/security/rsa/RSAPadding.java | 67 +++++++------------ .../sun/security/rsa/RSASignature.java | 26 +++---- .../sun/security/pkcs11/P11Signature.java | 11 +-- .../jdk/sun/security/rsa/RSAPaddingCheck.java | 63 +++++++++++++++++ 5 files changed, 132 insertions(+), 62 deletions(-) create mode 100644 test/jdk/sun/security/rsa/RSAPaddingCheck.java diff --git a/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java b/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java index 196acef1511..ac512e573a5 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2023, 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 @@ -355,21 +355,38 @@ private byte[] doFinal() throws BadPaddingException, switch (mode) { case MODE_SIGN: paddingCopy = padding.pad(buffer, 0, bufOfs); - result = RSACore.rsa(paddingCopy, privateKey, true); + if (paddingCopy != null) { + result = RSACore.rsa(paddingCopy, privateKey, true); + } else { + throw new BadPaddingException("Padding error in signing"); + } break; case MODE_VERIFY: byte[] verifyBuffer = RSACore.convert(buffer, 0, bufOfs); paddingCopy = RSACore.rsa(verifyBuffer, publicKey); result = padding.unpad(paddingCopy); + if (result == null) { + throw new BadPaddingException + ("Padding error in verification"); + } break; case MODE_ENCRYPT: paddingCopy = padding.pad(buffer, 0, bufOfs); - result = RSACore.rsa(paddingCopy, publicKey); + if (paddingCopy != null) { + result = RSACore.rsa(paddingCopy, publicKey); + } else { + throw new BadPaddingException + ("Padding error in encryption"); + } break; case MODE_DECRYPT: byte[] decryptBuffer = RSACore.convert(buffer, 0, bufOfs); paddingCopy = RSACore.rsa(decryptBuffer, privateKey, false); result = padding.unpad(paddingCopy); + if (result == null) { + throw new BadPaddingException + ("Padding error in decryption"); + } break; default: throw new AssertionError("Internal error"); @@ -378,9 +395,9 @@ private byte[] doFinal() throws BadPaddingException, } finally { Arrays.fill(buffer, 0, bufOfs, (byte)0); bufOfs = 0; - if (paddingCopy != null // will not happen + if (paddingCopy != null && paddingCopy != buffer // already cleaned - && paddingCopy != result) { // DO NOT CLEAN, THIS IS RESULT! + && paddingCopy != result) { // DO NOT CLEAN, THIS IS RESULT Arrays.fill(paddingCopy, (byte)0); } } diff --git a/src/java.base/share/classes/sun/security/rsa/RSAPadding.java b/src/java.base/share/classes/sun/security/rsa/RSAPadding.java index c54dbdb8d43..09d3936d872 100644 --- a/src/java.base/share/classes/sun/security/rsa/RSAPadding.java +++ b/src/java.base/share/classes/sun/security/rsa/RSAPadding.java @@ -30,7 +30,6 @@ import java.security.*; import java.security.spec.*; -import javax.crypto.BadPaddingException; import javax.crypto.spec.PSource; import javax.crypto.spec.OAEPParameterSpec; @@ -236,24 +235,22 @@ public int getMaxDataSize() { } /** - * Pad the data and return the padded block. + * Pad the data and return the result or null if error occurred. */ - public byte[] pad(byte[] data) throws BadPaddingException { + public byte[] pad(byte[] data) { return pad(data, 0, data.length); } /** - * Pad the data and return the padded block. + * Pad the data and return the result or null if error occurred. */ - public byte[] pad(byte[] data, int ofs, int len) - throws BadPaddingException { + public byte[] pad(byte[] data, int ofs, int len) { if (len > maxDataSize) { - throw new BadPaddingException("Data must be shorter than " - + (maxDataSize + 1) + " bytes but received " - + len + " bytes."); + return null; } switch (type) { case PAD_NONE: + // assert len == paddedSize and data.length - ofs > len? return RSACore.convert(data, ofs, len); case PAD_BLOCKTYPE_1: case PAD_BLOCKTYPE_2: @@ -266,31 +263,25 @@ public byte[] pad(byte[] data, int ofs, int len) } /** - * Unpad the padded block and return the data. + * Unpad the padded block and return the result or null if error occurred. */ - public byte[] unpad(byte[] padded) throws BadPaddingException { - if (padded.length != paddedSize) { - throw new BadPaddingException("Decryption error." + - "The padded array length (" + padded.length + - ") is not the specified padded size (" + paddedSize + ")"); - } - switch (type) { - case PAD_NONE: - return padded; - case PAD_BLOCKTYPE_1: - case PAD_BLOCKTYPE_2: - return unpadV15(padded); - case PAD_OAEP_MGF1: - return unpadOAEP(padded); - default: - throw new AssertionError(); + public byte[] unpad(byte[] padded) { + if (padded.length == paddedSize) { + return switch(type) { + case PAD_NONE -> padded; + case PAD_BLOCKTYPE_1, PAD_BLOCKTYPE_2 -> unpadV15(padded); + case PAD_OAEP_MGF1 -> unpadOAEP(padded); + default -> throw new AssertionError(); + }; + } else { + return null; } } /** * PKCS#1 v1.5 padding (blocktype 1 and 2). */ - private byte[] padV15(byte[] data, int ofs, int len) throws BadPaddingException { + private byte[] padV15(byte[] data, int ofs, int len) { byte[] padded = new byte[paddedSize]; System.arraycopy(data, ofs, padded, paddedSize - len, len); int psSize = paddedSize - 3 - len; @@ -327,10 +318,10 @@ private byte[] padV15(byte[] data, int ofs, int len) throws BadPaddingException /** * PKCS#1 v1.5 unpadding (blocktype 1 (signature) and 2 (encryption)). - * + * Return the result or null if error occurred. * Note that we want to make it a constant-time operation */ - private byte[] unpadV15(byte[] padded) throws BadPaddingException { + private byte[] unpadV15(byte[] padded) { int k = 0; boolean bp = false; @@ -366,10 +357,8 @@ private byte[] unpadV15(byte[] padded) throws BadPaddingException { byte[] data = new byte[n]; System.arraycopy(padded, p, data, 0, n); - BadPaddingException bpe = new BadPaddingException("Decryption error"); - if (bp) { - throw bpe; + return null; } else { return data; } @@ -378,8 +367,9 @@ private byte[] unpadV15(byte[] padded) throws BadPaddingException { /** * PKCS#1 v2.0 OAEP padding (MGF1). * Paragraph references refer to PKCS#1 v2.1 (June 14, 2002) + * Return the result or null if error occurred. */ - private byte[] padOAEP(byte[] M, int ofs, int len) throws BadPaddingException { + private byte[] padOAEP(byte[] M, int ofs, int len) { if (random == null) { random = JCAUtil.getSecureRandom(); } @@ -428,8 +418,9 @@ private byte[] padOAEP(byte[] M, int ofs, int len) throws BadPaddingException { /** * PKCS#1 v2.1 OAEP unpadding (MGF1). + * Return the result or null if error occurred. */ - private byte[] unpadOAEP(byte[] padded) throws BadPaddingException { + private byte[] unpadOAEP(byte[] padded) { byte[] EM = padded; boolean bp = false; int hLen = lHash.length; @@ -485,12 +476,6 @@ private byte[] unpadOAEP(byte[] padded) throws BadPaddingException { byte [] m = new byte[EM.length - mStart]; System.arraycopy(EM, mStart, m, 0, m.length); - BadPaddingException bpe = new BadPaddingException("Decryption error"); - - if (bp) { - throw bpe; - } else { - return m; - } + return (bp? null : m); } } diff --git a/src/java.base/share/classes/sun/security/rsa/RSASignature.java b/src/java.base/share/classes/sun/security/rsa/RSASignature.java index 795d5c59020..306f5dbbd81 100644 --- a/src/java.base/share/classes/sun/security/rsa/RSASignature.java +++ b/src/java.base/share/classes/sun/security/rsa/RSASignature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2023, 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,13 +190,15 @@ protected byte[] engineSign() throws SignatureException { try { byte[] encoded = encodeSignature(digestOID, digest); byte[] padded = padding.pad(encoded); - byte[] encrypted = RSACore.rsa(padded, privateKey, true); - return encrypted; + if (padded != null) { + return RSACore.rsa(padded, privateKey, true); + } } catch (GeneralSecurityException e) { throw new SignatureException("Could not sign data", e); } catch (IOException e) { throw new SignatureException("Could not encode data", e); } + throw new SignatureException("Could not sign data"); } // verify the data and return the result. See JCA doc @@ -208,20 +210,20 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException { } try { if (sigBytes.length != RSACore.getByteLength(publicKey)) { - throw new SignatureException("Signature length not correct: got " + + throw new SignatureException("Bad signature length: got " + sigBytes.length + " but was expecting " + RSACore.getByteLength(publicKey)); } - byte[] digest = getDigestValue(); + + // https://www.rfc-editor.org/rfc/rfc8017.html#section-8.2.2 + // Step 4 suggests comparing the encoded message byte[] decrypted = RSACore.rsa(sigBytes, publicKey); - byte[] unpadded = padding.unpad(decrypted); - byte[] decodedDigest = decodeSignature(digestOID, unpadded); - return MessageDigest.isEqual(digest, decodedDigest); + + byte[] digest = getDigestValue(); + byte[] encoded = encodeSignature(digestOID, digest); + byte[] padded = padding.pad(encoded); + return MessageDigest.isEqual(padded, decrypted); } catch (javax.crypto.BadPaddingException e) { - // occurs if the app has used the wrong RSA public key - // or if sigBytes is invalid - // return false rather than propagating the exception for - // compatibility/ease of use return false; } catch (IOException e) { throw new SignatureException("Signature encoding error", e); diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Signature.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Signature.java index 54206ee0a06..de48270fd59 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Signature.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Signature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2023, 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 @@ -759,9 +759,12 @@ private byte[] pkcs1Pad(byte[] data) { int len = (p11Key.length() + 7) >> 3; RSAPadding padding = RSAPadding.getInstance (RSAPadding.PAD_BLOCKTYPE_1, len); - byte[] padded = padding.pad(data); - return padded; - } catch (GeneralSecurityException e) { + byte[] result = padding.pad(data); + if (result == null) { + throw new ProviderException("Error padding data"); + } + return result; + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { throw new ProviderException(e); } } diff --git a/test/jdk/sun/security/rsa/RSAPaddingCheck.java b/test/jdk/sun/security/rsa/RSAPaddingCheck.java new file mode 100644 index 00000000000..807e4e3bf4b --- /dev/null +++ b/test/jdk/sun/security/rsa/RSAPaddingCheck.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023, 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 8302017 + * @summary Ensure that RSAPadding class works as expected after refactoring + * @modules java.base/sun.security.rsa + */ +import java.util.Arrays; +import sun.security.rsa.RSAPadding; + +public class RSAPaddingCheck { + + private static int[] PADDING_TYPES = { + RSAPadding.PAD_BLOCKTYPE_1, + RSAPadding.PAD_BLOCKTYPE_2, + RSAPadding.PAD_NONE, + RSAPadding.PAD_OAEP_MGF1, + }; + + public static void main(String[] args) throws Exception { + int size = 2048 >> 3; + byte[] testData = "This is some random to-be-padded Data".getBytes(); + for (int type : PADDING_TYPES) { + byte[] data = (type == RSAPadding.PAD_NONE? + Arrays.copyOf(testData, size) : testData); + System.out.println("Testing PaddingType: " + type); + RSAPadding padding = RSAPadding.getInstance(type, size); + byte[] paddedData = padding.pad(data); + if (paddedData == null) { + throw new RuntimeException("Unexpected padding op failure!"); + } + + byte[] data2 = padding.unpad(paddedData); + if (data2 == null) { + throw new RuntimeException("Unexpected unpadding op failure!"); + } + if (!Arrays.equals(data, data2)) { + throw new RuntimeException("diff check failure!"); + } + } + } +} From 5355fc9070e20d8c0a3f30fc79b277fa884eab6c Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Fri, 15 Dec 2023 16:23:14 +0000 Subject: [PATCH 2/8] 8320597: RSA signature verification fails on signed data that does not encode params correctly Reviewed-by: mbalao Backport-of: 11e4a925bec3c1f79e03045d48def53188b655e6 --- .../classes/sun/security/rsa/RSAPadding.java | 2 +- .../sun/security/rsa/RSASignature.java | 39 ++++++------- test/jdk/sun/security/rsa/WithoutNULL.java | 57 +++++++++++++++++++ 3 files changed, 78 insertions(+), 20 deletions(-) create mode 100644 test/jdk/sun/security/rsa/WithoutNULL.java diff --git a/src/java.base/share/classes/sun/security/rsa/RSAPadding.java b/src/java.base/share/classes/sun/security/rsa/RSAPadding.java index 09d3936d872..6954c2ad7ea 100644 --- a/src/java.base/share/classes/sun/security/rsa/RSAPadding.java +++ b/src/java.base/share/classes/sun/security/rsa/RSAPadding.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2023, 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 diff --git a/src/java.base/share/classes/sun/security/rsa/RSASignature.java b/src/java.base/share/classes/sun/security/rsa/RSASignature.java index 306f5dbbd81..8366ab8bfca 100644 --- a/src/java.base/share/classes/sun/security/rsa/RSASignature.java +++ b/src/java.base/share/classes/sun/security/rsa/RSASignature.java @@ -220,8 +220,17 @@ protected boolean engineVerify(byte[] sigBytes) throws SignatureException { byte[] decrypted = RSACore.rsa(sigBytes, publicKey); byte[] digest = getDigestValue(); + byte[] encoded = encodeSignature(digestOID, digest); byte[] padded = padding.pad(encoded); + if (MessageDigest.isEqual(padded, decrypted)) { + return true; + } + + // Some vendors might omit the NULL params in digest algorithm + // identifier. Try again. + encoded = encodeSignatureWithoutNULL(digestOID, digest); + padded = padding.pad(encoded); return MessageDigest.isEqual(padded, decrypted); } catch (javax.crypto.BadPaddingException e) { return false; @@ -247,27 +256,19 @@ public static byte[] encodeSignature(ObjectIdentifier oid, byte[] digest) } /** - * Decode the signature data. Verify that the object identifier matches - * and return the message digest. + * Encode the digest without the NULL params, return the to-be-signed data. + * This is only used by SunRsaSign. */ - public static byte[] decodeSignature(ObjectIdentifier oid, byte[] sig) + static byte[] encodeSignatureWithoutNULL(ObjectIdentifier oid, byte[] digest) throws IOException { - // Enforce strict DER checking for signatures - DerInputStream in = new DerInputStream(sig, 0, sig.length, false); - DerValue[] values = in.getSequence(2); - if ((values.length != 2) || (in.available() != 0)) { - throw new IOException("SEQUENCE length error"); - } - AlgorithmId algId = AlgorithmId.parse(values[0]); - if (algId.getOID().equals(oid) == false) { - throw new IOException("ObjectIdentifier mismatch: " - + algId.getOID()); - } - if (algId.getEncodedParams() != null) { - throw new IOException("Unexpected AlgorithmId parameters"); - } - byte[] digest = values[1].getOctetString(); - return digest; + DerOutputStream out = new DerOutputStream(); + DerOutputStream oidout = new DerOutputStream(); + oidout.putOID(oid); + out.write(DerValue.tag_Sequence, oidout); + out.putOctetString(digest); + DerValue result = + new DerValue(DerValue.tag_Sequence, out.toByteArray()); + return result.toByteArray(); } // set parameter, not supported. See JCA doc diff --git a/test/jdk/sun/security/rsa/WithoutNULL.java b/test/jdk/sun/security/rsa/WithoutNULL.java new file mode 100644 index 00000000000..64cf831099d --- /dev/null +++ b/test/jdk/sun/security/rsa/WithoutNULL.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2023, 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 8320597 + * @summary Verify RSA signature with omitted digest params (should be encoded as NULL) + * for backward compatibility + */ +import java.security.KeyFactory; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class WithoutNULL { + public static void main(String[] args) throws Exception { + + // A 1024-bit RSA public key + byte[] key = Base64.getMimeDecoder().decode(""" + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrfTrEm4KvdFSpGAM7InrFEzALTKdphT9fK6Gu + eVjHtKsuCSEaULCdjhJvPpFK40ONr1JEC1Ywp1UYrfBBdKunnbDZqNZL1cFv+IzF4Yj6JO6pOeHi + 1Zpur1GaQRRlYTvzmyWY/AATQDh8JfKObNnDVwXeezFODUG8h5+XL1ZXZQIDAQAB"""); + + // A SHA1withRSA signature on an empty input where the digestAlgorithm + // inside DigestInfo does not have a parameters field. + byte[] sig = Base64.getMimeDecoder().decode(""" + D1FpiT44WEXlDfYK880bdorLO+e9qJVXZWiBgqs9dfK7lYQwyEt9dL23mbUAKm5TVEj2ZxtHkEvk + b8oaWkxk069jDTM1RhllPJZkAjeQRbw4gkg4N6wKZz9B/jdSRMNJg/b9QdRYZOHOBxsEHMbUREPV + DoCOLaxB8eIXX0EWkiE="""); + + Signature s = Signature.getInstance("SHA1withRSA", "SunRsaSign"); + s.initVerify(KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(key))); + if (!s.verify(sig)) { + throw new RuntimeException("Does not verify"); + } + } +} From 3c760354d88291713671c0adf12dbed0ce31c73c Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Tue, 9 Jan 2024 15:46:05 +0100 Subject: [PATCH 3/8] 8308204: Enhanced certificate processing Reviewed-by: mbalao Backport-of: ef0ea85bf1398b73bd308ba2b395c917b449aa3b --- .../provider/certpath/ForwardBuilder.java | 52 +++++++++++++------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/java.base/share/classes/sun/security/provider/certpath/ForwardBuilder.java b/src/java.base/share/classes/sun/security/provider/certpath/ForwardBuilder.java index 2fbeb8856ee..50f2daa6081 100644 --- a/src/java.base/share/classes/sun/security/provider/certpath/ForwardBuilder.java +++ b/src/java.base/share/classes/sun/security/provider/certpath/ForwardBuilder.java @@ -336,8 +336,11 @@ private void getMatchingCACerts(ForwardState currentState, } } + // Thread-local gate to prevent recursive provider lookups + private static ThreadLocal gate = new ThreadLocal<>(); + /** - * Download Certificates from the given AIA and add them to the + * Download certificates from the given AIA and add them to the * specified Collection. */ // cs.getCertificates(caSelector) returns a collection of X509Certificate's @@ -349,32 +352,47 @@ private boolean getCerts(AuthorityInfoAccessExtension aiaExt, if (Builder.USE_AIA == false) { return false; } + List adList = aiaExt.getAccessDescriptions(); if (adList == null || adList.isEmpty()) { return false; } - boolean add = false; - for (AccessDescription ad : adList) { - CertStore cs = URICertStore.getInstance(ad); - if (cs != null) { - try { - if (certs.addAll((Collection) - cs.getCertificates(caSelector))) { - add = true; - if (!searchAllCertStores) { - return true; + if (gate.get() != null) { + // Avoid recursive fetching of certificates + if (debug != null) { + debug.println("Recursive fetching of certs via the AIA " + + "extension detected"); + } + return false; + } + + gate.set(gate); + try { + boolean add = false; + for (AccessDescription ad : adList) { + CertStore cs = URICertStore.getInstance(ad); + if (cs != null) { + try { + if (certs.addAll((Collection) + cs.getCertificates(caSelector))) { + add = true; + if (!searchAllCertStores) { + return true; + } + } + } catch (CertStoreException cse) { + if (debug != null) { + debug.println("exception getting certs from CertStore:"); + cse.printStackTrace(); } - } - } catch (CertStoreException cse) { - if (debug != null) { - debug.println("exception getting certs from CertStore:"); - cse.printStackTrace(); } } } + return add; + } finally { + gate.set(null); } - return add; } /** From 5d68630e8a2b9c7aed1df294614e8a79a1bff7b9 Mon Sep 17 00:00:00 2001 From: Martin Balao Date: Tue, 9 Jan 2024 15:46:55 +0100 Subject: [PATCH 4/8] 8314295: Enhance verification of verifier Reviewed-by: yan Backport-of: 08980a0a60bc48c17eacd57fd2d7065ac2d986a8 --- src/java.base/share/native/libverify/check_code.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/java.base/share/native/libverify/check_code.c b/src/java.base/share/native/libverify/check_code.c index 55d6fbcf42a..126ff76e652 100644 --- a/src/java.base/share/native/libverify/check_code.c +++ b/src/java.base/share/native/libverify/check_code.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2023, 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 @@ -81,6 +81,7 @@ #include #include #include +#include #include "jni.h" #include "jni_util.h" @@ -1195,7 +1196,7 @@ verify_opcode_operands(context_type *context, unsigned int inumber, int offset) } } if (opcode == JVM_OPC_tableswitch) { - keys = _ck_ntohl(lpc[2]) - _ck_ntohl(lpc[1]) + 1; + keys = _ck_ntohl(lpc[2]) - _ck_ntohl(lpc[1]) + 1; delta = 1; } else { keys = _ck_ntohl(lpc[1]); /* number of pairs */ @@ -1677,11 +1678,13 @@ static int instruction_length(unsigned char *iptr, unsigned char *end) switch (instruction) { case JVM_OPC_tableswitch: { int *lpc = (int *)UCALIGN(iptr + 1); - int index; if (lpc + 2 >= (int *)end) { return -1; /* do not read pass the end */ } - index = _ck_ntohl(lpc[2]) - _ck_ntohl(lpc[1]); + int64_t low = _ck_ntohl(lpc[1]); + int64_t high = _ck_ntohl(lpc[2]); + int64_t index = high - low; + // The value of low must be less than or equal to high - i.e. index >= 0 if ((index < 0) || (index > 65535)) { return -1; /* illegal */ } else { From e54afbe819f1379b3ca4ba736debc0d34313b54d Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Tue, 9 Jan 2024 15:48:02 +0100 Subject: [PATCH 5/8] 8276123: ZipFile::getEntry will not return a file entry when there is a directory entry of the same name within a Zip File Reviewed-by: mbalao Backport-of: b85500e52479c48b02a96b28fddefa2b25d5d9bd --- .../share/classes/java/util/zip/ZipFile.java | 15 +- .../ZipFile/ZipFileDuplicateEntryTest.java | 581 ++++++++++++++++++ 2 files changed, 591 insertions(+), 5 deletions(-) create mode 100644 test/jdk/java/util/zip/ZipFile/ZipFileDuplicateEntryTest.java diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index d47a1d26026..d10ad7f60a7 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -1790,13 +1790,18 @@ private int getEntryPos(String name, boolean addSlash) { // slash int entryLen = entry.length(); int nameLen = name.length(); - if ((entryLen == nameLen && entry.equals(name)) || - (addSlash && - nameLen + 1 == entryLen && - entry.startsWith(name) && - entry.charAt(entryLen - 1) == '/')) { + if (entryLen == nameLen && entry.equals(name)) { + // Found our match return pos; } + // If addSlash is true we'll now test for name+/ providing + if (addSlash && nameLen + 1 == entryLen + && entry.startsWith(name) && + entry.charAt(entryLen - 1) == '/') { + // Found the entry "name+/", now find the CEN entry pos + int exactPos = getEntryPos(name, false); + return exactPos == -1 ? pos : exactPos; + } } catch (IllegalArgumentException iae) { // Ignore } diff --git a/test/jdk/java/util/zip/ZipFile/ZipFileDuplicateEntryTest.java b/test/jdk/java/util/zip/ZipFile/ZipFileDuplicateEntryTest.java new file mode 100644 index 00000000000..95a223c8e1f --- /dev/null +++ b/test/jdk/java/util/zip/ZipFile/ZipFileDuplicateEntryTest.java @@ -0,0 +1,581 @@ +/* + * Copyright (c) 2021, 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. + * + */ + +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Formatter; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import static org.testng.Assert.*; + +/** + * @test + * @bug 8276123 + * @summary ZipFile::getEntry will not return a file entry when there is a + * directory entry of the same name within a Zip File + * @run testng/othervm ZipFileDuplicateEntryTest + */ +public class ZipFileDuplicateEntryTest { + + /** + * Name to use for creating Zip entries + */ + private static final String ENTRY_NAME = "entry"; + + /** + * Zip and Jar files to be created + */ + private static final Path ZIP_FILE = Paths.get("fileDirEntry.zip"); + private static final Path ZIP_FILE2 = Paths.get("OnlyDirEntry.zip"); + private static final Path DUPLICATE_FILE_ENTRY_FILE = Paths.get("DupFIleEntry.zip"); + private static final Path TEST_JAR = Paths.get("fileDirEntry.jar"); + + /** + * Directory entry added to the Zip File. + */ + private static final Entry DIR_ENTRY = + Entry.of(ENTRY_NAME + "/", ZipEntry.DEFLATED, + "I am a Directory"); + + /** + * File entry added to the Zip File. + */ + private static final Entry FILE_ENTRY = + Entry.of(ENTRY_NAME, ZipEntry.DEFLATED, "I am a File"); + + /** + * Duplicate File entry added to the Zip file. This is the 2nd entry added + * to the Zip file and is expected to be returned. + */ + private static final Entry DUPLICATE_FILE_ENTRY = + Entry.of(ENTRY_NAME, ZipEntry.DEFLATED, "Yet another File"); + /** + * Entries expected to be returned via ZipFile::stream + */ + private static final List EXPECTED_ENTRIES = + Arrays.asList(FILE_ENTRY.name, DIR_ENTRY.name); + + /** + * Max buffer size for readAllBytes method which can be used when + * InputStream::readAllBytes is not available + */ + private static final int MAX_BUFFER_SIZE = 1024; + + /** + * Flag to enable test output + */ + private static final boolean DEBUG = false; + + /** + * Array representing a Jar File with the entries: + * Name: entry, contents: "I am a File" + * Name: entry, contents: "Yet another File" + * See createByteArray() + */ + private static final byte[] DUPLICATE_ENTRY_JAR_BYTES = { + (byte) 0x50, (byte) 0x4b, (byte) 0x3, (byte) 0x4, (byte) 0x14, (byte) 0x0, (byte) 0x0, (byte) 0x8, + (byte) 0x8, (byte) 0x0, (byte) 0x60, (byte) 0x59, (byte) 0x55, (byte) 0x53, (byte) 0x8e, (byte) 0x39, + (byte) 0x14, (byte) 0x49, (byte) 0xd, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0xb, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x5, (byte) 0x0, (byte) 0x14, (byte) 0x0, (byte) 0x65, (byte) 0x6e, + (byte) 0x74, (byte) 0x72, (byte) 0x79, (byte) 0x1, (byte) 0x0, (byte) 0x10, (byte) 0x0, (byte) 0xb, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0xd, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0xf3, + (byte) 0x54, (byte) 0x48, (byte) 0xcc, (byte) 0x55, (byte) 0x48, (byte) 0x54, (byte) 0x70, (byte) 0xcb, + (byte) 0xcc, (byte) 0x49, (byte) 0x5, (byte) 0x0, (byte) 0x50, (byte) 0x4b, (byte) 0x3, (byte) 0x4, + (byte) 0x14, (byte) 0x0, (byte) 0x0, (byte) 0x8, (byte) 0x8, (byte) 0x0, (byte) 0x60, (byte) 0x59, + (byte) 0x55, (byte) 0x53, (byte) 0xe1, (byte) 0x4c, (byte) 0x29, (byte) 0xa4, (byte) 0x12, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x10, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x5, (byte) 0x0, + (byte) 0x14, (byte) 0x0, (byte) 0x65, (byte) 0x6e, (byte) 0x74, (byte) 0x72, (byte) 0x79, (byte) 0x1, + (byte) 0x0, (byte) 0x10, (byte) 0x0, (byte) 0x10, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x12, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x8b, (byte) 0x4c, (byte) 0x2d, (byte) 0x51, (byte) 0x48, + (byte) 0xcc, (byte) 0xcb, (byte) 0x2f, (byte) 0xc9, (byte) 0x48, (byte) 0x2d, (byte) 0x52, (byte) 0x70, + (byte) 0xcb, (byte) 0xcc, (byte) 0x49, (byte) 0x5, (byte) 0x0, (byte) 0x50, (byte) 0x4b, (byte) 0x1, + (byte) 0x2, (byte) 0x14, (byte) 0x0, (byte) 0x14, (byte) 0x0, (byte) 0x0, (byte) 0x8, (byte) 0x8, + (byte) 0x0, (byte) 0x60, (byte) 0x59, (byte) 0x55, (byte) 0x53, (byte) 0x8e, (byte) 0x39, (byte) 0x14, + (byte) 0x49, (byte) 0xd, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0xb, (byte) 0x0, (byte) 0x0, + (byte) 0x0, (byte) 0x5, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x65, (byte) 0x6e, (byte) 0x74, (byte) 0x72, (byte) 0x79, + (byte) 0x50, (byte) 0x4b, (byte) 0x1, (byte) 0x2, (byte) 0x14, (byte) 0x0, (byte) 0x14, (byte) 0x0, + (byte) 0x0, (byte) 0x8, (byte) 0x8, (byte) 0x0, (byte) 0x60, (byte) 0x59, (byte) 0x55, (byte) 0x53, + (byte) 0xe1, (byte) 0x4c, (byte) 0x29, (byte) 0xa4, (byte) 0x12, (byte) 0x0, (byte) 0x0, (byte) 0x0, + (byte) 0x10, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x5, (byte) 0x0, (byte) 0x0, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x44, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x65, (byte) 0x6e, + (byte) 0x74, (byte) 0x72, (byte) 0x79, (byte) 0x50, (byte) 0x4b, (byte) 0x5, (byte) 0x6, (byte) 0x0, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x2, (byte) 0x0, (byte) 0x2, (byte) 0x0, (byte) 0x66, + (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x8d, (byte) 0x0, (byte) 0x0, (byte) 0x0, (byte) 0x0, + (byte) 0x0, + }; + + /** + * Create Zip files used by the tests. + * + * @throws IOException If an error occurs + */ + @BeforeTest + public static void setup() throws IOException { + + /** + * Zip contains two entries named "entry" and "entry/" + */ + Files.deleteIfExists(ZIP_FILE); + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(ZIP_FILE))) { + zos.putNextEntry(new ZipEntry(FILE_ENTRY.name)); + zos.write(FILE_ENTRY.bytes); + zos.closeEntry(); + zos.putNextEntry(new ZipEntry(DIR_ENTRY.name)); + zos.write(DIR_ENTRY.bytes); + zos.closeEntry(); + } + + /** + * Jar contains two entries named "entry" and "entry/" + */ + Files.deleteIfExists(TEST_JAR); + try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(TEST_JAR))) { + jos.putNextEntry(new JarEntry(FILE_ENTRY.name)); + jos.write(FILE_ENTRY.bytes); + jos.closeEntry(); + jos.putNextEntry(new JarEntry(DIR_ENTRY.name)); + jos.write(DIR_ENTRY.bytes); + jos.closeEntry(); + } + + /** + * Zip contains the entry "entry/" + */ + Files.deleteIfExists(ZIP_FILE2); + try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(ZIP_FILE2))) { + zos.putNextEntry(new ZipEntry(DIR_ENTRY.name)); + zos.write(DIR_ENTRY.bytes); + zos.closeEntry(); + } + + /** + * Create a Jar that contains two entries named "entry" + */ + Files.deleteIfExists(DUPLICATE_FILE_ENTRY_FILE); + Files.write(DUPLICATE_FILE_ENTRY_FILE, DUPLICATE_ENTRY_JAR_BYTES); + } + + /** + * Clean up after the test run + * + * @throws IOException If an error occurs + */ + @AfterTest + public static void cleanup() throws IOException { + Files.deleteIfExists(ZIP_FILE); + Files.deleteIfExists(ZIP_FILE2); + Files.deleteIfExists(DUPLICATE_FILE_ENTRY_FILE); + Files.deleteIfExists(TEST_JAR); + } + + /** + * DataProvider used to specify the Zip entries to use + * + * @return The Entry to use within the test + */ + @DataProvider + public Object[][] entries() { + return new Object[][]{ + {FILE_ENTRY}, + {DIR_ENTRY} + }; + } + + /** + * Test whether ZipFile::getEntry can find a directory entry within a Zip + * file specifying "name" vs "name/" + * + * @throws IOException If an error occurs + */ + @Test + public void readDirWithoutSlash() throws IOException { + System.out.printf("%n%n**** readDirWithoutSlash ***%n"); + try (ZipFile zip = new ZipFile(ZIP_FILE2.toString())) { + ZipEntry ze = zip.getEntry(ENTRY_NAME); + if (DEBUG) { + System.out.printf(" Entry:%s, found:%s%n", ENTRY_NAME, ze != null); + } + assertNotNull(ze); + assertTrue(ze.isDirectory()); + try (InputStream in = zip.getInputStream(ze)) { + byte[] bytes = in.readAllBytes(); + if (DEBUG) { + System.out.printf("name: %s, isDirectory: %s, payload= %s%n", + ze.getName(), ze.isDirectory(), new String(bytes)); + } + assertEquals(bytes, DIR_ENTRY.bytes, + String.format("Expected payload: %s", + new String(DIR_ENTRY.bytes))); + } + } + } + + /** + * Validate that ZipFile::getEntry will return the correct entry when a file + * and directory have the same name + * + * @param entry The entry to search for + * @throws IOException If an error occurs + */ + @Test(dataProvider = "entries") + public void testSameFileDirEntryName(Entry entry) throws IOException { + System.out.printf("%n%n**** testSameFileDirEntryName ***%n"); + + try (ZipFile zip = new ZipFile(ZIP_FILE.toString())) { + ZipEntry ze = zip.getEntry(entry.name); + if (DEBUG) { + System.out.printf(" Entry:%s, found:%s%n", entry.name, ze != null); + } + assertNotNull(ze); + try (InputStream in = zip.getInputStream(ze)) { + byte[] bytes = in.readAllBytes(); + if (DEBUG) { + System.out.printf("name: %s, isDirectory: %s, payload= %s%n", + ze.getName(), ze.isDirectory(), new String(bytes)); + } + assertEquals(entry.bytes, bytes, + String.format("Expected payload: %s", new String(entry.bytes))); + } + } + } + + /** + * Validate that ZipFile::getEntry will return the correct entry, which + * is the second entry, when there are duplicate entries within the Zip file. + * + * @throws IOException If an error occurs + */ + @Test + public void DupFileEntryTest() throws IOException { + System.out.printf("%n%n**** DupFileEntryTest ***%n"); + try (ZipFile zip = + new ZipFile(DUPLICATE_FILE_ENTRY_FILE.toString())) { + ZipEntry ze = zip.getEntry(ENTRY_NAME); + if (DEBUG) { + System.out.printf(" Entry:%s, found:%s%n", ENTRY_NAME, ze != null); + } + assertNotNull(ze); + try (InputStream in = zip.getInputStream(ze)) { + byte[] bytes = in.readAllBytes(); + if (DEBUG) { + System.out.printf("name: %s, isDirectory: %s, payload= %s%n", + ze.getName(), ze.isDirectory(), new String(bytes)); + } + assertEquals(bytes, DUPLICATE_FILE_ENTRY.bytes, + String.format("Expected payload: %s", new String(DUPLICATE_FILE_ENTRY.bytes))); + } + } + } + + /** + * Verify that ZipInputStream can be used to read all Zip entries including + * a file and directory entry with the same name + * + * @throws IOException If an error occurs + */ + @Test + public void ZipInputStreamTest() throws IOException { + System.out.printf("%n%n**** ZipInputStreamTest ***%n"); + try (ZipInputStream zis = new ZipInputStream( + new FileInputStream(ZIP_FILE.toFile()))) { + ZipEntry zipEntry = zis.getNextEntry(); + assertNotNull(zipEntry); + while (zipEntry != null) { + Entry e; + if (zipEntry.getName().equals(FILE_ENTRY.name)) { + e = FILE_ENTRY; + } else if (zipEntry.getName().equals(DIR_ENTRY.name)) { + e = DIR_ENTRY; + } else { + throw new RuntimeException( + String.format("Invalid Zip entry: %s", zipEntry.getName())); + } + assertEquals(zipEntry.getMethod(), e.method); + assertEquals(zis.readAllBytes(), e.bytes, + String.format("Expected payload: %s", new String(e.bytes))); + zipEntry = zis.getNextEntry(); + } + } + } + + /** + * Verify that ZipFile::stream returns all Zip entries including + * a file and directory entry with the same name + * + * @throws IOException If an error occurs + */ + @Test + public void ZipFileStreamTest() throws IOException { + System.out.printf("%n%n**** ZipFileStreamTest ***%n"); + try (ZipFile zf = new ZipFile(ZIP_FILE.toFile())) { + List entries = zf.stream().collect(Collectors.toList()); + assertEquals(EXPECTED_ENTRIES.size(), entries.size()); + for (ZipEntry e : entries) { + assertTrue(EXPECTED_ENTRIES.contains(e.getName())); + } + } + } + + /** + * Verify that JarFile can be used to read all the entries including + * a file and directory entry with the same name + * + * @param entry The entry to validate + * @throws IOException If an error occurs + */ + @Test(dataProvider = "entries") + public static void JarFileInputStreamTest(Entry entry) throws IOException { + System.out.printf("%n%n**** JarFileInputStreamTest ***%n"); + try (JarFile jarFile = new JarFile(TEST_JAR.toFile())) { + JarEntry je = jarFile.getJarEntry(entry.name); + assertNotNull(je); + if (DEBUG) { + System.out.printf("Entry Name: %s, method: %s, Expected Method: %s%n", + entry.name, je.getMethod(), entry.method); + } + assertEquals(entry.method, je.getMethod()); + try (InputStream in = jarFile.getInputStream(je)) { + byte[] bytes = in.readAllBytes(); + if (DEBUG) { + System.out.printf("bytes= %s, expected=%s%n", + new String(bytes), new String(entry.bytes)); + } + assertEquals(bytes, entry.bytes, + String.format("Expected payload: %s", new String(entry.bytes))); + } + } + } + + /** + * Verify that JarInputStream can be used to read all entries including + * a file and directory entry with the same name + * + * @throws IOException If an error occurs + */ + @Test + public void JarInputStreamTest() throws IOException { + System.out.printf("%n%n**** JarInputStreamTest ***%n"); + try (JarInputStream jis = new JarInputStream( + new FileInputStream(TEST_JAR.toFile()))) { + JarEntry jarEntry = jis.getNextJarEntry(); + assertNotNull(jarEntry); + while (jarEntry != null) { + Entry e; + if (jarEntry.getName().equals(FILE_ENTRY.name)) { + e = FILE_ENTRY; + } else if (jarEntry.getName().equals(DIR_ENTRY.name)) { + e = DIR_ENTRY; + } else { + throw new RuntimeException( + String.format("Invalid Jar entry: %s", jarEntry.getName())); + } + assertEquals(jarEntry.getMethod(), e.method); + assertEquals(jis.readAllBytes(), e.bytes, + String.format("Expected payload: %s", new String(e.bytes))); + jarEntry = jis.getNextJarEntry(); + } + } + } + + /** + * Verify that JarURLConnection can be used to access all the entries including + * a file and directory entry with the same name within a jar file + * + * @param entry The entry to validate + * @throws IOException If an error occurs + */ + @Test(dataProvider = "entries") + public void JarURLConnectionTest(Entry entry) throws Exception { + System.out.printf("%n%n**** JarURLConnectionTest ***%n"); + URL url = new URL("jar:" + TEST_JAR.toUri().toURL() + "!/" + entry.name); + if (DEBUG) { + System.out.printf("URL=%s%n", url); + } + JarURLConnection con = (JarURLConnection) url.openConnection(); + con.connect(); + JarEntry je = con.getJarEntry(); + try (JarFile jarFile = con.getJarFile()) { + assertNotNull(je); + assertNotNull(jarFile); + assertNull(con.getAttributes()); + assertNull(con.getMainAttributes()); + assertNull(con.getManifest()); + assertEquals(je.getName(), entry.name); + assertEquals(con.getEntryName(), entry.name); + assertEquals(je.getMethod(), entry.method); + assertEquals(con.getJarFileURL(), TEST_JAR.toUri().toURL()); + if (DEBUG) { + System.out.printf(" getEntryName: %s, getJarFileURL:%s%n", + con.getEntryName(), con.getJarFileURL()); + System.out.printf(" Jar Entry= %s, size= %s%n", je.getName(), je.getSize()); + } + + try (InputStream is = jarFile.getInputStream(je)) { + byte[] bytes = is.readAllBytes(); + if (DEBUG) { + System.out.printf(" Bytes read:%s%n", new String(bytes)); + } + assertEquals(bytes, entry.bytes, + String.format("Expected payload: %s", new String(entry.bytes))); + } + } + } + + /** + * Verify that JarFile::stream returns all entries including + * a file and directory entry with the same name + * + * @throws IOException If an error occurs + */ + @Test + public void JarFileStreamTest() throws IOException { + System.out.printf("%n%n**** JarFileStreamTest ***%n"); + try (JarFile jf = new JarFile(TEST_JAR.toFile())) { + List entries = jf.stream().collect(Collectors.toList()); + assertEquals(EXPECTED_ENTRIES.size(), jf.size()); + for (JarEntry e : entries) { + assertTrue(EXPECTED_ENTRIES.contains(e.getName())); + } + } + } + + /** + * Method used to read the bytes from an InputStream. This method is + * here so that the test could be backported to JDK 8 if needed as + * InputStream::readAllBytes() does not exist + * + * @param is The InputStream to read from + * @return The byte array representing bytes read from the InputStream + * @throws IOException If an error occurs + */ + public static byte[] readAllBytes(InputStream is) throws IOException { + byte[] data = new byte[MAX_BUFFER_SIZE]; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int len; + while ((len = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, len); + } + buffer.flush(); + return buffer.toByteArray(); + } + + /** + * Method used to create a byte[] representing a Jar file with + * duplicate file entries. This uses ZipArchiveOutputStream as ZipOutputStream + * will fail with a "java.util.zip.ZipException: duplicate entry". + */ +// public static void createJarWithDuplicateFileEntries() throws IOException { +// Files.deleteIfExists(DUPFILE_ENTRY_FILE); +// try (ZipArchiveOutputStream zos = +// new ZipArchiveOutputStream(DUPFILE_ENTRY_FILE.toFile())) { +// zos.putArchiveEntry(new ZipArchiveEntry(FILE_ENTRY.name)); +// zos.write(FILE_ENTRY.bytes); +// zos.putArchiveEntry(new ZipArchiveEntry(FILE_ENTRY.name)); +// zos.write("Yet another File".getBytes(StandardCharsets.UTF_8)); +// zos.closeArchiveEntry(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// byte[] jarBytes = Files.readAllBytes(DUPFILE_ENTRY_FILE); +// String result = createByteArray(jarBytes, "DUPLICATE_ENTRY_JAR_BYTES"); +// System.out.println(result); +// } + + /** + * Utility method which takes a byte array and converts to byte array + * declaration. For example: + *
+     *     {@code
+     *        var fooJar = Files.readAllBytes(Path.of("foo.jar"));
+     *        var result = createByteArray(fooJar, "FOOBYTES");
+     *      }
+     * 
+ * + * @param bytes A byte array used to create a byte array declaration + * @param name Name to be used in the byte array declaration + * @return The formatted byte array declaration + */ + public static String createByteArray(byte[] bytes, String name) { + StringBuilder sb = new StringBuilder(bytes.length * 5); + Formatter fmt = new Formatter(sb); + fmt.format(" public static byte[] %s = {", name); + final int linelen = 8; + for (int i = 0; i < bytes.length; i++) { + if (i % linelen == 0) { + fmt.format("%n "); + } + fmt.format(" (byte) 0x%x,", bytes[i] & 0xff); + } + fmt.format("%n };%n"); + return sb.toString(); + } + + /** + * Represents an entry in a Zip file. An entry encapsulates a name, a + * compression method, and its contents/data. + */ + public static class Entry { + public final String name; + public final int method; + public final byte[] bytes; + + public Entry(String name, int method, String contents) { + this.name = name; + this.method = method; + this.bytes = contents.getBytes(StandardCharsets.UTF_8); + } + + public static Entry of(String name, int method, String contents) { + return new Entry(name, method, contents); + } + } +} From 5807e640bc9122dcd2e36b141e01613a17fffbbf Mon Sep 17 00:00:00 2001 From: Aleksei Voitylov Date: Tue, 9 Jan 2024 15:48:24 +0100 Subject: [PATCH 6/8] 8316976: Improve signature handling Reviewed-by: mbalao Backport-of: ed1269b7410759e8fa0d97d85328f20d11ae8d9a --- .../org/jcp/xml/dsig/internal/dom/DOMRSAPSSSignatureMethod.java | 1 - .../org/jcp/xml/dsig/internal/dom/DOMSignatureMethod.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/java.xml.crypto/share/classes/org/jcp/xml/dsig/internal/dom/DOMRSAPSSSignatureMethod.java b/src/java.xml.crypto/share/classes/org/jcp/xml/dsig/internal/dom/DOMRSAPSSSignatureMethod.java index 8e4c2424eb7..88bf38a0606 100644 --- a/src/java.xml.crypto/share/classes/org/jcp/xml/dsig/internal/dom/DOMRSAPSSSignatureMethod.java +++ b/src/java.xml.crypto/share/classes/org/jcp/xml/dsig/internal/dom/DOMRSAPSSSignatureMethod.java @@ -318,7 +318,6 @@ byte[] sign(Key key, SignedInfo si, XMLSignContext context) throw new XMLSignatureException(e); } LOG.debug("Signature provider: {}", signature.getProvider()); - LOG.debug("Signing with key: {}", key); LOG.debug("JCA Algorithm: {}", getJCAAlgorithm()); try (SignerOutputStream outputStream = new SignerOutputStream(signature)) { diff --git a/src/java.xml.crypto/share/classes/org/jcp/xml/dsig/internal/dom/DOMSignatureMethod.java b/src/java.xml.crypto/share/classes/org/jcp/xml/dsig/internal/dom/DOMSignatureMethod.java index 98cf1772fe8..12f923381a7 100644 --- a/src/java.xml.crypto/share/classes/org/jcp/xml/dsig/internal/dom/DOMSignatureMethod.java +++ b/src/java.xml.crypto/share/classes/org/jcp/xml/dsig/internal/dom/DOMSignatureMethod.java @@ -334,7 +334,6 @@ byte[] sign(Key key, SignedInfo si, XMLSignContext context) } signature.initSign((PrivateKey)key); LOG.debug("Signature provider: {}", signature.getProvider()); - LOG.debug("Signing with key: {}", key); LOG.debug("JCA Algorithm: {}", getJCAAlgorithm()); try (SignerOutputStream outputStream = new SignerOutputStream(signature)) { From 221f6f8c6d181ef3af32a860c5d7eef2f6dcff28 Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Tue, 9 Jan 2024 15:48:45 +0100 Subject: [PATCH 7/8] 8317547: Enhance TLS connection support Reviewed-by: mbalao Backport-of: 066482f9686ca81068f9386322afda8e73323f5e --- .../com/sun/crypto/provider/RSACipher.java | 22 +++-- .../classes/sun/security/util/KeyUtil.java | 55 +++++++------ .../sun/security/mscapi/CRSACipher.java | 82 ++++++++++++------- .../windows/native/libsunmscapi/security.cpp | 36 +++++--- 4 files changed, 119 insertions(+), 76 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java b/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java index ac512e573a5..b9438dfb309 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/RSACipher.java @@ -98,6 +98,7 @@ public final class RSACipher extends CipherSpi { // cipher parameter for OAEP padding and TLS RSA premaster secret private AlgorithmParameterSpec spec = null; + private boolean forTlsPremasterSecret = false; // buffer for the data private byte[] buffer; @@ -292,6 +293,7 @@ private void init(int opmode, Key key, SecureRandom random, } spec = params; + forTlsPremasterSecret = true; this.random = random; // for TLS RSA premaster secret } int blockType = (mode <= MODE_DECRYPT) ? RSAPadding.PAD_BLOCKTYPE_2 @@ -383,7 +385,7 @@ private byte[] doFinal() throws BadPaddingException, byte[] decryptBuffer = RSACore.convert(buffer, 0, bufOfs); paddingCopy = RSACore.rsa(decryptBuffer, privateKey, false); result = padding.unpad(paddingCopy); - if (result == null) { + if (result == null && !forTlsPremasterSecret) { throw new BadPaddingException ("Padding error in decryption"); } @@ -472,26 +474,22 @@ protected Key engineUnwrap(byte[] wrappedKey, String algorithm, boolean isTlsRsaPremasterSecret = algorithm.equals("TlsRsaPremasterSecret"); - Exception failover = null; byte[] encoded = null; update(wrappedKey, 0, wrappedKey.length); try { encoded = doFinal(); - } catch (BadPaddingException e) { - if (isTlsRsaPremasterSecret) { - failover = e; - } else { - throw new InvalidKeyException("Unwrapping failed", e); - } - } catch (IllegalBlockSizeException e) { - // should not occur, handled with length check above + } catch (BadPaddingException | IllegalBlockSizeException e) { + // BadPaddingException cannot happen for TLS RSA unwrap. + // In that case, padding error is indicated by returning null. + // IllegalBlockSizeException cannot happen in any case, + // because of the length check above. throw new InvalidKeyException("Unwrapping failed", e); } try { if (isTlsRsaPremasterSecret) { - if (!(spec instanceof TlsRsaPremasterSecretParameterSpec)) { + if (!forTlsPremasterSecret) { throw new IllegalStateException( "No TlsRsaPremasterSecretParameterSpec specified"); } @@ -500,7 +498,7 @@ protected Key engineUnwrap(byte[] wrappedKey, String algorithm, encoded = KeyUtil.checkTlsPreMasterSecretKey( ((TlsRsaPremasterSecretParameterSpec) spec).getClientVersion(), ((TlsRsaPremasterSecretParameterSpec) spec).getServerVersion(), - random, encoded, (failover != null)); + random, encoded, encoded == null); } return ConstructKeys.constructKey(encoded, algorithm, type); diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java index a62f92b7efc..d5ab03e62f4 100644 --- a/src/java.base/share/classes/sun/security/util/KeyUtil.java +++ b/src/java.base/share/classes/sun/security/util/KeyUtil.java @@ -288,13 +288,14 @@ public static final boolean isOracleJCEProvider(String providerName) { * contains the lower of that suggested by the client in the client * hello and the highest supported by the server. * @param encoded the encoded key in its "RAW" encoding format - * @param isFailOver whether or not the previous decryption of the - * encrypted PreMasterSecret message run into problem + * @param failure true if encoded is incorrect according to previous checks * @return the polished PreMasterSecret key in its "RAW" encoding format */ public static byte[] checkTlsPreMasterSecretKey( int clientVersion, int serverVersion, SecureRandom random, - byte[] encoded, boolean isFailOver) { + byte[] encoded, boolean failure) { + + byte[] tmp; if (random == null) { random = JCAUtil.getSecureRandom(); @@ -302,30 +303,38 @@ public static byte[] checkTlsPreMasterSecretKey( byte[] replacer = new byte[48]; random.nextBytes(replacer); - if (!isFailOver && (encoded != null)) { - // check the length - if (encoded.length != 48) { - // private, don't need to clone the byte array. - return replacer; - } - - int encodedVersion = - ((encoded[0] & 0xFF) << 8) | (encoded[1] & 0xFF); - if (clientVersion != encodedVersion) { - if (clientVersion > 0x0301 || // 0x0301: TLSv1 - serverVersion != encodedVersion) { - encoded = replacer; - } // Otherwise, For compatibility, we maintain the behavior - // that the version in pre_master_secret can be the - // negotiated version for TLS v1.0 and SSL v3.0. - } + if (failure) { + tmp = replacer; + } else { + tmp = encoded; + } + if (tmp == null) { + encoded = replacer; + } else { + encoded = tmp; + } + // check the length + if (encoded.length != 48) { // private, don't need to clone the byte array. - return encoded; + tmp = replacer; + } else { + tmp = encoded; } - // private, don't need to clone the byte array. - return replacer; + int encodedVersion = + ((tmp[0] & 0xFF) << 8) | (tmp[1] & 0xFF); + int check1 = 0; + int check2 = 0; + int check3 = 0; + if (clientVersion != encodedVersion) check1 = 1; + if (clientVersion > 0x0301) check2 = 1; + if (serverVersion != encodedVersion) check3 = 1; + if ((check1 & (check2 | check3)) == 1) { + return replacer; + } else { + return tmp; + } } /** diff --git a/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CRSACipher.java b/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CRSACipher.java index 133ceff4f32..e89743b8707 100644 --- a/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CRSACipher.java +++ b/src/jdk.crypto.mscapi/windows/classes/sun/security/mscapi/CRSACipher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2023, 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 @@ -30,6 +30,7 @@ import java.security.Key; import java.security.interfaces.*; import java.security.spec.*; +import java.util.Arrays; import javax.crypto.*; import javax.crypto.spec.*; @@ -61,6 +62,9 @@ */ public final class CRSACipher extends CipherSpi { + private static final int ERROR_INVALID_PARAMETER = 0x57; + private static final int NTE_INVALID_PARAMETER = 0x80090027; + // constant for an empty byte array private static final byte[] B0 = new byte[0]; @@ -101,6 +105,8 @@ public final class CRSACipher extends CipherSpi { // cipher parameter for TLS RSA premaster secret private AlgorithmParameterSpec spec = null; + private boolean forTlsPremasterSecret = false; + // the source of randomness private SecureRandom random; @@ -171,6 +177,9 @@ protected void engineInit(int opmode, Key key, } spec = params; this.random = random; // for TLS RSA premaster secret + this.forTlsPremasterSecret = true; + } else { + this.forTlsPremasterSecret = false; } init(opmode, key); } @@ -278,8 +287,7 @@ private void update(byte[] in, int inOfs, int inLen) { } // internal doFinal() method. Here we perform the actual RSA operation - private byte[] doFinal() throws BadPaddingException, - IllegalBlockSizeException { + private byte[] doFinal() throws IllegalBlockSizeException { if (bufOfs > buffer.length) { throw new IllegalBlockSizeException("Data must not be longer " + "than " + (buffer.length - paddingLength) + " bytes"); @@ -308,7 +316,7 @@ private byte[] doFinal() throws BadPaddingException, throw new AssertionError("Internal error"); } - } catch (KeyException e) { + } catch (KeyException | BadPaddingException e) { throw new ProviderException(e); } finally { @@ -331,14 +339,14 @@ protected int engineUpdate(byte[] in, int inOfs, int inLen, byte[] out, // see JCE spec protected byte[] engineDoFinal(byte[] in, int inOfs, int inLen) - throws BadPaddingException, IllegalBlockSizeException { + throws IllegalBlockSizeException { update(in, inOfs, inLen); return doFinal(); } // see JCE spec protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out, - int outOfs) throws ShortBufferException, BadPaddingException, + int outOfs) throws ShortBufferException, IllegalBlockSizeException { if (outputSize > out.length - outOfs) { throw new ShortBufferException @@ -354,6 +362,7 @@ protected int engineDoFinal(byte[] in, int inOfs, int inLen, byte[] out, // see JCE spec protected byte[] engineWrap(Key key) throws InvalidKeyException, IllegalBlockSizeException { + byte[] encoded = key.getEncoded(); // TODO - unextractable key if ((encoded == null) || (encoded.length == 0)) { throw new InvalidKeyException("Could not obtain encoded key"); @@ -362,12 +371,7 @@ protected byte[] engineWrap(Key key) throws InvalidKeyException, throw new InvalidKeyException("Key is too long for wrapping"); } update(encoded, 0, encoded.length); - try { - return doFinal(); - } catch (BadPaddingException e) { - // should not occur - throw new InvalidKeyException("Wrapping failed", e); - } + return doFinal(); } // see JCE spec @@ -388,31 +392,31 @@ protected java.security.Key engineUnwrap(byte[] wrappedKey, update(wrappedKey, 0, wrappedKey.length); try { encoded = doFinal(); - } catch (BadPaddingException e) { - if (isTlsRsaPremasterSecret) { - failover = e; - } else { - throw new InvalidKeyException("Unwrapping failed", e); - } } catch (IllegalBlockSizeException e) { // should not occur, handled with length check above throw new InvalidKeyException("Unwrapping failed", e); } - if (isTlsRsaPremasterSecret) { - if (!(spec instanceof TlsRsaPremasterSecretParameterSpec)) { - throw new IllegalStateException( - "No TlsRsaPremasterSecretParameterSpec specified"); + try { + if (isTlsRsaPremasterSecret) { + if (!forTlsPremasterSecret) { + throw new IllegalStateException( + "No TlsRsaPremasterSecretParameterSpec specified"); + } + + // polish the TLS premaster secret + encoded = KeyUtil.checkTlsPreMasterSecretKey( + ((TlsRsaPremasterSecretParameterSpec) spec).getClientVersion(), + ((TlsRsaPremasterSecretParameterSpec) spec).getServerVersion(), + random, encoded, encoded == null); } - // polish the TLS premaster secret - encoded = KeyUtil.checkTlsPreMasterSecretKey( - ((TlsRsaPremasterSecretParameterSpec)spec).getClientVersion(), - ((TlsRsaPremasterSecretParameterSpec)spec).getServerVersion(), - random, encoded, (failover != null)); + return constructKey(encoded, algorithm, type); + } finally { + if (encoded != null) { + Arrays.fill(encoded, (byte) 0); + } } - - return constructKey(encoded, algorithm, type); } // see JCE spec @@ -496,7 +500,23 @@ private static Key constructKey(byte[] encodedKey, * Encrypt/decrypt a data buffer using Microsoft Crypto API with HCRYPTKEY. * It expects and returns ciphertext data in big-endian form. */ - private native static byte[] encryptDecrypt(byte[] data, int dataSize, - long hCryptKey, boolean doEncrypt) throws KeyException; + private byte[] encryptDecrypt(byte[] data, int dataSize, + long hCryptKey, boolean doEncrypt) throws KeyException, BadPaddingException { + int[] returnStatus = new int[1]; + byte[] result= encryptDecrypt(returnStatus, data, dataSize, hCryptKey, doEncrypt); + if ((returnStatus[0] == ERROR_INVALID_PARAMETER) || (returnStatus[0] == NTE_INVALID_PARAMETER)) { + if (forTlsPremasterSecret) { + result = null; + } else { + throw new BadPaddingException("Error " + returnStatus[0] + " returned by MSCAPI"); + } + } else if (returnStatus[0] != 0) { + throw new KeyException("Error " + returnStatus[0] + " returned by MSCAPI"); + } + + return result; + } + private static native byte[] encryptDecrypt(int[] returnStatus, byte[] data, int dataSize, + long key, boolean doEncrypt) throws KeyException; } diff --git a/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp b/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp index 7d8b13470fe..da1e3222632 100644 --- a/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp +++ b/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp @@ -1889,18 +1889,25 @@ JNIEXPORT void JNICALL Java_sun_security_mscapi_CKeyStore_destroyKeyContainer /* * Class: sun_security_mscapi_CRSACipher * Method: encryptDecrypt - * Signature: ([BIJZ)[B + * Signature: ([I[BIJZ)[B */ JNIEXPORT jbyteArray JNICALL Java_sun_security_mscapi_CRSACipher_encryptDecrypt - (JNIEnv *env, jclass clazz, jbyteArray jData, jint jDataSize, jlong hKey, + (JNIEnv *env, jclass clazz, jintArray jResultStatus, jbyteArray jData, jint jDataSize, jlong hKey, jboolean doEncrypt) { jbyteArray result = NULL; jbyte* pData = NULL; + jbyte* resultData = NULL; DWORD dwDataLen = jDataSize; DWORD dwBufLen = env->GetArrayLength(jData); DWORD i; BYTE tmp; + BOOL success; + DWORD ss = ERROR_SUCCESS; + DWORD lastError = ERROR_SUCCESS; + DWORD resultLen = 0; + DWORD pmsLen = 48; + jbyte pmsArr[48] = {0}; __try { @@ -1927,6 +1934,8 @@ JNIEXPORT jbyteArray JNICALL Java_sun_security_mscapi_CRSACipher_encryptDecrypt pData[i] = pData[dwBufLen - i -1]; pData[dwBufLen - i - 1] = tmp; } + resultData = pData; + resultLen = dwBufLen; } else { // convert to little-endian for (i = 0; i < dwBufLen / 2; i++) { @@ -1936,21 +1945,28 @@ JNIEXPORT jbyteArray JNICALL Java_sun_security_mscapi_CRSACipher_encryptDecrypt } // decrypt - if (! ::CryptDecrypt((HCRYPTKEY) hKey, 0, TRUE, 0, (BYTE *)pData, //deprecated - &dwBufLen)) { - - ThrowException(env, KEY_EXCEPTION, GetLastError()); - __leave; + success = ::CryptDecrypt((HCRYPTKEY) hKey, 0, TRUE, 0, (BYTE *)pData, //deprecated + &dwBufLen); + lastError = GetLastError(); + if (success) { + ss = ERROR_SUCCESS; + resultData = pData; + resultLen = dwBufLen; + } else { + ss = lastError; + resultData = pmsArr; + resultLen = pmsLen; } + env->SetIntArrayRegion(jResultStatus, 0, 1, (jint*) &ss); } - // Create new byte array - if ((result = env->NewByteArray(dwBufLen)) == NULL) { + // Create new byte array + if ((result = env->NewByteArray(resultLen)) == NULL) { __leave; } // Copy data from native buffer to Java buffer - env->SetByteArrayRegion(result, 0, dwBufLen, (jbyte*) pData); + env->SetByteArrayRegion(result, 0, resultLen, (jbyte*) resultData); } __finally { From 10d2feeaea612e474d0385d6bed43f374485c1d5 Mon Sep 17 00:00:00 2001 From: Christoph Langer Date: Tue, 9 Jan 2024 22:18:15 +0100 Subject: [PATCH 8/8] 8323422: [17u] Remove designator DEFAULT_PROMOTED_VERSION_PRE=ea for release 17.0.10 Reviewed-by: goetz --- make/conf/version-numbers.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index 6c35e79d1be..9433c11c1aa 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -39,4 +39,4 @@ DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 DEFAULT_ACCEPTABLE_BOOT_VERSIONS="16 17" DEFAULT_JDK_SOURCE_TARGET_VERSION=17 -DEFAULT_PROMOTED_VERSION_PRE=ea +DEFAULT_PROMOTED_VERSION_PRE=