DtlsAS.java

/*******************************************************************************
 * Copyright (c) 2019, RISE AB
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/
package se.sics.ace.coap.as;

import java.net.InetSocketAddress;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.logging.Logger;

import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.config.DtlsConfig;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.SignatureAndHashAlgorithm;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.californium.scandium.dtls.cipher.XECDHECryptography.SupportedGroup;
import org.eclipse.californium.scandium.dtls.x509.AsyncNewAdvancedCertificateVerifier;
import org.eclipse.californium.scandium.dtls.x509.SingleCertificateProvider;

import org.eclipse.californium.cose.CoseException;
import org.eclipse.californium.cose.KeyKeys;
import org.eclipse.californium.cose.OneKey;
import org.eclipse.californium.elements.auth.RawPublicKeyIdentity;
import org.eclipse.californium.elements.config.CertificateAuthenticationMode;
import org.eclipse.californium.elements.config.Configuration;

import se.sics.ace.AceException;
import se.sics.ace.TimeProvider;
import se.sics.ace.as.Introspect;
import se.sics.ace.as.PDP;
import se.sics.ace.as.Token;

/**
 * An authorization server listening to CoAP requests
 * over DTLS.
 * 
 * Create an instance of this server with the constructor then call
 * CoapsAS.start();
 * 
 * @author Ludwig Seitz and Marco Tiloca
 *
 */
public class DtlsAS extends CoapServer implements AutoCloseable {
    
    /**
     * The logger
     */
    private static final Logger LOGGER 
        = Logger.getLogger(DtlsAS.class.getName());

    /**
     * The token endpoint
     */
    Token t = null;
    
    /**
     * The introspect endpoint
     */
    Introspect i = null;

    private CoapDtlsEndpoint token;

    private CoapDtlsEndpoint introspect;

    /**
     * Constructor.
     * 
     * @param asId  identifier of the AS
     * @param db    database connector of the AS
     * @param pdp   PDP for deciding who gets which token
     * @param time  time provider, must not be null
     * @param asymmetricKey  asymmetric key pair of the AS for RPK handshakes,
     *   can be null if the AS only ever does PSK handshakes
     * @param port  the port number to run the server on
     * 
     * @throws AceException 
     * @throws CoseException 
     * 
     */
    public DtlsAS(String asId, CoapDBConnector db, PDP pdp, TimeProvider time, 
            OneKey asymmetricKey, int port) 
                    throws AceException, CoseException {
        this(asId, db, pdp, time, asymmetricKey, "token", "introspect", port,
                null, false);
    }
    
    
    /**
     * Constructor.
     * 
     * @param asId  identifier of the AS
     * @param db    database connector of the AS
     * @param pdp   PDP for deciding who gets which token
     * @param time  time provider, must not be null
     * @param asymmetricKey  asymmetric key pair of the AS for RPK handshakes,
     *   can be null if the AS only ever does PSK handshakes
     * @throws AceException 
     * @throws CoseException 
     * 
     */
    public DtlsAS(String asId, CoapDBConnector db, PDP pdp, TimeProvider time, 
            OneKey asymmetricKey) throws AceException, CoseException {
        this(asId, db, pdp, time, asymmetricKey, "token", "introspect",
                CoAP.DEFAULT_COAP_SECURE_PORT, null, false);
    }
    
    /**
     * Constructor with endpoint names
     * 
     * @param asId  identifier of the AS
     * @param db    database connector of the AS
     * @param pdp   PDP for deciding who gets which token
     * @param time  time provider, must not be null
     * @param asymmetricKey  asymmetric key pair of the AS for RPK handshakes,
     *   can be null if the AS only ever does PSK handshakes
     * @param tokenName  the name of the token endpoint 
     *  (will be converted into the address as well)
     * @param introspectName  the name of the introspect endpoint 
     *  (will be converted into the address as well), if this is null,
     *  no introspection endpoint will be offered
     * @param port  the port number to run the server on
     * @param claims  the claim types to include in tokens issued by this 
     *                AS, can be null to use default set.
     * @param setAudHeader  insert the AUD as header in the CWT. 
     * See {@link se.sics.ace.as.Token} for details.
     * @throws AceException 
     * @throws CoseException 
     * 
     */
    public DtlsAS(String asId, CoapDBConnector db, PDP pdp, 
            TimeProvider time, OneKey asymmetricKey, String tokenName,
            String introspectName, int port, Set<Short> claims, 
            boolean setAudHeader) 
                    throws AceException, CoseException {
        this.t = new Token(asId, pdp, db, time, asymmetricKey, claims, setAudHeader, null);
        this.token = new CoapDtlsEndpoint(tokenName, this.t);
        add(this.token);
        
        if (introspectName != null) {
            if (asymmetricKey == null) {
                this.i = new Introspect(pdp, db, time, null, null);
            } else {
                this.i = new Introspect(pdp, db, time, asymmetricKey.PublicKey(), null);
            }
            this.introspect = new CoapDtlsEndpoint(introspectName, this.i);
            add(this.introspect);    
        }

       Configuration dtlsConfig = Configuration.getStandard();
       dtlsConfig.set(DtlsConfig.DTLS_USE_SERVER_NAME_INDICATION, false);
       dtlsConfig.set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.NEEDED);

