All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.californium.scandium.config.DtlsConnectorConfig Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2015 Bosch Software Innovations GmbH and others.
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * 
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at
 *    http://www.eclipse.org/org/documents/edl-v10.html.
 * 
 * Contributors:
 *    Kai Hudalla (Bosch Software Innovations GmbH) - re-factor DTLSConnectorConfig into
 *                                               an immutable, provide a "builder" for easier
 *                                               instantiation/configuration
 *    Kai Hudalla (Bosch Software Innovations GmbH) - add support for anonymous client-only
 *                                               configuration
 *******************************************************************************/

package org.eclipse.californium.scandium.config;

import java.net.InetSocketAddress;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.californium.scandium.dtls.pskstore.PskStore;

/**
 * A container for all configuration options of a DTLSConnector.
 * 

* Instances of this class are immutable and can only be created by means of * the {@link Builder}, e.g. *

*
 * InetSocketAddress bindToAddress = new InetSocketAddress("localhost", 0); // use ephemeral port
 * DtlsConnectorConfig config = new DtlsConnectorConfig.Builder(bindToAddress)
 *    .setPskStore(new StaticPskStore("identity", "secret".getBytes()));
 *    .set... // additional configuration
 *    .build();
 * 
 * DTLSConnector connector = new DTLSConnector(config);
 * connector.start();
 * ...
 * 
*/ public class DtlsConnectorConfig { private static final String EC_ALGORITHM_NAME = "EC"; private InetSocketAddress address; private Certificate[] trustStore = new Certificate[0]; /** * The maximum fragment length this connector can process at once. */ private Integer maxFragmentLengthCode = null; /** The initial timer value for retransmission; rfc6347, section: 4.2.4.1 */ private int retransmissionTimeout = 1000; /** * Maximal number of retransmissions before the attempt to transmit a * message is canceled */ private int maxRetransmissions = 4; /** does the server require the client to authenticate */ private boolean clientAuthenticationRequired = true; /** do we send only the raw key (RPK) and not the full certificate (X509) */ private boolean sendRawKey = true; /** store of the PSK */ private PskStore pskStore = null; /** the private key for RPK and X509 mode */ private PrivateKey privateKey = null; /** the public key for both RPK and X.509 mode */ private PublicKey publicKey = null; /** the certificate for RPK and X509 mode */ private Certificate[] certChain; /** the supported cipher suites in order of preference */ private CipherSuite[] supportedCipherSuites; private int outboundMessageBufferSize = 100000; private DtlsConnectorConfig() { // empty } /** * Gets the maximum amount of message payload data that this connector can receive in a * single DTLS record. *

* The code returned is either null or one of the following: *

    *
  • 1 - 2^9 bytes
  • *
  • 2 - 2^10 bytes
  • *
  • 3 - 2^11 bytes
  • *
  • 4 - 2^12 bytes
  • *
* * @return the code indicating the maximum payload length */ public Integer getMaxFragmentLengthCode() { return maxFragmentLengthCode; } /** * Gets the (intial) time to wait before a handshake flight of messages gets re-transmitted. * * This timeout gets adjusted during the course of repeated re-transmission of a flight. * The DTLS spec suggests an exponential back-off strategy, i.e. after each re-transmission the * timeout value is doubled. * * @return the (initial) time to wait in milliseconds */ public int getRetransmissionTimeout() { return retransmissionTimeout; } /** * Gets the maximum number of times a flight of handshake messages gets re-transmitted * to a peer. * * @return the maximum number of re-transmissions */ public int getMaxRetransmissions() { return maxRetransmissions; } /** * Gets the number of outbound messages that can be buffered in memory before * messages are dropped. * * @return the number of messages */ public int getOutboundMessageBufferSize() { return outboundMessageBufferSize; } /** * Gets the IP address and port the connector is bound to. * * @return the address */ public InetSocketAddress getAddress() { return address; } /** * Gets the certificates forming the chain-of-trust from * a root CA down to the certificate asserting the server's identity. * * @return the certificates or null if the connector is * not supposed to support certificate based authentication */ public final Certificate[] getCertificateChain() { if (certChain == null) { return new Certificate[0]; } else { return Arrays.copyOf(certChain, certChain.length); } } /** * Gets the cipher suites the connector should advertise in a DTLS * handshake. * * @return the supported cipher suites (ordered by preference) */ public final CipherSuite[] getSupportedCipherSuites() { if (supportedCipherSuites == null) { return new CipherSuite[0]; } else { return Arrays.copyOf(supportedCipherSuites, supportedCipherSuites.length); } } /** * Gets the private key to use for proving identity to a peer * during a DTLS handshake. * * @return the key */ public final PrivateKey getPrivateKey() { return privateKey; } /** * Gets the registry of shared secrets used for authenticating * clients during a DTLS handshake. * * @return the registry */ public final PskStore getPskStore() { return pskStore; } /** * Gets the public key to send to peers during the DTLS handshake * for authentication purposes. * * @return the key */ public final PublicKey getPublicKey() { return publicKey; } /** * Gets the trusted root certificates to use when verifying * a peer's certificate during authentication. * * @return the root certificates */ public final Certificate[] getTrustStore() { return trustStore; } /** * Sets whether the connector requires DTLS clients to authenticate during * the handshake. * * @return true if clients need to authenticate */ public final boolean isClientAuthenticationRequired() { return clientAuthenticationRequired; } /** * Checks whether the connector will send a raw public key * instead of an X.509 certificate in order to authenticate to the peer * during a DTLS handshake. * * Note that this property is only relevant for cipher suites using certificate * based authentication. * * @return true if RawPublicKey is used by the connector */ public final boolean isSendRawKey() { return sendRawKey; } /** * A helper for creating instances of DtlsConnectorConfig * based on the builder pattern. * */ public static final class Builder { private DtlsConnectorConfig config; private boolean clientOnly; /** * Creates a new instance for setting configuration options * for a DTLSConnector instance. * * Once all options are set, clients should use the {@link #build()} * method to create an immutable DtlsConfigurationConfig * instance which can be passed into the DTLSConnector * constructor. * * The builder is initialized to the following default values *
    *
  • maxFragmentLength: 4096 bytes
  • *
  • maxPayloadSize: 4096 + 25 bytes (max fragment size + 25 bytes for headers)
  • *
  • maxRetransmissions: 4
  • *
  • retransmissionTimeout: 1000ms
  • *
  • clientAuthenticationRequired: true
  • *
  • outboundMessageBufferSize: 100.000
  • *
  • trustStore: empty array
  • *
* * Note that when keeping the default values, at least one of the {@link #setPskStore(PskStore)} * or {@link #setIdentity(PrivateKey, PublicKey)} methods need to be used to * get a working configuration for a DTLSConnector that can be used * as a client and server. * * It is possible to create a configuration for a DTLSConnector that can operate * as a client only without the need for setting an identity. However, this is possible * only if the server does not require clients to authenticate, i.e. this only * works with the ECDH based cipher suites. If you want to create such a client-only * configuration, you need to use the {@link #setClientOnly()} method on the builder. * * @param address the IP address and port the connector should bind to * @throws IllegalArgumentException if the given addess is unresolved */ public Builder(InetSocketAddress address) { if (address.isUnresolved()) { throw new IllegalArgumentException("Bind address must not be unresolved"); } config = new DtlsConnectorConfig(); config.address = address; } /** * Indicates that the DTLSConnector will only be used as a * DTLS client. * * The {@link #build()} method will allow creation of a configuration * without any identity being set under the following conditions: *
    *
  • only support for ECDH based cipher suites is configured
  • *
  • this method has been invoked
  • *
* * @return this builder for command chaining */ public Builder setClientOnly() { clientOnly = true; return this; } /** * Sets the maximum amount of payload data that can be received and processed by this connector * in a single DTLS record. *

* The value of this property is used to indicate to peers the Maximum Fragment Length * as defined in RFC 6066, Section 4. * It is also used to determine the amount of memory that will be allocated for receiving UDP datagrams * sent by peers from the network interface. *

* The code must be either null or one of the following: *
    *
  • 1 - 2^9 bytes
  • *
  • 2 - 2^10 bytes
  • *
  • 3 - 2^11 bytes
  • *
  • 4 - 2^12 bytes
  • *
*

* If this property is set to null, the DTLSConnector will * derive its value from the network interface's Maximum Transmission Unit. * This means that it will set it to a value small enough to make sure that inbound * messages fit into a UDP datagram having a size less or equal to the MTU. *

* * @param lengthCode the code indicating the maximum length or null to determine * the maximum fragment length based on the network interface's MTU * @return this builder for command chaining * @throws IllegalArgumentException if the code is not one of {1, 2, 3, 4} */ public Builder setMaxFragmentLengthCode(Integer lengthCode) { if (lengthCode != null && (lengthCode < 1 || lengthCode > 4)) { throw new IllegalArgumentException("Maximum fragment length code must be one of {1, 2, 3, 4}"); } else { config.maxFragmentLengthCode = lengthCode; return this; } } /** * Sets the number of outbound messages that can be buffered in memory before * dropping messages. * * @param capacity the number of messages to buffer * @return this builder for command chaining * @throws IllegalArgumentException if capacity < 1 */ public Builder setOutboundMessageBufferSize(int capacity) { if (capacity < 1) { throw new IllegalArgumentException("Outbound message buffer size must be at least 1"); } else { config.outboundMessageBufferSize = capacity; return this; } } /** * Sets the maximum number of times a flight of handshake messages gets re-transmitted * to a peer. * * @param count the maximum number of re-transmissions * @return this builder for command chaining */ public Builder setMaxRetransmissions(int count) { if (count < 1) { throw new IllegalArgumentException("Maximum number of retransmissions must be greater than zero"); } else { config.maxRetransmissions = count; return this; } } /** * Sets whether the connector requires DTLS clients to authenticate during * the handshake. * * @param authRequired * true if clients need to authenticate * @return this builder for command chaining */ public Builder setClientAuthenticationRequired(boolean authRequired) { config.clientAuthenticationRequired = authRequired; return this; } /** * Sets the cipher suites supported by the connector. *

* The connector will use these cipher suites (in exactly the same order) during * the DTLS handshake when negotiating a cipher suite with a peer. * * @param cipherSuites the supported cipher suites in the order of preference * @return this builder for command chaining * @throws IllegalArgumentException if the given array is null, is * empty or contains {@link CipherSuite#TLS_NULL_WITH_NULL_NULL} */ public Builder setSupportedCipherSuites(CipherSuite[] cipherSuites) { if (cipherSuites == null || cipherSuites.length == 0) { throw new IllegalArgumentException("Connector must support at least one cipher suite"); } else { for (CipherSuite suite : cipherSuites) { if (CipherSuite.TLS_NULL_WITH_NULL_NULL.equals(suite)) { throw new IllegalArgumentException("NULL Cipher Suite is not supported by connector"); } } config.supportedCipherSuites = Arrays.copyOf(cipherSuites, cipherSuites.length); return this; } } /** * Sets the cipher suites supported by the connector. *

* The connector will use these cipher suites (in exactly the same order) during * the DTLS handshake when negotiating a cipher suite with a peer. * * @param cipherSuites the names of supported cipher suites in the order of preference * (see * IANA registry for a list of cipher suite names) * @return this builder for command chaining * @throws IllegalArgumentException if the given array contains TLS_NULL_WITH_NULL_NULL * or if a name from the given list is unsupported (yet) */ public Builder setSupportedCipherSuites(String[] cipherSuites) { CipherSuite[] suites = new CipherSuite[cipherSuites.length]; for (int i = 0; i < cipherSuites.length; i++) { if (CipherSuite.TLS_NULL_WITH_NULL_NULL.name().equals(cipherSuites[i])) { throw new IllegalArgumentException("NULL Cipher Suite is not supported by connector"); } else { CipherSuite knownSuite = CipherSuite.getTypeByName(cipherSuites[i]); if (knownSuite != null) { suites[i] = knownSuite; } else { throw new IllegalArgumentException( String.format("Cipher suite [%s] is not (yet) supported", cipherSuites[i])); } } } config.supportedCipherSuites = suites; return this; } /** * Sets the time to wait before a handshake package gets re-transmitted. * * @param timeout the time in milliseconds * @return this builder for command chaining * @throws IllegalArgumentException if the given timeout is negative */ public Builder setRetransmissionTimeout(int timeout) { if (timeout < 0) { throw new IllegalArgumentException("Retransmission timeout must not be negative"); } else { config.retransmissionTimeout = timeout; return this; } } /** * Sets the key store to use for authenticating clients based * on a pre-shared key. * * @param pskStore * the key store * @return this builder for command chaining */ public Builder setPskStore(PskStore pskStore) { config.pskStore = pskStore; return this; } /** * Sets the connector's identifying properties by means of a private * and public key pair. *

* Using this method implies that the connector only supports * RawPublicKey mode for authenticating to a peer. * * @param privateKey the private key used for creating signatures * @param publicKey the public key a peer can use to verify possession of the private key * @return this builder for command chaining * @throws NullPointerException if any of the given keys is null */ public Builder setIdentity(PrivateKey privateKey, PublicKey publicKey) { if (privateKey == null) throw new NullPointerException("The private key must not be null"); if (publicKey == null) throw new NullPointerException("The public key must not be null"); config.privateKey = privateKey; config.publicKey = publicKey; config.certChain = null; config.sendRawKey = true; return this; } /** * Sets the connector's identifying properties by means of a private key * and a corresponding issuer certificates chain. *

* In server mode the key and certificates are used to prove the server's * identity to the client. In client mode the key and certificates are used * to prove the client's identity to the server. * * @param privateKey * the private key used for creating signatures * @param certificateChain * the chain of certificates asserting the private key subject's * identity * @param preferRawPublicKeys * true if the connector should indicate preference for * using RawPublicKeys for authentication purposes in the * handshake with a peer (instead of including the full X.509 certificate chain) * @return this builder for command chaining * @throws NullPointerException if the given private key or certificate chain is null * or the certificate chain does not contain any certificates * @see #setIdentity(PrivateKey, PublicKey) for configuring RawPublicKey * mode only */ public Builder setIdentity(PrivateKey privateKey, Certificate[] certificateChain, boolean preferRawPublicKeys) { if (privateKey == null) { throw new NullPointerException("The private key must not be null"); } else if (certificateChain == null || certificateChain.length < 1) { throw new NullPointerException("The certificate chain must not be null or empty"); } else { config.privateKey = privateKey; config.certChain = Arrays.copyOf(certificateChain, certificateChain.length); config.publicKey = config.certChain[0].getPublicKey(); config.sendRawKey = preferRawPublicKeys; return this; } } /** * Sets the root certificates the connector should use as the trust anchor when verifying * a peer's identity based on an X.509 certificate chain. * * @param trustedCerts the trusted root certificates * @return this builder for command chaining * @throws NullPointerException if the given array is null */ public Builder setTrustStore(Certificate[] trustedCerts) { if (trustedCerts == null) { throw new NullPointerException("Trust store must not be null"); } else { config.trustStore = Arrays.copyOf(trustedCerts, trustedCerts.length); return this; } } private boolean isConfiguredWithKeyPair() { return config.privateKey != null && config.publicKey != null; } /** * Creates an instance of DtlsConnectorConfig based on the properties * set on this builder. *

* If the supportedCipherSuites property has not been set, the * builder tries to derive a reasonable set of cipher suites from the * pskStore and identity properties as follows: *

    *
  1. If only the pskStore is set: {TLS_PSK_WITH_AES_128_CCM_8, * TLS_PSK_WITH_AES_128_CBC_SHA256}
  2. *
  3. If only the identity is set: {TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, * TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}
  4. *
  5. If both the pskStore and the identity are set: * {TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, * TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256}
  6. *
* * @return the configuration object * @throws IllegalStateException if the configuration is inconsistent */ public DtlsConnectorConfig build() { if (config.getSupportedCipherSuites().length == 0) { determineCipherSuitesFromConfig(); } if (config.getSupportedCipherSuites().length == 0) { throw new IllegalStateException("Supported cipher suites must be set either " + "explicitly or implicitly by means of setting the identity or PSK store"); } for (CipherSuite suite : config.getSupportedCipherSuites()) { switch (suite) { case TLS_PSK_WITH_AES_128_CCM_8: case TLS_PSK_WITH_AES_128_CBC_SHA256: verifyPskBasedCipherConfig(); break; case TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: verifyEcBasedCipherConfig(); break; default: break; } } return config; } private void verifyPskBasedCipherConfig() { if (config.pskStore == null) { throw new IllegalStateException("PSK store must be set when support for " + CipherSuite.TLS_PSK_WITH_AES_128_CCM_8.name() + " is configured"); } } private void verifyEcBasedCipherConfig() { if (!clientOnly) { if (config.getPrivateKey() == null || config.getPublicKey() == null) { throw new IllegalStateException("Identity must be set"); } else if ( !EC_ALGORITHM_NAME.equals(config.privateKey.getAlgorithm()) || !EC_ALGORITHM_NAME.equals(config.getPublicKey().getAlgorithm()) ) { // test if private & public key are ECDSA capable throw new IllegalStateException("Keys must be ECDSA capable when support for an " + "ECDHE_ECDSA based cipher suite is configured"); } } } private void determineCipherSuitesFromConfig() { // user has not explicitly set cipher suites // try to guess his intentions from properties he has set List ciphers = new ArrayList<>(); if (isConfiguredWithKeyPair()) { ciphers.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8); ciphers.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256); } if (config.pskStore != null) { ciphers.add(CipherSuite.TLS_PSK_WITH_AES_128_CCM_8); ciphers.add(CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256); } config.supportedCipherSuites = ciphers.toArray(new CipherSuite[0]); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy