diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java index 9b7418bf3..4516c1777 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultHostnameVerifier.java @@ -355,7 +355,17 @@ static List 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)); } } } @@ -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(); + } + } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java b/httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java index 6ca2ac488..482994d59 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/ssl/TestDefaultHostnameVerifier.java @@ -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; @@ -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> entries = new ArrayList<>(); + final List entry = new ArrayList<>(); + entry.add(SubjectName.IP); + entry.add(ipAsByteArray); + entries.add(entry); + + // Mocking the certificate behavior + final X509Certificate mockCert = generateX509Certificate(entries); + + final List 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> entries = new ArrayList<>(); + final List entry = new ArrayList<>(); + entry.add(SubjectName.IP); + entry.add(ipv6AsByteArray); + entries.add(entry); + + // Mocking the certificate behavior + final X509Certificate mockCert = generateX509Certificate(entries); + + final List 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> entries = new ArrayList<>(); + final List entry = new ArrayList<>(); + entry.add(SubjectName.IP); + entry.add(rawHexByteArray); + entries.add(entry); + + // Mocking the certificate behavior + final X509Certificate mockCert = generateX509Certificate(entries); + + final List 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> entries) { + return new X509Certificate() { + + @Override + public boolean hasUnsupportedCriticalExtension() { + return false; + } + + @Override + public Set getCriticalExtensionOIDs() { + return null; + } + + @Override + public Set 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> getSubjectAlternativeNames() { + return entries; + } + }; + + } + } \ No newline at end of file