Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decode Subject Alternative Names (SAN) for X.509 Certificates #610

Merged
merged 1 commit into from
Jan 20, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,14 @@ static List<SubjectName> getSubjectAltNames(final X509Certificate cert, final in
if (o instanceof String) {
result.add(new SubjectName((String) o, type));
} else if (o instanceof byte[]) {
// TODO ASN.1 DER encoded form
final byte[] bytes = (byte[]) o;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arturobernalg We have got to be super careful here as this method has security implications. It is better to be too strict and too lax here. I trust you know what you are doing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arturobernalg We have got to be super careful here as this method has security implications. It is better to be too strict and too lax here. I trust you know what you are doing.

@ok2c I've ensured strict validation in the updated method: DNS names now reject unexpected byte[], IP addresses only allow 4 or 16 bytes with exceptions for invalid lengths, and unrecognized types log warnings while falling back.

if (type == SubjectName.IP) {
if (bytes.length == 4) {
result.add(new SubjectName(byteArrayToIp(bytes), type)); // IPv4
} else if (bytes.length == 16) {
result.add(new SubjectName(byteArrayToIPv6(bytes), type)); // IPv6
}
}
}
}
}
Expand All @@ -380,4 +387,29 @@ static String normaliseAddress(final String hostname) {
return hostname;
}
}

private static String byteArrayToIp(final byte[] bytes) {
if (bytes.length != 4) {
throw new IllegalArgumentException("Invalid byte array length for IPv4 address");
}
return (bytes[0] & 0xFF) + "." +
(bytes[1] & 0xFF) + "." +
(bytes[2] & 0xFF) + "." +
(bytes[3] & 0xFF);
}

private static String byteArrayToIPv6(final byte[] bytes) {
if (bytes.length != 16) {
throw new IllegalArgumentException("Invalid byte array length for IPv6 address");
}
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i += 2) {
sb.append(String.format("%02x%02x", bytes[i], bytes[i + 1]));
if (i < bytes.length - 2) {
sb.append(":");
}
}
return sb.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,28 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.net.ssl.SSLException;

Expand Down Expand Up @@ -548,4 +564,193 @@ void testMatchIdentity() {
);
}


@Test
void testSimulatedByteProperties() throws Exception {
// Simulated byte array for an IP address
final byte[] ipAsByteArray = {1, 1, 1, 1}; // 1.1.1.1 in byte form

final List<List<?>> entries = new ArrayList<>();
final List<Object> entry = new ArrayList<>();
entry.add(SubjectName.IP);
entry.add(ipAsByteArray);
entries.add(entry);

// Mocking the certificate behavior
final X509Certificate mockCert = generateX509Certificate(entries);

final List<SubjectName> result = DefaultHostnameVerifier.getSubjectAltNames(mockCert, -1);
Assertions.assertEquals(1, result.size(), "Should have one SubjectAltName");

final SubjectName sn = result.get(0);
Assertions.assertEquals(SubjectName.IP, sn.getType(), "Should be an IP type");
// Here, you'll need logic to convert byte array to string for assertion
Assertions.assertEquals("1.1.1.1", sn.getValue(), "IP address should match after conversion");
}

@Test
void testSimulatedBytePropertiesIPv6() throws Exception {
final byte[] ipv6AsByteArray = InetAddress.getByName("2001:db8:85a3::8a2e:370:7334").getAddress();
// IPv6 2001:db8:85a3::8a2e:370:7334

final List<List<?>> entries = new ArrayList<>();
final List<Object> entry = new ArrayList<>();
entry.add(SubjectName.IP);
entry.add(ipv6AsByteArray);
entries.add(entry);

// Mocking the certificate behavior
final X509Certificate mockCert = generateX509Certificate(entries);

final List<SubjectName> result = DefaultHostnameVerifier.getSubjectAltNames(mockCert, -1);
Assertions.assertEquals(1, result.size(), "Should have one SubjectAltName");

final SubjectName sn = result.get(0);
Assertions.assertEquals(SubjectName.IP, sn.getType(), "Should be an IP type");
// Here, you'll need logic to convert byte array to string for assertion
Assertions.assertEquals("2001:0db8:85a3:0000:0000:8a2e:0370:7334", sn.getValue(), "IP address should match after conversion");
}


private X509Certificate generateX509Certificate(final List<List<?>> entries) {
return new X509Certificate() {

@Override
public boolean hasUnsupportedCriticalExtension() {
return false;
}

@Override
public Set<String> getCriticalExtensionOIDs() {
return null;
}

@Override
public Set<String> getNonCriticalExtensionOIDs() {
return null;
}

@Override
public byte[] getExtensionValue(final String oid) {
return new byte[0];
}

@Override
public byte[] getEncoded() throws CertificateEncodingException {
return new byte[0];
}

@Override
public void verify(final PublicKey key) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {

}

@Override
public void verify(final PublicKey key, final String sigProvider) throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException {

}

@Override
public String toString() {
return "";
}

@Override
public PublicKey getPublicKey() {
return null;
}

@Override
public void checkValidity() throws CertificateExpiredException, CertificateNotYetValidException {

}

@Override
public void checkValidity(final Date date) throws CertificateExpiredException, CertificateNotYetValidException {

}

@Override
public int getVersion() {
return 0;
}

@Override
public BigInteger getSerialNumber() {
return null;
}

@Override
public Principal getIssuerDN() {
return null;
}

@Override
public Principal getSubjectDN() {
return null;
}

@Override
public Date getNotBefore() {
return null;
}

@Override
public Date getNotAfter() {
return null;
}

@Override
public byte[] getTBSCertificate() throws CertificateEncodingException {
return new byte[0];
}

@Override
public byte[] getSignature() {
return new byte[0];
}

@Override
public String getSigAlgName() {
return "";
}

@Override
public String getSigAlgOID() {
return "";
}

@Override
public byte[] getSigAlgParams() {
return new byte[0];
}

@Override
public boolean[] getIssuerUniqueID() {
return new boolean[0];
}

@Override
public boolean[] getSubjectUniqueID() {
return new boolean[0];
}

@Override
public boolean[] getKeyUsage() {
return new boolean[0];
}

@Override
public int getBasicConstraints() {
return 0;
}

@Override
public Collection<List<?>> getSubjectAlternativeNames() {
return entries;
}
};

}

}
Loading