Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 54 additions & 42 deletions src/java.base/share/classes/sun/security/x509/DNSName.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2026, 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
Expand Down Expand Up @@ -52,8 +52,8 @@
public class DNSName implements GeneralNameInterface {
private final String name;

private static final String alphaDigits =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static final String DNS_ALLOWED =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-";

/**
* Create the DNSName object from the passed encoded Der value.
Expand All @@ -73,52 +73,64 @@ public DNSName(DerValue derValue) throws IOException {
* @throws IOException if the name is not a valid DNSName
*/
public DNSName(String name, boolean allowWildcard) throws IOException {
if (name == null || name.isEmpty())

// Check the full name.
if (name == null || name.isEmpty()) {
throw new IOException("DNSName must not be null or empty");
if (name.contains(" "))
throw new IOException("DNSName with blank components is not permitted");
if (name.startsWith(".") || name.endsWith("."))
}

if (name.contains(" ")) {
throw new IOException(
"DNSName with blank labels is not permitted");
}

if (name.startsWith(".") || name.endsWith(".")) {
throw new IOException("DNSName may not begin or end with a .");
/*
* Name will consist of label components separated by "."
* startIndex is the index of the first character of a component
* endIndex is the index of the last character of a component plus 1
*/
for (int endIndex,startIndex = 0; startIndex < name.length(); startIndex = endIndex+1) {
endIndex = name.indexOf('.', startIndex);
if (endIndex < 0) {
endIndex = name.length();
}

// RFC 1123 Section 2.1 and RFC 2181 Section 11
if (name.length() > 253) {
throw new IOException(
"DNSName can't be longer than 253 characters");
}

// Check the labels.
String[] labels = name.split("\\.");

for (int i = 0; i < labels.length; i++) {
String label = labels[i];

if (label.isEmpty()) {
throw new IOException(
"DNSName with empty labels is not permitted");
}
if (endIndex - startIndex < 1)
throw new IOException("DNSName with empty components are not permitted");

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) ||
(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");

// RFC 1123 Section 2.1
if (label.length() > 63) {
throw new IOException(
"DNSName label can't be longer than 63 characters");
}

// RFC 1035 Section 2.3.1
if (label.startsWith("-") || label.endsWith("-")) {
throw new IOException(
"DNSName label may not begin or end with a hyphen");
}

//nonStartIndex: index for characters in the component beyond the first one
for (int nonStartIndex=startIndex+1; nonStartIndex < endIndex; nonStartIndex++) {
char x = name.charAt(nonStartIndex);
if ((alphaDigits).indexOf(x) < 0 && x != '-')
throw new IOException("DNSName components must consist of letters, digits, and hyphens");
// RFC 9525 Section 6.3
if (allowWildcard && label.equals("*") && i == 0
&& labels.length > 1) {
continue;
}

for (char c : label.toCharArray()) {
if (DNS_ALLOWED.indexOf(c) < 0) {
throw new IOException("DNSName labels must consist of "
+ "letters, digits, and hyphens");
}
}
}

this.name = name;
}

Expand Down
176 changes: 85 additions & 91 deletions test/jdk/sun/security/x509/GeneralName/DNSNameTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2026, 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
Expand All @@ -21,132 +21,126 @@
* questions.
*/

/**
import static jdk.test.lib.Asserts.fail;

import java.io.IOException;
import java.net.IDN;
import java.util.List;
import java.util.stream.Stream;
import sun.security.x509.DNSName;

/*
* @test
* @summary DNSName parsing tests
* @bug 8213952 8186143
* @bug 8213952 8186143 8381771
* @library /test/lib
* @modules java.base/sun.security.x509
* @run testng DNSNameTest
* @run main DNSNameTest
*/

import java.io.IOException;
import sun.security.x509.DNSName;
public class DNSNameTest {

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
private static final List<String> GOOD_NAMES = List.of(
"abc",
String.join(".", "a".repeat(63), "b".repeat(63),
"c".repeat(63), "d".repeat(61)),
"abc.com",
"tesT.Abc.com",
"ABC.COM",
"a12.com",
"a1b2c3.com",
"1abc.com",
"123.com",
"a-b-c.com", // hyphens
IDN.toASCII("公司.江利子") // IDN punycode
);

import static org.testng.Assert.*;
private static final List<String> GOOD_SAN_NAMES = Stream.concat(
Stream.of(
"*.domain.com", // wildcard in 1st level subdomain
"*.com"),
GOOD_NAMES.stream())
.toList();

public class DNSNameTest {
@DataProvider(name = "goodNames")
public Object[][] goodNames() {
Object[][] data = {
{"abc.com"},
{"ABC.COM"},
{"a12.com"},
{"a1b2c3.com"},
{"1abc.com"},
{"123.com"},
{"abc.com-"}, // end with hyphen
{"a-b-c.com"}, // hyphens
};
return data;
}
private static final List<String> BAD_NAMES = List.of(
// DNSName too long
String.join(".", "a".repeat(63), "b".repeat(63),
"c".repeat(63), "d".repeat(62)),
// DNSName label too long
"a".repeat(64),
" 1abc.com", // begin with space
"1abc.com ", // end with space
"1a bc.com ", // no space allowed
"-abc.com", // name begins with a hyphen
"abc.com-", // name ends with a hyphen
"abc.-com", // label begins with a hyphen
"abc-.com", // label ends with a hyphen
"a..b", // ..
".a", // begin with .
"a.", // end with .
"", // empty
" ", // space only
"*.domain.com", // wildcard not allowed
"a*.com" // only allow letter, digit, or hyphen
);

@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;
}
private static final List<String> BAD_SAN_NAMES = Stream.concat(
Stream.of(
"*", // wildcard only
"*.", // wildcard with a period
"*a.com", // partial wildcard disallowed
"abc.*.com", // wildcard not allowed in 2nd level
"**.domain.com", // double wildcard not allowed
"*.domain.com*", // can't end with wildcard
"a*.com"), // only allow letter, digit, or hyphen
BAD_NAMES.stream().filter(n -> !n.contains("*")))
.toList();

@DataProvider(name = "badNames")
public Object[][] badNames() {
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
{"*.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;
public static void main(String[] args) {
GOOD_NAMES.forEach(DNSNameTest::testGoodDNSName);
GOOD_SAN_NAMES.forEach(DNSNameTest::testGoodSanDNSName);
BAD_NAMES.forEach(DNSNameTest::testBadDNSName);
BAD_SAN_NAMES.forEach(DNSNameTest::testBadSanDNSName);
}


@Test(dataProvider = "goodNames")
public void testGoodDNSName(String dnsNameString) {
private static void testGoodDNSName(String dnsNameString) {
try {
DNSName dn = new DNSName(dnsNameString);
} catch (IOException e) {
fail("Unexpected IOException");
fail("Unexpected IOException with input " + dnsNameString + ": "
+ e.getMessage());
}
}

@Test(dataProvider = "goodSanNames")
public void testGoodSanDNSName(String dnsNameString) {
private static void testGoodSanDNSName(String dnsNameString) {
try {
DNSName dn = new DNSName(dnsNameString, true);
} catch (IOException e) {
fail("Unexpected IOException");
fail("Unexpected IOException with input " + dnsNameString + ": "
+ e.getMessage());
}
}

@Test(dataProvider = "badNames")
public void testBadDNSName(String dnsNameString) {
private static void testBadDNSName(String dnsNameString) {
try {
DNSName dn = new DNSName(dnsNameString);
fail("IOException expected");
fail("IOException expected with input " + dnsNameString);
} catch (IOException e) {
if (!e.getMessage().contains("DNSName"))
if (!e.getMessage().contains("DNSName")) {
fail("Unexpected message: " + e);
}
}
}

@Test(dataProvider = "badSanNames")
public void testBadSanDNSName(String dnsNameString) {
private static void testBadSanDNSName(String dnsNameString) {
try {
DNSName dn = new DNSName(dnsNameString, true);
fail("IOException expected");
fail("IOException expected with input " + dnsNameString);
} catch (IOException e) {
if (!e.getMessage().contains("DNSName"))
if (!e.getMessage().contains("DNSName")) {
fail("Unexpected message: " + e);
}
}
}
}