This is an automated email from the ASF dual-hosted git repository.
rawlin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 0d2560d Add SSL Certificate Validation to Traffic Router (#3380)
0d2560d is described below
commit 0d2560d98795202088ea058c095b4ad32cd49840
Author: Andy Schmidt <[email protected]>
AuthorDate: Tue Mar 12 14:36:10 2019 -0700
Add SSL Certificate Validation to Traffic Router (#3380)
* updated tr docker config so it would work
* added start and stop scripts for TR docker containers
* fixed bug in Tomcat which wasn't unregistering old SslHostConfigs
* Added validation checks of SSL certs while being loaded by
CertificateRegistry
* Changed error message for Certificate Expired
* Corrected a parsing error in certificate validation checking
Set the startup/shutdown scripts for the TR docker container to executable
* Corrected some code formatting and one NullPointerException
* Added info to CHANGELOG.md
---
CHANGELOG.md | 4 +
infrastructure/docker/traffic_router/Dockerfile | 2 +
infrastructure/docker/traffic_router/run.sh | 2 +-
infrastructure/docker/traffic_router/shutdowntr.sh | 36 ++++++
infrastructure/docker/traffic_router/starttr.sh | 44 +++++++
.../traffic_router/protocol/RouterNioEndpoint.java | 26 ++++
.../secure/CertificateDataConverter.java | 118 ++++++++++++++++--
.../traffic_router/secure/CertificateRegistry.java | 12 +-
.../java/secure/CertificateDataConverterTest.java | 138 +++++++++++++++------
.../1.3/cdns/name/thecdn/sslkeys-missing-1.json | 2 +-
.../api/1.3/cdns/name/thecdn/sslkeys.json | 2 +-
traffic_router/shared/pom.xml | 14 ---
.../src/test/java/secure/BindPrivateKeyTest.java | 3 +-
.../shared/DeliveryServiceCertificatesTest.java | 3 +-
14 files changed, 338 insertions(+), 68 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e4bd37..91d2c4c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -23,6 +23,10 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- Snapshotting the CRConfig now deletes HTTPS certificates in Riak for
delivery services which have been deleted in Traffic Ops.
### Changed
+- Traffic Router, added TLS certificate validation on certificates imported
from Traffic Ops
+ - validates modulus of private and public keys
+ - validates current timestamp falls within the certificate date bracket
+ - validates certificate subjects against the DS URL
- Traffic Ops Golang Endpoints
- Updated /api/1.1/cachegroups: Cache Group Fallbacks are included
- Updated /api/1.1/cachegroups: fixed so fallbackToClosest can be set
through API
diff --git a/infrastructure/docker/traffic_router/Dockerfile
b/infrastructure/docker/traffic_router/Dockerfile
index 3a6a0f1..c16dcc0 100644
--- a/infrastructure/docker/traffic_router/Dockerfile
+++ b/infrastructure/docker/traffic_router/Dockerfile
@@ -35,6 +35,8 @@ ARG TC_REPO=traffic-control.repo
ADD $TMCAT /
ADD $RPM /
ADD $TC_REPO /etc/yum.repos.d/
+ADD starttr.sh /
+ADD shutdowntr.sh /
### Common for all sub-component builds
RUN yum -y install \
diff --git a/infrastructure/docker/traffic_router/run.sh
b/infrastructure/docker/traffic_router/run.sh
index 1329cdf..3c03f6c 100755
--- a/infrastructure/docker/traffic_router/run.sh
+++ b/infrastructure/docker/traffic_router/run.sh
@@ -28,7 +28,7 @@
# ORIGIN_URI # origin server (e.g. hotair), used to create a delivery service
start() {
- systemctl start traffic_router
+ ./starttr.sh
touch /opt/traffic_router/var/log/traffic_router.log
exec tail -f /opt/traffic_router/var/log/traffic_router.log
}
diff --git a/infrastructure/docker/traffic_router/shutdowntr.sh
b/infrastructure/docker/traffic_router/shutdowntr.sh
new file mode 100755
index 0000000..e6ffa5e
--- /dev/null
+++ b/infrastructure/docker/traffic_router/shutdowntr.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+# Script for running the Dockerfile for Traffic Router.
+# The Dockerfile sets up a Docker image which can be used for any new
container;
+# This script, which should be run when the container is run (it's the
ENTRYPOINT), will configure the container.
+#
+# The following environment variables must be set (ordinarily by `docker run
-e` arguments):
+# TRAFFIC_OPS_URI
+# TRAFFIC_OPS_USER
+# TRAFFIC_OPS_PASS
+# TRAFFIC_MONITORS # list of semicolon-delimited FQDN:port monitors. E.g.
`monitor.foo.com:80;monitor2.bar.org:80`
+# ORIGIN_URI # origin server (e.g. hotair), used to create a delivery service
+
+export JAVA_HOME=/usr/java/jdk1.8.0_92/jre
+export CATALINA_PID=/opt/traffic_router/temp/tomcat.pid
+export CATALINA_HOME=/opt/tomcat
+export CATALINA_BASE=/opt/traffic_router
+export CATALINA_OUT=/opt/tomcat/logs/catalina.log
+source /opt/traffic_router/conf/startup.properties
+/opt/tomcat/bin/shutdown.sh
diff --git a/infrastructure/docker/traffic_router/starttr.sh
b/infrastructure/docker/traffic_router/starttr.sh
new file mode 100755
index 0000000..adbb9ab
--- /dev/null
+++ b/infrastructure/docker/traffic_router/starttr.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+
+# Script for running the Dockerfile for Traffic Router.
+# The Dockerfile sets up a Docker image which can be used for any new
container;
+# This script simulates the systemd unit file that is used to start traffic
router on
+# servers in the real world, but in Docker containers systemd is disabled.
+# Therefore it is important to keep this script up to date with any changes
that are
+# made to traffic_router/build/build_rpm.sh and traffic_router/build/pom.xml
+
+export JAVA_HOME=/usr/java/jdk1.8.0_92/jre
+export CATALINA_PID=/opt/traffic_router/temp/tomcat.pid
+export CATALINA_HOME=/opt/tomcat
+export CATALINA_BASE=/opt/traffic_router
+export CATALINA_OUT=/opt/tomcat/logs/catalina.log
+export CATALINA_OPTS="\
+ -server -Xms512m -Xmx1g \
+ -Dlog4j.configuration=file://$CATALINA_BASE/conf/log4j.properties \
+ -Djava.library.path=/usr/lib64 \
+
-Dorg.apache.catalina.connector.Response.ENFORCE_ENCODING_IN_GET_WRITER=false \
+ -XX:+UseG1GC \
+ -XX:+UnlockExperimentalVMOptions \
+ -XX:InitiatingHeapOccupancyPercent=30"
+export JAVA_OPTS="\
+ -Djava.awt.headless=true \
+ -Djava.security.egd=file:/dev/./urandom"
+
+ulimit -c unlimited
+/opt/tomcat/bin/startup.sh
diff --git
a/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/protocol/RouterNioEndpoint.java
b/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/protocol/RouterNioEndpoint.java
index 657e573..85f8c07 100644
---
a/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/protocol/RouterNioEndpoint.java
+++
b/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/protocol/RouterNioEndpoint.java
@@ -19,6 +19,7 @@ import
com.comcast.cdn.traffic_control.traffic_router.secure.CertificateRegistry
import com.comcast.cdn.traffic_control.traffic_router.secure.HandshakeData;
import com.comcast.cdn.traffic_control.traffic_router.secure.KeyManager;
import org.apache.log4j.Logger;
+import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.net.NioEndpoint;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
@@ -92,4 +93,29 @@ public class RouterNioEndpoint extends NioEndpoint {
protected SSLHostConfig getSSLHostConfig(final String sniHostName) {
return super.getSSLHostConfig(sniHostName.toLowerCase());
}
+
+ private void unregisterJmx(final SSLHostConfig sslHostConfig) {
+ final Registry registry = Registry.getRegistry(null, null);
+ registry.unregisterComponent(sslHostConfig.getObjectName());
+ for (final SSLHostConfigCertificate sslHostConfigCert :
sslHostConfig.getCertificates()) {
+ registry.unregisterComponent(sslHostConfigCert.getObjectName());
+ }
+ }
+
+ @Override
+ public void addSslHostConfig(final SSLHostConfig sslHostConfig, final
boolean replace) throws IllegalArgumentException {
+ final String key = sslHostConfig.getHostName();
+ if (key == null || key.length() == 0) {
+ throw new
IllegalArgumentException(sm.getString("endpoint.noSslHostName"));
+ }
+
+ SSLHostConfig previous = null;
+ if (replace) {
+ previous = sslHostConfigs.get(key);
+ }
+ super.addSslHostConfig(sslHostConfig, replace);
+ if (previous != null) {
+ unregisterJmx(previous);
+ }
+ }
}
diff --git
a/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/secure/CertificateDataConverter.java
b/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/secure/CertificateDataConverter.java
index 9324145..b3990fb 100644
---
a/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/secure/CertificateDataConverter.java
+++
b/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/secure/CertificateDataConverter.java
@@ -17,11 +17,18 @@ package
com.comcast.cdn.traffic_control.traffic_router.secure;
import com.comcast.cdn.traffic_control.traffic_router.shared.CertificateData;
import org.apache.log4j.Logger;
+import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateCrtKey;
+import sun.security.rsa.RSAPrivateCrtKeyImpl;
+import sun.security.rsa.RSAPublicKeyImpl;
+import java.math.BigInteger;
import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.List;
-import java.util.stream.Collectors;
public class CertificateDataConverter {
private static final Logger log =
Logger.getLogger(CertificateDataConverter.class);
@@ -29,18 +36,43 @@ public class CertificateDataConverter {
private PrivateKeyDecoder privateKeyDecoder = new PrivateKeyDecoder();
private CertificateDecoder certificateDecoder = new
CertificateDecoder();
+ @SuppressWarnings({"PMD.CyclomaticComplexity"})
public HandshakeData toHandshakeData(final CertificateData
certificateData) {
try {
final PrivateKey privateKey =
privateKeyDecoder.decode(certificateData.getCertificate().getKey());
final List<String> encodedCertificates =
certificateDecoder.doubleDecode(certificateData.getCertificate().getCrt());
- final List<X509Certificate> x509Chain =
encodedCertificates.stream()
- .map(encodedCertificate ->
certificateDecoder.toCertificate(encodedCertificate))
- .collect(Collectors.toList());
-
- return new
HandshakeData(certificateData.getDeliveryservice(),
certificateData.getHostname(),
- x509Chain.toArray(new
X509Certificate[x509Chain.size()]), privateKey);
+ final List<X509Certificate> x509Chain = new
ArrayList<>();
+ boolean hostMatch = false;
+ boolean modMatch = false;
+ for (final String encodedCertificate :
encodedCertificates) {
+ final X509Certificate certificate =
certificateDecoder.toCertificate(encodedCertificate);
+ certificate.checkValidity();
+ if (!hostMatch && verifySubject(certificate,
certificateData.alias())) {
+ hostMatch = true;
+ }
+ if (!modMatch && verifyModulus(privateKey,
certificate)) {
+ modMatch = true;
+ }
+ x509Chain.add(certificate);
+ }
+ if (hostMatch && modMatch) {
+ return new
HandshakeData(certificateData.getDeliveryservice(),
certificateData.getHostname(),
+ x509Chain.toArray(new
X509Certificate[x509Chain.size()]), privateKey);
+ }
+ else if (!hostMatch) {
+ log.warn("Service name doesn't match the
subject of the certificate = "+certificateData.getHostname());
+ }
+ else if (!modMatch) {
+ log.error("Modulus of the private key does not
match the public key modulus for certificate host:
"+certificateData.getHostname());
+ }
+ } catch (CertificateNotYetValidException er) {
+ log.error("Failed to convert certificate data for
delivery service = " + certificateData.getHostname()
+ + ", because the
certificate is not valid yet. ");
+ } catch (CertificateExpiredException ex ) {
+ log.error("Failed to convert certificate data for
delivery service = " + certificateData.getHostname()
+ + ", because the certificate has
expired. ");
} catch (Exception e) {
log.error("Failed to convert certificate data (delivery
service = " + certificateData.getDeliveryservice()
+ ", hostname = " +
certificateData.getHostname() + ") from traffic ops to handshake data! "
@@ -49,6 +81,78 @@ public class CertificateDataConverter {
return null;
}
+ public boolean verifySubject(final X509Certificate certificate, final
String hostAlias ) {
+ final String host = certificate.getSubjectDN().getName();
+ if (hostCompare(hostAlias,host)) {
+ return true;
+ }
+
+ try {
+ // This approach is probably the only one that is JDK
independent
+ if (certificate.getSubjectAlternativeNames() != null) {
+ for (final List<?> altName :
certificate.getSubjectAlternativeNames()) {
+ if (hostCompare(hostAlias, (String)
altName.get(1))) {
+ return true;
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ log.error("Encountered an error while validating the
certificate subject for service: "+hostAlias+", " +
+ "error:
"+e.getClass().getSimpleName()+": " + e.getMessage(), e);
+ return false;
+ }
+
+ return false;
+ }
+
+ private boolean hostCompare(final String hostAlias, final String
subject) {
+ if (hostAlias.contains(subject) || subject.contains(hostAlias))
{
+ return true;
+ }
+ final String[] chopped = subject.split("CN=", 2);
+ if (chopped != null && chopped.length > 1) {
+ String chop = chopped[1];
+ chop = chop.replaceFirst("\\*\\.", ".");
+ chop = chop.split(",", 2)[0];
+ if (chop.length()>0 && (hostAlias.contains(chop) ||
chop.contains(hostAlias))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean verifyModulus(final PrivateKey privateKey, final
X509Certificate certificate) {
+ BigInteger privModulus = null;
+ if (privateKey instanceof BCRSAPrivateCrtKey) {
+ privModulus = ((BCRSAPrivateCrtKey)
privateKey).getModulus();
+ } else if (privateKey instanceof RSAPrivateCrtKeyImpl) {
+ privModulus = ((RSAPrivateCrtKeyImpl)
privateKey).getModulus();
+ } else {
+ return false;
+ }
+ BigInteger pubModulus = null;
+ final PublicKey publicKey = certificate.getPublicKey();
+ if ((publicKey instanceof RSAPublicKeyImpl)) {
+ pubModulus = ((RSAPublicKeyImpl)
publicKey).getModulus();
+ } else {
+ final String[] keyparts =
publicKey.toString().split(System.getProperty("line.separator"));
+ for (final String part : keyparts) {
+ final int start = part.indexOf("modulus: ") + 9;
+ if (start < 9) {
+ continue;
+ } else {
+ pubModulus = new
BigInteger(part.substring(start));
+ break;
+ }
+ }
+ }
+ if (privModulus.equals(pubModulus)) {
+ return true;
+ }
+ return false;
+ }
+
public PrivateKeyDecoder getPrivateKeyDecoder() {
return privateKeyDecoder;
}
diff --git
a/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/secure/CertificateRegistry.java
b/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/secure/CertificateRegistry.java
index e3f773c..848c8b0 100644
---
a/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/secure/CertificateRegistry.java
+++
b/traffic_router/connector/src/main/java/com/comcast/cdn/traffic_control/traffic_router/secure/CertificateRegistry.java
@@ -73,10 +73,12 @@ public class CertificateRegistry {
if (!master.containsKey(alias)) {
final HandshakeData handshakeData =
certificateDataConverter.toHandshakeData(certificateData);
- master.put(alias, handshakeData);
- if
(!certificateData.equals(previousData.get(alias))) {
- changes.put(alias,
handshakeData);
- log.warn("Imported handshake
data with alias " + alias);
+ if (handshakeData != null) {
+ master.put(alias,
handshakeData);
+ if
(!certificateData.equals(previousData.get(alias))) {
+ changes.put(alias,
handshakeData);
+ log.warn("Imported
handshake data with alias " + alias);
+ }
}
}
else {
@@ -103,7 +105,7 @@ public class CertificateRegistry {
previousData.clear();
for (final CertificateData certificateData :
certificateDataList) {
final String alias = certificateData.alias();
- if (!previousData.containsKey(alias)) {
+ if (!previousData.containsKey(alias) &&
master.containsKey(alias)) {
previousData.put(alias, certificateData);
}
}
diff --git
a/traffic_router/connector/src/test/java/secure/CertificateDataConverterTest.java
b/traffic_router/connector/src/test/java/secure/CertificateDataConverterTest.java
index 6de4ae9..0554de6 100644
---
a/traffic_router/connector/src/test/java/secure/CertificateDataConverterTest.java
+++
b/traffic_router/connector/src/test/java/secure/CertificateDataConverterTest.java
@@ -16,73 +16,137 @@
package secure;
import
com.comcast.cdn.traffic_control.traffic_router.secure.CertificateDataConverter;
-import
com.comcast.cdn.traffic_control.traffic_router.secure.CertificateDecoder;
import com.comcast.cdn.traffic_control.traffic_router.secure.HandshakeData;
-import com.comcast.cdn.traffic_control.traffic_router.secure.PrivateKeyDecoder;
import com.comcast.cdn.traffic_control.traffic_router.shared.Certificate;
import com.comcast.cdn.traffic_control.traffic_router.shared.CertificateData;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
+import org.powermock.core.classloader.annotations.PrepareForTest;
-import java.security.PrivateKey;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
+import java.time.Instant;
+import java.util.Date;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.fail;
public class CertificateDataConverterTest {
private CertificateDataConverter certificateDataConverter;
private CertificateData certificateData;
- private X509Certificate x509Certificate1;
- private X509Certificate x509Certificate2;
- private X509Certificate x509Certificate3;
- private PrivateKey privateKey;
+ private Date certDate;
+ private final static String SUBJECT_MISS_CERT_DATA =
+ " {\n" +
+ " \"deliveryservice\": \"https-subject-miss\",\n" +
+ " \"certificate\": {\n" +
+ " \"comment\" : \"The following is a self-signed
key for *.subject-miss.thecdn.example.com\",\n" +
+ " \"key\":
\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQzhBWVVFYk1YcHZiVUMKaDBrNWRxYURnTHJGL3Y5VDdtOFNLUnVuRldYYUhFalVvcWlZc29tekhuZjNyUkVNRWpkVXB0M0lCVzk3M090cApqNmlkNUNLTHlFVDNUQ3h2ZHNERzhiYXB3UEdNT0dzQWhTMGxucmlrRll6ejArZXpxMWhzczcxRDBqN3o1TzlLCmxPVUJxSUgzOG16YU1JaFN3VXpsSGdFRzJjdlJiK1RwajhpU0k3Z3psek8rMVM1OEExS21UbjVDMC9ia0lvcFYKREJ5V3FySmpqSXZuWjBvK2I1MkRMcExzdlVnRU5BOVdHRzkycG8wS0RDZnFmNjN0RW5oRGYvZStFT0o5NUs5U
[...]
+ " \"crt\":
\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURvakNDQW9vQ0NRQ1JFTVdLWEhZYkhUQU5CZ2txaGtpRzl3MEJBUXNGQURDQmtURUxNQWtHQTFVRUJoTUMKVlZNeEN6QUpCZ05WQkFnTUFrTlBNUXd3Q2dZRFZRUUhEQU5FUlU0eEN6QUpCZ05WQkFvTUFsUkRNUXN3Q1FZRApWUVFMREFKVVF6RXFNQ2dHQTFVRUF3d2hLaTV6ZFdKcVpXTjBMVzFwYzNNdWRHaGxZMlJ1TG1WNFlXMXdiR1V1ClkyOXRNU0V3SHdZSktvWklodmNOQVFrQkZoSjBZMEJ6Wld4bUxYTnBaMjVsWkM1amIyMHdJQmNOTVRrd016QTEKTVRjME1ETXhXaGdQTWpFeE9UQXlNRGt4TnpRd016RmFNSUdSTVFzd0NRWURWUVFHRXdKVlV6RUxNQWtHQTFVR
[...]
+ " },\n" +
+ " \"hostname\":
\"*.https-subject-miss.thecdn.example.com\"\n" +
+ " }";
+ private final static String VALID_CERT_DATA =
+ " {\n" +
+ " \"deliveryservice\": \"https-valid-test\",\n" +
+ " \"certificate\": {\n" +
+ " \"comment\" : \"The following is just a self
signed certificate and key to use for testing this is NOT private data from a
CA\",\n" +
+ " \"key\": " +
+
"\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRQzVEMVhNbXJiQy9CT1gKUkZMVkczbTNSbmhWZ0ZJdUQ5dXhWSEJDYXR2TEFuc2ZyalhCM2tyZjVNTDVuS3dZRWl3OCtkQWo2N1Z2QkR4cwpDMTYvbFFBbFM4YnBxT1NRbzU5T0RDcVBNZmZaYzVVazdVdjUzN0R5MWFHMjRiT1R0eUxjQzIxc2MxSm1YWHVjCkVlQlZUZldFWUVLdS9McHEvZDZZUlNsa1lXUUt2TDBmUzRja0FtcUJkRVk2Q0s3ajZyYnphZGJIVHB2SXdQWGgKWVNTOWlJOFQxKzRTYTZDOTljcnRUR2ZZb21BL2hFWVFPTnVSVk42VUl5c1Bob2RCVndsVTJEV1pYNndyZm5DZApNOFBCajNHSXVV
[...]
+ " \"crt\": " +
+
"\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURzakNDQXBvQ0NRRHpibHduYzNBLzVqQU5CZ2txaGtpRzl3MEJBUXNGQURDQm1URUxNQWtHQTFVRUJoTUMKVlZNeEN6QUpCZ05WQkFnTUFrTlBNUXd3Q2dZRFZRUUhEQU5FUlU0eER6QU5CZ05WQkFvTUJrRndZV05vWlRFTApNQWtHQTFVRUN3d0NWRU14TGpBc0JnTlZCQU1NSlNvdWFIUjBjSE10ZG1Gc2FXUXRkR1Z6ZEM1MGFHVmpaRzR1ClpYaGhiWEJzWlM1amIyMHhJVEFmQmdrcWhraUc5dzBCQ1FFV0VuUmpRSE5sYkdZdGMybG5ibVZrTG1OdmJUQWcKRncweE9UQXpNRFV4TnpRMk1UTmFHQTh5TVRFNU1ESXdPVEUzTkRZeE0xb3dnWmt4Q3pBSkJnTlZCQVlUQWxWVApNUXN3Q1FZRFZR
[...]
+ " },\n" +
+ " \"hostname\":
\"*.https-valid-test.thecdn.example.com\"\n" +
+ " }";
+ private final static String EXPIRED_CERT_DATA =
+ " {\n" +
+ " \"deliveryservice\": \"http-to-https-test\",\n" +
+ " \"certificate\": {\n" +
+ " \"comment\" : \"The following self signed
certificate which expired on 3/5/2019 \",\n" +
+ " \"key\": " +
+
"\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2paZ0xpTHNRS0o0UXgKS0F5WnFqT0NOL2lXUmgzdXkyZCtyd0J1VWJ2NTBIVEk0QUdnOHE2eG9pbzNtZHFHZXRXdVJIemUvSmQ5ckxJSQpvWDFXOGNFeTVybW0wV0xXYnJDbzlJQUE5K0dHcTgyNXUreGplemdiSHg3TEt1N3lqdUJVVjI4SyszTXNOQUhGCjZsemZNdTJ5VEFUMExvU25waDRWeHZLRDlzM05rNzdtaW5vcVR0aGFlSldxWXVEQlZ4WTBNS3JGbWJuQkEybkcKV295ZHIrelBROHB2N3Fka0FmcXlnZ0loWjloM0JBNnVESHRtcDNueWlsc2ZSMHpGeFR4QnRhaVhTbnRZdXM4NAphVUNwaHVjWkIvY
[...]
+ " \"crt\": " +
+
"\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxakNDQXBJQ0NRQ0Q1OFZ3U2IrNWJqQU5CZ2txaGtpRzl3MEJBUXNGQURDQmxqRUxNQWtHQTFVRUJoTUMKVlZNeEN6QUpCZ05WQkFnTUFrTlBNUXd3Q2dZRFZRUUhEQU5FUlU0eER6QU5CZ05WQkFvTUJrRndZV05vWlRFTApNQWtHQTFVRUN3d0NWRU14TURBdUJnTlZCQU1NSnlvdWFIUjBjQzEwYnkxb2RIUndjeTEwWlhOMExuUm9aV05rCmJpNWxlR0Z0Y0d4bExtTnZiVEVjTUJvR0NTcUdTSWIzRFFFSkFSWU5kR05BWVhCaFkyaGxMbU52YlRBZUZ3MHgKT1RBek1EUXlNekl6TkRKYUZ3MHhPVEF6TURVeU16SXpOREphTUlHV01Rc3dDUVlEVlFRR0V3SlZVekVMTUFrRwpBMVVFQ0F3Q1EwO
[...]
+ " },\n" +
+ " \"hostname\":
\"*.http-to-https-test.thecdn.example.com\"\n" +
+ " }";
+ private final static String MOD_MISS_CERT_DATA =
+ " {\n" +
+ " \"deliveryservice\": \"https-mod-miss\",\n" +
+ " \"certificate\": {\n" +
+ " \"comment\" : \"The following certificate and
key are for the same subject but have " +
+ "mismatched modulus between the private and
public keys\",\n" +
+ " \"key\": " +
+
"\"LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQ3lIMW91SmpXcE5SeVAKdE1nVDM3emhCc0tYM1VhTWlhdm5CUHNJUDhidmdaUnNicFNYeVVSdTJsaWdsYWZpWlAybTZxZE1LZ3BHcUo4ZgpwZHQzODFsMHduakI5YURleU92NzZrcnJBdzhlME9VOW9ZU0Q1VVlWMU11M0I4ZTV1UGFLYkdNcDZvc3o1WDJSCmlDYnRjcTAvUzJaeWhWNXIvRkJqNUtsN2I0UlBNTjdPVXJNVW5LcHlXZ2hZNzdXOXpzVm96cTg5cldOL0g5VUEKZjFMRmFSdU1mckNvYVVKZHRKZEIyY2FGblkwWmI1MDhzcmFGWXplaFUvK3FjZE9heWtVa0hMMTVweDVFbS9mOQpYVlpNcmVJeFlob
[...]
+ " \"crt\": " +
+
"\"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURyRENDQXBRQ0NRQ0pZQWd0a1JtN3hUQU5CZ2txaGtpRzl3MEJBUXNGQURDQmxqRUxNQWtHQTFVRUJoTUMKVlZNeEN6QUpCZ05WQkFnTUFrTlBNUXd3Q2dZRFZRUUhEQU5FUlU0eER6QU5CZ05WQkFvTUJrRndZV05vWlRFTApNQWtHQTFVRUN3d0NWRU14S3pBcEJnTlZCQU1NSWlvdWFIUjBjQzF0YjJRdGJXbHpjeTUwYUdWalpHNHVaWGhoCmJYQnNaUzVqYjIweElUQWZCZ2txaGtpRzl3MEJDUUVXRW5SalFITmxiR1l0YzJsbmJtVmtMbU52YlRBZ0Z3MHgKT1RBek1EVXhPREE0TkRsYUdBOHlNVEU1TURJd09URTRNRGcwT1Zvd2daWXhDekFKQmdOVkJBWVRBbFZUTVFzdwpDUVlEVlFRSURBS
[...]
+ " },\n" +
+ " \"hostname\":
\"*.http-mod-miss.thecdn.example.com\"\n" +
+ " }";
@Before
+ @PrepareForTest({Instant.class})
public void before() throws Exception {
- PrivateKeyDecoder privateKeyDecoder =
mock(PrivateKeyDecoder.class);
- CertificateDecoder certificateDecoder =
mock(CertificateDecoder.class);
-
Certificate certificate = new Certificate();
certificate.setCrt("encodedchaindata");
certificate.setKey("encodedkeydata");
-
certificateData = new CertificateData();
certificateData.setCertificate(certificate);
certificateData.setDeliveryservice("some-delivery-service");
certificateData.setHostname("example.com");
+ certificateDataConverter = new CertificateDataConverter();
+ }
- privateKey = mock(PrivateKey.class);
-
when(privateKeyDecoder.decode("encodedkeydata")).thenReturn(privateKey);
-
-
when(certificateDecoder.doubleDecode("encodedchaindata")).thenReturn(Arrays.asList(
- "encodedcert1", "encodedcert2", "encodedcert3"
- ));
-
- x509Certificate1 = mock(X509Certificate.class);
- x509Certificate2 = mock(X509Certificate.class);
- x509Certificate3 = mock(X509Certificate.class);
-
-
when(certificateDecoder.toCertificate("encodedcert1")).thenReturn(x509Certificate1);
-
when(certificateDecoder.toCertificate("encodedcert2")).thenReturn(x509Certificate2);
-
when(certificateDecoder.toCertificate("encodedcert3")).thenReturn(x509Certificate3);
+ @Test
+ public void itConvertsValidCertToHandshakeData() throws Exception {
+ try {
+ certificateData = ((CertificateData) new
ObjectMapper().readValue(VALID_CERT_DATA,
+ new TypeReference<CertificateData>() {
}));
+ } catch (Exception e) {
+ fail("Failed parsing json data: " + e.getMessage());
+ }
+ HandshakeData handshakeData =
certificateDataConverter.toHandshakeData(certificateData);
+ assertThat(handshakeData, notNullValue());
+ assertThat(handshakeData.getDeliveryService(),
equalTo(certificateData.getDeliveryservice()));
+ assertThat(handshakeData.getHostname(),
equalTo(certificateData.getHostname()));
+ }
- certificateDataConverter = new CertificateDataConverter();
-
certificateDataConverter.setCertificateDecoder(certificateDecoder);
-
certificateDataConverter.setPrivateKeyDecoder(privateKeyDecoder);
+ @Test
+ public void itRejectsExpiredCert() throws Exception {
+ try {
+ certificateData = ((CertificateData) new
ObjectMapper().readValue(EXPIRED_CERT_DATA,
+ new TypeReference<CertificateData>() {
}));
+ } catch (Exception e) {
+ fail("Failed parsing json data: " + e.getMessage());
+ }
+ HandshakeData handshakeData =
certificateDataConverter.toHandshakeData(certificateData);
+ assertThat(handshakeData, nullValue());
}
@Test
- public void itConvertsToHandshakeData() throws Exception {
+ public void itRejectsModulusMismatch() throws Exception {
+ try {
+ certificateData = ((CertificateData) new
ObjectMapper().readValue(MOD_MISS_CERT_DATA,
+ new TypeReference<CertificateData>() {
}));
+ } catch (Exception e) {
+ fail("Failed parsing json data: " + e.getMessage());
+ }
HandshakeData handshakeData =
certificateDataConverter.toHandshakeData(certificateData);
+ assertThat(handshakeData, nullValue());
+ }
- assertThat(handshakeData.getDeliveryService(),
equalTo("some-delivery-service"));
- assertThat(handshakeData.getHostname(), equalTo("example.com"));
- assertThat(handshakeData.getPrivateKey(), equalTo(privateKey));
- assertThat(handshakeData.getCertificateChain(), equalTo(new
X509Certificate[]{x509Certificate1, x509Certificate2, x509Certificate3}));
+ @Test
+ public void itRejectsSubjectMismatch() throws Exception {
+ try {
+ certificateData = ((CertificateData) new
ObjectMapper().readValue(SUBJECT_MISS_CERT_DATA,
+ new TypeReference<CertificateData>() {
}));
+ } catch (Exception e) {
+ fail("Failed parsing json data: " + e.getMessage());
+ }
+ HandshakeData handshakeData =
certificateDataConverter.toHandshakeData(certificateData);
+ assertThat(handshakeData, nullValue());
}
}
diff --git
a/traffic_router/core/src/test/resources/api/1.3/cdns/name/thecdn/sslkeys-missing-1.json
b/traffic_router/core/src/test/resources/api/1.3/cdns/name/thecdn/sslkeys-missing-1.json
index 2d6c882..f0a8f23 100644
---
a/traffic_router/core/src/test/resources/api/1.3/cdns/name/thecdn/sslkeys-missing-1.json
+++
b/traffic_router/core/src/test/resources/api/1.3/cdns/name/thecdn/sslkeys-missing-1.json
@@ -22,7 +22,7 @@
"deliveryservice": "http-to-https-test",
"certificate": {
"comment" : "The following is just a self signed certificate and key
to use for testing this is NOT private data from a CA",
- "key":
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBc2Y0NnV5OGJ2\nQk5rMGhCaEVsbHdGT0dqREh6M1hJY1hteDRVNThNZG9Fa1JId0VTCjVONnd3NFV6bDAvRDcyMlJV\nODlMeHB4bldvclJmdVZNQldnOGVFcXBUb2NUS2NOZHhtZmdEUWZTcTZ1ODNTWkUKTmFCZFArK2g5\nYTJJRFZXWGFldVRhcVA3Q3lVVG52Sld5Mm1JalJWZkRGQWRWWHNhU1M4RGRYUWdibEJTelJ6NwpL\nMXFHVWt1RlZQc0R0ODZBYVF3TnN5R2ZDN3ltcUkzNU1FQ3hTdzNPd2lXSlAyZTg3U2E5UG9Pdjcr\nZUs2NVJnCmM3dzNkSXQxZUlyS3B6OWpQV1RPTkJOK0JhWFdvcHNXZ3UvdVd4Q1pnUk9qaXBWVUFK\nNHhrNFRG
[...]
+ "key":
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBMHBBWXlmamRz\nZ0NSL0ZXeEQvWE1vbDNwYjRWazlHSFdqTnR4bkdvWHA3OGxxM2p1CitLd012ZlU2ZDVScmdXbHp6\nMlJjZnFhMHdjbkYrNU1abkdkbzRGaGwycnhnT3ZBV2NSb3ZXS3BUaXNUUkcrQXcKbnFneUZTWjNT\nalhUcE9YUjV4dUJJOWI1c3ZIN3RhbzdBcWFLR2I0V3d1TnA3cTZzcUtRYlRxaUlXcE9JQWtFKwpS\nVnJXblBVdHRvaHlTV08yL2dIbDQ2NlU1S0czdC9TU3lqZVRPZ1ZFU0xoQUhlWlk2dExyTGd1YmdM\nanlLVWE5CkpDcWJLa1laZ2UrdWlhKzVVMzZ6alhYRUdBUEdwWWNrM2Zqb1pYN01zM3hkUzV6Zit6\nUDQzSnVV
[...]
"crt":
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZuakNDQTRhZ0F3SUJBZ0lDRUFFd0RRWUpL\nb1pJaHZjTkFRRUxCUUF3WkRFTE1Ba0dBMVVFQmhNQ1ZWTXgKRVRBUEJnTlZCQWdNQ0VOdmJHOXlZ\nV1J2TVJBd0RnWURWUVFLREFkRGIyMWpZWE4wTVE0d0RBWURWUVFMREFWSgpVRU5FVGpFZ01CNEdB\nMVVFQXd3WFZHVnpkR2x1WnlCSmJuUmxjbTFsWkdsaGRHVWdRMEV3SGhjTk1UWXdPVEl6Ck1qSXpO\nREl4V2hjTk16VXhNVEl6TWpJek5ESXhXakNCaFRFTE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVkJB\nZ1QKQ0VOdmJHOXlZV1J2TVE4d0RRWURWUVFIRXdaRVpXNTJaWEl4RURBT0JnTlZCQW9UQjBOdmJX\nTmhjM1F4
[...]
},
"hostname": "*.http-to-https-test.thecdn.example.com"
diff --git
a/traffic_router/core/src/test/resources/api/1.3/cdns/name/thecdn/sslkeys.json
b/traffic_router/core/src/test/resources/api/1.3/cdns/name/thecdn/sslkeys.json
index 804c83a..c5a9b3d 100644
---
a/traffic_router/core/src/test/resources/api/1.3/cdns/name/thecdn/sslkeys.json
+++
b/traffic_router/core/src/test/resources/api/1.3/cdns/name/thecdn/sslkeys.json
@@ -31,7 +31,7 @@
"deliveryservice": "http-to-https-test",
"certificate": {
"comment" : "The following is just a self signed certificate and key
to use for testing this is NOT private data from a CA",
- "key":
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBc2Y0NnV5OGJ2\nQk5rMGhCaEVsbHdGT0dqREh6M1hJY1hteDRVNThNZG9Fa1JId0VTCjVONnd3NFV6bDAvRDcyMlJV\nODlMeHB4bldvclJmdVZNQldnOGVFcXBUb2NUS2NOZHhtZmdEUWZTcTZ1ODNTWkUKTmFCZFArK2g5\nYTJJRFZXWGFldVRhcVA3Q3lVVG52Sld5Mm1JalJWZkRGQWRWWHNhU1M4RGRYUWdibEJTelJ6NwpL\nMXFHVWt1RlZQc0R0ODZBYVF3TnN5R2ZDN3ltcUkzNU1FQ3hTdzNPd2lXSlAyZTg3U2E5UG9Pdjcr\nZUs2NVJnCmM3dzNkSXQxZUlyS3B6OWpQV1RPTkJOK0JhWFdvcHNXZ3UvdVd4Q1pnUk9qaXBWVUFK\nNHhrNFRG
[...]
+ "key":
"LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBMHBBWXlmamRz\nZ0NSL0ZXeEQvWE1vbDNwYjRWazlHSFdqTnR4bkdvWHA3OGxxM2p1CitLd012ZlU2ZDVScmdXbHp6\nMlJjZnFhMHdjbkYrNU1abkdkbzRGaGwycnhnT3ZBV2NSb3ZXS3BUaXNUUkcrQXcKbnFneUZTWjNT\nalhUcE9YUjV4dUJJOWI1c3ZIN3RhbzdBcWFLR2I0V3d1TnA3cTZzcUtRYlRxaUlXcE9JQWtFKwpS\nVnJXblBVdHRvaHlTV08yL2dIbDQ2NlU1S0czdC9TU3lqZVRPZ1ZFU0xoQUhlWlk2dExyTGd1YmdM\nanlLVWE5CkpDcWJLa1laZ2UrdWlhKzVVMzZ6alhYRUdBUEdwWWNrM2Zqb1pYN01zM3hkUzV6Zit6\nUDQzSnVV
[...]
"crt":
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZuakNDQTRhZ0F3SUJBZ0lDRUFFd0RRWUpL\nb1pJaHZjTkFRRUxCUUF3WkRFTE1Ba0dBMVVFQmhNQ1ZWTXgKRVRBUEJnTlZCQWdNQ0VOdmJHOXlZ\nV1J2TVJBd0RnWURWUVFLREFkRGIyMWpZWE4wTVE0d0RBWURWUVFMREFWSgpVRU5FVGpFZ01CNEdB\nMVVFQXd3WFZHVnpkR2x1WnlCSmJuUmxjbTFsWkdsaGRHVWdRMEV3SGhjTk1UWXdPVEl6Ck1qSXpO\nREl4V2hjTk16VXhNVEl6TWpJek5ESXhXakNCaFRFTE1Ba0dBMVVFQmhNQ1ZWTXhFVEFQQmdOVkJB\nZ1QKQ0VOdmJHOXlZV1J2TVE4d0RRWURWUVFIRXdaRVpXNTJaWEl4RURBT0JnTlZCQW9UQjBOdmJX\nTmhjM1F4
[...]
},
"hostname": "*.http-to-https-test.thecdn.example.com"
diff --git a/traffic_router/shared/pom.xml b/traffic_router/shared/pom.xml
index 59a3819..7f0af11 100644
--- a/traffic_router/shared/pom.xml
+++ b/traffic_router/shared/pom.xml
@@ -109,20 +109,6 @@ under the License.
<version>1.57</version>
</dependency>
<dependency>
- <groupId>dnsjava</groupId>
- <artifactId>dnsjava</artifactId>
- <version>2.1.7</version>
- </dependency>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- </dependency>
- <dependency>
- <groupId>org.hamcrest</groupId>
- <artifactId>hamcrest-all</artifactId>
- <scope>compile</scope>
- </dependency>
- <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
diff --git a/traffic_router/shared/src/test/java/secure/BindPrivateKeyTest.java
b/traffic_router/shared/src/test/java/secure/BindPrivateKeyTest.java
index 9da5145..9d149ec 100644
--- a/traffic_router/shared/src/test/java/secure/BindPrivateKeyTest.java
+++ b/traffic_router/shared/src/test/java/secure/BindPrivateKeyTest.java
@@ -19,6 +19,7 @@ import
com.comcast.cdn.traffic_control.traffic_router.secure.BindPrivateKey;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@@ -69,7 +70,7 @@ public class BindPrivateKeyTest {
"Exponent2: " +
encode(privateCrtKey.getPrimeExponentQ())+ "\n" +
"Coefficient: " +
encode(privateCrtKey.getCrtCoefficient())+ "\n";
- privateKey = mock(PrivateKey.class);
+ privateKey = Mockito.mock(PrivateKey.class);
KeyFactory keyFactory = PowerMockito.mock(KeyFactory.class);
PowerMockito.mockStatic(KeyFactory.class);
diff --git
a/traffic_router/shared/src/test/java/shared/DeliveryServiceCertificatesTest.java
b/traffic_router/shared/src/test/java/shared/DeliveryServiceCertificatesTest.java
index d78af4a..6670b0a 100644
---
a/traffic_router/shared/src/test/java/shared/DeliveryServiceCertificatesTest.java
+++
b/traffic_router/shared/src/test/java/shared/DeliveryServiceCertificatesTest.java
@@ -21,6 +21,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@@ -41,7 +42,7 @@ import static
org.powermock.api.mockito.PowerMockito.mockStatic;
public class DeliveryServiceCertificatesTest {
@Before
public void before() throws Exception {
- mockStatic(System.class);
+ PowerMockito.mockStatic(System.class);
when(System.currentTimeMillis()).thenReturn(1234L);
}