mirror of https://github.com/grpc/grpc-java.git
xds: SdsX509TrustManager implementation for XDS and SDS (#6254)
This commit is contained in:
parent
024a46bd11
commit
a633b53f95
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* Copyright 2019 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.xds.sds.trust;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Contains certificate utility method(s).
|
||||
*/
|
||||
final class CertificateUtils {
|
||||
|
||||
private static CertificateFactory factory;
|
||||
|
||||
private static synchronized void initInstance() throws CertificateException {
|
||||
if (factory == null) {
|
||||
factory = CertificateFactory.getInstance("X.509");
|
||||
}
|
||||
}
|
||||
|
||||
static synchronized X509Certificate[] toX509Certificates(String fileName)
|
||||
throws CertificateException, IOException {
|
||||
initInstance();
|
||||
FileInputStream fis = new FileInputStream(fileName);
|
||||
BufferedInputStream bis = new BufferedInputStream(fis);
|
||||
try {
|
||||
Collection<? extends Certificate> certs = factory.generateCertificates(bis);
|
||||
return certs.toArray(new X509Certificate[0]);
|
||||
} finally {
|
||||
bis.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* Copyright 2019 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.xds.sds.trust;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import io.envoyproxy.envoy.api.v2.auth.CertificateValidationContext;
|
||||
import java.net.Socket;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
/**
|
||||
* Extension of {@link X509ExtendedTrustManager} that implements verification of
|
||||
* SANs (subject-alternate-names) against the list in CertificateValidationContext.
|
||||
*/
|
||||
final class SdsX509TrustManager extends X509ExtendedTrustManager implements X509TrustManager {
|
||||
|
||||
// ref: io.grpc.okhttp.internal.OkHostnameVerifier and
|
||||
// sun.security.x509.GeneralNameInterface
|
||||
private static final int ALT_DNS_NAME = 2;
|
||||
private static final int ALT_URI_NAME = 6;
|
||||
private static final int ALT_IPA_NAME = 7;
|
||||
|
||||
private final X509ExtendedTrustManager delegate;
|
||||
private final CertificateValidationContext certContext;
|
||||
|
||||
SdsX509TrustManager(@Nullable CertificateValidationContext certContext,
|
||||
X509ExtendedTrustManager delegate) {
|
||||
checkNotNull(delegate, "delegate");
|
||||
this.certContext = certContext;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
// Copied from OkHostnameVerifier.verifyHostName().
|
||||
private static boolean verifyDnsNameInPattern(String pattern, String sanToVerify) {
|
||||
// Basic sanity checks
|
||||
// Check length == 0 instead of .isEmpty() to support Java 5.
|
||||
if (sanToVerify == null
|
||||
|| sanToVerify.length() == 0
|
||||
|| sanToVerify.startsWith(".")
|
||||
|| sanToVerify.endsWith("..")) {
|
||||
// Invalid domain name
|
||||
return false;
|
||||
}
|
||||
if (pattern == null
|
||||
|| pattern.length() == 0
|
||||
|| pattern.startsWith(".")
|
||||
|| pattern.endsWith("..")) {
|
||||
// Invalid pattern/domain name
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize sanToVerify and pattern by turning them into absolute domain names if they are not
|
||||
// yet absolute. This is needed because server certificates do not normally contain absolute
|
||||
// names or patterns, but they should be treated as absolute. At the same time, any sanToVerify
|
||||
// presented to this method should also be treated as absolute for the purposes of matching
|
||||
// to the server certificate.
|
||||
// www.android.com matches www.android.com
|
||||
// www.android.com matches www.android.com.
|
||||
// www.android.com. matches www.android.com.
|
||||
// www.android.com. matches www.android.com
|
||||
if (!sanToVerify.endsWith(".")) {
|
||||
sanToVerify += '.';
|
||||
}
|
||||
if (!pattern.endsWith(".")) {
|
||||
pattern += '.';
|
||||
}
|
||||
// sanToVerify and pattern are now absolute domain names.
|
||||
|
||||
pattern = pattern.toLowerCase(Locale.US);
|
||||
// sanToVerify and pattern are now in lower case -- domain names are case-insensitive.
|
||||
|
||||
if (!pattern.contains("*")) {
|
||||
// Not a wildcard pattern -- sanToVerify and pattern must match exactly.
|
||||
return sanToVerify.equals(pattern);
|
||||
}
|
||||
// Wildcard pattern
|
||||
|
||||
// WILDCARD PATTERN RULES:
|
||||
// 1. Asterisk (*) is only permitted in the left-most domain name label and must be the
|
||||
// only character in that label (i.e., must match the whole left-most label).
|
||||
// For example, *.example.com is permitted, while *a.example.com, a*.example.com,
|
||||
// a*b.example.com, a.*.example.com are not permitted.
|
||||
// 2. Asterisk (*) cannot match across domain name labels.
|
||||
// For example, *.example.com matches test.example.com but does not match
|
||||
// sub.test.example.com.
|
||||
// 3. Wildcard patterns for single-label domain names are not permitted.
|
||||
|
||||
if (!pattern.startsWith("*.") || pattern.indexOf('*', 1) != -1) {
|
||||
// Asterisk (*) is only permitted in the left-most domain name label and must be the only
|
||||
// character in that label
|
||||
return false;
|
||||
}
|
||||
|
||||
// Optimization: check whether sanToVerify is too short to match the pattern. sanToVerify must
|
||||
// be at
|
||||
// least as long as the pattern because asterisk must match the whole left-most label and
|
||||
// sanToVerify starts with a non-empty label. Thus, asterisk has to match one or more
|
||||
// characters.
|
||||
if (sanToVerify.length() < pattern.length()) {
|
||||
// sanToVerify too short to match the pattern.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ("*.".equals(pattern)) {
|
||||
// Wildcard pattern for single-label domain name -- not permitted.
|
||||
return false;
|
||||
}
|
||||
|
||||
// sanToVerify must end with the region of pattern following the asterisk.
|
||||
String suffix = pattern.substring(1);
|
||||
if (!sanToVerify.endsWith(suffix)) {
|
||||
// sanToVerify does not end with the suffix
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that asterisk did not match across domain name labels.
|
||||
int suffixStartIndexInHostName = sanToVerify.length() - suffix.length();
|
||||
// Asterisk is matching across domain name labels -- not permitted.
|
||||
return suffixStartIndexInHostName <= 0
|
||||
|| sanToVerify.lastIndexOf('.', suffixStartIndexInHostName - 1) == -1;
|
||||
|
||||
// sanToVerify matches pattern
|
||||
}
|
||||
|
||||
private static boolean verifyDnsNameInSanList(String altNameFromCert,
|
||||
List<String> verifySanList) {
|
||||
for (String verifySan : verifySanList) {
|
||||
if (verifyDnsNameInPattern(altNameFromCert, verifySan)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* helper function for verifying URI or IP address. For now we compare IP addresses as strings
|
||||
* without any regard to IPv4 vs IPv6.
|
||||
*
|
||||
* @param stringFromCert either URI or IP address
|
||||
* @param verifySanList list of SANs from certificate context
|
||||
* @return true if there is a match
|
||||
*/
|
||||
private static boolean verifyStringInSanList(String stringFromCert, List<String> verifySanList) {
|
||||
for (String sanToVerify : verifySanList) {
|
||||
if (sanToVerify.equalsIgnoreCase(stringFromCert)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean verifyOneSanInList(List<?> entry, List<String> verifySanList)
|
||||
throws CertificateParsingException {
|
||||
// from OkHostnameVerifier.getSubjectAltNames
|
||||
if (entry == null || entry.size() < 2) {
|
||||
throw new CertificateParsingException("Invalid SAN entry");
|
||||
}
|
||||
Integer altNameType = (Integer) entry.get(0);
|
||||
if (altNameType == null) {
|
||||
throw new CertificateParsingException("Invalid SAN entry: null altNameType");
|
||||
}
|
||||
String altNameFromCert = (String) entry.get(1);
|
||||
switch (altNameType) {
|
||||
case ALT_DNS_NAME:
|
||||
return verifyDnsNameInSanList(altNameFromCert, verifySanList);
|
||||
case ALT_URI_NAME:
|
||||
case ALT_IPA_NAME:
|
||||
return verifyStringInSanList(altNameFromCert, verifySanList);
|
||||
default:
|
||||
throw new CertificateParsingException("Unsupported altNameType: " + altNameType);
|
||||
}
|
||||
}
|
||||
|
||||
// logic from Envoy::Extensions::TransportSockets::Tls::ContextImpl::verifySubjectAltName
|
||||
private static void verifySubjectAltNameInLeaf(X509Certificate cert, List<String> verifyList)
|
||||
throws CertificateException {
|
||||
Collection<List<?>> names = cert.getSubjectAlternativeNames();
|
||||
if (names == null || names.size() == 0) {
|
||||
throw new CertificateException("Peer certificate SAN check failed");
|
||||
}
|
||||
for (List<?> name : names) {
|
||||
if (verifyOneSanInList(name, verifyList)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// at this point there's no match
|
||||
throw new CertificateException("Peer certificate SAN check failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies SANs in the peer cert chain against verify_subject_alt_name in the certContext.
|
||||
* This is called from various check*Trusted methods.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void verifySubjectAltNameInChain(X509Certificate[] peerCertChain) throws CertificateException {
|
||||
if (certContext == null) {
|
||||
return;
|
||||
}
|
||||
List<String> verifyList = certContext.getVerifySubjectAltNameList();
|
||||
if (verifyList == null || verifyList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (peerCertChain == null || peerCertChain.length < 1) {
|
||||
throw new CertificateException("Peer certificate(s) missing");
|
||||
}
|
||||
// verify SANs only in the top cert (leaf cert)
|
||||
verifySubjectAltNameInLeaf(peerCertChain[0], verifyList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket)
|
||||
throws CertificateException {
|
||||
delegate.checkClientTrusted(chain, authType, socket);
|
||||
verifySubjectAltNameInChain(chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)
|
||||
throws CertificateException {
|
||||
delegate.checkClientTrusted(chain, authType, sslEngine);
|
||||
verifySubjectAltNameInChain(chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
delegate.checkClientTrusted(chain, authType);
|
||||
verifySubjectAltNameInChain(chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket)
|
||||
throws CertificateException {
|
||||
delegate.checkServerTrusted(chain, authType, socket);
|
||||
verifySubjectAltNameInChain(chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine)
|
||||
throws CertificateException {
|
||||
delegate.checkServerTrusted(chain, authType, sslEngine);
|
||||
verifySubjectAltNameInChain(chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType)
|
||||
throws CertificateException {
|
||||
delegate.checkServerTrusted(chain, authType);
|
||||
verifySubjectAltNameInChain(chain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return delegate.getAcceptedIssuers();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC6TCCAlKgAwIBAgIBCjANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET
|
||||
MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
||||
dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTEwMDEwOTU4WhcNMjUxMTA3
|
||||
MDEwOTU4WjBaMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8G
|
||||
A1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDDAp0ZXN0Y2xp
|
||||
ZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDsVEfbob4W3lVCDLOVmx9K
|
||||
cdJnoZdvurGaTY87xNiopmaR8zCR7pFR9BX5L4bNG/PkuVLfVTVAKndyDCQggBBr
|
||||
UTaEITNbfWK9swHJEr20WnKfhS/wo/Xg5sqNNCrFRmnnnwOA4eDlvmYZEzSnJXV6
|
||||
pEro9bBH9uOCWWLqmaev7QIDAQABo4HCMIG/MAkGA1UdEwQCMAAwCwYDVR0PBAQD
|
||||
AgXgMB0GA1UdDgQWBBQAdbW5Vml/CnYwqdP3mOHDARU+8zBwBgNVHSMEaTBnoVqk
|
||||
WDBWMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMY
|
||||
SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2GCCQCRxhke
|
||||
HRoqBzAJBgNVHREEAjAAMAkGA1UdEgQCMAAwDQYJKoZIhvcNAQELBQADgYEAf4MM
|
||||
k+sdzd720DfrQ0PF2gDauR3M9uBubozDuMuF6ufAuQBJSKGQEGibXbUelrwHmnql
|
||||
UjTyfolVcxEBVaF4VFHmn7u6vP7S1NexIDdNUHcULqxIb7Tzl8JYq8OOHD2rQy4H
|
||||
s8BXaVIzw4YcaCGAMS0iDX052Sy7e2JhP8Noxvo=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET
|
||||
MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
|
||||
dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx
|
||||
MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
|
||||
BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50
|
||||
ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco
|
||||
LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg
|
||||
zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd
|
||||
9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw
|
||||
CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy
|
||||
em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G
|
||||
CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6
|
||||
hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh
|
||||
y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* Copyright 2019 The gRPC Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.grpc.xds.sds.trust;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import io.envoyproxy.envoy.api.v2.auth.CertificateValidationContext;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SdsX509TrustManager}.
|
||||
*/
|
||||
@RunWith(JUnit4.class)
|
||||
public class SdsX509TrustManagerTest {
|
||||
/**
|
||||
* server1 has 4 SANs.
|
||||
*/
|
||||
private static final String SERVER_1_PEM_FILE = "src/test/certs/server1.pem";
|
||||
|
||||
/**
|
||||
* client has no SANs.
|
||||
*/
|
||||
private static final String CLIENT_PEM_FILE = "src/test/certs/client.pem";
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private X509ExtendedTrustManager mockDelegate;
|
||||
|
||||
@Test
|
||||
public void nullCertContextTest() throws CertificateException, IOException {
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(null, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptySanListContextTest() throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext.getDefaultInstance();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingPeerCerts() throws CertificateException, FileNotFoundException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("foo.com")
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
try {
|
||||
trustManager.verifySubjectAltNameInChain(null);
|
||||
Assert.fail("no exception thrown");
|
||||
} catch (CertificateException expected) {
|
||||
assertThat(expected).hasMessageThat()
|
||||
.isEqualTo("Peer certificate(s) missing");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyArrayPeerCerts() throws CertificateException, FileNotFoundException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("foo.com")
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
try {
|
||||
trustManager.verifySubjectAltNameInChain(new X509Certificate[0]);
|
||||
Assert.fail("no exception thrown");
|
||||
} catch (CertificateException expected) {
|
||||
assertThat(expected).hasMessageThat()
|
||||
.isEqualTo("Peer certificate(s) missing");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noSansInPeerCerts() throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("foo.com")
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(CLIENT_PEM_FILE);
|
||||
try {
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
Assert.fail("no exception thrown");
|
||||
} catch (CertificateException expected) {
|
||||
assertThat(expected).hasMessageThat()
|
||||
.isEqualTo("Peer certificate SAN check failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oneSanInPeerCertsVerifies() throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("waterzooi.test.google.be")
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oneSanInPeerCertsVerifiesMultipleVerifySans()
|
||||
throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext =
|
||||
CertificateValidationContext.newBuilder()
|
||||
.addVerifySubjectAltName("x.foo.com")
|
||||
.addVerifySubjectAltName("waterzooi.test.google.be")
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oneSanInPeerCertsNotFoundException()
|
||||
throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext =
|
||||
CertificateValidationContext.newBuilder().addVerifySubjectAltName("x.foo.com").build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
try {
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
Assert.fail("no exception thrown");
|
||||
} catch (CertificateException expected) {
|
||||
assertThat(expected).hasMessageThat().isEqualTo("Peer certificate SAN check failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wildcardSanInPeerCertsVerifiesMultipleVerifySans()
|
||||
throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("x.foo.com")
|
||||
.addVerifySubjectAltName("abc.test.youtube.com") // should match *.test.youtube.com
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wildcardSanInPeerCertsVerifiesMultipleVerifySans1()
|
||||
throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("x.foo.com")
|
||||
.addVerifySubjectAltName("abc.test.google.fr") // should match *.test.google.fr
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wildcardSanInPeerCertsSubdomainMismatch()
|
||||
throws CertificateException, IOException {
|
||||
// 2. Asterisk (*) cannot match across domain name labels.
|
||||
// For example, *.example.com matches test.example.com but does not match
|
||||
// sub.test.example.com.
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("sub.abc.test.youtube.com")
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
try {
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
Assert.fail("no exception thrown");
|
||||
} catch (CertificateException expected) {
|
||||
assertThat(expected).hasMessageThat()
|
||||
.isEqualTo("Peer certificate SAN check failed");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oneIpAddressInPeerCertsVerifies() throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("x.foo.com")
|
||||
.addVerifySubjectAltName("192.168.1.3")
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oneIpAddressInPeerCertsMismatch() throws CertificateException, IOException {
|
||||
CertificateValidationContext certContext = CertificateValidationContext
|
||||
.newBuilder()
|
||||
.addVerifySubjectAltName("x.foo.com")
|
||||
.addVerifySubjectAltName("192.168.2.3")
|
||||
.build();
|
||||
SdsX509TrustManager trustManager = new SdsX509TrustManager(certContext, mockDelegate);
|
||||
X509Certificate[] certs = CertificateUtils.toX509Certificates(SERVER_1_PEM_FILE);
|
||||
try {
|
||||
trustManager.verifySubjectAltNameInChain(certs);
|
||||
Assert.fail("no exception thrown");
|
||||
} catch (CertificateException expected) {
|
||||
assertThat(expected).hasMessageThat()
|
||||
.isEqualTo("Peer certificate SAN check failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue