diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/BaseCertificateVerifier.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/BaseCertificateVerifier.java new file mode 100644 index 0000000000..0f9655ad94 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/BaseCertificateVerifier.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2020 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium; + +import java.net.InetSocketAddress; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; + +import javax.security.auth.x500.X500Principal; + +import org.eclipse.californium.scandium.dtls.AlertMessage; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertDescription; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertLevel; +import org.eclipse.californium.scandium.dtls.CertificateMessage; +import org.eclipse.californium.scandium.dtls.CertificateType; +import org.eclipse.californium.scandium.dtls.CertificateVerificationResult; +import org.eclipse.californium.scandium.dtls.ConnectionId; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; +import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; +import org.eclipse.californium.scandium.util.ServerNames; +import org.eclipse.leshan.core.util.X509CertUtil; + +public abstract class BaseCertificateVerifier implements NewAdvancedCertificateVerifier { + + private final List supportedCertificateType = Arrays.asList(CertificateType.X_509); + + @Override + public List getSupportedCertificateType() { + return supportedCertificateType; + } + + @Override + public void setResultHandler(HandshakeResultHandler resultHandler) { + // we don't use async mode. + } + + @Override + public List getAcceptedIssuers() { + return null; + } + + @Override + public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, + Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) { + try { + CertPath validatedCertPath = verifyCertificate(clientUsage, message, session); + return new CertificateVerificationResult(cid, validatedCertPath, null); + } catch (HandshakeException exception) { + return new CertificateVerificationResult(cid, exception, null); + } + } + + protected abstract CertPath verifyCertificate(Boolean clientUsage, CertificateMessage message, DTLSSession session) + throws HandshakeException; + + /** + * Ensure that chain is not empty + */ + protected void validateCertificateChainNotEmpty(CertPath certChain, InetSocketAddress foreignPeerAddress) + throws HandshakeException { + if (certChain.getCertificates().size() == 0) { + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE, + foreignPeerAddress); + throw new HandshakeException("Certificate chain could not be validated : server cert chain is empty", + alert); + } + } + + /** + * Ensure that received certificate is x509 certificate + */ + protected X509Certificate validateReceivedCertificateIsSupported(CertPath certChain, + InetSocketAddress foreignPeerAddress) throws HandshakeException { + Certificate receivedServerCertificate = certChain.getCertificates().get(0); + if (!(receivedServerCertificate instanceof X509Certificate)) { + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.UNSUPPORTED_CERTIFICATE, + foreignPeerAddress); + throw new HandshakeException("Certificate chain could not be validated - unknown certificate type", alert); + } + return (X509Certificate) receivedServerCertificate; + } + + protected void validateSubject(final DTLSSession session, final X509Certificate receivedServerCertificate) + throws HandshakeException { + final InetSocketAddress peerSocket = session.getPeer(); + + if (X509CertUtil.matchSubjectDnsName(receivedServerCertificate, peerSocket.getHostName())) + return; + + if (X509CertUtil.matchSubjectInetAddress(receivedServerCertificate, peerSocket.getAddress())) + return; + + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE, session.getPeer()); + throw new HandshakeException( + "Certificate chain could not be validated - server identity does not match certificate", alert); + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaConstraintCertificateVerifier.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaConstraintCertificateVerifier.java new file mode 100644 index 0000000000..dc08211dcf --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaConstraintCertificateVerifier.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2020 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium; + +import java.security.GeneralSecurityException; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import org.eclipse.californium.scandium.dtls.AlertMessage; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertDescription; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertLevel; +import org.eclipse.californium.scandium.dtls.CertificateMessage; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.leshan.core.util.Validate; + +/** + * This class implements Certificate Usage (0) - CA Constraint + * + * From RFC 6698: + * + *
+ * 0 -- Certificate usage 0 is used to specify a CA certificate, or
+ *       the public key of such a certificate, that MUST be found in any of
+ *       the PKIX certification paths for the end entity certificate given
+ *       by the server in TLS.  This certificate usage is sometimes
+ *       referred to as "CA constraint" because it limits which CA can be
+ *       used to issue certificates for a given service on a host.  The
+ *       presented certificate MUST pass PKIX certification path
+ *       validation, and a CA certificate that matches the TLSA record MUST
+ *       be included as part of a valid certification path.  Because this
+ *       certificate usage allows both trust anchors and CA certificates,
+ *       the certificate might or might not have the basicConstraints
+ *       extension present.
+ * 
+ * + * For details about Certificate Usage please see: + * rfc6698#section-2.1.1 - The Certificate Usage Field + */ +public class CaConstraintCertificateVerifier extends BaseCertificateVerifier { + + private final Certificate caCertificate; + private final X509Certificate[] trustedCertificates; + + public CaConstraintCertificateVerifier(Certificate caCertificate, X509Certificate[] trustedCertificates) { + Validate.notNull(caCertificate); + Validate.notNull(trustedCertificates); + Validate.notEmpty(trustedCertificates); + this.caCertificate = caCertificate; + this.trustedCertificates = trustedCertificates; + } + + @Override + public CertPath verifyCertificate(Boolean clientUsage, CertificateMessage message, DTLSSession session) + throws HandshakeException { + CertPath messageChain = message.getCertificateChain(); + + validateCertificateChainNotEmpty(messageChain, session.getPeer()); + X509Certificate receivedServerCertificate = validateReceivedCertificateIsSupported(messageChain, + session.getPeer()); + + // - must do PKIX validation with trustStore + CertPath certPath; + try { + certPath = X509Util.applyPKIXValidation(messageChain, trustedCertificates); + } catch (GeneralSecurityException e) { + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE, + session.getPeer()); + throw new HandshakeException("Certificate chain could not be validated", alert, e); + } + + // - must check that given certificate is part of certPath + if (!certPath.getCertificates().contains(caCertificate)) { + // No match found -> throw exception about it + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE, + session.getPeer()); + throw new HandshakeException("Certificate chain could not be validated", alert); + } + + // - validate server name + validateSubject(session, receivedServerCertificate); + + return certPath; + } + +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java index 0c3b1d8d0d..d0f4dfb55e 100644 --- a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/CaliforniumEndpointsManager.java @@ -30,6 +30,7 @@ import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.elements.Connector; import org.eclipse.californium.elements.auth.RawPublicKeyIdentity; +import org.eclipse.californium.elements.util.CertPathUtil; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig.Builder; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; @@ -40,6 +41,7 @@ import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.client.servers.ServerIdentity.Role; import org.eclipse.leshan.client.servers.ServerInfo; +import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.californium.EndpointContextUtil; import org.eclipse.leshan.core.californium.EndpointFactory; @@ -123,9 +125,65 @@ public synchronized ServerIdentity createEndpoint(ServerInfo serverInfo) { // set identity newBuilder.setIdentity(serverInfo.privateKey, new Certificate[] { serverInfo.clientCertificate }); - // set X509 verifier - newBuilder.setAdvancedCertificateVerifier( - new DefaultLeshanCertificateVerifier(serverInfo.serverCertificate)); + // LWM2M v1.1.1 - 5.2.8.7. Certificate Usage Field + // + // 0: Certificate usage 0 ("CA constraint") + // - trustStore is combination of client's configured trust store and provided certificate in server + // info + // - must do PKIX validation with trustStore to build certPath + // - must check that given certificate is part of certPath + // - validate server name + // + // 1: Certificate usage 1 ("service certificate constraint") + // - trustStore is client's configured trust store + // - must do PKIX validation with trustStore + // - target certificate must match what is provided certificate in server info + // - validate server name + // + // 2: Certificate usage 2 ("trust anchor assertion") + // - trustStore is only the provided certificate in server info + // - must do PKIX validation with trustStore + // - validate server name + // + // 3: Certificate usage 3 ("domain-issued certificate") (default mode if missing) + // - no trustStore used in this mode + // - target certificate must match what is provided certificate in server info + // - validate server name + + CertificateUsage certificateUsage = serverInfo.certificateUsage != null ? serverInfo.certificateUsage + : CertificateUsage.DOMAIN_ISSUER_CERTIFICATE; + + if (certificateUsage == CertificateUsage.CA_CONSTRAINT) { + X509Certificate[] trustedCertificates = null; + // - trustStore is combination of client's configured trust store and provided certificate in server + // info + ArrayList newTrustedCertificatesList = new ArrayList<>(); + if (this.trustStore != null) { + newTrustedCertificatesList.addAll(CertPathUtil.toX509CertificatesList(this.trustStore)); + } + newTrustedCertificatesList.add((X509Certificate) serverInfo.serverCertificate); + trustedCertificates = newTrustedCertificatesList.toArray(new X509Certificate[0]); + newBuilder.setAdvancedCertificateVerifier( + new CaConstraintCertificateVerifier(serverInfo.serverCertificate, trustedCertificates)); + } else if (certificateUsage == CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT) { + X509Certificate[] trustedCertificates = null; + + // - trustStore is client's configured trust store + if (this.trustStore != null) { + trustedCertificates = CertPathUtil.toX509CertificatesList(this.trustStore) + .toArray(new X509Certificate[0]); + } + + newBuilder.setAdvancedCertificateVerifier(new ServiceCertificateConstraintCertificateVerifier( + serverInfo.serverCertificate, trustedCertificates)); + } else if (certificateUsage == CertificateUsage.TRUST_ANCHOR_ASSERTION) { + newBuilder.setAdvancedCertificateVerifier(new TrustAnchorAssertionCertificateVerifier( + (X509Certificate) serverInfo.serverCertificate)); + } else if (certificateUsage == CertificateUsage.DOMAIN_ISSUER_CERTIFICATE) { + newBuilder.setAdvancedCertificateVerifier( + new DomainIssuerCertificateVerifier(serverInfo.serverCertificate)); + } + serverIdentity = Identity.x509(serverInfo.getAddress(), EndpointContextUtil.extractCN( ((X509Certificate) serverInfo.serverCertificate).getSubjectX500Principal().getName())); filterCipherSuites(newBuilder, dtlsConfigbuilder.getIncompleteConfig().getSupportedCipherSuites(), diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/DomainIssuerCertificateVerifier.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/DomainIssuerCertificateVerifier.java new file mode 100644 index 0000000000..4c9e949490 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/DomainIssuerCertificateVerifier.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2020 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium; + +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import org.eclipse.californium.scandium.dtls.AlertMessage; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertDescription; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertLevel; +import org.eclipse.californium.scandium.dtls.CertificateMessage; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.leshan.core.util.Validate; + +/** + * This class implements Certificate Usage (3) - Domain Issuer Certificate + * + * From RFC 6698: + * + *
+ * 3 -- Certificate usage 3 is used to specify a certificate, or the
+ *       public key of such a certificate, that MUST match the end entity
+ *       certificate given by the server in TLS.  This certificate usage is
+ *       sometimes referred to as "domain-issued certificate" because it
+ *       allows for a domain name administrator to issue certificates for a
+ *       domain without involving a third-party CA.  The target certificate
+ *       MUST match the TLSA record.  The difference between certificate
+ *       usage 1 and certificate usage 3 is that certificate usage 1
+ *       requires that the certificate pass PKIX validation, but PKIX
+ *       validation is not tested for certificate usage 3.
+ * 
+ * + * For details about Certificate Usage please see: + * rfc6698#section-2.1.1 - The Certificate Usage Field + */ +public class DomainIssuerCertificateVerifier extends BaseCertificateVerifier { + private final Certificate domainIssuerCertificate; + + public DomainIssuerCertificateVerifier(Certificate domainIssuerCertificate) { + Validate.notNull(domainIssuerCertificate); + this.domainIssuerCertificate = domainIssuerCertificate; + } + + @Override + public CertPath verifyCertificate(Boolean clientUsage, CertificateMessage message, DTLSSession session) + throws HandshakeException { + CertPath messageChain = message.getCertificateChain(); + + validateCertificateChainNotEmpty(messageChain, session.getPeer()); + + X509Certificate receivedServerCertificate = validateReceivedCertificateIsSupported(messageChain, + session.getPeer()); + + // - target certificate must match what is provided certificate in server info + if (!domainIssuerCertificate.equals(receivedServerCertificate)) { + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE, + session.getPeer()); + throw new HandshakeException("Certificate chain could not be validated", alert); + } + + // - validate server name + validateSubject(session, receivedServerCertificate); + + return messageChain; + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/ServiceCertificateConstraintCertificateVerifier.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/ServiceCertificateConstraintCertificateVerifier.java new file mode 100644 index 0000000000..4b75b0530a --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/ServiceCertificateConstraintCertificateVerifier.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2020 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium; + +import java.security.GeneralSecurityException; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import org.eclipse.californium.scandium.dtls.AlertMessage; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertDescription; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertLevel; +import org.eclipse.californium.scandium.dtls.CertificateMessage; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.leshan.core.util.Validate; + +/** + * This class implements Certificate Usage (1) - Service Certificate Constraint + * + * From RFC 6698: + * + *
+ * 1 -- Certificate usage 1 is used to specify an end entity
+ *       certificate, or the public key of such a certificate, that MUST be
+ *       matched with the end entity certificate given by the server in
+ *       TLS.  This certificate usage is sometimes referred to as "service
+ *       certificate constraint" because it limits which end entity
+ *       certificate can be used by a given service on a host.  The target
+ *       certificate MUST pass PKIX certification path validation and MUST
+ *       match the TLSA record.
+ * 
+ * + * For details about Certificate Usage please see: + * rfc6698#section-2.1.1 - The Certificate Usage Field + */ +public class ServiceCertificateConstraintCertificateVerifier extends BaseCertificateVerifier { + + private final Certificate serviceCertificate; + private final X509Certificate[] trustedCertificates; + + public ServiceCertificateConstraintCertificateVerifier(Certificate serviceCertificate, + X509Certificate[] trustedCertificates) { + Validate.notNull(serviceCertificate); + Validate.notNull(trustedCertificates); + Validate.notEmpty(trustedCertificates); + this.serviceCertificate = serviceCertificate; + this.trustedCertificates = trustedCertificates; + } + + @Override + public CertPath verifyCertificate(Boolean clientUsage, CertificateMessage message, DTLSSession session) + throws HandshakeException { + CertPath messageChain = message.getCertificateChain(); + + validateCertificateChainNotEmpty(messageChain, session.getPeer()); + + X509Certificate receivedServerCertificate = validateReceivedCertificateIsSupported(messageChain, + session.getPeer()); + + // - must do PKIX validation with trustStore + CertPath certPath; + try { + certPath = X509Util.applyPKIXValidation(messageChain, trustedCertificates); + } catch (GeneralSecurityException e) { + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE, + session.getPeer()); + throw new HandshakeException("Certificate chain could not be validated", alert, e); + } + + // - target certificate must match what is provided certificate in server info + if (!serviceCertificate.equals(receivedServerCertificate)) { + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE, + session.getPeer()); + throw new HandshakeException("Certificate chain could not be validated", alert); + } + + // - validate server name + validateSubject(session, receivedServerCertificate); + + return certPath; + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/TrustAnchorAssertionCertificateVerifier.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/TrustAnchorAssertionCertificateVerifier.java new file mode 100644 index 0000000000..4aba4a7797 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/TrustAnchorAssertionCertificateVerifier.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2020 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium; + +import java.security.GeneralSecurityException; +import java.security.cert.CertPath; +import java.security.cert.X509Certificate; + +import org.eclipse.californium.scandium.dtls.AlertMessage; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertDescription; +import org.eclipse.californium.scandium.dtls.AlertMessage.AlertLevel; +import org.eclipse.californium.scandium.dtls.CertificateMessage; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.leshan.core.util.Validate; + +/** + * This class implements Certificate Usage (2) - Trust Anchor Assertion + * + * From RFC 6698: + * + *
+ * 2 -- Certificate usage 2 is used to specify a certificate, or the
+ *       public key of such a certificate, that MUST be used as the trust
+ *       anchor when validating the end entity certificate given by the
+ *       server in TLS.  This certificate usage is sometimes referred to as
+ *       "trust anchor assertion" and allows a domain name administrator to
+ *       specify a new trust anchor -- for example, if the domain issues
+ *       its own certificates under its own CA that is not expected to be
+ *       in the end users' collection of trust anchors.  The target
+ *       certificate MUST pass PKIX certification path validation, with any
+ *       certificate matching the TLSA record considered to be a trust
+ *       anchor for this certification path validation.
+ * 
+ * + * For details about Certificate Usage please see: + * rfc6698#section-2.1.1 - The Certificate Usage Field + */ +public class TrustAnchorAssertionCertificateVerifier extends BaseCertificateVerifier { + + private final X509Certificate[] trustAnchor; + + public TrustAnchorAssertionCertificateVerifier(X509Certificate trustAnchor) { + Validate.notNull(trustAnchor); + this.trustAnchor = new X509Certificate[] { trustAnchor }; + } + + @Override + public CertPath verifyCertificate(Boolean clientUsage, CertificateMessage message, DTLSSession session) + throws HandshakeException { + CertPath messageChain = message.getCertificateChain(); + + validateCertificateChainNotEmpty(messageChain, session.getPeer()); + X509Certificate receivedServerCertificate = validateReceivedCertificateIsSupported(messageChain, + session.getPeer()); + + // - must do PKIX validation with trustStore + CertPath certPath; + try { + certPath = X509Util.applyPKIXValidation(messageChain, trustAnchor); + } catch (GeneralSecurityException e) { + AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE, + session.getPeer()); + throw new HandshakeException("Certificate chain could not be validated : server cert chain is empty", + alert); + } + + // - validate server name + validateSubject(session, receivedServerCertificate); + + return certPath; + } +} diff --git a/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/X509Util.java b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/X509Util.java new file mode 100644 index 0000000000..afe6663676 --- /dev/null +++ b/leshan-client-cf/src/main/java/org/eclipse/leshan/client/californium/X509Util.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2020 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.client.californium; + +import java.security.GeneralSecurityException; +import java.security.cert.CertPath; +import java.security.cert.CertPathValidator; +import java.security.cert.CertPathValidatorResult; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.PKIXCertPathValidatorResult; +import java.security.cert.PKIXParameters; +import java.security.cert.TrustAnchor; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.californium.elements.util.CertPathUtil; + +public class X509Util { + + /** + * Validate Certificate Path using Java + * PKIX algorithm which implement RFC3280. + * + * @param certPath a certificate path to validate + * @param trustedCertificates list of trusted certificates + * @return the certpath from certificate to validate (end node) to first trusted anchor included. + */ + public static CertPath applyPKIXValidation(CertPath certPath, X509Certificate[] trustedCertificates) + throws GeneralSecurityException { + // We first need to adapt certificate path to PKIX algorithm : + // - trust anchor must not be in certificate path + // See : https://tools.ietf.org/html/rfc3280#section-6 + CertPath adaptedCertPath = truncateToFirstTrustedCert(certPath, trustedCertificates); + + if (adaptedCertPath.getCertificates().isEmpty()) + throw new IllegalArgumentException( + "Invalid certificate path : certificate path is empty or end node certificate is directly trusted"); + + // Create trustAnchor for PKIX algorithm + Set trustAnchors = new HashSet(); + for (X509Certificate cert : trustedCertificates) { + trustAnchors.add(new TrustAnchor(cert, null)); + } + + // Apply PKIX Validation agorithm + String algorithm = CertPathValidator.getDefaultType(); + CertPathValidator validator = CertPathValidator.getInstance(algorithm); + PKIXParameters params = new PKIXParameters(trustAnchors); + // TODO: implement alternative means of revocation checking ? + params.setRevocationEnabled(false); + CertPathValidatorResult result = validator.validate(adaptedCertPath, params); + + // Create complete validated certification path + X509Certificate trustedCertificate = ((PKIXCertPathValidatorResult) result).getTrustAnchor().getTrustedCert(); + return add(adaptedCertPath, trustedCertificate); + } + + /** + * Truncate certificate path just before the first trusted certificates. + * + * @param certPath a certificate path to eventually truncate + * @param trustedCertificates list of trusted certificates + * @return a certPath without trusted any trusted certificates + * @throws CertificateEncodingException + */ + public static CertPath truncateToFirstTrustedCert(CertPath certPath, X509Certificate[] trustedCertificates) + throws CertificateEncodingException { + List certificates = CertPathUtil.toX509CertificatesList(certPath.getCertificates()); + for (int index = 0; index < certificates.size(); ++index) { + X509Certificate certificate = certificates.get(index); + if (contains(certificate, trustedCertificates)) { + return CertPathUtil.generateCertPath(certificates, index); + } + } + return certPath; + } + + /** + * Add certificate at the end of the given certificate path. + * + * @param certPath a certificate path to extend + * @param certificate to add at the end + * @return a certPath ending with given certificate + * @throws CertificateEncodingException + */ + public static CertPath add(CertPath certPath, X509Certificate certificate) throws CertificateEncodingException { + List certificates = CertPathUtil.toX509CertificatesList(certPath.getCertificates()); + certificates.add(certificate); + return CertPathUtil.generateCertPath(certificates); + } + + /** + * Search certificate in trusts. + * + * @param certificate certificate to search + * @param certificates to search + * @return {@code true}, if certificate is contained, {@code false}, otherwise. + * @throws CertificateEncodingException if encoding a certificate failed! + */ + public static boolean contains(X509Certificate certificate, X509Certificate[] certificates) + throws CertificateEncodingException { + for (X509Certificate trust : certificates) { + if (certificate.equals(trust)) { + return true; + } + } + return false; + } + + /** + * Convert array of {@link Certificate} to array of {@link X509Certificate} + */ + public static X509Certificate[] asX509Certificates(Certificate[] certificates) throws CertificateException { + ArrayList x509Certificates = new ArrayList<>(); + + for (Certificate cert : certificates) { + if (!(cert instanceof X509Certificate)) { + throw new CertificateException(String.format( + "%s certificate format is not supported, Only X.509 certificate is supported", cert.getType())); + } + x509Certificates.add((X509Certificate) cert); + } + + return x509Certificates.toArray(new X509Certificate[0]); + } +} diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java index d85bc54a91..39bfe91611 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/object/Security.java @@ -23,6 +23,7 @@ import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel.Type; @@ -30,6 +31,7 @@ import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteResponse; +import org.eclipse.leshan.core.util.datatype.ULong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,7 +43,8 @@ public class Security extends BaseInstanceEnabler { private static final Logger LOG = LoggerFactory.getLogger(Security.class); private final static List supportedResources = Arrays.asList(SEC_SERVER_URI, SEC_BOOTSTRAP, - SEC_SECURITY_MODE, SEC_PUBKEY_IDENTITY, SEC_SERVER_PUBKEY, SEC_SECRET_KEY, SEC_SERVER_ID); + SEC_SECURITY_MODE, SEC_PUBKEY_IDENTITY, SEC_SERVER_PUBKEY, SEC_SECRET_KEY, SEC_SERVER_ID, + SEC_CERTIFICATE_USAGE); private String serverUri; /* coaps://host:port */ private boolean bootstrapServer; @@ -53,12 +56,15 @@ public class Security extends BaseInstanceEnabler { private Integer shortServerId; + private ULong certificateUsage; + public Security() { // should only be used at bootstrap time + this.certificateUsage = CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code; } public Security(String serverUri, boolean bootstrapServer, int securityMode, byte[] publicKeyOrIdentity, - byte[] serverPublicKey, byte[] secretKey, Integer shortServerId) { + byte[] serverPublicKey, byte[] secretKey, Integer shortServerId, ULong certificateUsage) { this.serverUri = serverUri; this.bootstrapServer = bootstrapServer; this.securityMode = securityMode; @@ -66,13 +72,15 @@ public Security(String serverUri, boolean bootstrapServer, int securityMode, byt this.serverPublicKey = serverPublicKey; this.secretKey = secretKey; this.shortServerId = shortServerId; + this.certificateUsage = certificateUsage; } /** * Returns a new security instance (NoSec) for a bootstrap server. */ public static Security noSecBootstap(String serverUri) { - return new Security(serverUri, true, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], 0); + return new Security(serverUri, true, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], 0, + CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code); } /** @@ -80,7 +88,7 @@ public static Security noSecBootstap(String serverUri) { */ public static Security pskBootstrap(String serverUri, byte[] pskIdentity, byte[] privateKey) { return new Security(serverUri, true, SecurityMode.PSK.code, pskIdentity.clone(), new byte[0], - privateKey.clone(), 0); + privateKey.clone(), 0, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code); } /** @@ -89,7 +97,7 @@ public static Security pskBootstrap(String serverUri, byte[] pskIdentity, byte[] public static Security rpkBootstrap(String serverUri, byte[] clientPublicKey, byte[] clientPrivateKey, byte[] serverPublicKey) { return new Security(serverUri, true, SecurityMode.RPK.code, clientPublicKey.clone(), serverPublicKey.clone(), - clientPrivateKey.clone(), 0); + clientPrivateKey.clone(), 0, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code); } /** @@ -98,7 +106,7 @@ public static Security rpkBootstrap(String serverUri, byte[] clientPublicKey, by public static Security x509Bootstrap(String serverUri, byte[] clientCertificate, byte[] clientPrivateKey, byte[] serverPublicKey) { return new Security(serverUri, true, SecurityMode.X509.code, clientCertificate.clone(), serverPublicKey.clone(), - clientPrivateKey.clone(), 0); + clientPrivateKey.clone(), 0, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code); } /** @@ -106,7 +114,7 @@ public static Security x509Bootstrap(String serverUri, byte[] clientCertificate, */ public static Security noSec(String serverUri, int shortServerId) { return new Security(serverUri, false, SecurityMode.NO_SEC.code, new byte[0], new byte[0], new byte[0], - shortServerId); + shortServerId, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code); } /** @@ -114,7 +122,7 @@ public static Security noSec(String serverUri, int shortServerId) { */ public static Security psk(String serverUri, int shortServerId, byte[] pskIdentity, byte[] privateKey) { return new Security(serverUri, false, SecurityMode.PSK.code, pskIdentity.clone(), new byte[0], - privateKey.clone(), shortServerId); + privateKey.clone(), shortServerId, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code); } /** @@ -123,7 +131,7 @@ public static Security psk(String serverUri, int shortServerId, byte[] pskIdenti public static Security rpk(String serverUri, int shortServerId, byte[] clientPublicKey, byte[] clientPrivateKey, byte[] serverPublicKey) { return new Security(serverUri, false, SecurityMode.RPK.code, clientPublicKey.clone(), serverPublicKey.clone(), - clientPrivateKey.clone(), shortServerId); + clientPrivateKey.clone(), shortServerId, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code); } /** @@ -132,7 +140,17 @@ public static Security rpk(String serverUri, int shortServerId, byte[] clientPub public static Security x509(String serverUri, int shortServerId, byte[] clientCertificate, byte[] clientPrivateKey, byte[] serverPublicKey) { return new Security(serverUri, false, SecurityMode.X509.code, clientCertificate.clone(), - serverPublicKey.clone(), clientPrivateKey.clone(), shortServerId); + serverPublicKey.clone(), clientPrivateKey.clone(), shortServerId, + CertificateUsage.DOMAIN_ISSUER_CERTIFICATE.code); + } + + /** + * Returns a new security instance (X509) for a device management server. + */ + public static Security x509(String serverUri, int shortServerId, byte[] clientCertificate, byte[] clientPrivateKey, + byte[] serverPublicKey, ULong certificateUsage) { + return new Security(serverUri, false, SecurityMode.X509.code, clientCertificate.clone(), + serverPublicKey.clone(), clientPrivateKey.clone(), shortServerId, certificateUsage); } @Override @@ -186,6 +204,12 @@ public WriteResponse write(ServerIdentity identity, int resourceId, LwM2mResourc } shortServerId = ((Long) value.getValue()).intValue(); return WriteResponse.success(); + case SEC_CERTIFICATE_USAGE: // certificate usage + if (value.getType() != Type.UNSIGNED_INTEGER) { + return WriteResponse.badRequest("invalid type"); + } + certificateUsage = (ULong) value.getValue(); + return WriteResponse.success(); default: return super.write(identity, resourceId, value); @@ -219,6 +243,9 @@ public ReadResponse read(ServerIdentity identity, int resourceid) { case SEC_SERVER_ID: // short server id return ReadResponse.success(resourceid, shortServerId); + case SEC_CERTIFICATE_USAGE: // certificate usage + return ReadResponse.success(resourceid, certificateUsage); + default: return super.read(identity, resourceid); } diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java index 06d2d99204..a53e0b9f0c 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServerInfo.java @@ -22,6 +22,7 @@ import java.security.PublicKey; import java.security.cert.Certificate; +import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.LwM2m; import org.eclipse.leshan.core.SecurityMode; import org.slf4j.Logger; @@ -40,6 +41,7 @@ public class ServerInfo { public boolean bootstrap = false; public URI serverUri; public SecurityMode secureMode; + public CertificateUsage certificateUsage; public String pskId; public byte[] pskKey; diff --git a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java index 6383f83508..7e38f731dc 100644 --- a/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java +++ b/leshan-client-core/src/main/java/org/eclipse/leshan/client/servers/ServersInfoExtractor.java @@ -38,6 +38,7 @@ import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; +import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.node.LwM2mObject; @@ -47,6 +48,7 @@ import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.core.util.datatype.ULong; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,6 +96,7 @@ public static ServersInfo getInfo(Map objectEnabler info.clientCertificate = getClientCertificate(security); info.serverCertificate = getServerCertificate(security); info.privateKey = getPrivateKey(security); + info.certificateUsage = getCertificateUsage(security); } infos.bootstrap = info; } @@ -115,6 +118,7 @@ public static ServersInfo getInfo(Map objectEnabler info.clientCertificate = getClientCertificate(security); info.serverCertificate = getServerCertificate(security); info.privateKey = getPrivateKey(security); + info.certificateUsage = getCertificateUsage(security); } // search corresponding device management server for (LwM2mObjectInstance server : servers.getInstances().values()) { @@ -208,6 +212,10 @@ public static SecurityMode getSecurityMode(LwM2mObjectInstance securityInstance) return SecurityMode.fromCode((long) securityInstance.getResource(SEC_SECURITY_MODE).getValue()); } + public static CertificateUsage getCertificateUsage(LwM2mObjectInstance securityInstance) { + return CertificateUsage.fromCode((ULong) securityInstance.getResource(SEC_CERTIFICATE_USAGE).getValue()); + } + public static String getPskIdentity(LwM2mObjectInstance securityInstance) { byte[] pubKey = (byte[]) securityInstance.getResource(SEC_PUBKEY_IDENTITY).getValue(); return new String(pubKey); diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/CertificateUsage.java b/leshan-core/src/main/java/org/eclipse/leshan/core/CertificateUsage.java new file mode 100644 index 0000000000..242a5b399e --- /dev/null +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/CertificateUsage.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2020 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial API and implementation + *******************************************************************************/ +package org.eclipse.leshan.core; + +import org.eclipse.leshan.core.util.datatype.ULong; + +/** + * For details about Certificate Usage please see: + * rfc6698#section-2.1.1 - The Certificate Usage Field + */ +public enum CertificateUsage { + CA_CONSTRAINT(0), SERVICE_CERTIFICATE_CONSTRAINT(1), TRUST_ANCHOR_ASSERTION(2), DOMAIN_ISSUER_CERTIFICATE(3); + + public final ULong code; + + private CertificateUsage(int code) { + this.code = ULong.valueOf(code); + } + + public static CertificateUsage fromCode(int code) { + return fromCode(ULong.valueOf(code)); + } + + public static CertificateUsage fromCode(ULong code) { + for (CertificateUsage sm : CertificateUsage.values()) { + if (sm.code.equals(code)) { + return sm; + } + } + throw new IllegalArgumentException(String.format("Unsupported certificate usage code : %s", code)); + } +} diff --git a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java index 392df7d8f6..9696245df7 100644 --- a/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java +++ b/leshan-core/src/main/java/org/eclipse/leshan/core/LwM2mId.java @@ -41,6 +41,7 @@ public interface LwM2mId { public static final int SEC_SERVER_PUBKEY = 4; public static final int SEC_SECRET_KEY = 5; public static final int SEC_SERVER_ID = 10; + public static final int SEC_CERTIFICATE_USAGE = 15; /* SERVER RESOURCES */ diff --git a/leshan-integration-tests/credentials/clientKeyStore.jks b/leshan-integration-tests/credentials/clientKeyStore.jks index c93f252bef..46430a78c0 100644 Binary files a/leshan-integration-tests/credentials/clientKeyStore.jks and b/leshan-integration-tests/credentials/clientKeyStore.jks differ diff --git a/leshan-integration-tests/credentials/generate_credentials.sh b/leshan-integration-tests/credentials/generate_credentials.sh index 0d69603041..5fec982aa8 100755 --- a/leshan-integration-tests/credentials/generate_credentials.sh +++ b/leshan-integration-tests/credentials/generate_credentials.sh @@ -1,12 +1,20 @@ #!/bin/bash # Keystore parameters + CLIENT_STORE=clientKeyStore.jks CLIENT_STORE_PWD=client SERVER_STORE=serverKeyStore.jks SERVER_STORE_PWD=server +TRUSTED_CA_STORE=trustedCaKeyStore.jks +TRUSTED_CA_STORE_PWD=trusted +MANUFACTURER_CA_STORE=manufacturerCaKeyStore.jks +MANUFACTURER_CA_STORE_PWD=manufacturer +UNKNOWN_CA_STORE=unknownCaKeyStore.jks +UNKNOWN_CA_STORE_PWD=unknown VALIDITY=36500 #days +DEFAULT_STORE_TYPE=JKS #PKCS12 is not supported by Java7 # Color output stuff red=`tput setaf 1` @@ -17,26 +25,83 @@ H1=${green}${bold} H2=${blue} RESET=`tput sgr0` -# Generation of the keystore needed for Leshan integration tests. -echo "${H1}Server Keystore : ${RESET}" -echo "${H1}==================${RESET}" +# Generation of the Trusted CA keystore needed for Leshan integration tests. +echo "${H1}Trusted CA Keystore : ${RESET}" +echo "${H1}======================${RESET}" echo "${H2}Creating the trusted root CA key and certificate...${RESET}" keytool -genkeypair -alias rootCA -keyalg EC -dname 'CN=Leshan root CA' \ -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ -ext BasicConstraints:critical=ca:true \ -ext KeyUsage:critical=keyCertSign,cRLSign \ - -keypass $SERVER_STORE_PWD -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD + -keypass $TRUSTED_CA_STORE_PWD -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD +keytool -exportcert -alias rootCA -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD -file rootCA.der +echo +echo "${H2}Creating the intermediate CA key and certificate...${RESET}" +keytool -genkeypair -alias intermediateCA -keyalg EC -dname 'CN=Leshan intermediate CA' \ + -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ + -ext BasicConstraints:critical=ca:true,pathlen:0 \ + -ext KeyUsage:critical=keyCertSign,cRLSign \ + -keypass $TRUSTED_CA_STORE_PWD -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD +echo +keytool -certreq -alias intermediateCA -dname 'CN=Leshan intermediate CA' -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD | \ + keytool -gencert -alias rootCA -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD \ + -validity $VALIDITY \ + -ext BasicConstraints:critical=ca:true,pathlen:0 \ + -ext KeyUsage:critical=keyCertSign,cRLSign | \ + keytool -importcert -alias intermediateCA -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD +keytool -exportcert -alias intermediateCA -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD -file intermediateCA.der +echo + +# Generation of the Manufacturer CA keystore needed for Leshan integration tests. +echo "${H1}Manufacturer CA Keystore : ${RESET}" +echo "${H1}======================${RESET}" +echo "${H2}Creating the trusted root CA key and certificate...${RESET}" +keytool -genkeypair -alias mfgProductsRootCA -keyalg EC -dname 'CN=Products Root CA,O=Manufacturer' \ + -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ + -ext BasicConstraints:critical=ca:true \ + -ext KeyUsage:critical=keyCertSign,cRLSign \ + -keypass $MANUFACTURER_CA_STORE_PWD -keystore $MANUFACTURER_CA_STORE -storepass $MANUFACTURER_CA_STORE_PWD +keytool -exportcert -alias mfgProductsRootCA -keystore $MANUFACTURER_CA_STORE -storepass $MANUFACTURER_CA_STORE_PWD -file mfgProductsRootCA.der echo +echo "${H2}Creating the Devices CA key and certificate...${RESET}" +keytool -genkeypair -alias mfgDevicesCA -keyalg EC -dname 'CN=Devices CA,O=Manufacturer' \ + -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ + -ext BasicConstraints:critical=ca:true,pathlen:0 \ + -ext KeyUsage:critical=keyCertSign,cRLSign \ + -keypass $MANUFACTURER_CA_STORE_PWD -keystore $MANUFACTURER_CA_STORE -storepass $MANUFACTURER_CA_STORE_PWD +echo +keytool -certreq -alias mfgDevicesCA -dname 'CN=Devices CA,O=Manufacturer' -keystore $MANUFACTURER_CA_STORE -storepass $MANUFACTURER_CA_STORE_PWD | \ + keytool -gencert -alias mfgProductsRootCA -keystore $MANUFACTURER_CA_STORE -storepass $MANUFACTURER_CA_STORE_PWD \ + -validity $VALIDITY \ + -ext BasicConstraints:critical=ca:true,pathlen:0 \ + -ext KeyUsage:critical=keyCertSign,cRLSign | \ + keytool -importcert -alias mfgDevicesCA -keystore $MANUFACTURER_CA_STORE -storepass $MANUFACTURER_CA_STORE_PWD +keytool -exportcert -alias mfgDevicesCA -keystore $MANUFACTURER_CA_STORE -storepass $MANUFACTURER_CA_STORE_PWD -file mfgDevicesCA.der +echo + +# Generation of the Unknown CA keystore needed for Leshan integration tests. +echo "${H1}Unknown CA Keystore : ${RESET}" +echo "${H1}======================${RESET}" echo "${H2}Creating an untrusted root CA key and certificate...${RESET}" -keytool -genkeypair -alias untrustedrootCA -keyalg EC -dname 'CN=Leshan untrusted root CA' \ +keytool -genkeypair -alias untrustedRootCA -keyalg EC -dname 'CN=Leshan untrusted root CA' \ -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ -ext BasicConstraints:critical=ca:true \ -ext KeyUsage:critical=keyCertSign,cRLSign \ - -keypass $SERVER_STORE_PWD -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD + -keypass $UNKNOWN_CA_STORE_PWD -keystore $UNKNOWN_CA_STORE -storepass $UNKNOWN_CA_STORE_PWD +keytool -exportcert -alias untrustedRootCA -keystore $UNKNOWN_CA_STORE -storepass $UNKNOWN_CA_STORE_PWD -file untrustedRootCA.der echo +# Generation of the keystore needed for Leshan integration tests. +echo "${H1}Server Keystore : ${RESET}" +echo "${H1}==================${RESET}" echo "${H2}Creating server key and self-signed certificate ...${RESET}" -keytool -genkeypair -alias server -keyalg EC -dname 'CN=Leshan server self-signed' \ +keytool -genkeypair -alias server -keyalg EC -dname 'CN=localhost' \ -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ -ext BasicConstraints=ca:false \ -ext KeyUsage:critical=digitalSignature,keyAgreement \ -ext ExtendedkeyUsage=serverAuth \ @@ -44,15 +109,42 @@ keytool -genkeypair -alias server -keyalg EC -dname 'CN=Leshan server self-signe keytool -exportcert -alias server -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD | \ keytool -importcert -alias server_self_signed -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD -noprompt +echo +echo "${H2}Creating second server key and self-signed certificate ...${RESET}" +keytool -genkeypair -alias serverInt -keyalg EC -dname 'CN=Server signed with Intermediate CA' -ext san=dns:localhost \ + -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ + -ext BasicConstraints=ca:false \ + -ext KeyUsage:critical=digitalSignature,keyAgreement \ + -ext ExtendedkeyUsage=serverAuth \ + -keypass $SERVER_STORE_PWD -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD +keytool -exportcert -alias serverInt -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD | \ + keytool -importcert -alias serverInt_self_signed -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD -noprompt +echo +echo "${H2}Importing Root CA certificate ...${RESET}" +keytool -importcert -alias rootCA -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD -noprompt -file rootCA.der +echo +echo "${H2}Importing Intermediate CA certificate ...${RESET}" +keytool -importcert -alias intermediateCA -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD -noprompt -file intermediateCA.der echo echo "${H2}Creating server certificate signed by root CA...${RESET}" -keytool -certreq -alias server -dname 'CN=Leshan server' -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD | \ - keytool -gencert -alias rootCA -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD \ +keytool -certreq -alias server -dname 'CN=localhost' -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD | \ + keytool -gencert -alias rootCA -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD \ -validity $VALIDITY \ -ext BasicConstraints=ca:false \ -ext KeyUsage:critical=digitalSignature,keyAgreement \ -ext ExtendedkeyUsage=serverAuth | \ keytool -importcert -alias server -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD +echo +echo "${H2}Creating server certificate signed by intermediate CA...${RESET}" +keytool -certreq -alias serverInt -dname 'CN=Server signed with Intermediate CA' -ext san=dns:localhost -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD | \ + keytool -gencert -alias intermediateCA -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD \ + -validity $VALIDITY \ + -ext BasicConstraints=ca:false \ + -ext KeyUsage:critical=digitalSignature,keyAgreement \ + -ext ExtendedkeyUsage=serverAuth \ + -ext san=dns:localhost | \ + keytool -importcert -alias serverInt -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD echo echo "${H1}Client Keystore : ${RESET}" @@ -60,6 +152,7 @@ echo "${H1}==================${RESET}" echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}" keytool -genkeypair -alias client -keyalg EC -dname 'CN=leshan_integration_test' \ -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ -ext BasicConstraints=ca:false \ -ext KeyUsage:critical=digitalSignature,keyAgreement \ -ext ExtendedkeyUsage=clientAuth \ @@ -68,12 +161,11 @@ keytool -exportcert -alias client -keystore $CLIENT_STORE -storepass $CLIENT_STO keytool -importcert -alias client_self_signed -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD -noprompt echo echo "${H2}Import root certificate just to be able to sign certificate ...${RESET}" -keytool -exportcert -alias rootCA -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD | \ - keytool -importcert -alias rootCA -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD -noprompt +keytool -importcert -alias rootCA -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD -noprompt -file rootCA.der echo echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}" keytool -certreq -alias client -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD | \ - keytool -gencert -alias rootCA -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD \ + keytool -gencert -alias rootCA -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD \ -validity $VALIDITY \ -ext BasicConstraints=ca:false \ -ext KeyUsage:critical=digitalSignature,keyAgreement \ @@ -82,7 +174,7 @@ keytool -certreq -alias client -keystore $CLIENT_STORE -storepass $CLIENT_STORE_ echo echo "${H2}Creating client certificate signed by root CA with bad/unexpected CN...${RESET}" keytool -certreq -alias client -dname 'CN=leshan_client_with_bad_cn' -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD | \ - keytool -gencert -alias rootCA -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD \ + keytool -gencert -alias rootCA -keystore $TRUSTED_CA_STORE -storepass $TRUSTED_CA_STORE_PWD \ -validity $VALIDITY \ -ext BasicConstraints=ca:false \ -ext KeyUsage:critical=digitalSignature,keyAgreement \ @@ -91,9 +183,33 @@ keytool -certreq -alias client -dname 'CN=leshan_client_with_bad_cn' -keystore $ echo echo "${H2}Creating client certificate signed by untrusted root CA with expected CN...${RESET}" keytool -certreq -alias client -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD | \ - keytool -gencert -alias untrustedrootCA -keystore $SERVER_STORE -storepass $SERVER_STORE_PWD \ + keytool -gencert -alias untrustedRootCA -keystore $UNKNOWN_CA_STORE -storepass $UNKNOWN_CA_STORE_PWD \ -validity $VALIDITY \ -ext BasicConstraints=ca:false \ -ext KeyUsage:critical=digitalSignature,keyAgreement \ -ext ExtendedkeyUsage=clientAuth | \ keytool -importcert -alias client_not_trusted -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD -noprompt +echo +echo "${H2}Creating mfg client key and self-signed certificate with expected CN...${RESET}" +keytool -genkeypair -alias mfgClient -keyalg EC -dname 'CN=urn:dev:ops:32473-IoT_Device-K1234567,O=Manufacturer' \ + -validity $VALIDITY \ + -storetype $DEFAULT_STORE_TYPE \ + -ext BasicConstraints=ca:false \ + -ext KeyUsage:critical=digitalSignature,keyAgreement \ + -ext ExtendedkeyUsage=clientAuth \ + -keypass $CLIENT_STORE_PWD -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD +echo +echo "${H2}Import mfg products root CA certificate just to be able to sign certificate ...${RESET}" +keytool -importcert -alias mfgProductsRootCA -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD -noprompt -file mfgProductsRootCA.der +echo +echo "${H2}Import mfg devices CA certificate just to be able to sign certificate ...${RESET}" +keytool -importcert -alias mfgDevicesCA -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD -noprompt -file mfgDevicesCA.der +echo +echo "${H2}Creating mfg client certificate signed by root CA with expected CN...${RESET}" +keytool -certreq -alias mfgClient -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD | \ + keytool -gencert -alias mfgDevicesCA -keystore $MANUFACTURER_CA_STORE -storepass $MANUFACTURER_CA_STORE_PWD \ + -validity $VALIDITY \ + -ext BasicConstraints=ca:false \ + -ext KeyUsage:critical=digitalSignature,keyAgreement \ + -ext ExtendedkeyUsage=clientAuth | \ + keytool -importcert -alias mfgClient -keystore $CLIENT_STORE -storepass $CLIENT_STORE_PWD -noprompt diff --git a/leshan-integration-tests/credentials/manufacturerCaKeyStore.jks b/leshan-integration-tests/credentials/manufacturerCaKeyStore.jks new file mode 100644 index 0000000000..007b04e4cb Binary files /dev/null and b/leshan-integration-tests/credentials/manufacturerCaKeyStore.jks differ diff --git a/leshan-integration-tests/credentials/serverKeyStore.jks b/leshan-integration-tests/credentials/serverKeyStore.jks index c6f8040118..c9095c43b8 100644 Binary files a/leshan-integration-tests/credentials/serverKeyStore.jks and b/leshan-integration-tests/credentials/serverKeyStore.jks differ diff --git a/leshan-integration-tests/credentials/trustedCaKeyStore.jks b/leshan-integration-tests/credentials/trustedCaKeyStore.jks new file mode 100644 index 0000000000..bcde873d62 Binary files /dev/null and b/leshan-integration-tests/credentials/trustedCaKeyStore.jks differ diff --git a/leshan-integration-tests/credentials/unknownCaKeyStore.jks b/leshan-integration-tests/credentials/unknownCaKeyStore.jks new file mode 100644 index 0000000000..8c5295e484 Binary files /dev/null and b/leshan-integration-tests/credentials/unknownCaKeyStore.jks differ diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java index 0069c7c26e..7a6be8e28f 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/BootstrapIntegrationTestHelper.java @@ -188,7 +188,7 @@ public Security withoutSecurity() { // Create Security Object (with bootstrap server only) String bsUrl = "coap://" + bootstrapServer.getUnsecuredAddress().getHostString() + ":" + bootstrapServer.getUnsecuredAddress().getPort(); - return new Security(bsUrl, true, 3, new byte[0], new byte[0], new byte[0], 0); + return Security.noSecBootstap(bsUrl); } @Override diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java index 34537b7295..296f639b83 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecureIntegrationTestHelper.java @@ -37,6 +37,8 @@ import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.KeySpec; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.crypto.SecretKey; @@ -53,6 +55,7 @@ import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.californium.scandium.dtls.pskstore.AdvancedPskStore; import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.californium.X509Util; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Device; import org.eclipse.leshan.client.object.Security; @@ -60,6 +63,7 @@ import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; +import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.californium.EndpointFactory; import org.eclipse.leshan.core.util.Hex; @@ -89,8 +93,12 @@ public class SecureIntegrationTestHelper extends IntegrationTestHelper { // client private key used for X509 public final PrivateKey clientPrivateKeyFromCert; + // mfg client private key used for X509 + public final PrivateKey mfgClientPrivateKeyFromCert; // server private key used for X509 public final PrivateKey serverPrivateKeyFromCert; + // server private key used for X509 + public final PrivateKey serverIntPrivateKeyFromCert; // client certificate signed by rootCA with a good CN (CN start by leshan_integration_test) public final X509Certificate clientX509Cert; // client certificate signed by rootCA but with bad CN (CN does not start by leshan_integration_test) @@ -99,14 +107,25 @@ public class SecureIntegrationTestHelper extends IntegrationTestHelper { public final X509Certificate clientX509CertSelfSigned; // client certificate signed by another CA (not rootCA) with a good CN (CN start by leshan_integration_test) public final X509Certificate clientX509CertNotTrusted; + // client certificate signed by manufacturer CA's with a good CN + // (CN=urn:dev:ops:32473-IoT_Device-K1234567,O=Manufacturer) + public final X509Certificate[] mfgClientX509CertChain; // server certificate signed by rootCA public final X509Certificate serverX509Cert; + // server certificate signed by intermediateCA + public final X509Certificate[] serverIntX509CertChain; // self-signed server certificate public final X509Certificate serverX509CertSelfSigned; + // self-signed server certificate + public final X509Certificate serverIntX509CertSelfSigned; // rootCA used by the server public final X509Certificate rootCAX509Cert; // certificates trustedby the server (should contain rootCA) public final Certificate[] trustedCertificates = new Certificate[1]; + // client's initial trust store + public final List clientTrustStore; + // client's initial empty trust store + public final List clientEmptyTrustStore = new ArrayList<>(); public SecureIntegrationTestHelper() { // create client credentials @@ -145,6 +164,9 @@ public SecureIntegrationTestHelper() { clientX509CertWithBadCN = (X509Certificate) clientKeyStore.getCertificate("client_bad_cn"); clientX509CertSelfSigned = (X509Certificate) clientKeyStore.getCertificate("client_self_signed"); clientX509CertNotTrusted = (X509Certificate) clientKeyStore.getCertificate("client_not_trusted"); + + mfgClientPrivateKeyFromCert = (PrivateKey) clientKeyStore.getKey("mfgClient", clientKeyStorePwd); + mfgClientX509CertChain = X509Util.asX509Certificates(clientKeyStore.getCertificateChain("mfgClient")); } catch (GeneralSecurityException | IOException e) { throw new RuntimeException(e); } @@ -181,11 +203,14 @@ public SecureIntegrationTestHelper() { } serverPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("server", serverKeyStorePwd); + serverIntPrivateKeyFromCert = (PrivateKey) serverKeyStore.getKey("serverint", serverKeyStorePwd); rootCAX509Cert = (X509Certificate) serverKeyStore.getCertificate("rootCA"); serverX509Cert = (X509Certificate) serverKeyStore.getCertificate("server"); serverX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("server_self_signed"); + serverIntX509CertSelfSigned = (X509Certificate) serverKeyStore.getCertificate("serverInt_self_signed"); + serverIntX509CertChain = X509Util.asX509Certificates(serverKeyStore.getCertificateChain("serverint")); trustedCertificates[0] = rootCAX509Cert; - + clientTrustStore = Arrays.asList(new Certificate[] { rootCAX509Cert }); } catch (GeneralSecurityException | IOException e) { throw new RuntimeException(e); } @@ -333,6 +358,38 @@ public void createX509CertClient(Certificate clientCertificate, PrivateKey priva setupClientMonitoring(); } + public void createX509CertClient(X509Certificate[] clientCertificate, PrivateKey privatekey, + List clientTrustStore, X509Certificate serverCertificate, CertificateUsage certificateUsage) + throws CertificateEncodingException { + /* Make sure there is only 1 certificate in chain before client certificate chains are supported */ + assert (clientCertificate.length == 1); + + ObjectsInitializer initializer = new ObjectsInitializer(); + initializer.setInstancesForObject(LwM2mId.SECURITY, + Security.x509( + "coaps://" + server.getSecuredAddress().getHostString() + ":" + + server.getSecuredAddress().getPort(), + 12345, clientCertificate[0].getEncoded(), privatekey.getEncoded(), + serverCertificate.getEncoded(), certificateUsage.code)); + initializer.setInstancesForObject(LwM2mId.SERVER, new Server(12345, LIFETIME)); + initializer.setInstancesForObject(LwM2mId.DEVICE, new Device("Eclipse Leshan", MODEL_NUMBER, "12345")); + initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); + List objects = initializer.createAll(); + + InetSocketAddress clientAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); + LeshanClientBuilder builder = new LeshanClientBuilder(getCurrentEndpoint()); + builder.setLocalAddress(clientAddress.getHostString(), clientAddress.getPort()); + builder.setTrustStore(clientTrustStore); + + Builder dtlsConfig = new DtlsConnectorConfig.Builder(); + dtlsConfig.setClientOnly(); + builder.setDtlsConfig(dtlsConfig); + + builder.setObjects(objects); + client = builder.build(); + setupClientMonitoring(); + } + @Override protected LeshanServerBuilder createServerBuilder() { return createServerBuilder(null); @@ -367,9 +424,19 @@ public void createServerWithX509Cert() { } public void createServerWithX509Cert(X509Certificate serverCertificate, PrivateKey privateKey, Boolean serverOnly) { + createServerWithX509Cert(new X509Certificate[] { serverCertificate }, privateKey, serverOnly); + } + + public void createServerWithX509Cert(X509Certificate serverCertificateChain[], PrivateKey privateKey, + Boolean serverOnly) { + createServerWithX509Cert(serverCertificateChain, privateKey, this.trustedCertificates, serverOnly); + } + + public void createServerWithX509Cert(X509Certificate serverCertificateChain[], PrivateKey privateKey, + Certificate[] trustedCertificates, Boolean serverOnly) { LeshanServerBuilder builder = createServerBuilder(serverOnly); builder.setPrivateKey(privateKey); - builder.setCertificateChain(new X509Certificate[] { serverCertificate }); + builder.setCertificateChain(serverCertificateChain); builder.setTrustedCertificates(trustedCertificates); builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { @Override diff --git a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java index d8d6969353..2c50b6ccf7 100644 --- a/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java +++ b/leshan-integration-tests/src/test/java/org/eclipse/leshan/integration/tests/SecurityTest.java @@ -23,6 +23,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; import org.eclipse.californium.core.coap.CoAP.Type; import org.eclipse.californium.core.coap.Request; @@ -38,6 +39,7 @@ import org.eclipse.californium.elements.util.SimpleMessageCallback; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.leshan.core.CertificateUsage; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.exception.SendFailedException; import org.eclipse.leshan.core.request.exception.TimeoutException; @@ -692,6 +694,896 @@ public void registered_device_with_selfsigned_x509cert_to_server_with_x509cert() helper.ensureNoRegistration(1); } + /* ---- CA_CONSTRAINT ---- */ + + /** + *
+     * Test scenario:
+     * - Certificate Usage = CA constraint
+     * - Server Certificate = server certificate
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection  (direct trust is not allowed with "CA constraint" usage)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_ca() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[0], CertificateUsage.CA_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = CA constraint
+     * - Server Certificate = intermediate CA certificate
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client is able to connect (intermediate CA cert is part of the chain)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_ca_intca_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[1], CertificateUsage.CA_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + + assertNotNull(helper.getCurrentRegistration()); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = CA constraint
+     * - Server Certificate = root CA certificate (not end-entity certificate)
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client is able to connect (root CA cert is part of the chain)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_ca_domain_root_ca_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.rootCAX509Cert, CertificateUsage.CA_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + + assertNotNull(helper.getCurrentRegistration()); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = CA constraint
+     * - Server Certificate = other end-entity certificate with same dns name signed by same root ca
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_ca_other_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverX509Cert, CertificateUsage.CA_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = CA constraint
+     * - Server Certificate = self signed certificate given
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_ca_selfsigned_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertSelfSigned, CertificateUsage.CA_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = CA constraint
+     * - Server Certificate = self signed certificate
+     * - Server's TLS Server Certificate = self signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection  (direct trust is not allowed with "CA constraint" usage)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_selfsigned_certificate_usage_ca_selfsigned_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(new X509Certificate[] { helper.serverIntX509CertSelfSigned }, + helper.serverIntPrivateKeyFromCert, helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertSelfSigned, CertificateUsage.CA_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = CA constraint
+     * - Server Certificate = intermediate signed certificate/wo chain
+     * - Server's TLS Server Certificate = intermediate signed certificate/wo chain (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection  (direct trust is not allowed with "CA constraint" usage)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_server_certificate_usage_ca_server_cert_wo_chain_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(new X509Certificate[] { helper.serverIntX509CertChain[0] }, + helper.serverIntPrivateKeyFromCert, helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[0], CertificateUsage.CA_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /* ---- SERVICE_CERTIFICATE_CONSTRAINT ---- */ + + /** + *
+     * Test scenario:
+     * - Certificate Usage = service certificate constraint
+     * - Server Certificate = server certificate
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client is able to connect
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_service() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[0], + CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + + assertNotNull(helper.getCurrentRegistration()); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = service certificate constraint
+     * - Server Certificate = root CA certificate (not end-entity certificate)
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_service_domain_root_ca_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.rootCAX509Cert, CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = service certificate constraint
+     * - Server Certificate = other end-entity certificate with same dns name signed by same root ca
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_service_other_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverX509Cert, CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = service certificate constraint
+     * - Server Certificate = self signed certificate given
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_service_selfsigned_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertSelfSigned, + CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = service certificate constraint
+     * - Server Certificate = self signed certificate
+     * - Server's TLS Server Certificate = self signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection (self-signed is not PKIX chainable)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_selfsigned_certificate_usage_service_selfsigned_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(new X509Certificate[] { helper.serverIntX509CertSelfSigned }, + helper.serverIntPrivateKeyFromCert, helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertSelfSigned, + CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = service certificate constraint
+     * - Server Certificate = intermediate signed certificate/wo chain
+     * - Server's TLS Server Certificate = intermediate signed certificate/wo chain (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection (missing intermediate CA aka. "server chain configuration problem")
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_server_certificate_usage_service_server_cert_wo_chain_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(new X509Certificate[] { helper.serverIntX509CertChain[0] }, + helper.serverIntPrivateKeyFromCert, helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[0], + CertificateUsage.SERVICE_CERTIFICATE_CONSTRAINT); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /* ---- TRUST_ANCHOR_ASSERTION ---- */ + + /** + *
+     * Test scenario:
+     * - Certificate Usage = trust anchor assertion
+     * - Server Certificate = server certificate
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection  (direct trust is not allowed with "trust constraint" usage)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_taa() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[0], CertificateUsage.TRUST_ANCHOR_ASSERTION); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = trust anchor assertion
+     * - Server Certificate = intermediate CA certificate
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client is able to connect (pkix path terminates in intermediate CA (TA), root CA is not available as client trust store not in use)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_taa_intca_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[1], CertificateUsage.TRUST_ANCHOR_ASSERTION); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + + assertNotNull(helper.getCurrentRegistration()); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = trust anchor assertion
+     * - Server Certificate = root CA certificate
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client is able to connect (root CA cert is part of the chain)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_taa_domain_root_ca_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.rootCAX509Cert, CertificateUsage.TRUST_ANCHOR_ASSERTION); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + + assertNotNull(helper.getCurrentRegistration()); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = trust anchor assertion
+     * - Server Certificate = other end-entity certificate with same dns name signed by same root ca
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_taa_other_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverX509Cert, CertificateUsage.TRUST_ANCHOR_ASSERTION); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = trust anchor assertion
+     * - Server Certificate = self signed certificate given
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_taa_selfsigned_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertSelfSigned, CertificateUsage.TRUST_ANCHOR_ASSERTION); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = trust anchor assertion
+     * - Server Certificate = self signed certificate
+     * - Server's TLS Server Certificate = self signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection  (direct trust is not allowed with "trust anchor" usage)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_selfsigned_certificate_usage_taa_selfsigned_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(new X509Certificate[] { helper.serverIntX509CertSelfSigned }, + helper.serverIntPrivateKeyFromCert, helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertSelfSigned, CertificateUsage.TRUST_ANCHOR_ASSERTION); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = trust anchor assertion
+     * - Server Certificate = intermediate signed certificate/wo chain
+     * - Server's TLS Server Certificate = intermediate signed certificate/wo chain (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection  (direct trust is not allowed with "trust anchor" usage)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_server_certificate_usage_taa_server_cert_wo_chain_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(new X509Certificate[] { helper.serverIntX509CertChain[0] }, + helper.serverIntPrivateKeyFromCert, helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[0], CertificateUsage.TRUST_ANCHOR_ASSERTION); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /* ---- DOMAIN_ISSUER_CERTIFICATE ---- */ + + /** + *
+     * Test scenario:
+     * - Certificate Usage = domain issuer certificate
+     * - Server Certificate = server certificate
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client is able to connect
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_domain() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[0], CertificateUsage.DOMAIN_ISSUER_CERTIFICATE); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + + assertNotNull(helper.getCurrentRegistration()); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = domain issuer certificate
+     * - Server Certificate = root CA certificate (not end-entity certificate)
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection (no end-entity certificate given)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_domain_root_ca_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.rootCAX509Cert, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = domain issuer certificate
+     * - Server Certificate = other end-entity certificate with same dns name signed by same root ca
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection (different server cert given even thou hostname matches)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_domain_other_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverX509Cert, CertificateUsage.DOMAIN_ISSUER_CERTIFICATE); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = domain issuer certificate
+     * - Server Certificate = self signed certificate given
+     * - Server's TLS Server Certificate = intermediate signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client denied the connection (different certificate self-signed vs. signed -- even thou the public key is same)
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_rootca_certificate_usage_domain_selfsigned_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(helper.serverIntX509CertChain, helper.serverIntPrivateKeyFromCert, + helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertSelfSigned, + CertificateUsage.DOMAIN_ISSUER_CERTIFICATE); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.ensureNoRegistration(1); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = domain issuer certificate
+     * - Server Certificate = self signed certificate
+     * - Server's TLS Server Certificate = self signed certificate (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client is able to connect
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_selfsigned_certificate_usage_domain_selfsigned_server_cert_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(new X509Certificate[] { helper.serverIntX509CertSelfSigned }, + helper.serverIntPrivateKeyFromCert, helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertSelfSigned, + CertificateUsage.DOMAIN_ISSUER_CERTIFICATE); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + + assertNotNull(helper.getCurrentRegistration()); + } + + /** + *
+     * Test scenario:
+     * - Certificate Usage = service certificate constraint
+     * - Server Certificate = intermediate signed certificate/wo chain
+     * - Server's TLS Server Certificate = intermediate signed certificate/wo chain (with SAN DNS entry)
+     * - Server accepts client
+     * - Client Trust Store = root CA
+     *
+     * Expected outcome:
+     * - Client is able to connect
+     * 
+ */ + @Test + public void registered_device_with_x509cert_to_server_with_x509cert_server_certificate_usage_domain_server_cert_wo_chain_given() + throws NonUniqueSecurityInfoException, CertificateEncodingException { + helper.createServerWithX509Cert(new X509Certificate[] { helper.serverIntX509CertChain[0] }, + helper.serverIntPrivateKeyFromCert, helper.trustedCertificates, true); + + helper.server.start(); + + helper.setEndpointNameFromX509(helper.clientX509Cert); + + helper.createX509CertClient(new X509Certificate[] { helper.clientX509Cert }, helper.clientPrivateKeyFromCert, + helper.clientTrustStore, helper.serverIntX509CertChain[0], CertificateUsage.DOMAIN_ISSUER_CERTIFICATE); + + helper.getSecurityStore().add(SecurityInfo.newX509CertInfo(helper.getCurrentEndpoint())); + + helper.assertClientNotRegisterered(); + helper.client.start(); + helper.waitForRegistrationAtServerSide(1); + + assertNotNull(helper.getCurrentRegistration()); + } + + /* ---- */ + @Test public void registered_device_with_x509cert_to_server_with_rpk() throws NonUniqueSecurityInfoException, CertificateEncodingException {