Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Certificate usage support client side (#912 with small modification) #923

Merged
merged 4 commits into from
Nov 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<CertificateType> supportedCertificateType = Arrays.asList(CertificateType.X_509);

@Override
public List<CertificateType> getSupportedCertificateType() {
return supportedCertificateType;
}

@Override
public void setResultHandler(HandshakeResultHandler resultHandler) {
// we don't use async mode.
}

@Override
public List<X500Principal> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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:
*
* <pre>
* 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.
* </pre>
sbernard31 marked this conversation as resolved.
Show resolved Hide resolved
*
* For details about Certificate Usage please see:
* <a href="https://tools.ietf.org/html/rfc6698#section-2.1.1">rfc6698#section-2.1.1</a> - 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;
sbernard31 marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public CertPath verifyCertificate(Boolean clientUsage, CertificateMessage message, DTLSSession session)
throws HandshakeException {
CertPath messageChain = message.getCertificateChain();

validateCertificateChainNotEmpty(messageChain, session.getPeer());
X509Certificate receivedServerCertificate = validateReceivedCertificateIsSupported(messageChain,
sbernard31 marked this conversation as resolved.
Show resolved Hide resolved
session.getPeer());

// - must do PKIX validation with trustStore
CertPath certPath;
try {
sbernard31 marked this conversation as resolved.
Show resolved Hide resolved
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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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")
sbernard31 marked this conversation as resolved.
Show resolved Hide resolved
// - 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<X509Certificate> newTrustedCertificatesList = new ArrayList<>();
if (this.trustStore != null) {
newTrustedCertificatesList.addAll(CertPathUtil.toX509CertificatesList(this.trustStore));
}
newTrustedCertificatesList.add((X509Certificate) serverInfo.serverCertificate);
sbernard31 marked this conversation as resolved.
Show resolved Hide resolved
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(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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:
*
* <pre>
* 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.
* </pre>
*
* For details about Certificate Usage please see:
* <a href="https://tools.ietf.org/html/rfc6698#section-2.1.1">rfc6698#section-2.1.1</a> - 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;
}
}
Loading