Skip to content

Commit

Permalink
Decode Subject Alternative Names for IP, DNS, and binary data
Browse files Browse the repository at this point in the history
Supports DNS names as plain text, IPv4 and IPv6 addresses in binary form, and falls back to hex encoding for unknown types.
  • Loading branch information
arturobernalg committed Jan 16, 2025
1 parent 8b1ee82 commit 33e31ef
Show file tree
Hide file tree
Showing 2 changed files with 264 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,17 @@ 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;
final String decodedValue;
if (type == SubjectName.IP && bytes.length == 4) {
decodedValue = byteArrayToIp(bytes);
} else if (type == SubjectName.IP && bytes.length == 16) {
decodedValue = byteArrayToIPv6(bytes);
} else {
decodedValue = TextUtils.toHexString(bytes);
}

result.add(new SubjectName(decodedValue, type));
}
}
}
Expand All @@ -380,4 +390,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,216 @@ 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");
}


@Test
void testSimulatedBytePropertiesRawHex() throws Exception {
final byte[] rawHexByteArray = {0x0A, 0x1B, 0x2C, 0x3D, 0x4E, 0x5F}; // Random binary data

final List<List<?>> entries = new ArrayList<>();
final List<Object> entry = new ArrayList<>();
entry.add(SubjectName.IP);
entry.add(rawHexByteArray);
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("0a1b2c3d4e5f", 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;
}
};

}

}

0 comments on commit 33e31ef

Please sign in to comment.