| /* | |
| * Copyright (c) 2019, 2022, 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 8231950 8257497 | |
| * @summary keytool -ext camel-case shorthand not working | |
| * @modules java.base/sun.security.tools.keytool | |
| * java.base/sun.security.tools.keytool:open | |
| * java.base/sun.security.util | |
| * java.base/sun.security.x509 | |
| * @compile -XDignore.symbol.file ExtOptionCamelCase.java | |
| * @run main ExtOptionCamelCase | |
| */ | |
| import sun.security.tools.keytool.Main; | |
| import sun.security.util.DerOutputStream; | |
| import sun.security.util.DerValue; | |
| import sun.security.x509.BasicConstraintsExtension; | |
| import sun.security.x509.CertificateExtensions; | |
| import sun.security.x509.Extension; | |
| import sun.security.x509.KeyIdentifier; | |
| import sun.security.x509.KeyUsageExtension; | |
| import java.lang.reflect.Constructor; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Method; | |
| import java.security.KeyPairGenerator; | |
| import java.security.PublicKey; | |
| import java.util.List; | |
| public class ExtOptionCamelCase { | |
| static Method createV3Extensions; | |
| static Constructor<Main> ctor; | |
| static PublicKey pk; | |
| static Method oneOf; | |
| public static void main(String[] args) throws Exception { | |
| prepare(); | |
| // Unseen ext name | |
| testCreateFail("abc"); | |
| // camelCase match, both cases work | |
| testCreate("bc", BasicConstraintsExtension.class); | |
| testCreate("BC", BasicConstraintsExtension.class); | |
| // Prefix match | |
| testCreate("BasicC", BasicConstraintsExtension.class); | |
| // Ambiguous, digitalSignature or dataEncipherment? | |
| testCreateFail("ku=d"); | |
| // prefix match | |
| testCreate("ku:c=dig", KeyUsageExtension.class, | |
| x -> x.get(KeyUsageExtension.DIGITAL_SIGNATURE)); | |
| // camelCase match | |
| testCreate("ku=kE", KeyUsageExtension.class, | |
| x -> x.get(KeyUsageExtension.KEY_ENCIPHERMENT)); | |
| // camelCase match must be only 1st+CAPITALs | |
| testCreateFail("ku=KeUs"); | |
| // camelCase match, must be only 1st + all CAPITALs | |
| testCreate("ku=kCS", KeyUsageExtension.class, | |
| x -> x.get(KeyUsageExtension.KEY_CERTSIGN)); | |
| // ... not all CAPITALs | |
| testCreateFail("ku=kC"); | |
| // ... has lowercase letters | |
| testCreateFail("ku=keCeSi"); | |
| // Ambiguous, keyAgreement or keyCertSign | |
| testCreateFail("ku:c=ke"); | |
| // camelCase natch | |
| testCreate("ku:c=dE", KeyUsageExtension.class, | |
| x -> x.get(KeyUsageExtension.DATA_ENCIPHERMENT)); | |
| // prefix match | |
| testCreate("ku:c=de", KeyUsageExtension.class, | |
| x -> x.get(KeyUsageExtension.DECIPHER_ONLY)); | |
| // camelCase match | |
| testCreate("ku:c=kA", KeyUsageExtension.class, | |
| x -> x.get(KeyUsageExtension.KEY_AGREEMENT)); | |
| // camelCase match, fallback | |
| testCreate("ku:c=ka", KeyUsageExtension.class, | |
| x -> x.get(KeyUsageExtension.KEY_AGREEMENT)); | |
| // Testing oneOf() directly | |
| testOneOf("a", -1, "b", "c"); // -1 means not found | |
| testOneOf("a", -2, "ab", "ac"); // -2 means ambiguous | |
| testOneOf("a", 0, "a", "ac"); //exact match | |
| testOneOf("a", 0, "a", "b"); | |
| testOneOf("ac", 1, "a", "ac"); | |
| testOneOf("a", 0, "abc", "bcd"); | |
| testOneOf("ab", 0, "abc", "ABC"); | |
| testOneOf("ab", 0, "abc", "aBC"); | |
| testOneOf("ab", 0, "abc", "Abc"); | |
| testOneOf("AB", 1, "abc", "ABC"); | |
| testOneOf("aB", 0, "abcBcd", "abcDef"); | |
| testOneOf("ab", -2, "abcBcd", "abcDef"); | |
| testOneOf("aB", -2, "abcBcdEfg", "abcDef"); | |
| testOneOf("ab", 0, "abcDef", "axyBuv"); | |
| testOneOf("aB", 1, "abcDef", "axyBuv"); | |
| testOneOf("a", -2, "abcDef", "axyBuv"); | |
| testOneOf("aBC", -1, "a12BxyCuvDmn"); // 12 is not removed | |
| testOneOf("a12BCD", 0, "a12BxyCuvDmn"); | |
| testOneOf("a12BC", -1, "a12BxyCuvDmn"); // must be full | |
| // Fallback | |
| testOneOf("bc", 0, "BasicConstraints"); | |
| testOneOf("BC", 0, "BasicConstraints"); | |
| testOneOf("BasicConstraints", 0, "BasicConstraints"); | |
| testOneOf("basicconstraints", 0, "BasicConstraints"); | |
| testOneOf("Basic", 0, "BasicConstraints"); | |
| testOneOf("basic", 0, "BasicConstraints"); | |
| testOneOf("BaCo", -1, "BasicConstraints"); | |
| } | |
| // Expose some private methods | |
| static void prepare() throws Exception { | |
| createV3Extensions = Main.class.getDeclaredMethod( | |
| "createV3Extensions", | |
| CertificateExtensions.class, | |
| CertificateExtensions.class, | |
| List.class, | |
| PublicKey.class, | |
| KeyIdentifier.class); | |
| createV3Extensions.setAccessible(true); | |
| ctor = Main.class.getDeclaredConstructor(); | |
| ctor.setAccessible(true); | |
| KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); | |
| pk = kpg.generateKeyPair().getPublic(); | |
| oneOf = Main.class.getDeclaredMethod( | |
| "oneOf", String.class, String[].class); | |
| oneOf.setAccessible(true); | |
| } | |
| /** | |
| * Ensures the given type of extension is created with the option | |
| */ | |
| static <T extends Extension> void testCreate(String option, Class<T> clazz) | |
| throws Exception { | |
| testCreate(option, clazz, null); | |
| } | |
| /** | |
| * Ensures an option is invalid and will be rejected | |
| */ | |
| static <T extends Extension> void testCreateFail(String option) | |
| throws Exception { | |
| testCreate(option, null, null); | |
| } | |
| /** | |
| * Ensures the given type of extension is created and match the rule | |
| * with the option. | |
| * | |
| * @param option the -ext option provided to keytool | |
| * @param clazz the expected extension to create, null means none | |
| * @param rule a predicate to check if the extension created is acceptable | |
| * @param <T> the extected extension type | |
| * @throws Exception if test result is unexpected | |
| */ | |
| static <T extends Extension> void testCreate(String option, Class<T> clazz, | |
| PredicateWithException<T> rule) throws Exception { | |
| try { | |
| CertificateExtensions exts = (CertificateExtensions) | |
| createV3Extensions.invoke(ctor.newInstance(), | |
| null, null, List.of(option), pk, null); | |
| // ATTENTION: the extensions created above might contain raw | |
| // extensions (not of a subtype) and we need to store and reload | |
| // it to resolve them to subtypes. | |
| DerOutputStream dout = new DerOutputStream(); | |
| exts.encode(dout); | |
| exts = new CertificateExtensions(new DerValue(dout.toByteArray()).data); | |
| if (clazz == null) { | |
| throw new Exception("Should fail"); | |
| } else { | |
| for (Extension e : exts.getAllExtensions()) { | |
| if (e.getClass() == clazz) { | |
| if (rule == null || rule.test((T) e)) { | |
| return; | |
| } | |
| } | |
| } | |
| throw new Exception("Unexpected result: " + exts); | |
| } | |
| } catch (InvocationTargetException e) { | |
| if (clazz == null) { | |
| return; | |
| } else { | |
| throw e; | |
| } | |
| } | |
| } | |
| @FunctionalInterface | |
| interface PredicateWithException<T> { | |
| boolean test(T t) throws Exception; | |
| } | |
| /** | |
| * Ensures oneOf returns the expected result. | |
| * | |
| * @param s input | |
| * @param expected expected value, -2 if ambiguous, -1 if no match | |
| * @param items existing strings to match | |
| * @throws Exception if test result is unexpected | |
| */ | |
| static void testOneOf(String s, int expected, String... items) | |
| throws Exception { | |
| try { | |
| int res = (int)oneOf.invoke(null, s, items); | |
| if (expected == -2) { | |
| throw new Exception("Should fail"); | |
| } else { | |
| if (expected != res) { | |
| throw new Exception( | |
| "Expected " + expected + ", actually " + res); | |
| } | |
| } | |
| } catch (InvocationTargetException e) { | |
| if (expected == -2) { | |
| return; | |
| } else { | |
| throw e; | |
| } | |
| } | |
| } | |
| } |