       if (asymmetricKey != null && (asymmetricKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_EC2 ||
					 			     asymmetricKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_OKP)) {
           LOGGER.info("Starting CoapsAS with PSK and RPK");
           dtlsConfig.set(DtlsConfig.DTLS_CIPHER_SUITES,
        		          Arrays.asList(CipherSuite.TLS_PSK_WITH_AES_128_CCM_8,
        		        		        CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8));
           if (asymmetricKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_EC2) {
        	   if (asymmetricKey.get(KeyKeys.OKP_Curve) == KeyKeys.EC2_P256) {
		   		   dtlsConfig.set(DtlsConfig.DTLS_CURVES, Arrays.asList(SupportedGroup.secp256r1));
		   		   dtlsConfig.set(DtlsConfig.DTLS_SIGNATURE_AND_HASH_ALGORITHMS,
		   				          Arrays.asList(SignatureAndHashAlgorithm.SHA256_WITH_ECDSA));
        	   }
           }
           if (asymmetricKey.get(KeyKeys.KeyType) == KeyKeys.KeyType_OKP) {
        	   if (asymmetricKey.get(KeyKeys.OKP_Curve) == KeyKeys.OKP_Ed25519) {
		   		   dtlsConfig.set(DtlsConfig.DTLS_CURVES, Arrays.asList(SupportedGroup.X25519));
		   		   dtlsConfig.set(DtlsConfig.DTLS_SIGNATURE_AND_HASH_ALGORITHMS,
		   				          Arrays.asList(SignatureAndHashAlgorithm.INTRINSIC_WITH_ED25519));
        	   }
        	   if (asymmetricKey.get(KeyKeys.OKP_Curve) == KeyKeys.OKP_Ed448) {
		   		   dtlsConfig.set(DtlsConfig.DTLS_CURVES, Arrays.asList(SupportedGroup.X448));
		   		   dtlsConfig.set(DtlsConfig.DTLS_SIGNATURE_AND_HASH_ALGORITHMS,
		   				          Arrays.asList(SignatureAndHashAlgorithm.INTRINSIC_WITH_ED448));
        	   }
           }
       } else {
           LOGGER.info("Starting CoapsAS with PSK only");
           dtlsConfig.set(DtlsConfig.DTLS_CIPHER_SUITES, Collections.singletonList(CipherSuite.TLS_PSK_WITH_AES_128_CCM_8));
       }
       
       
       
       
        
       DtlsConnectorConfig.Builder config =  new DtlsConnectorConfig.Builder(dtlsConfig)
               .setAddress(new InetSocketAddress(port));

       ArrayList<CertificateType> certTypes = new ArrayList<CertificateType>();
       certTypes.add(CertificateType.RAW_PUBLIC_KEY);
       certTypes.add(CertificateType.X_509);
       AsyncNewAdvancedCertificateVerifier verifier = new AsyncNewAdvancedCertificateVerifier(new X509Certificate[0],
               new RawPublicKeyIdentity[0], certTypes);
       config.setAdvancedCertificateVerifier(verifier);

       config.setAdvancedPskStore(db);
       if (asymmetricKey != null) {
           config.setCertificateIdentityProvider(
                   new SingleCertificateProvider(asymmetricKey.AsPrivateKey(), asymmetricKey.AsPublicKey()));
       }
       DTLSConnector connector = new DTLSConnector(config.build());
       
       addEndpoint(new CoapEndpoint.Builder()
               .setConnector(connector).setConfiguration(
                       Configuration.getStandard()).build());
       //Add a CoAP (no 's') endpoint for error messages
       //CoapEndpoint coap = new CoapEndpointBuilder().setInetSocketAddress(
       //       new InetSocketAddress(CoAP.DEFAULT_COAP_PORT)).build();
       //addEndpoint(coap);
    }

    @Override
    public void close() throws Exception {
       LOGGER.info("Closing down DtlsAS ...");
       this.token.close();
       this.introspect.close();
    }
}