diff --git a/jdk/src/share/classes/sun/security/tools/keytool/Main.java b/jdk/src/share/classes/sun/security/tools/keytool/Main.java index 00287e7b4f3..9c04e605890 100644 --- a/jdk/src/share/classes/sun/security/tools/keytool/Main.java +++ b/jdk/src/share/classes/sun/security/tools/keytool/Main.java @@ -3982,9 +3982,10 @@ private static int oneOf(String s, String... list) throws Exception { * Create a GeneralName object from known types * @param t one of 5 known types * @param v value + * @param exttype X.509 extension type * @return which one */ - private GeneralName createGeneralName(String t, String v) + private GeneralName createGeneralName(String t, String v, int exttype) throws Exception { GeneralNameInterface gn; int p = oneOf(t, "EMAIL", "URI", "DNS", "IP", "OID"); @@ -3995,7 +3996,14 @@ private GeneralName createGeneralName(String t, String v) switch (p) { case 0: gn = new RFC822Name(v); break; case 1: gn = new URIName(v); break; - case 2: gn = new DNSName(v); break; + case 2: + if (exttype == 3) { + // Allow wildcard only for SAN extension + gn = new DNSName(v, true); + } else { + gn = new DNSName(v); + } + break; case 3: gn = new IPAddressName(v); break; default: gn = new OIDName(v); break; //4 } @@ -4249,7 +4257,7 @@ private CertificateExtensions createV3Extensions( } String t = item.substring(0, colonpos); String v = item.substring(colonpos+1); - gnames.add(createGeneralName(t, v)); + gnames.add(createGeneralName(t, v, exttype)); } if (exttype == 3) { ext.set(SubjectAlternativeNameExtension.NAME, @@ -4305,7 +4313,7 @@ private CertificateExtensions createV3Extensions( oid = new ObjectIdentifier("1.3.6.1.5.5.7.48." + p); } accessDescriptions.add(new AccessDescription( - oid, createGeneralName(t, v))); + oid, createGeneralName(t, v, exttype))); } if (exttype == 5) { ext.set(SubjectInfoAccessExtension.NAME, @@ -4330,7 +4338,7 @@ private CertificateExtensions createV3Extensions( } String t = item.substring(0, colonpos); String v = item.substring(colonpos+1); - gnames.add(createGeneralName(t, v)); + gnames.add(createGeneralName(t, v, exttype)); } ext.set(CRLDistributionPointsExtension.NAME, new CRLDistributionPointsExtension( diff --git a/jdk/src/share/classes/sun/security/x509/DNSName.java b/jdk/src/share/classes/sun/security/x509/DNSName.java index edc46771706..0be1f224039 100644 --- a/jdk/src/share/classes/sun/security/x509/DNSName.java +++ b/jdk/src/share/classes/sun/security/x509/DNSName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2020, 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 @@ -69,9 +69,10 @@ public DNSName(DerValue derValue) throws IOException { * Create the DNSName object with the specified name. * * @param name the DNSName. - * @throws IOException if the name is not a valid DNSName subjectAltName + * @param allowWildcard the flag for wildcard checking. + * @throws IOException if the name is not a valid DNSName */ - public DNSName(String name) throws IOException { + public DNSName(String name, boolean allowWildcard) throws IOException { if (name == null || name.length() == 0) throw new IOException("DNSName must not be null or empty"); if (name.contains(" ")) @@ -91,9 +92,26 @@ public DNSName(String name) throws IOException { if (endIndex - startIndex < 1) throw new IOException("DNSName with empty components are not permitted"); - // RFC 1123: DNSName components must begin with a letter or digit - if (alphaDigits.indexOf(name.charAt(startIndex)) < 0) - throw new IOException("DNSName components must begin with a letter or digit"); + if (allowWildcard) { + // RFC 1123: DNSName components must begin with a letter or digit + // or RFC 4592: the first component of a DNSName can have only a wildcard + // character * (asterisk), i.e. *.example.com. Asterisks at other components + // will not be allowed as a wildcard. + if (alphaDigits.indexOf(name.charAt(startIndex)) < 0) { + // Checking to make sure the wildcard only appears in the first component, + // and it has to be at least 3-char long with the form of *.[alphaDigit] + if ((name.length() < 3) || (name.indexOf('*', 0) != 0) || + (name.charAt(startIndex+1) != '.') || + (alphaDigits.indexOf(name.charAt(startIndex+2)) < 0)) + throw new IOException("DNSName components must begin with a letter, digit, " + + "or the first component can have only a wildcard character *"); + } + } else { + // RFC 1123: DNSName components must begin with a letter or digit + if (alphaDigits.indexOf(name.charAt(startIndex)) < 0) + throw new IOException("DNSName components must begin with a letter or digit"); + } + //nonStartIndex: index for characters in the component beyond the first one for (int nonStartIndex=startIndex+1; nonStartIndex < endIndex; nonStartIndex++) { char x = name.charAt(nonStartIndex); @@ -104,6 +122,15 @@ public DNSName(String name) throws IOException { this.name = name; } + /** + * Create the DNSName object with the specified name. + * + * @param name the DNSName. + * @throws IOException if the name is not a valid DNSName + */ + public DNSName(String name) throws IOException { + this(name, false); + } /** * Return the type of the GeneralName. diff --git a/jdk/test/sun/security/x509/GeneralName/DNSNameTest.java b/jdk/test/sun/security/x509/GeneralName/DNSNameTest.java index 490c8f534dc..be7f082e70b 100644 --- a/jdk/test/sun/security/x509/GeneralName/DNSNameTest.java +++ b/jdk/test/sun/security/x509/GeneralName/DNSNameTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -24,7 +24,7 @@ /** * @test * @summary DNSName parsing tests - * @bug 8213952 + * @bug 8213952 8186143 * @modules java.base/sun.security.x509 * @run testng DNSNameTest */ @@ -53,6 +53,23 @@ public Object[][] goodNames() { return data; } + @DataProvider(name = "goodSanNames") + public Object[][] goodSanNames() { + Object[][] data = { + {"abc.com"}, + {"ABC.COM"}, + {"a12.com"}, + {"a1b2c3.com"}, + {"1abc.com"}, + {"123.com"}, + {"abc.com-"}, // end with hyphen + {"a-b-c.com"}, // hyphens + {"*.domain.com"}, // wildcard in 1st level subdomain + {"*.com"}, + }; + return data; + } + @DataProvider(name = "badNames") public Object[][] badNames() { Object[][] data = { @@ -65,10 +82,34 @@ public Object[][] badNames() { {"a."}, // end with . {""}, // empty {" "}, // space only + {"*.domain.com"}, // wildcard not allowed + {"a*.com"}, // only allow letter, digit, or hyphen + }; + return data; + } + + @DataProvider(name = "badSanNames") + public Object[][] badSanNames() { + Object[][] data = { + {" 1abc.com"}, // begin with space + {"1abc.com "}, // end with space + {"1a bc.com "}, // no space allowed + {"-abc.com"}, // begin with hyphen + {"a..b"}, // .. + {".a"}, // begin with . + {"a."}, // end with . + {""}, // empty + {" "}, // space only + {"*"}, // wildcard only + {"*a.com"}, // partial wildcard disallowed + {"abc.*.com"}, // wildcard not allowed in 2nd level + {"*.*.domain.com"}, // double wildcard not allowed + {"a*.com"}, // only allow letter, digit, or hyphen }; return data; } + @Test(dataProvider = "goodNames") public void testGoodDNSName(String dnsNameString) { try { @@ -78,6 +119,15 @@ public void testGoodDNSName(String dnsNameString) { } } + @Test(dataProvider = "goodSanNames") + public void testGoodSanDNSName(String dnsNameString) { + try { + DNSName dn = new DNSName(dnsNameString, true); + } catch (IOException e) { + fail("Unexpected IOException"); + } + } + @Test(dataProvider = "badNames") public void testBadDNSName(String dnsNameString) { try { @@ -88,4 +138,15 @@ public void testBadDNSName(String dnsNameString) { fail("Unexpeceted message: " + e); } } + + @Test(dataProvider = "badSanNames") + public void testBadSanDNSName(String dnsNameString) { + try { + DNSName dn = new DNSName(dnsNameString, true); + fail("IOException expected"); + } catch (IOException e) { + if (!e.getMessage().contains("DNSName")) + fail("Unexpeceted message: " + e); + } + } }