/* * 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
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 void testCreate(String option, Class clazz) throws Exception { testCreate(option, clazz, null); } /** * Ensures an option is invalid and will be rejected */ static 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 the extected extension type * @throws Exception if test result is unexpected */ static void testCreate(String option, Class clazz, PredicateWithException 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 { 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; } } } }