Skip to content

Instantly share code, notes, and snippets.

@mikeshepherd
Last active July 5, 2023 11:19
Show Gist options
  • Save mikeshepherd/c25501e7af6c2ef44e245447f1e4e789 to your computer and use it in GitHub Desktop.
Save mikeshepherd/c25501e7af6c2ef44e245447f1e4e789 to your computer and use it in GitHub Desktop.
Reproduce Name Constraints bug

Reproduces a bug in OpenJDK 17 related to TLS certificate Name Constraints.

This bug occurs when a CA NameConstraint starts with a leading ..
This is a valid constrait value as it allows certifcates to be issued for subdomains of the constraint, but not the domain itself.
i.e. a name constrait of example.com would allow certifcates for www.example.com and example.com whereas a name constraint of .example.com would only allow www.example.com (or any other subdomain) but not example.com

However Java will reject a certificate issued by a CA with such a name constraint with java.security.cert.CertPathValidatorException: name constraints check failed.

The generate.sh script creates two sets of CAs and certificates, one with a Name Constraint with a leading period, and one without, that are otherwise identical. Running java NameConstraintBug.java wil attempt to load both sets of certificates and validate them but will fail for the certificates with the leading period.

There awas a similar bug previously raised in go for further reference: golang/go#16347

-----BEGIN CERTIFICATE-----
MIICjjCCAfegAwIBAgIUJjGfjDNPPC/PLnFBIZA9p2jhGL8wDQYJKoZIhvcNAQEL
BQAwQDELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0V4YW1wbGUxHzAdBgNVBAMMFkV4
YW1wbGUgQ0Egd2l0aCBwZXJpb2QwHhcNMjMwNzA0MTM0NTMyWhcNMjMwODAzMTM0
NTMyWjBAMQswCQYDVQQGEwJVUzEQMA4GA1UECgwHRXhhbXBsZTEfMB0GA1UEAwwW
RXhhbXBsZSBDQSB3aXRoIHBlcmlvZDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
gYEA01pnOkafA4Os3zLjwTi5sofqDr31mL7v169wrHoLJbukj9aaLpqVXDRFvepn
zQwicbLmZRteCPwbKJJfV3HrFUXH6wx8sY0ArxhA73qJ3+COo5HqEIYPFsxEfYpj
+8y4Qe12tvp8m/rI7l0uKzZPJaFyrgUXesJpRXzbm42HpJsCAwEAAaOBhDCBgTAd
BgNVHQ4EFgQUTnq0DyUXjDbB1H/kmlBSCoIEhaMwHwYDVR0jBBgwFoAUTnq0DyUX
jDbB1H/kmlBSCoIEhaMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAgQw
HgYDVR0eAQH/BBQwEqAQMA6CDC5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOB
gQApYqREyXvKb2aGYprHq2Z2hax2UQfqL5ldsQuGTnfw1gzUS9KL32Cw3Rr/Zjlx
7ejiB3YPfKAyA4UHqdXrc7+Ysh/6ATPk9/mQ2UCuWWh+b1PaVbXclyqP4VoQg4q4
1K4jt6tsWZQ27mYEq4r6ao+E0vzUMTvdBfzfJrGpza/UuA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICkzCCAfygAwIBAgIUQfUFC9gIcfSvOSgEx6oU5CAISekwDQYJKoZIhvcNAQEL
BQAwQzELMAkGA1UEBhMCVVMxEDAOBgNVBAoMB0V4YW1wbGUxIjAgBgNVBAMMGUV4
YW1wbGUgQ0Egd2l0aG91dCBwZXJpb2QwHhcNMjMwNzA0MTM0NTMyWhcNMjMwODAz
MTM0NTMyWjBDMQswCQYDVQQGEwJVUzEQMA4GA1UECgwHRXhhbXBsZTEiMCAGA1UE
AwwZRXhhbXBsZSBDQSB3aXRob3V0IHBlcmlvZDCBnzANBgkqhkiG9w0BAQEFAAOB
jQAwgYkCgYEAsFd3YJovFKYPW7CN2b5XQv7hbJ6WqBrsRtGEQYWlMQ+uJZprRame
G+iFpjLGr6SDRainWJezO1YAKnW4/WG1uxwdJjnE2cR6Osso2AXdlUGIlokvShwO
P+vhxRIy4EnqTF5F6wu0t12l+EJ6d5xgEwOuMfTzlSJPm0pTB5BCwWMCAwEAAaOB
gzCBgDAdBgNVHQ4EFgQUlBZJP0Dp/iaByjW1jPZXwuXyJf8wHwYDVR0jBBgwFoAU
lBZJP0Dp/iaByjW1jPZXwuXyJf8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
BAMCAgQwHQYDVR0eAQH/BBMwEaAPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEB
CwUAA4GBABNdnbRqyCS41hIwV9zZoQbyKzms8Mn6P85xRhsEVQjRqhnp4n9YHnAL
3qvzhrjIGVMU7jq3MfVlqigVcVCd6+076I/OMJWkImW/LJcPPIytbRqQ99qBwIn6
PVsUhMmiyUWx6kJUiXWpwLCO3gSF45d6ap1UoTtf0AXmLaFv8th9
-----END CERTIFICATE-----
#!/usr/bin/env bash
set -e
###############################################################
# CA with a leading period in the name contraint #
###############################################################
mkdir -p withLeadingPeriod
openssl req \
-newkey rsa:1024 \
-keyout withLeadingPeriod/ca.key \
-out withLeadingPeriod/ca.csr \
-subj "/C=US/O=Example/CN=Example CA with period" \
-nodes
openssl x509 \
-req \
-in withLeadingPeriod/ca.csr \
-extfile openssl.cnf \
-extensions withLeadingPeriod \
-signkey withLeadingPeriod/ca.key \
-out withLeadingPeriod/ca.pem
# leaf certificate
openssl req \
-newkey rsa:1024 \
-keyout withLeadingPeriod/leaf.key \
-out withLeadingPeriod/leaf.csr \
-subj '/CN=demo.example.com' \
-addext 'subjectAltName = DNS:demo.example.com' \
-nodes
openssl x509 \
-req \
-in withLeadingPeriod/leaf.csr \
-CA withLeadingPeriod/ca.pem \
-CAkey withLeadingPeriod/ca.key \
-out withLeadingPeriod/leaf.pem
# ##################################################################
# # CA without a leading period in the name contraint #
# ##################################################################
mkdir -p withoutLeadingPeriod
openssl req \
-newkey rsa:1024 \
-keyout withoutLeadingPeriod/ca.key \
-out withoutLeadingPeriod/ca.csr \
-subj "/C=US/O=Example/CN=Example CA without period" \
-nodes
openssl x509 \
-req \
-in withoutLeadingPeriod/ca.csr \
-extfile openssl.cnf \
-extensions withoutLeadingPeriod \
-signkey withoutLeadingPeriod/ca.key \
-out withoutLeadingPeriod/ca.pem
# leaf certificate
openssl req \
-newkey rsa:1024 \
-keyout withoutLeadingPeriod/leaf.key \
-out withoutLeadingPeriod/leaf.csr \
-subj '/CN=demo.example.com' \
-addext 'subjectAltName = DNS:demo.example.com' \
-nodes
openssl x509 \
-req \
-in withoutLeadingPeriod/leaf.csr \
-CA withoutLeadingPeriod/ca.pem \
-CAkey withoutLeadingPeriod/ca.key \
-out withoutLeadingPeriod/leaf.pem
# # Verify both leaf certificates
set +e
openssl verify \
-CAfile withLeadingPeriod/ca.pem \
withLeadingPeriod/leaf.pem
openssl verify \
-CAfile withoutLeadingPeriod/ca.pem \
withoutLeadingPeriod/leaf.pem
-----BEGIN CERTIFICATE-----
MIIB2DCCAUECFGPeSPO6zl27LdZ4+kbEkKqxDYjPMA0GCSqGSIb3DQEBCwUAMEAx
CzAJBgNVBAYTAlVTMRAwDgYDVQQKDAdFeGFtcGxlMR8wHQYDVQQDDBZFeGFtcGxl
IENBIHdpdGggcGVyaW9kMB4XDTIzMDcwNDEzNDUzMloXDTIzMDgwMzEzNDUzMlow
FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
AoGBALC3194XRwMKTdafXJRzyVo4aF1y/TYtwp7Hzm33Yt7dvTqSKgtumFCiWshq
SbYuMBOtJl0pG5b0oDrQDqpVLus2Zd2p/yRd1e/S8svSCw4j9bCvVInWAuf+tgzO
xnbhHbWE9eHqzqB6zXwjSW7/VF3D72c+KolIk759rPdw/8B7AgMBAAEwDQYJKoZI
hvcNAQELBQADgYEAF5uAXTpk1fKCf8+DWW4uN/fKvecPqXZ/8wvhjtN4Zob4qHut
kQ+4SPRFK3Gn6f62HRJJTmCirmf+jn17ntWpJBoQmlIcOl1WJULdPXNA6/rhG30x
vPUjiqWOGD8c3PCKtr7BiC2otDM4I5XRLru+6ovdJVXI4otwcR/EmHbloH8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB2zCCAUQCFH1WiGQ0adt4Cv0V/0wGjmx84XRXMA0GCSqGSIb3DQEBCwUAMEMx
CzAJBgNVBAYTAlVTMRAwDgYDVQQKDAdFeGFtcGxlMSIwIAYDVQQDDBlFeGFtcGxl
IENBIHdpdGhvdXQgcGVyaW9kMB4XDTIzMDcwNDEzNDUzMloXDTIzMDgwMzEzNDUz
MlowFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0A
MIGJAoGBANIDpHlLU5hC67SQxgg03rqWPJL/PYaqTUeboPVFXzaGt4MVimZ8Qc1o
m+ySh1t0nnUC7so4xGsDCu0ys2hA0qyw+NxXTt3Y9XBkoVk0u5bbeXUkpaifzrIs
QPOOogjTc3GssXzwa0XMtNpIvb7fui7YPn4ljFK4V8Cu9Y5OXV8BAgMBAAEwDQYJ
KoZIhvcNAQELBQADgYEAMx5UetGIRnsKObjILx0E1MZCe2BWqGjz8lf72qjr0Ua/
PU+swoxZTengU8BMdNYd7xd5QuAPUmxHJmFxBTCh8EjS+D4D6deAEghvcFQN/Prk
gQbTWs2ml9K/c4XsyR4BthVDotwsAkinEssv4ZRoS5fu8TiG5jNtGbzYcx2gTug=
-----END CERTIFICATE-----
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.security.Security;
import java.security.cert.*;
public class NameConstraintBug {
private static CertPath generateCertificatePath(String caStr, String targetCertStr) throws CertificateException {
// generate certificate from cert strings
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteArrayInputStream is;
is = new ByteArrayInputStream(targetCertStr.getBytes());
Certificate targetCert = cf.generateCertificate(is);
is = new ByteArrayInputStream(caStr.getBytes());
Certificate ca = cf.generateCertificate(is);
// generate certification path
List<Certificate> list = Arrays.asList(new Certificate[] { targetCert, ca });
return cf.generateCertPath(list);
}
private static PKIXParameters generateParams(String caStr) throws Exception {
// generate certificate from cert string
CertificateFactory cf = CertificateFactory.getInstance("X.509");
ByteArrayInputStream is = new ByteArrayInputStream(caStr.getBytes());
Certificate selfSignedCert = cf.generateCertificate(is);
// generate a trust anchor
TrustAnchor anchor = new TrustAnchor((X509Certificate) selfSignedCert, null);
Set<TrustAnchor> anchors = Collections.singleton(anchor);
PKIXParameters params = new PKIXParameters(anchors);
// disable certificate revocation checking
params.setRevocationEnabled(false);
// set the validation time
params.setDate(new Date(123, 6, 20)); // 2023-07-04
// disable OCSP checker
Security.setProperty("ocsp.enable", "false");
// disable CRL checker
System.setProperty("com.sun.security.enableCRLDP", "false");
return params;
}
public static void main(String args[]) throws Exception {
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
// Load certificates with a NameConstraint where the DNS value does not begin with a period
Path targetFromCAWithoutPeriodPath = Path.of("./leaf-without-leading-period.pem");
String targetFromCAWithoutPeriod = Files.readString(targetFromCAWithoutPeriodPath);
Path caWithoutLeadingPeriodPath = Path.of("./ca-without-leading-period.pem");
String caWithoutLeadingPeriod = Files.readString(caWithoutLeadingPeriodPath);
PKIXParameters paramsForCAWithoutLeadingPeriod = generateParams(caWithoutLeadingPeriod);
CertPath pathWithoutLeadingPeriod = generateCertificatePath(caWithoutLeadingPeriod, targetFromCAWithoutPeriod);
try {
validator.validate(pathWithoutLeadingPeriod, paramsForCAWithoutLeadingPeriod);
} catch (CertPathValidatorException uoe) {
// unexpected exception, rethrow it.
throw uoe;
}
// Load certificates with a NameConstraint where the DNS value does begin with a period
Path targetFromCAWithPeriodPath = Path.of("./leaf-with-leading-period.pem");
String targetFromCAWithPeriod = Files.readString(targetFromCAWithPeriodPath);
Path caWithLeadingPeriodPath = Path.of("./ca-with-leading-period.pem");
String caWithLeadingPeriod = Files.readString(caWithLeadingPeriodPath);
PKIXParameters paramsForCAWithLeadingPeriod = generateParams(caWithLeadingPeriod);
CertPath pathWithLeadingPeriod = generateCertificatePath(caWithLeadingPeriod, targetFromCAWithPeriod);
try {
validator.validate(pathWithLeadingPeriod, paramsForCAWithLeadingPeriod);
} catch (CertPathValidatorException uoe) {
// unexpected exception, rethrow it.
throw uoe;
}
}
}
#
# OpenSSL configuration file.
#
[ withLeadingPeriod ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign
nameConstraints = critical,permitted;DNS:.example.com
[ withoutLeadingPeriod ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign
nameConstraints = critical,permitted;DNS:example.com
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true
keyUsage = critical,keyCertSign
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_ca # The extentions to add to self signed certs
req_extensions = v3_req # The extensions to add to req's
prompt = no
[req_distinguished_name]
C = US
O = Example
CN = example.com
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment