diff --git a/MIGRATION_HINTS_4.md b/MIGRATION_HINTS_4.md index f336a3f8ce..792e2d629a 100644 --- a/MIGRATION_HINTS_4.md +++ b/MIGRATION_HINTS_4.md @@ -60,6 +60,14 @@ In some cases `SHA384` was misspelled as `SHA378`. That's fixed but causes also Supports virtual threads for DTLS receivers, if the JVM supports it. Otherwise platform daemon threads are used. Chosen by `-1` as number of threads in `DTLS.RECEIVER_THREAD_COUNT`. +The `DTLSConnector` uses Connection ID to identify DLTS context now also for outgoing messages, if available. + +The `DefaultCipherSuiteSelector` supports now `CertificateAuthenticationMode.WANTED` even if no common client certificate type is available. It omits the `CertificateRequest` in this case. + +The `InMemoryConnectionStore` removes now `Connection`s without `Principals` when the ip-address is reused. + +Using the `DTLS.APPLICATION_AUTHORIZATION_TIMEOUT` removes now connections with anonymous clients after that timeout, if the application doesn't authorize them using the `ApplicationAuthorizer`. + ### Element-Connector-TCP-Netty: ### Californium-Core: @@ -120,6 +128,12 @@ Remove the "Advanced" from PSK-stores. Replace `AdvancedPskStore` by `PskStore`, Remove the "NewAdvanced" from CertificateVerifier. Replace `NewAdvancedCertificateVerifier` by `CertificateVerifier`, `StaticNewAdvancedCertificateVerifier` by `StaticCertificateVerifier` and `AsyncNewAdvancedCertificateVerifier` by `AsyncCertificateVerifier`. +Rename `Connection.refreshAutoResumptionTime` into `updateLastMessageNanos`. + +`DTLSConnector.cleanupRecentHandshakes` returns `int` instead of `void`. + +Remove `restoreConnection` from `DTLSConnector`. + ### Californium-Core: The functions of the obsolete and removed `ExtendedCoapStack` are moved into diff --git a/californium-core/src/main/java/org/eclipse/californium/core/network/CoapEndpoint.java b/californium-core/src/main/java/org/eclipse/californium/core/network/CoapEndpoint.java index 3405b1063a..cbfa2dee89 100644 --- a/californium-core/src/main/java/org/eclipse/californium/core/network/CoapEndpoint.java +++ b/californium-core/src/main/java/org/eclipse/californium/core/network/CoapEndpoint.java @@ -131,6 +131,7 @@ import org.eclipse.californium.elements.RawDataChannel; import org.eclipse.californium.elements.UDPConnector; import org.eclipse.californium.elements.UdpMulticastConnector; +import org.eclipse.californium.elements.auth.ApplicationAuthorizer; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.elements.util.ClockUtil; import org.eclipse.californium.elements.util.DaemonThreadFactory; @@ -229,6 +230,13 @@ public class CoapEndpoint implements Endpoint, Executor { /** The connector over which the endpoint connects to the network */ private final Connector connector; + /** + * Application authorizer for anonymous client support. + * + * @since 4.0 + */ + private final ApplicationAuthorizer authorizer; + private final String scheme; /** @@ -389,6 +397,7 @@ protected CoapEndpoint(Connector connector, Configuration config, TokenGenerator } this.config = config; this.connector = connector; + this.authorizer = (connector instanceof ApplicationAuthorizer) ? (ApplicationAuthorizer) connector : null; this.connector.setRawDataReceiver(new InboxImpl()); this.scheme = CoAP.getSchemeForProtocol(connector.getProtocol()); this.multicastBaseMid = config.get(CoapConfig.MULTICAST_BASE_MID); @@ -1339,6 +1348,11 @@ public void cancelObservation(Token token) { matcher.cancelObserve(token); } + @Override + public ApplicationAuthorizer getApplicationAuthorizer() { + return authorizer; + } + /** * {@inheritDoc} * diff --git a/californium-core/src/main/java/org/eclipse/californium/core/network/Endpoint.java b/californium-core/src/main/java/org/eclipse/californium/core/network/Endpoint.java index eec0d9b8a5..cf55979463 100644 --- a/californium-core/src/main/java/org/eclipse/californium/core/network/Endpoint.java +++ b/californium-core/src/main/java/org/eclipse/californium/core/network/Endpoint.java @@ -35,6 +35,7 @@ import org.eclipse.californium.core.observe.NotificationListener; import org.eclipse.californium.core.server.MessageDeliverer; import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.elements.auth.ApplicationAuthorizer; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.elements.util.ProtocolScheduledExecutorService; @@ -286,4 +287,13 @@ public interface Endpoint { * @throws IllegalArgumentException if the token has client-local scope. */ void cancelObservation(Token token); + + /** + * Gets application authorizer. + * + * @return application authorizer, or {@code null}, if not supported by this + * endpoint. + * @since 4.0 + */ + ApplicationAuthorizer getApplicationAuthorizer(); } diff --git a/californium-core/src/main/java/org/eclipse/californium/core/network/EndpointContextMatcherFactory.java b/californium-core/src/main/java/org/eclipse/californium/core/network/EndpointContextMatcherFactory.java index 801bc27b61..a94818ae4e 100644 --- a/californium-core/src/main/java/org/eclipse/californium/core/network/EndpointContextMatcherFactory.java +++ b/californium-core/src/main/java/org/eclipse/californium/core/network/EndpointContextMatcherFactory.java @@ -30,6 +30,7 @@ import org.eclipse.californium.core.config.CoapConfig.MatcherMode; import org.eclipse.californium.elements.Connector; import org.eclipse.californium.elements.EndpointContextMatcher; +import org.eclipse.californium.elements.PrincipalAndAnonymousEndpointContextMatcher; import org.eclipse.californium.elements.PrincipalEndpointContextMatcher; import org.eclipse.californium.elements.RelaxedDtlsEndpointContextMatcher; import org.eclipse.californium.elements.StrictDtlsEndpointContextMatcher; @@ -43,64 +44,106 @@ */ public class EndpointContextMatcherFactory { - /** - * Create endpoint context matcher related to connector according the + * Creates endpoint context matcher related to connector according the * configuration. - * - * If connector supports "coaps:", RESPONSE_MATCHING is used to determine, - * if {@link StrictDtlsEndpointContextMatcher}, + *
+ * If connector supports "DTLS", COAP.RESPONSE_MATCHING is used to + * determine, if {@link StrictDtlsEndpointContextMatcher}, * {@link RelaxedDtlsEndpointContextMatcher}, or * {@link PrincipalEndpointContextMatcher} is used. - * - * If connector supports "coap:", RESPONSE_MATCHING is used to determine, if - * {@link UdpEndpointContextMatcher} is used with disabled + *
+ * If connector supports "UDP", COAP.RESPONSE_MATCHING is used to determine, + * if {@link UdpEndpointContextMatcher} is used with disabled * ({@link MatcherMode#RELAXED}) or enabled address check (otherwise). - * + *
* For other protocol flavors the corresponding matcher is used. * * @param connector connector to create related endpoint context matcher. * @param config configuration. * @return endpoint context matcher - * @since 3.0 (changed parameter to Configuration) + * @throws NullPointerException if one of the provided arguments is + * {@code null} + * @throws IllegalArgumentException if the protocol of the connector is not + * supported. + * @since 4.0 (added exceptions) */ public static EndpointContextMatcher create(Connector connector, Configuration config) { - String protocol = null; - if (null != connector) { - protocol = connector.getProtocol(); - if (CoAP.PROTOCOL_TCP.equalsIgnoreCase(protocol)) { - return new TcpEndpointContextMatcher(); - } else if (CoAP.PROTOCOL_TLS.equalsIgnoreCase(protocol)) { - return new TlsEndpointContextMatcher(); - } + if (connector == null) { + throw new NullPointerException("Connector must not be null!"); + } + return create(connector.getProtocol(), false, config); + } + + /** + * Creates endpoint context matcher related to the protocol according the + * configuration. + *
+ * For "DTLS" COAP.RESPONSE_MATCHING is used to determine, if + * {@link StrictDtlsEndpointContextMatcher}, + * {@link RelaxedDtlsEndpointContextMatcher}, or + * {@link PrincipalEndpointContextMatcher} is used. If PRINCIPAL_IDENTITY is + * used for COAP.RESPONSE_MATCHING and anonymous clients are enabled, then + * {@link PrincipalAndAnonymousEndpointContextMatcher} is used. Anonymous + * clients are only implemented for DTLS, see + * DTLS.CLIENT_AUTHENTICATION_MODE and DTLS.DTLS_APPLICATION_AUTHORIZATION. + *
+ * For "UDP", COAP.RESPONSE_MATCHING is used to determine, if + * {@link UdpEndpointContextMatcher} is used with disabled + * ({@link MatcherMode#RELAXED}) or enabled address check (otherwise). + *
+ * For other protocol flavors the corresponding matcher is used.
+ *
+ * @param protocol protocol.
+ * @param anonymous {@code true} if anonymous clients must be supported.
+ * @param config configuration.
+ * @return endpoint context matcher
+ * @throws NullPointerException if one of the provided arguments is
+ * {@code null}
+ * @throws IllegalArgumentException if the protocol is not supported.
+ * @since 4.0
+ */
+ public static EndpointContextMatcher create(String protocol, boolean anonymous, Configuration config) {
+ if (protocol == null) {
+ throw new NullPointerException("Protocol must not be null!");
}
+ if (config == null) {
+ throw new NullPointerException("Configuration must not be null!");
+ }
+ if (CoAP.PROTOCOL_TCP.equalsIgnoreCase(protocol)) {
+ return new TcpEndpointContextMatcher();
+ } else if (CoAP.PROTOCOL_TLS.equalsIgnoreCase(protocol)) {
+ return new TlsEndpointContextMatcher();
+ }
+
MatcherMode mode = config.get(CoapConfig.RESPONSE_MATCHING);
- switch (mode) {
- case RELAXED:
- if (CoAP.PROTOCOL_UDP.equalsIgnoreCase(protocol)) {
+ if (CoAP.PROTOCOL_UDP.equalsIgnoreCase(protocol)) {
+ switch (mode) {
+ case RELAXED:
return new UdpEndpointContextMatcher(false);
- } else {
- return new RelaxedDtlsEndpointContextMatcher();
- }
- case PRINCIPAL:
- if (CoAP.PROTOCOL_UDP.equalsIgnoreCase(protocol)) {
- return new UdpEndpointContextMatcher(true);
- } else {
- return new PrincipalEndpointContextMatcher();
- }
- case PRINCIPAL_IDENTITY:
- if (CoAP.PROTOCOL_UDP.equalsIgnoreCase(protocol)) {
+ case PRINCIPAL:
+ case PRINCIPAL_IDENTITY:
+ case STRICT:
+ default:
return new UdpEndpointContextMatcher(true);
- } else {
- return new PrincipalEndpointContextMatcher(true);
}
- case STRICT:
- default:
- if (CoAP.PROTOCOL_UDP.equalsIgnoreCase(protocol)) {
- return new UdpEndpointContextMatcher(true);
- } else {
+ } else if (CoAP.PROTOCOL_DTLS.equalsIgnoreCase(protocol)) {
+ switch (mode) {
+ case RELAXED:
+ return new RelaxedDtlsEndpointContextMatcher();
+ case PRINCIPAL:
+ return new PrincipalEndpointContextMatcher();
+ case PRINCIPAL_IDENTITY:
+ if (anonymous) {
+ return new PrincipalAndAnonymousEndpointContextMatcher();
+ } else {
+ return new PrincipalEndpointContextMatcher(true);
+ }
+ case STRICT:
+ default:
return new StrictDtlsEndpointContextMatcher();
}
}
+ throw new IllegalArgumentException("Protocol " + protocol + " is not supported!");
}
}
diff --git a/californium-core/src/main/java/org/eclipse/californium/core/network/Exchange.java b/californium-core/src/main/java/org/eclipse/californium/core/network/Exchange.java
index 1b1812b1ba..e1a7f93071 100644
--- a/californium-core/src/main/java/org/eclipse/californium/core/network/Exchange.java
+++ b/californium-core/src/main/java/org/eclipse/californium/core/network/Exchange.java
@@ -81,6 +81,7 @@
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.EndpointIdentityResolver;
import org.eclipse.californium.elements.UdpMulticastConnector;
+import org.eclipse.californium.elements.auth.ApplicationAuthorizer;
import org.eclipse.californium.elements.util.CheckedExecutor;
import org.eclipse.californium.elements.util.ClockUtil;
import org.eclipse.californium.elements.util.SerialExecutor;
@@ -861,6 +862,18 @@ public Object getPeersIdentity() {
return peersIdentity;
}
+ /**
+ * Gets application authorizer.
+ *
+ * @return application authorizer, or {@code null}, if not supported by this
+ * exchange.
+ * @since 4.0
+ */
+ public ApplicationAuthorizer getApplicationAuthorizer() {
+ Endpoint endpoint = getEndpoint();
+ return endpoint == null ? null : endpoint.getApplicationAuthorizer();
+ }
+
/**
* Indicated, that this exchange retransmission reached the timeout.
*
diff --git a/californium-core/src/main/java/org/eclipse/californium/core/observe/ObserveRelation.java b/californium-core/src/main/java/org/eclipse/californium/core/observe/ObserveRelation.java
index 9f2a25ab97..81925217de 100644
--- a/californium-core/src/main/java/org/eclipse/californium/core/observe/ObserveRelation.java
+++ b/californium-core/src/main/java/org/eclipse/californium/core/observe/ObserveRelation.java
@@ -569,7 +569,7 @@ public static State onResponse(ObserveRelation relation, Response response) {
}
boolean noNotification = result == State.NONE || result == State.CANCELED;
if (response.isNotification() && (!response.isSuccess() || noNotification)) {
- LOGGER.warn("Application notification, not longer observing, remove observe-option {}", response);
+ LOGGER.info("Application notification, not longer observing, remove observe-option {}", response);
response.getOptions().removeObserve();
}
return result;
diff --git a/californium-core/src/main/java/org/eclipse/californium/core/server/resources/CoapExchange.java b/californium-core/src/main/java/org/eclipse/californium/core/server/resources/CoapExchange.java
index fdf21cf611..c9853be606 100644
--- a/californium-core/src/main/java/org/eclipse/californium/core/server/resources/CoapExchange.java
+++ b/californium-core/src/main/java/org/eclipse/californium/core/server/resources/CoapExchange.java
@@ -22,6 +22,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.security.Principal;
import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.coap.CoAP.Code;
@@ -37,6 +38,7 @@
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.MapBasedEndpointContext;
import org.eclipse.californium.elements.MapBasedEndpointContext.Attributes;
+import org.eclipse.californium.elements.auth.ApplicationAuthorizer;
import org.eclipse.californium.elements.UdpMulticastConnector;
/**
@@ -69,14 +71,35 @@ public CoapExchange(Exchange exchange) {
this.exchange = exchange;
}
+ /**
+ * Gets the source principal.
+ *
+ * @return the source principal. May be {@code null}, if source is
+ * anonymous.
+ * @since 4.0
+ */
+ public final Principal getSourcePrincipal() {
+ return getSourceContext().getPeerIdentity();
+ }
+
+ /**
+ * Gets the source context.
+ *
+ * @return the source context.
+ * @since 4.0
+ */
+ public final EndpointContext getSourceContext() {
+ return exchange.getRequest().getSourceContext();
+ }
+
/**
* Gets the source socket address of the request.
*
* @return the source socket address
* @since 2.1
*/
- public InetSocketAddress getSourceSocketAddress() {
- return exchange.getRequest().getSourceContext().getPeerAddress();
+ public final InetSocketAddress getSourceSocketAddress() {
+ return getSourceContext().getPeerAddress();
}
/**
@@ -84,8 +107,8 @@ public InetSocketAddress getSourceSocketAddress() {
*
* @return the source address
*/
- public InetAddress getSourceAddress() {
- return exchange.getRequest().getSourceContext().getPeerAddress().getAddress();
+ public final InetAddress getSourceAddress() {
+ return getSourceSocketAddress().getAddress();
}
/**
@@ -93,8 +116,19 @@ public InetAddress getSourceAddress() {
*
* @return the source port
*/
- public int getSourcePort() {
- return exchange.getRequest().getSourceContext().getPeerAddress().getPort();
+ public final int getSourcePort() {
+ return getSourceSocketAddress().getPort();
+ }
+
+ /**
+ * Gets application authorizer.
+ *
+ * @return application authorizer, or {@code null}, if not supported by this
+ * exchange.
+ * @since 4.0
+ */
+ public ApplicationAuthorizer getApplicationAuthorizer() {
+ return exchange.getApplicationAuthorizer();
}
/**
diff --git a/californium-core/src/test/java/org/eclipse/californium/core/server/ServerPersistenceComponentsTest.java b/californium-core/src/test/java/org/eclipse/californium/core/server/ServerPersistenceComponentsTest.java
index fb2ec7705b..f6745b7338 100644
--- a/californium-core/src/test/java/org/eclipse/californium/core/server/ServerPersistenceComponentsTest.java
+++ b/californium-core/src/test/java/org/eclipse/californium/core/server/ServerPersistenceComponentsTest.java
@@ -47,6 +47,7 @@
import org.eclipse.californium.elements.util.DataStreamReader;
import org.eclipse.californium.elements.util.DatagramWriter;
import org.eclipse.californium.elements.util.EncryptedPersistentComponentUtil;
+import org.eclipse.californium.elements.util.EncryptedStreamUtil;
import org.eclipse.californium.elements.util.PersistentComponentUtil;
import org.eclipse.californium.elements.util.SerializationUtil;
import org.eclipse.californium.rule.CoapNetworkRule;
@@ -106,6 +107,7 @@ public void testSaveSkipAndLoad() throws IOException {
util.saveComponents(out, 1000);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
util = setup(connector2);
+ logging.setLoggingLevel("ERROR", PersistentComponentUtil.class);
util.loadComponents(in);
assertThat(connector1.data, is(nullValue()));
assertArrayEquals(connector2.mark, connector2.data);
@@ -118,6 +120,7 @@ public void testSaveLoadAndSkip() throws IOException {
util.saveComponents(out, 1000);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
util = setup(connector1);
+ logging.setLoggingLevel("ERROR", PersistentComponentUtil.class);
util.loadComponents(in);
assertArrayEquals(connector1.mark, connector1.data);
assertThat(connector2.data, is(nullValue()));
@@ -155,6 +158,7 @@ public void testEncryptedSaveSkipAndLoad() throws IOException {
util.saveComponents(out, key, 1000);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
util = setup(connector2);
+ logging.setLoggingLevel("ERROR", PersistentComponentUtil.class);
util.loadComponents(in, key);
assertThat(connector1.data, is(nullValue()));
assertArrayEquals(connector2.mark, connector2.data);
@@ -168,6 +172,7 @@ public void testEncryptedSaveLoadAndSkip() throws IOException {
util.saveComponents(out, key, 1000);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
util = setup(connector1);
+ logging.setLoggingLevel("ERROR", PersistentComponentUtil.class);
util.loadComponents(in, key);
assertArrayEquals(connector1.mark, connector1.data);
assertThat(connector2.data, is(nullValue()));
@@ -181,6 +186,7 @@ public void testEncryptedSaveLoadSkipAndLoad() throws IOException {
util.saveComponents(out, key, 1000);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
util = setup(connector1, connector3);
+ logging.setLoggingLevel("ERROR", PersistentComponentUtil.class);
util.loadComponents(in, key);
assertArrayEquals(connector1.mark, connector1.data);
assertThat(connector2.data, is(nullValue()));
@@ -220,7 +226,7 @@ public void testUnencryptedSaveAndEncryptedLoad() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
util.saveComponents(out, 1000);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
- logging.setLoggingLevel("ERROR", PersistentComponentUtil.class);
+ logging.setLoggingLevel("ERROR", EncryptedStreamUtil.class);
util.loadComponents(in, key);
assertThat(connector1.data, is(nullValue()));
assertThat(connector2.data, is(nullValue()));
diff --git a/californium-core/src/test/java/org/eclipse/californium/core/test/ResourceAttributesTest.java b/californium-core/src/test/java/org/eclipse/californium/core/test/ResourceAttributesTest.java
index 45d34fd287..4016465882 100755
--- a/californium-core/src/test/java/org/eclipse/californium/core/test/ResourceAttributesTest.java
+++ b/californium-core/src/test/java/org/eclipse/californium/core/test/ResourceAttributesTest.java
@@ -41,6 +41,7 @@
import org.eclipse.californium.core.server.MessageDeliverer;
import org.eclipse.californium.core.server.resources.DiscoveryResource;
import org.eclipse.californium.core.server.resources.Resource;
+import org.eclipse.californium.elements.auth.ApplicationAuthorizer;
import org.eclipse.californium.elements.category.Small;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.rule.TestNameLoggerRule;
@@ -245,5 +246,10 @@ public List
@@ -141,15 +187,19 @@ public String toString() {
* @param principal the principal
* @return principal info, or {@code null}, if not available.
* @see EndpointContext#getPeerIdentity()
+ * @since 4.0 (supports {@link ApplicationAnonymous#ANONYMOUS_INFO}, if
+ * {@code null} is provided as principal.)
*/
public static PrincipalInfo getPrincipalInfo(Principal principal) {
- if (principal instanceof ExtensiblePrincipal) {
+ if (principal == null) {
+ return ApplicationAnonymous.ANONYMOUS_INFO;
+ } else if (principal instanceof ExtensiblePrincipal) {
@SuppressWarnings("unchecked")
ExtensiblePrincipal extends Principal> extensiblePrincipal = (ExtensiblePrincipal extends Principal>) principal;
PrincipalInfoProvider provider = extensiblePrincipal.getExtendedInfo().get(INFO_PROVIDER,
PrincipalInfoProvider.class);
if (provider != null) {
- return provider.getPrincipalInfo(extensiblePrincipal);
+ return provider.getPrincipalInfo(principal);
}
}
return null;
diff --git a/demo-apps/cf-plugtest-server/src/main/java/org/eclipse/californium/plugtests/AbstractTestServer.java b/demo-apps/cf-plugtest-server/src/main/java/org/eclipse/californium/plugtests/AbstractTestServer.java
index 58dcf5d890..ed74816abf 100644
--- a/demo-apps/cf-plugtest-server/src/main/java/org/eclipse/californium/plugtests/AbstractTestServer.java
+++ b/demo-apps/cf-plugtest-server/src/main/java/org/eclipse/californium/plugtests/AbstractTestServer.java
@@ -39,13 +39,13 @@
import org.eclipse.californium.core.CoapServer;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.config.CoapConfig;
-import org.eclipse.californium.core.config.CoapConfig.MatcherMode;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.Endpoint;
+import org.eclipse.californium.core.network.EndpointContextMatcherFactory;
import org.eclipse.californium.core.network.interceptors.AnonymizedOriginTracer;
import org.eclipse.californium.core.network.interceptors.HealthStatisticLogger;
import org.eclipse.californium.core.network.interceptors.MessageTracer;
-import org.eclipse.californium.elements.PrincipalEndpointContextMatcher;
+import org.eclipse.californium.elements.config.CertificateAuthenticationMode;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.config.IntegerDefinition;
import org.eclipse.californium.elements.config.SystemConfig;
@@ -69,8 +69,8 @@
import org.eclipse.californium.scandium.dtls.pskstore.AsyncPskStore;
import org.eclipse.californium.scandium.dtls.pskstore.MultiPskFileStore;
import org.eclipse.californium.scandium.dtls.resumption.AsyncResumptionVerifier;
-import org.eclipse.californium.scandium.dtls.x509.AsyncKeyManagerCertificateProvider;
import org.eclipse.californium.scandium.dtls.x509.AsyncCertificateVerifier;
+import org.eclipse.californium.scandium.dtls.x509.AsyncKeyManagerCertificateProvider;
import org.eclipse.californium.scandium.util.SecretUtil;
import org.eclipse.californium.scandium.util.ServerNames;
import org.slf4j.Logger;
@@ -375,9 +375,10 @@ public void addEndpoints(BaseConfig cliConfig) {
DTLSConnector connector = new DTLSConnector(dtlsConfigBuilder.build());
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setConnector(connector);
- if (MatcherMode.PRINCIPAL == dtlsConfig.get(CoapConfig.RESPONSE_MATCHING)) {
- builder.setEndpointContextMatcher(new PrincipalEndpointContextMatcher(true));
- }
+ boolean anonymous = CertificateAuthenticationMode.NEEDED != dtlsConfig
+ .get(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE);
+ builder.setEndpointContextMatcher(
+ EndpointContextMatcherFactory.create(CoAP.PROTOCOL_DTLS, anonymous, dtlsConfig));
builder.setConfiguration(dtlsConfig);
CoapEndpoint endpoint = builder.build();
addEndpoint(endpoint);
diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/S3ProxyServer.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/S3ProxyServer.java
index b7f4b41a5a..4fb31c3579 100755
--- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/S3ProxyServer.java
+++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/S3ProxyServer.java
@@ -71,6 +71,7 @@
import org.eclipse.californium.core.coap.option.MapBasedOptionRegistry;
import org.eclipse.californium.core.coap.option.OptionRegistry;
import org.eclipse.californium.core.coap.option.StandardOptionRegistry;
+import org.eclipse.californium.elements.config.CertificateAuthenticationMode;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.config.Configuration.DefinitionsProvider;
import org.eclipse.californium.elements.config.IntegerDefinition;
@@ -78,6 +79,7 @@
import org.eclipse.californium.elements.util.SslContextUtil.Credentials;
import org.eclipse.californium.proxy2.config.Proxy2Config;
import org.eclipse.californium.proxy2.http.HttpClientFactory;
+import org.eclipse.californium.scandium.config.DtlsConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -420,6 +422,8 @@ public void defaults() {
@Override
public void applyDefinitions(Configuration config) {
BaseServer.DEFAULTS.applyDefinitions(config);
+ config.set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.WANTED);
+ config.set(DtlsConfig.DTLS_APPLICATION_AUTHORIZATION_TIMEOUT, 15, TimeUnit.SECONDS);
config.set(USER_CREDENTIALS_RELOAD_INTERVAL, 30, TimeUnit.SECONDS);
config.set(S3_PROCESSING_INITIAL_DELAY, 20, TimeUnit.SECONDS);
config.set(S3_PROCESSING_INTERVAL, 0, TimeUnit.HOURS);
diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/BasicHttpForwardConfiguration.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/BasicHttpForwardConfiguration.java
index 07f1e29963..7d9e06efae 100644
--- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/BasicHttpForwardConfiguration.java
+++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/BasicHttpForwardConfiguration.java
@@ -21,6 +21,7 @@
import java.util.Map;
import java.util.regex.Pattern;
+import org.eclipse.californium.cloud.s3.util.DomainPrincipalInfo;
import org.eclipse.californium.cloud.s3.util.Domains;
import org.eclipse.californium.cloud.util.DeviceParser;
@@ -230,7 +231,7 @@ public boolean isValid() {
* {@link HttpForwardConfigurationProvider} providing itself.
*/
@Override
- public HttpForwardConfiguration getConfiguration(String domain, String name) {
+ public HttpForwardConfiguration getConfiguration(DomainPrincipalInfo principalInfo) {
return this;
}
diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/HttpForwardConfigurationProvider.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/HttpForwardConfigurationProvider.java
index 2929cf5225..a10cd9652f 100644
--- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/HttpForwardConfigurationProvider.java
+++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/HttpForwardConfigurationProvider.java
@@ -14,6 +14,8 @@
********************************************************************************/
package org.eclipse.californium.cloud.s3.forward;
+import org.eclipse.californium.cloud.s3.util.DomainPrincipalInfo;
+
/**
* Http forward provider.
*
@@ -27,11 +29,10 @@ public interface HttpForwardConfigurationProvider {
/**
* Gets http forward configuration.
*
- * @param domain domain name
- * @param name device name
+ * @param principalInfo principal info (with domain and device name)
* @return http forward configuration, or {@code null}, if http forwarding
* is not used.
*/
- HttpForwardConfiguration getConfiguration(String domain, String name);
+ HttpForwardConfiguration getConfiguration(DomainPrincipalInfo principalInfo);
}
diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/HttpForwardConfigurationProviders.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/HttpForwardConfigurationProviders.java
index c6a42321a4..e953ede355 100644
--- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/HttpForwardConfigurationProviders.java
+++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/forward/HttpForwardConfigurationProviders.java
@@ -17,6 +17,8 @@
import java.util.ArrayList;
import java.util.List;
+import org.eclipse.californium.cloud.s3.util.DomainPrincipalInfo;
+
/**
* Http forward providers.
*
@@ -71,10 +73,10 @@ public void add(HttpForwardConfigurationProvider provider) {
* the one from the general configuration.
*/
@Override
- public HttpForwardConfiguration getConfiguration(String domain, String name) {
+ public HttpForwardConfiguration getConfiguration(DomainPrincipalInfo principalInfo) {
HttpForwardConfiguration configuration = null;
for (HttpForwardConfigurationProvider provider : list) {
- configuration = BasicHttpForwardConfiguration.merge(configuration, provider.getConfiguration(domain, name));
+ configuration = BasicHttpForwardConfiguration.merge(configuration, provider.getConfiguration(principalInfo));
}
return configuration;
}
diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/http/Aws4Authorizer.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/http/Aws4Authorizer.java
index 50fb455ae5..4568731df3 100644
--- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/http/Aws4Authorizer.java
+++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/http/Aws4Authorizer.java
@@ -516,6 +516,11 @@ public WebAppAuthorization amend(AdditionalInfo additionalInfo) {
public AdditionalInfo getExtendedInfo() {
return additionalInfo;
}
+
+ @Override
+ public boolean isAnonymous() {
+ return false;
+ }
}
/**
diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/resources/S3Devices.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/resources/S3Devices.java
index 1c7220e5b9..1e68bc7fd7 100644
--- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/resources/S3Devices.java
+++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/resources/S3Devices.java
@@ -29,6 +29,7 @@
import static org.eclipse.californium.core.coap.MediaTypeRegistry.TEXT_PLAIN;
import static org.eclipse.californium.core.coap.MediaTypeRegistry.UNDEFINED;
+import java.security.Principal;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
@@ -58,26 +59,31 @@
import org.eclipse.californium.cloud.s3.proxy.S3ProxyClientProvider;
import org.eclipse.californium.cloud.s3.proxy.S3ProxyRequest;
import org.eclipse.californium.cloud.s3.proxy.S3ProxyRequest.Builder;
+import org.eclipse.californium.cloud.s3.util.DomainApplicationAnonymous;
import org.eclipse.californium.cloud.s3.util.DomainPrincipalInfo;
import org.eclipse.californium.cloud.s3.util.MultiConsumer;
import org.eclipse.californium.cloud.util.PrincipalInfo;
+import org.eclipse.californium.cloud.util.PrincipalInfo.Type;
import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.WebLink;
+import org.eclipse.californium.core.coap.CoAP.Code;
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
import org.eclipse.californium.core.coap.LinkFormat;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.Option;
import org.eclipse.californium.core.coap.OptionSet;
-import org.eclipse.californium.core.coap.option.OpaqueOption;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.coap.UriQueryParameter;
+import org.eclipse.californium.core.coap.option.OpaqueOption;
import org.eclipse.californium.core.network.Exchange;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.core.server.resources.ResourceAttributes;
+import org.eclipse.californium.elements.auth.ApplicationAuthorizer;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.elements.util.LeastRecentlyUpdatedCache;
+import org.eclipse.californium.elements.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -251,7 +257,7 @@ public class S3Devices extends ProtectedCoapResource {
*/
public S3Devices(Configuration config, S3ProxyClientProvider s3Clients,
HttpForwardConfigurationProvider httpForwardConfigurationProvider) {
- super(RESOURCE_NAME);
+ super(RESOURCE_NAME, Type.DEVICE, Type.ANONYMOUS_DEVICE, Type.APPL_AUTH_DEVICE);
if (s3Clients == null) {
throw new NullPointerException("s3client must not be null!");
}
@@ -265,6 +271,17 @@ public S3Devices(Configuration config, S3ProxyClientProvider s3Clients,
this.httpForwardConfigurationProvider = httpForwardConfigurationProvider;
}
+ @Override
+ protected ResponseCode checkOperationPermission(PrincipalInfo info, Exchange exchange, boolean write) {
+ if (info.type == Type.DEVICE || info.type == Type.APPL_AUTH_DEVICE) {
+ return null;
+ }
+ if (info.type == Type.ANONYMOUS_DEVICE && exchange.getRequest().getCode() == Code.POST) {
+ return null;
+ }
+ return FORBIDDEN;
+ }
+
@Override
public void add(Resource child) {
throw new UnsupportedOperationException("Not supported!");
@@ -319,7 +336,8 @@ public void handlePOST(final CoapExchange exchange) {
return;
}
- final DomainPrincipalInfo info = DomainPrincipalInfo.getPrincipalInfo(getPrincipal(exchange));
+ final Principal principal = getPrincipal(exchange);
+ final DomainPrincipalInfo info = DomainPrincipalInfo.getPrincipalInfo(principal);
boolean forward = false;
String read = null;
String write = null;
@@ -354,13 +372,45 @@ public void handlePOST(final CoapExchange exchange) {
final TimeOption timeOption = TimeOption.getMessageTime(request);
final long time = timeOption.getLongValue();
+ if (info.type == Type.ANONYMOUS_DEVICE ||
+ info.type == Type.APPL_AUTH_DEVICE) {
+
+ if (forward && read == null && write == null) {
+ // forward support for anonymous clients.
+ if (forward && httpForwardConfigurationProvider != null) {
+ final HttpForwardConfiguration configuration = httpForwardConfigurationProvider
+ .getConfiguration(info);
+ if (configuration != null && configuration.isValid()) {
+ String serviceName = configuration.getServiceName();
+ HttpForwardService service = HttpForwardServiceManager.getService(serviceName);
+ if (service != null) {
+ service.forwardPOST(request, info, configuration, (response) -> {
+ if (principal == null && response.isSuccess()) {
+ ApplicationAuthorizer authorizer = exchange.advanced().getApplicationAuthorizer();
+ if (authorizer != null) {
+ LOGGER.info("HTTP-forward: {} anonymous client authorized!",
+ StringUtil.toLog(request.getSourceContext().getPeerAddress()));
+ authorizer.authorize(request.getSourceContext(),
+ DomainApplicationAnonymous.APPL_AUTH_PRINCIPAL);
+ }
+ }
+ exchange.respond(response);
+ });
+ return;
+ }
+ }
+ }
+ }
+ Response response = new Response(ResponseCode.UNAUTHORIZED);
+ exchange.respond(response);
+ return;
+ }
Response response = new Response(CHANGED);
final String timestamp = format(time, ChronoUnit.MILLIS);
- final String domain = info.domain;
- S3ProxyClient s3Client = s3Clients.getProxyClient(domain);
+ S3ProxyClient s3Client = s3Clients.getProxyClient(info.domain);
String position = null;
- LOGGER.info("S3: {}, {}", domain, s3Client.getExternalEndpoint());
+ LOGGER.info("S3: {}, {}", info.domain, s3Client.getExternalEndpoint());
String writeExpanded = replaceVars(write, timestamp);
request.setProtectFromOffload();
String acl = S3ProxyRequest.getAcl(request, s3Client.getAcl());
@@ -443,8 +493,7 @@ public void complete(Map
@@ -68,9 +100,13 @@ public String toString() {
* @param principal the principal
* @return principal info, or {@code null}, if not available.
* @see EndpointContext#getPeerIdentity()
+ * @since 4.0 (supports {@link DomainApplicationAnonymous#ANONYMOUS_INFO}, if {@code null} is provided as
+ * principal.)
*/
public static DomainPrincipalInfo getPrincipalInfo(Principal principal) {
- if (principal instanceof ExtensiblePrincipal) {
+ if (principal == null) {
+ return DomainApplicationAnonymous.ANONYMOUS_INFO;
+ } else if (principal instanceof ExtensiblePrincipal) {
@SuppressWarnings("unchecked")
ExtensiblePrincipal extends Principal> extensiblePrincipal = (ExtensiblePrincipal extends Principal>) principal;
DomainPrincipalInfoProvider provider = extensiblePrincipal.getExtendedInfo().get(INFO_PROVIDER,
diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/Domains.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/Domains.java
index 7c5fc16e4a..28fd03070e 100644
--- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/Domains.java
+++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/Domains.java
@@ -179,6 +179,10 @@ public class Domains
* Section for web resources.
*/
public static final String WEB_SECTION = "web";
+ /**
+ * Section for anonymous clients.
+ */
+ public static final String ANONYOUS_SECTION = "anonymous";
/**
* Field name for maximum devices.
*/
@@ -238,6 +242,8 @@ private Domain() {
*/
private final Domain webDomain;
+ private final HttpForwardConfiguration anonymousHttpForwardingConfiguration;
+
/**
* Create domains setup.
*
@@ -250,6 +256,7 @@ public Domains(SystemResourceMonitors monitors, LinuxConfigParser domainDefiniti
int maxDevices = config.get(BaseServer.CACHE_MAX_DEVICES);
String web = domainDefinition.get(WEB_SECTION, "domain");
Domain webDomain = null;
+ HttpForwardConfiguration httpForwardingConfiguration = null;
this.monitors = monitors;
this.configuration = domainDefinition;
@@ -295,12 +302,31 @@ public Domains(SystemResourceMonitors monitors, LinuxConfigParser domainDefiniti
webDomain = domain;
}
}
+ } else if (ANONYOUS_SECTION.equals(section)) {
+ List
+ * Matches DTLS based on the used principal or the session ID, if the principal
+ * is anonymous. Requires unique and stable credentials.
+ *
+ * @since 4.0
+ */
+public class PrincipalAndAnonymousEndpointContextMatcher implements EndpointContextMatcher {
+
+ public PrincipalAndAnonymousEndpointContextMatcher() {
+ }
+
+ @Override
+ public String getName() {
+ return "principal and anonymous correlation";
+ }
+
+ /**
+ * Gets identity from endpoint context.
+ *
+ * Use the {@link Principal} if available and not
+ * {@link ExtensiblePrincipal#isAnonymous()}. Otherwise use the DTLS session
+ * ID.
+ *
+ * @param context endpoint context
+ * @return identity, or {@code null}, if none is available.
+ */
+ private Object getIdentity(EndpointContext context) {
+ Principal identity = context.getPeerIdentity();
+ if (identity instanceof ExtensiblePrincipal>) {
+ if (((ExtensiblePrincipal>) identity).isAnonymous()) {
+ // anonymous principals don't have an identity.
+ identity = null;
+ }
+ }
+ if (identity != null) {
+ return identity;
+ }
+ Bytes id = context.get(DtlsEndpointContext.KEY_SESSION_ID);
+ if (id != null && !id.isEmpty()) {
+ return id;
+ }
+ return null;
+ }
+
+ @Override
+ public Object getEndpointIdentity(EndpointContext context) {
+ Object identity = getIdentity(context);
+ if (identity == null) {
+ throw new IllegalArgumentException(
+ "Principal identity and session id are missing in provided endpoint context!");
+ }
+ return identity;
+ }
+
+ @Override
+ public boolean isResponseRelatedToRequest(EndpointContext requestContext, EndpointContext responseContext) {
+ return internalMatch(requestContext, responseContext);
+ }
+
+ @Override
+ public boolean isToBeSent(EndpointContext messageContext, EndpointContext connectorContext) {
+ if (null == connectorContext) {
+ return true;
+ }
+ return internalMatch(messageContext, connectorContext);
+ }
+
+ private final boolean internalMatch(EndpointContext requestedContext, EndpointContext availableContext) {
+
+ Object identity = getIdentity(requestedContext);
+ if (identity != null) {
+ return identity.equals(getIdentity(availableContext));
+ }
+ String cipher = requestedContext.getString(DtlsEndpointContext.KEY_CIPHER);
+ if (cipher != null) {
+ if (!cipher.equals(availableContext.getString(DtlsEndpointContext.KEY_CIPHER))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toRelevantState(EndpointContext context) {
+ if (context == null) {
+ return "n.a.";
+ } else {
+ StringBuilder builder = new StringBuilder();
+ builder.append("[");
+ builder.append(getIdentity(context));
+ String cipher = context.getString(DtlsEndpointContext.KEY_CIPHER);
+ if (cipher != null) {
+ builder.append(",").append(cipher);
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+ }
+}
diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/PrincipalEndpointContextMatcher.java b/element-connector/src/main/java/org/eclipse/californium/elements/PrincipalEndpointContextMatcher.java
index 47d14dd2ea..8c3f76bf8e 100644
--- a/element-connector/src/main/java/org/eclipse/californium/elements/PrincipalEndpointContextMatcher.java
+++ b/element-connector/src/main/java/org/eclipse/californium/elements/PrincipalEndpointContextMatcher.java
@@ -19,8 +19,9 @@
/**
* Principal based endpoint context matcher.
- *
- * Matches DTLS based on the used principal. Requires unique and stable credentials.
+ *
+ * Matches DTLS based on the used principal. Requires unique and stable
+ * credentials.
*/
public class PrincipalEndpointContextMatcher implements EndpointContextMatcher {
@@ -66,11 +67,13 @@ public boolean isToBeSent(EndpointContext messageContext, EndpointContext connec
}
private final boolean internalMatch(EndpointContext requestedContext, EndpointContext availableContext) {
- if (requestedContext.getPeerIdentity() != null) {
- if (availableContext.getPeerIdentity() == null) {
+ Principal identity = requestedContext.getPeerIdentity();
+ if (identity != null) {
+ Principal availableIdentity = availableContext.getPeerIdentity();
+ if (availableIdentity == null) {
return false;
}
- if (!matchPrincipals(requestedContext.getPeerIdentity(), availableContext.getPeerIdentity())) {
+ if (!matchPrincipals(identity, availableIdentity)) {
return false;
}
}
@@ -102,7 +105,7 @@ public String toRelevantState(EndpointContext context) {
/**
* Match principals.
- *
+ *
* Intended to be overwritten, when asymmetric principal implementations are
* used, and {@link #equals(Object)} doesn't work.
*
diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/auth/AbstractExtensiblePrincipal.java b/element-connector/src/main/java/org/eclipse/californium/elements/auth/AbstractExtensiblePrincipal.java
index c497127ca5..5411615107 100644
--- a/element-connector/src/main/java/org/eclipse/californium/elements/auth/AbstractExtensiblePrincipal.java
+++ b/element-connector/src/main/java/org/eclipse/californium/elements/auth/AbstractExtensiblePrincipal.java
@@ -49,11 +49,13 @@ protected AbstractExtensiblePrincipal(AdditionalInfo additionalInformation) {
}
}
- /**
- * {@inheritDoc}
- */
@Override
public final AdditionalInfo getExtendedInfo() {
return additionalInfo;
}
+
+ @Override
+ public boolean isAnonymous() {
+ return false;
+ }
}
diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/auth/ApplicationAuthorizer.java b/element-connector/src/main/java/org/eclipse/californium/elements/auth/ApplicationAuthorizer.java
new file mode 100644
index 0000000000..9791fcf260
--- /dev/null
+++ b/element-connector/src/main/java/org/eclipse/californium/elements/auth/ApplicationAuthorizer.java
@@ -0,0 +1,57 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
+ * v1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ ********************************************************************************/
+package org.eclipse.californium.elements.auth;
+
+import java.security.Principal;
+import java.util.concurrent.Future;
+
+import org.eclipse.californium.elements.EndpointContext;
+
+/**
+ * Application authorize.
+ *
+ * Sets {@link Principal} from application layers. Used with
+ * {@code DtlsConfig.DTLS_APPLICATION_AUTHORIZATION} to authorize or reject
+ * anonymous clients.
+ *
+ * @since 4.0
+ */
+public interface ApplicationAuthorizer {
+
+ /**
+ * Authorize the associated connection with the {@link ApplicationPrincipal}
+ * to prevent connection from being removed after in short time.
+ *
+ * The Authorization may be processed asynchronous. A future request
+ * therefore may still not contain the provided principal! Only if the
+ * connection has not already a principal assigned, the provided one will be
+ * assigned.
+ *
+ * @param context endpoint context
+ * @param principal anonymous principal
+ * @return future with boolean result. Completes with {@code true}, if the
+ * principal was assigned, {@code false}, otherwise.
+ */
+ Future
- * The additional information can be retrieved from the returned copy using the
- * {@link #getExtendedInfo()} method.
+ * The additional information can be retrieved from the returned copy using
+ * the {@link #getExtendedInfo()} method.
*
* @param additionalInfo The additional information.
* @return The copy.
@@ -44,4 +44,15 @@ public interface ExtensiblePrincipal
+ * Anonymous principals are not used as identities.
+ *
+ * @return {@code true}, if principal represents an anonymous,
+ * {@code false}, otherwise.
+ * @since 4.0
+ */
+ boolean isAnonymous();
}
diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/auth/PreSharedKeyIdentity.java b/element-connector/src/main/java/org/eclipse/californium/elements/auth/PreSharedKeyIdentity.java
index 8921ee2156..50cdabb9e5 100644
--- a/element-connector/src/main/java/org/eclipse/californium/elements/auth/PreSharedKeyIdentity.java
+++ b/element-connector/src/main/java/org/eclipse/californium/elements/auth/PreSharedKeyIdentity.java
@@ -46,11 +46,12 @@ public PreSharedKeyIdentity(String identity) {
* Creates a new instance for an identity scoped to a virtual host.
*
* @param virtualHost The virtual host name that the identity is scoped to.
- * The host name will be converted to lower case.
+ * The host name will be converted to lower case.
* @param identity the identity.
* @throws NullPointerException if the identity is {@code null}
* @throws IllegalArgumentException if virtual host is not a valid host name
- * as per RFC 1123.
+ * as per RFC 1123.
*/
public PreSharedKeyIdentity(String virtualHost, String identity) {
this(true, virtualHost, identity, null);
@@ -66,10 +67,11 @@ public PreSharedKeyIdentity(String virtualHost, String identity) {
* @param additionalInformation Additional information for this principal.
* @throws NullPointerException if the identity is {@code null}
* @throws IllegalArgumentException if virtual host is not a valid host name
- * as per RFC
- * 1123.
+ * as per RFC 1123.
*/
- private PreSharedKeyIdentity(boolean sni, String virtualHost, String identity, AdditionalInfo additionalInformation) {
+ private PreSharedKeyIdentity(boolean sni, String virtualHost, String identity,
+ AdditionalInfo additionalInformation) {
super(additionalInformation);
if (identity == null) {
throw new NullPointerException("Identity must not be null");
@@ -99,7 +101,8 @@ private PreSharedKeyIdentity(boolean sni, String virtualHost, String identity, A
}
}
- private PreSharedKeyIdentity(boolean scopedIdentity, String virtualHost, String identity, String name, AdditionalInfo additionalInfo) {
+ private PreSharedKeyIdentity(boolean scopedIdentity, String virtualHost, String identity, String name,
+ AdditionalInfo additionalInfo) {
super(additionalInfo);
this.scopedIdentity = scopedIdentity;
this.virtualHost = virtualHost;
@@ -160,7 +163,7 @@ public String getName() {
/**
* Gets a string representation of this principal.
- *
+ *
* Clients should not assume any particular format of the returned string
* since it may change over time.
*
@@ -169,11 +172,9 @@ public String getName() {
@Override
public String toString() {
if (scopedIdentity) {
- return new StringBuilder("PreSharedKey Identity [").append("virtual host: ").append(virtualHost)
- .append(", identity: ").append(identity).append("]").toString();
+ return "PreSharedKey Identity [virtual host: " + virtualHost + ", identity: " + identity + "]";
} else {
- return new StringBuilder("PreSharedKey Identity [").append("identity: ").append(identity).append("]")
- .toString();
+ return "PreSharedKey Identity [identity: " + identity + "]";
}
}
@@ -185,8 +186,9 @@ public int hashCode() {
/**
* Compares another object to this identity.
*
- * @return {@code true} if the other object is a {@code PreSharedKeyIdentity} and
- * its name property has the same value as this instance.
+ * @return {@code true} if the other object is a
+ * {@code PreSharedKeyIdentity} and its name property has the same
+ * value as this instance.
*/
@Override
public boolean equals(Object obj) {
@@ -200,4 +202,5 @@ public boolean equals(Object obj) {
PreSharedKeyIdentity other = (PreSharedKeyIdentity) obj;
return Objects.equals(name, other.name);
}
+
}
diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/auth/RawPublicKeyIdentity.java b/element-connector/src/main/java/org/eclipse/californium/elements/auth/RawPublicKeyIdentity.java
index bfcd2cc17d..9afb38a0de 100644
--- a/element-connector/src/main/java/org/eclipse/californium/elements/auth/RawPublicKeyIdentity.java
+++ b/element-connector/src/main/java/org/eclipse/californium/elements/auth/RawPublicKeyIdentity.java
@@ -66,7 +66,8 @@ private RawPublicKeyIdentity(PublicKey key, AdditionalInfo additionalInformation
}
/**
- * Creates a new instance for a given ASN.1 subject public key info structure.
+ * Creates a new instance for a given ASN.1 subject public key info
+ * structure.
*
* @param subjectInfo the ASN.1 encoded X.509 subject public key info.
* @throws NullPointerException if the subject info is {@code null}
@@ -78,35 +79,40 @@ public RawPublicKeyIdentity(byte[] subjectInfo) throws GeneralSecurityException
}
/**
- * Creates a new instance for a given ASN.1 subject public key info structure.
+ * Creates a new instance for a given ASN.1 subject public key info
+ * structure.
*
* @param subjectInfo the ASN.1 encoded X.509 subject public key info.
* @param keyAlgorithm the algorithm name to verify, that the subject public
* key uses this key algorithm, or to support currently not
- * supported key algorithms for serialization/deserialization.
- * If {@code null}, the key algorithm provided by the ASN.1
- * DER encoded subject public key is used.
+ * supported key algorithms for serialization/deserialization. If
+ * {@code null}, the key algorithm provided by the ASN.1 DER
+ * encoded subject public key is used.
* @throws NullPointerException if the subject info is {@code null}
- * @throws GeneralSecurityException if the JVM does not support the given key algorithm.
+ * @throws GeneralSecurityException if the JVM does not support the given
+ * key algorithm.
*/
public RawPublicKeyIdentity(byte[] subjectInfo, String keyAlgorithm) throws GeneralSecurityException {
this(subjectInfo, keyAlgorithm, null);
}
/**
- * Creates a new instance for a given ASN.1 subject public key info structure.
+ * Creates a new instance for a given ASN.1 subject public key info
+ * structure.
*
* @param subjectInfo the ASN.1 encoded X.509 subject public key info.
* @param keyAlgorithm the algorithm name to verify, that the subject public
* key uses this key algorithm, or to support currently not
- * supported key algorithms for serialization/deserialization.
- * If {@code null}, the key algorithm provided by the ASN.1
- * DER encoded subject public key is used.
+ * supported key algorithms for serialization/deserialization. If
+ * {@code null}, the key algorithm provided by the ASN.1 DER
+ * encoded subject public key is used.
* @param additionalInformation Additional information for this principal.
* @throws NullPointerException if the subject info is {@code null}
- * @throws GeneralSecurityException if the JVM does not support the given key algorithm.
+ * @throws GeneralSecurityException if the JVM does not support the given
+ * key algorithm.
*/
- private RawPublicKeyIdentity(byte[] subjectInfo, String keyAlgorithm, AdditionalInfo additionalInformation) throws GeneralSecurityException {
+ private RawPublicKeyIdentity(byte[] subjectInfo, String keyAlgorithm, AdditionalInfo additionalInformation)
+ throws GeneralSecurityException {
super(additionalInformation);
if (subjectInfo == null) {
throw new NullPointerException("SubjectPublicKeyInfo must not be null");
@@ -151,22 +157,21 @@ private void createNamedInformationUri(byte[] subjectPublicKeyInfo) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(subjectPublicKeyInfo);
- byte[] digest = md.digest();
- String base64urlDigest = Base64.getUrlEncoder().withoutPadding().encodeToString(digest);
- StringBuilder b = new StringBuilder("ni:///sha-256;").append(base64urlDigest);
- niUri = b.toString();
+ niUri = "ni:///sha-256;" + Base64.getUrlEncoder().withoutPadding().encodeToString(md.digest());
} catch (NoSuchAlgorithmException e) {
- // should not happen because SHA-256 is a mandatory message digest algorithm for any Java 7 VM
+ // should not happen because SHA-256 is a mandatory message digest
+ // algorithm for any Java 7 VM
// no Base64 encoding of InputStream is done
}
}
/**
* Gets the Named Information URI representing this raw public key.
- *
+ *
* The URI is created using the SHA-256 hash algorithm on the key's
* SubjectPublicKeyInfo as described in
- * RFC 6920, section 2.
+ * RFC 6920, section 2.
*
* @return the named information URI
*/
@@ -195,19 +200,20 @@ public final byte[] getSubjectInfo() {
/**
* Gets a string representation of this principal.
- *
+ *
* Clients should not assume any particular format of the returned string
* since it may change over time.
- *
+ *
* @return the string representation
*/
@Override
public String toString() {
- return new StringBuilder("RawPublicKey Identity [").append(niUri).append("]").toString();
+ return "RawPublicKey Identity [" + niUri + "]";
}
/**
- * Creates a hash code based on the key's ASN.1 encoded SubjectPublicKeyInfo.
+ * Creates a hash code based on the key's ASN.1 encoded
+ * SubjectPublicKeyInfo.
*
* @return the hash code
*/
@@ -219,8 +225,9 @@ public int hashCode() {
/**
* Checks if this instance is equal to another object.
*
- * @return {@code true}, if the other object is a {@code RawPublicKeyIdentity}
- * and has the same SubjectPublicKeyInfo as this instance
+ * @return {@code true}, if the other object is a
+ * {@code RawPublicKeyIdentity} and has the same
+ * SubjectPublicKeyInfo as this instance
*/
@Override
public boolean equals(Object obj) {
diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/auth/X509CertPath.java b/element-connector/src/main/java/org/eclipse/californium/elements/auth/X509CertPath.java
index 5cc7085240..d0f0604c6f 100644
--- a/element-connector/src/main/java/org/eclipse/californium/elements/auth/X509CertPath.java
+++ b/element-connector/src/main/java/org/eclipse/californium/elements/auth/X509CertPath.java
@@ -231,7 +231,7 @@ public int hashCode() {
/**
* Gets a string representation of this principal.
- *
+ *
* Clients should not assume any particular format of the returned string
* since it may change over time.
*
diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/exception/MissingApplicationAuthorizationException.java b/element-connector/src/main/java/org/eclipse/californium/elements/exception/MissingApplicationAuthorizationException.java
new file mode 100644
index 0000000000..920dc077c6
--- /dev/null
+++ b/element-connector/src/main/java/org/eclipse/californium/elements/exception/MissingApplicationAuthorizationException.java
@@ -0,0 +1,48 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
+ * v1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ ********************************************************************************/
+package org.eclipse.californium.elements.exception;
+
+/**
+ * This class indicates missing application authorization for anonymous clients.
+ *
+ * @since 4.0
+ */
+public class MissingApplicationAuthorizationException extends Exception {
+
+ private static final long serialVersionUID = 9209664901497784712L;
+
+ private final boolean rejected;
+
+ /**
+ * Creates missing application authorization exception.
+ *
+ * @param rejected {@code true}, if application rejected the authorization,
+ * {@code false}, if the authorization timed out.
+ */
+ public MissingApplicationAuthorizationException(boolean rejected) {
+ super(rejected ? "rejected application authorization!" : "missing application authorization!");
+ this.rejected = rejected;
+ }
+
+ /**
+ * Checks the cause of the missing application authorization
+ *
+ * @return {@code true}, if application rejected the authorization,
+ * {@code false}, if the authorization timed out.
+ */
+ public boolean isRejected() {
+ return rejected;
+ }
+}
diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/util/SerialExecutor.java b/element-connector/src/main/java/org/eclipse/californium/elements/util/SerialExecutor.java
index c9d28e5d83..036a9de4ab 100644
--- a/element-connector/src/main/java/org/eclipse/californium/elements/util/SerialExecutor.java
+++ b/element-connector/src/main/java/org/eclipse/californium/elements/util/SerialExecutor.java
@@ -103,15 +103,34 @@ public SerialExecutor(final Executor executor) {
}
@Override
- public void execute(final Runnable command) {
+ public void execute(Runnable command) {
+ execute(command, false);
+ }
+
+ /**
+ * Execute a command.
+ *
+ * @param command the command to execute.
+ * @param force {@code true} to execute command even on shutdown.
+ * @throws RejectedExecutionException if this task cannot be accepted for
+ * execution
+ * @throws NullPointerException if command is null
+ * @since 4.0
+ */
+ public void execute(Runnable command, boolean force) {
lock.lock();
try {
if (shutdown) {
- throw new RejectedExecutionException("SerialExecutor already shutdown!");
- }
- tasks.offer(command);
- if (currentlyExecutedJob == null) {
- scheduleNextJob();
+ if (force) {
+ command.run();
+ } else {
+ throw new RejectedExecutionException("SerialExecutor already shutdown!");
+ }
+ } else {
+ tasks.offer(command);
+ if (currentlyExecutedJob == null) {
+ scheduleNextJob();
+ }
}
} finally {
lock.unlock();
@@ -122,7 +141,7 @@ public void execute(final Runnable command) {
* {@inheritDoc}
*
* {@link #currentlyExecutedJob} is used for the current job.
- */
+ */
@Override
public void assertOwner() {
final Thread me = Thread.currentThread();
@@ -140,7 +159,7 @@ public void assertOwner() {
* {@inheritDoc}
*
* {@link #currentlyExecutedJob} is used for the current job.
- */
+ */
@Override
public boolean checkOwner() {
return owner.get() == Thread.currentThread();
@@ -278,39 +297,7 @@ private final void scheduleNextJob() {
currentlyExecutedJob = tasks.poll();
if (currentlyExecutedJob != null) {
final Runnable command = currentlyExecutedJob;
- executor.execute(new Runnable() {
-
- @Override
- public void run() {
- try {
- setOwner();
- ExecutionListener current = listener.get();
- try {
- if (current != null) {
- current.beforeExecution();
- }
- command.run();
- } catch (Throwable t) {
- LOGGER.error("unexpected error occurred:", t);
- } finally {
- try {
- if (current != null) {
- current.afterExecution();
- }
- } catch (Throwable t) {
- LOGGER.error("unexpected error occurred after execution:", t);
- }
- clearOwner();
- }
- } finally {
- try {
- scheduleNextJob();
- } catch (RejectedExecutionException ex) {
- LOGGER.debug("shutdown?", ex);
- }
- }
- }
- });
+ executor.execute(() -> run(command));
} else if (shutdown) {
terminated.signalAll();
}
@@ -319,6 +306,42 @@ public void run() {
}
}
+ /**
+ * Execute command.
+ *
+ * @param command the command
+ * @since 4.0
+ */
+ private void run(final Runnable command) {
+ try {
+ setOwner();
+ ExecutionListener current = listener.get();
+ try {
+ if (current != null) {
+ current.beforeExecution();
+ }
+ command.run();
+ } catch (Throwable t) {
+ LOGGER.error("unexpected error occurred:", t);
+ } finally {
+ try {
+ if (current != null) {
+ current.afterExecution();
+ }
+ } catch (Throwable t) {
+ LOGGER.error("unexpected error occurred after execution:", t);
+ }
+ clearOwner();
+ }
+ } finally {
+ try {
+ scheduleNextJob();
+ } catch (RejectedExecutionException ex) {
+ LOGGER.debug("shutdown?", ex);
+ }
+ }
+ }
+
/**
* Set execution listener.
*
diff --git a/element-connector/src/test/java/org/eclipse/californium/elements/PrincipalAndAnonymousEndpointContextMatcherTest.java b/element-connector/src/test/java/org/eclipse/californium/elements/PrincipalAndAnonymousEndpointContextMatcherTest.java
new file mode 100644
index 0000000000..25bfef5a93
--- /dev/null
+++ b/element-connector/src/test/java/org/eclipse/californium/elements/PrincipalAndAnonymousEndpointContextMatcherTest.java
@@ -0,0 +1,117 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * https://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License
+ * v1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ ********************************************************************************/
+package org.eclipse.californium.elements;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.net.InetSocketAddress;
+import java.security.Principal;
+
+import org.eclipse.californium.elements.auth.ApplicationPrincipal;
+import org.eclipse.californium.elements.util.Bytes;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PrincipalAndAnonymousEndpointContextMatcherTest {
+
+ private static final InetSocketAddress ADDRESS = new InetSocketAddress(0);
+
+ private Principal principal1;
+ private Principal principal2;
+ private Principal principal3;
+ private Principal anonymous = ApplicationPrincipal.ANONYMOUS;
+ private EndpointContext connectionContext;
+ private EndpointContext messageContext;
+ private EndpointContext differentMessageContext;
+ private EndpointContext unsecureMessageContext;
+ private EndpointContext anonymousMessageContext;
+ private EndpointContext anonymousMessageContext2;
+ private EndpointContext anonymousConnectionContext;
+ private EndpointContextMatcher matcher;
+
+ @Before
+ public void setup() {
+ Bytes session = new Bytes("session".getBytes());
+ Bytes session2 = new Bytes("session2".getBytes());
+ principal1 = new TestPrincipal("P1");
+ principal2 = new TestPrincipal("P1"); // intended to have the same name as principal1
+ principal3 = new TestPrincipal("P3");
+
+ connectionContext = new DtlsEndpointContext(ADDRESS, null, principal1, session, 1, "CIPHER", 100);
+ anonymousConnectionContext = new DtlsEndpointContext(ADDRESS, null, anonymous, session2, 1, "CIPHER", 100);
+ messageContext = new AddressEndpointContext(ADDRESS, principal2);
+ differentMessageContext = new AddressEndpointContext(ADDRESS, principal3);
+ unsecureMessageContext = new AddressEndpointContext(ADDRESS, null);
+ anonymousMessageContext = new DtlsEndpointContext(ADDRESS, null, anonymous, session2, 1, "CIPHER", 100);
+ anonymousMessageContext2 = new DtlsEndpointContext(ADDRESS, null, null, session2, 1, "CIPHER", 100);
+ matcher = new PrincipalAndAnonymousEndpointContextMatcher();
+ }
+
+ @Test
+ public void testWithConnectionEndpointContext() {
+ assertThat(matcher.isToBeSent(messageContext, connectionContext), is(true));
+ assertThat(matcher.isToBeSent(differentMessageContext, connectionContext), is(false));
+ assertThat(matcher.isToBeSent(unsecureMessageContext, connectionContext), is(true));
+ assertThat(matcher.isToBeSent(anonymousMessageContext, connectionContext), is(false));
+ assertThat(matcher.isToBeSent(anonymousMessageContext2, connectionContext), is(false));
+ }
+
+ @Test
+ public void testWithAnonymousConnectionEndpointContext() {
+ assertThat(matcher.isToBeSent(messageContext, anonymousConnectionContext), is(false));
+ assertThat(matcher.isToBeSent(differentMessageContext, anonymousConnectionContext), is(false));
+ assertThat(matcher.isToBeSent(unsecureMessageContext, anonymousConnectionContext), is(true));
+ assertThat(matcher.isToBeSent(anonymousMessageContext, anonymousConnectionContext), is(true));
+ assertThat(matcher.isToBeSent(anonymousMessageContext2, anonymousConnectionContext), is(true));
+ }
+
+ @Test
+ public void testWithoutConnectionEndpointContext() {
+ assertThat(matcher.isToBeSent(messageContext, null), is(true));
+ assertThat(matcher.isToBeSent(differentMessageContext, null), is(true));
+ assertThat(matcher.isToBeSent(unsecureMessageContext, null), is(true));
+ assertThat(matcher.isToBeSent(anonymousMessageContext, null), is(true));
+ assertThat(matcher.isToBeSent(anonymousMessageContext2, null), is(true));
+ }
+
+ private static class TestPrincipal implements Principal {
+ private final String name;
+ public TestPrincipal(String name) {
+ this.name = name;
+ }
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ } else if (other == null || !(other instanceof Principal)) {
+ return false;
+ }
+ return name.equals(((Principal)other).getName());
+ }
+
+ }
+
+}
diff --git a/element-connector/src/test/java/org/eclipse/californium/elements/UDPConnectorTest.java b/element-connector/src/test/java/org/eclipse/californium/elements/UDPConnectorTest.java
index b99d863a1a..d2aad1847e 100644
--- a/element-connector/src/test/java/org/eclipse/californium/elements/UDPConnectorTest.java
+++ b/element-connector/src/test/java/org/eclipse/californium/elements/UDPConnectorTest.java
@@ -35,6 +35,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.californium.elements.rule.LoggingRule;
import org.eclipse.californium.elements.rule.NetworkRule;
import org.eclipse.californium.elements.rule.ThreadsRule;
import org.eclipse.californium.elements.util.SimpleMessageCallback;
@@ -48,7 +49,7 @@
import org.slf4j.LoggerFactory;
public class UDPConnectorTest {
- public static final Logger LOGGER = LoggerFactory.getLogger(UDPConnectorTest.class);
+ private static final Logger LOGGER = LoggerFactory.getLogger(UDPConnectorTest.class);
private static final long TIMEOUT_MILLIS = 1500;
@@ -58,6 +59,9 @@ public class UDPConnectorTest {
@Rule
public ThreadsRule cleanup = new ThreadsRule();
+ @Rule
+ public LoggingRule logging = new LoggingRule();
+
UDPConnector connector;
UDPConnector destination;
TestEndpointContextMatcher matcher;
@@ -179,6 +183,7 @@ public void testMessageCallbackOnError() throws InterruptedException {
SimpleMessageCallback callback = new SimpleMessageCallback(1, false);
RawData message = RawData.outbound(data, context, callback, false);
+ logging.setLoggingLevel("ERROR", UDPConnector.class);
connector.send(message);
assertThat(callback.await(TIMEOUT_MILLIS), is(true));
assertThat(callback.toString(), callback.getError(), is(notNullValue()));
diff --git a/element-connector/src/test/java/org/eclipse/californium/elements/rule/ThreadsRule.java b/element-connector/src/test/java/org/eclipse/californium/elements/rule/ThreadsRule.java
index 5cecd4c1ea..77ebcbfa78 100644
--- a/element-connector/src/test/java/org/eclipse/californium/elements/rule/ThreadsRule.java
+++ b/element-connector/src/test/java/org/eclipse/californium/elements/rule/ThreadsRule.java
@@ -279,10 +279,10 @@ public void dump(String message, List
* Used for none notifying conditions, which must be polled.
*
* @param timeout timeout in {@code unit}
@@ -74,6 +75,24 @@ public static boolean waitForCondition(long timeout, long interval, TimeUnit uni
return check.isFulFilled();
}
+ /**
+ * Assert condition to come {@code true}.
+ *
+ * Used for none notifying conditions, which must be polled.
+ *
+ * @param timeout timeout in {@code unit}
+ * @param interval interval of condition check in {@code unit}
+ * @param unit time units for {@code timeout} and {@code interval}
+ * @param check callback for condition test
+ * @throws InterruptedException if the Thread is interrupted.
+ * @throws AssertionError if assertion has failed
+ * @since 4.0
+ */
+ public static void assertCondition(long timeout, long interval, TimeUnit unit, TestCondition check)
+ throws InterruptedException {
+ assertThat(waitForCondition(timeout, interval, unit, check), is(true));
+ }
+
/**
* Assert, that a statistic counter reaches the matcher's criterias within
* the provided timeout.
@@ -84,6 +103,7 @@ public static boolean waitForCondition(long timeout, long interval, TimeUnit uni
* @param timeout timeout to match
* @param unit unit of timeout
* @throws InterruptedException if wait is interrupted.
+ * @throws AssertionError if assertion has failed
* @see TestConditionTools#assertStatisticCounter(CounterStatisticManager,
* String, Matcher)
*/
@@ -113,6 +133,7 @@ public boolean isFulFilled() throws IllegalStateException {
* @param timeout timeout to match
* @param unit unit of timeout
* @throws InterruptedException if wait is interrupted.
+ * @throws AssertionError if assertion has failed
* @see TestConditionTools#assertStatisticCounter(CounterStatisticManager,
* String, Matcher)
* @since 2.4
@@ -138,6 +159,7 @@ public boolean isFulFilled() throws IllegalStateException {
* @param manager statistic manager
* @param name name of statistic.
* @param matcher matcher for statistic counter value
+ * @throws AssertionError if assertion has failed
*/
public static void assertStatisticCounter(CounterStatisticManager manager, String name,
Matcher super Long> matcher) {
@@ -151,6 +173,7 @@ public static void assertStatisticCounter(CounterStatisticManager manager, Strin
* @param manager statistic manager
* @param name name of statistic.
* @param matcher matcher for statistic counter value
+ * @throws AssertionError if assertion has failed
* @since 2.4
*/
public static void assertStatisticCounter(String message, CounterStatisticManager manager, String name,
diff --git a/scandium-core/README.md b/scandium-core/README.md
index 625db009ca..3923b76173 100644
--- a/scandium-core/README.md
+++ b/scandium-core/README.md
@@ -81,6 +81,32 @@ builder.setCertificateVerifier(trust);
DTLSConnector connector = new DTLSConnector(builder.build());
```
+Starting with Californium 4.0 the support for anonymous clients is extended in order to match existing http authentication mechanisms. In those case the server authenticates itself using a certificate (RPK or x509) but the client stays anonymous in the DTLS handshake. It is the consider that such a client gets authorized by the application using an `ApplicationAuthorizer` by the means of the application. The new configuration parameter `DTLS.APPLICATION_AUTHORIZATION_TIMEOUT` enables to remove such anonymous clients after that timeout. One simple implementation may be to support an CoAP2HTTP-cross-proxy, with a HTTP server using username/password or tokens to authorize the clients. The actual authentication, e.g. converting a custom coap-option into an http token header is then intended to be implemented in the proxy-application.
+
+The `ApplicationAuthorizer` is implemented by the `DTLSConnector`and is available at the `CoapEndpoint` and the `Exchange`.
+
+Specific `CoapResource` implementation:
+
+```
+@Override
+public void handlePOST(final CoapExchange exchange) {
+ // ... do some check, e.g. forward request using http
+ // authorized := result of authorization
+ if (exchange.getSourcePrincipal() == null) {
+ // anonymous client
+ ApplicationAuthorizer authorizer = getApplicationAuthorizer();
+ if (authorizer != null) {
+ if (authorized) {
+ authorizer.authorize(exchange.getSourceContext(),
+ ApplicationPrincipal.ANONYMOUS);
+ } else {
+ authorizer.rejectAuthorization(exchange.getSourceContext());
+ }
+ }
+ }
+}
+```
+
## Additional Parameters
In order to limit the usage of some parameter, it is possible to provide them by the
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/ConnectionListener.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/ConnectionListener.java
index 49ef8b5c43..27e63bcf72 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/ConnectionListener.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/ConnectionListener.java
@@ -19,7 +19,7 @@
/**
* Listener for Connections life cycle.
- *
+ *
* The callbacks are execution within the serial execution of the provided
* connection. Therefore it's important to not block, otherwise the performance
* will be downgraded. Though access to the connection must generally be done
@@ -58,9 +58,9 @@ public interface ConnectionListener {
/**
* Callback, when connection gets removed from the connection store.
- *
- * Note: since 3.0, the {@link Connection} is now always cleaned up before
- * it is used in this callback. {@link Connection#getPeerAddress()},
+ *
+ * Note: since 3.0, the {@link Connection} is now always cleaned up
+ * before it is used in this callback. {@link Connection#getPeerAddress()},
* {@link Connection#getOngoingHandshake()}, and
* {@link Connection#getDtlsContext()} (including the other variants) will
* return {@code null}.
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/DTLSConnector.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/DTLSConnector.java
index 26526fe9cc..f3b80a1568 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/DTLSConnector.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/DTLSConnector.java
@@ -142,6 +142,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -168,10 +169,13 @@
import org.eclipse.californium.elements.RawData;
import org.eclipse.californium.elements.RawDataChannel;
import org.eclipse.californium.elements.auth.AdditionalInfo;
+import org.eclipse.californium.elements.auth.ApplicationAuthorizer;
+import org.eclipse.californium.elements.auth.ApplicationPrincipal;
import org.eclipse.californium.elements.auth.ExtensiblePrincipal;
import org.eclipse.californium.elements.config.SystemConfig;
import org.eclipse.californium.elements.exception.EndpointMismatchException;
import org.eclipse.californium.elements.exception.EndpointUnconnectedException;
+import org.eclipse.californium.elements.exception.MissingApplicationAuthorizationException;
import org.eclipse.californium.elements.exception.MulticastNotSupportedException;
import org.eclipse.californium.elements.util.Bytes;
import org.eclipse.californium.elements.util.ClockUtil;
@@ -256,7 +260,7 @@
* side and a separate Connector is created for each address to receive incoming
* traffic.
*/
-public class DTLSConnector implements Connector, PersistentComponent, RecordLayer {
+public class DTLSConnector implements Connector, ApplicationAuthorizer, PersistentComponent, RecordLayer {
/**
* The {@code EndpointContext} key used to store the host name indicated by
@@ -338,6 +342,8 @@ public class DTLSConnector implements Connector, PersistentComponent, RecordLaye
/**
* Queue with recent successful handshakes.
+ *
+ * Used to protect from Client_Hello replays.
*
* @since 3.0
*/
@@ -348,6 +354,13 @@ public class DTLSConnector implements Connector, PersistentComponent, RecordLaye
* @since 3.4
*/
private final AtomicInteger recentHandshakesCounter = new AtomicInteger();
+ /**
+ * Ensures {@link #cleanupRecentHandshakes(int)} is executed only by one
+ * thread.
+ *
+ * @since 4.0
+ */
+ private final AtomicBoolean recentHandshakesCleanup = new AtomicBoolean();
/**
* General auto resumption timeout in milliseconds. {@code null}, if auto
@@ -455,7 +468,8 @@ public class DTLSConnector implements Connector, PersistentComponent, RecordLaye
/**
* (Down-)counter for pending handshake results.
*
- * Initialized with {@link DtlsConfig#DTLS_MAX_PENDING_HANDSHAKE_RESULT_JOBS}.
+ * Initialized with
+ * {@link DtlsConfig#DTLS_MAX_PENDING_HANDSHAKE_RESULT_JOBS}.
*
* @since 3.5
*/
@@ -567,8 +581,7 @@ protected static ConnectionStore createConnectionStore(DtlsConnectorConfig confi
* @throws IllegalStateException if the connection store has already a cid
* generator.
*/
- protected DTLSConnector(final DtlsConnectorConfig configuration,
- final ConnectionStore connectionStore) {
+ protected DTLSConnector(final DtlsConnectorConfig configuration, final ConnectionStore connectionStore) {
if (configuration == null) {
throw new NullPointerException("Configuration must not be null");
} else if (connectionStore == null) {
@@ -682,6 +695,10 @@ public void handshakeCompleted(final Handshaker handshaker) {
@Override
public void handshakeFailed(Handshaker handshaker, Throwable error) {
if (health != null) {
+ if (error instanceof MissingApplicationAuthorizationException) {
+ MissingApplicationAuthorizationException authorizationException = (MissingApplicationAuthorizationException) error;
+ health.applicationAuthorizationRejected(authorizationException.isRejected());
+ }
health.endHandshake(false);
}
List
* Remove starting hello client, if expired.
*
* @param calls number of calls
- * @since 3.4 (added parameter calls)
- */
- private void cleanupRecentHandshakes(int calls) {
- boolean started = false;
- long time = ClockUtil.nanoRealtime();
- int loop = 0;
- int count = 0;
- int size = recentHandshakesCounter.get();
- int log = Math.max(10000, size / 5);
- boolean full = (calls % 6) == 0;
- String qualifier = full ? " (full)" : "";
- try {
- long expires = calculateRecentHandshakeExpires();
- Iterator
* This method invokes {@link #stop()} and clears the
- * {@link ConnectionStore} used to manage connections to
- * peers. Thus, contrary to the behavior specified for
- * {@link Connector#destroy()}, this connector can be re-started using the
- * {@link #start()} method but subsequent invocations of the
- * {@link #send(RawData)} method will trigger the establishment of a new
- * connection to the corresponding peer.
+ * {@link ConnectionStore} used to manage connections to peers. Thus,
+ * contrary to the behavior specified for {@link Connector#destroy()}, this
+ * connector can be re-started using the {@link #start()} method but
+ * subsequent invocations of the {@link #send(RawData)} method will trigger
+ * the establishment of a new connection to the corresponding peer.
*
+ * Uses an advanced MAC error filter.
*
* @since 3.5
*/
@@ -48,7 +49,7 @@ public class DtlsDatagramFilter implements DatagramFilter {
private final int macErrorFilterThreshold;
/**
- * Create dtls datagram filter without MAC error filter.
+ * Creates DTLS datagram filter without MAC error filter.
*/
public DtlsDatagramFilter() {
this.macErrorFilterQuietTimeNanos = 0;
@@ -56,9 +57,12 @@ public DtlsDatagramFilter() {
}
/**
- * Create dtls datagram filter with MAC error filter, if configured.
+ * Creates DTLS datagram filter with MAC error filter, if configured.
*
* @param config configuration for the MAC error filter.
+ * @throws IllegalArgumentException if config contains ambiguous values for
+ * {@link DtlsConfig#DTLS_MAC_ERROR_FILTER_QUIET_TIME} and
+ * {@link DtlsConfig#DTLS_MAC_ERROR_FILTER_THRESHOLD}
* @since 3.6
*/
public DtlsDatagramFilter(Configuration config) {
@@ -115,19 +119,40 @@ public boolean onReceiving(Record record, Connection connection) {
@Override
public boolean onMacError(Record record, Connection connection) {
if (macErrorFilterThreshold > 0) {
- Object filterData = connection.getFilterData();
- if (filterData == null) {
- filterData = new MacErrorFilter(record.getReceiveNanos());
- connection.setFilterData(filterData);
- }
- if (filterData instanceof MacErrorFilter) {
- ((MacErrorFilter) filterData).incrementMacErrors(record.getReceiveNanos(),
- macErrorFilterQuietTimeNanos);
- }
+ filter(record.getReceiveNanos(), connection);
}
return false;
}
+ @Override
+ public void onDrop(DatagramPacket packet) {
+ // empty by intention
+ }
+
+ @Override
+ public void onDrop(Record record) {
+ // empty by intention
+ }
+
+ @Override
+ public void onApplicationAuthorizationRejected(Connection connection) {
+ // use MAC error filter to throttle unauthorized clients
+ if (macErrorFilterThreshold > 0) {
+ filter(ClockUtil.nanoRealtime(), connection);
+ }
+ }
+
+ private void filter(long uptimeNanos, Connection connection) {
+ Object filterData = connection.getFilterData();
+ if (filterData == null) {
+ filterData = new MacErrorFilter(uptimeNanos);
+ connection.setFilterData(filterData);
+ }
+ if (filterData instanceof MacErrorFilter) {
+ ((MacErrorFilter) filterData).incrementMacErrors(uptimeNanos, macErrorFilterQuietTimeNanos);
+ }
+ }
+
/**
* MAC error filter data per {@link Connection}.
*
@@ -192,13 +217,4 @@ private void resetMacErrorFilter(long now, long quietTimeNanos) {
}
- @Override
- public void onDrop(DatagramPacket packet) {
- // empty by intention
- }
-
- @Override
- public void onDrop(Record record) {
- // empty by intention
- }
}
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsHealth.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsHealth.java
index 6b101c1c9f..8a9b1a5713 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsHealth.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsHealth.java
@@ -105,4 +105,13 @@ public interface DtlsHealth {
*/
void setPendingHandshakeJobs(int count);
+ /**
+ * Report missing application authorization.
+ *
+ * @param rejected {@code true}, if authorization was rejected,
+ * {@code false}, if the authorization is missing after a
+ * timeout.
+ * @since 4.0
+ */
+ void applicationAuthorizationRejected(boolean rejected);
}
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsHealthLogger.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsHealthLogger.java
index 68917277e4..28fb8123fa 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsHealthLogger.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsHealthLogger.java
@@ -60,6 +60,10 @@ public class DtlsHealthLogger extends CounterStatisticManager implements DtlsHea
private final SimpleCounterStatistic pendingOutgoing = new SimpleCounterStatistic("pending out jobs", align);
private final SimpleCounterStatistic pendingHandshakeJobs = new SimpleCounterStatistic("pending handshake jobs",
align);
+ private final SimpleCounterStatistic missingAuthorizations = new SimpleCounterStatistic(
+ "application missing authorizations", align);
+ private final SimpleCounterStatistic rejectedAuthorizations = new SimpleCounterStatistic(
+ "application rejected authorizations", align);
/**
* Create passive dtls health logger.
@@ -91,6 +95,8 @@ private void init() {
add(pendingIncoming);
add(pendingOutgoing);
add(pendingHandshakeJobs);
+ add(missingAuthorizations);
+ add(rejectedAuthorizations);
}
@Override
@@ -116,6 +122,10 @@ public void dump() {
log.append(eol).append(head).append(pendingIncoming);
log.append(eol).append(head).append(pendingOutgoing);
log.append(eol).append(head).append(pendingHandshakeJobs);
+ if (rejectedAuthorizations.isStarted() || missingAuthorizations.isStarted()) {
+ log.append(eol).append(head).append(missingAuthorizations);
+ log.append(eol).append(head).append(rejectedAuthorizations);
+ }
dump(head, log);
LOGGER.debug("{}", log);
}
@@ -126,6 +136,7 @@ public void dump() {
}
}
+ @Override
public void dump(String tag, int maxConnections, int remainingCapacity) {
try {
if (isEnabled()) {
@@ -159,6 +170,10 @@ public void dump(String tag, int maxConnections, int remainingCapacity) {
log.append(eol).append(head).append(pendingIncoming);
log.append(eol).append(head).append(pendingOutgoing);
log.append(eol).append(head).append(pendingHandshakeJobs);
+ if (rejectedAuthorizations.isStarted() || missingAuthorizations.isStarted()) {
+ log.append(eol).append(head).append(missingAuthorizations);
+ log.append(eol).append(head).append(rejectedAuthorizations);
+ }
dump(head, log);
LOGGER.debug("{}", log);
}
@@ -256,4 +271,13 @@ public void setPendingHandshakeJobs(int count) {
pendingHandshakeJobs.set(count);
}
+ @Override
+ public void applicationAuthorizationRejected(boolean rejected) {
+ if (rejected) {
+ rejectedAuthorizations.increment();
+ } else {
+ missingAuthorizations.increment();
+ }
+ }
+
}
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/auth/PrincipalSerializer.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/auth/PrincipalSerializer.java
index 9ff0c4c38d..c83dc98187 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/auth/PrincipalSerializer.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/auth/PrincipalSerializer.java
@@ -23,6 +23,7 @@
import java.security.GeneralSecurityException;
import java.security.Principal;
+import org.eclipse.californium.elements.auth.ApplicationPrincipal;
import org.eclipse.californium.elements.auth.PreSharedKeyIdentity;
import org.eclipse.californium.elements.auth.RawPublicKeyIdentity;
import org.eclipse.californium.elements.auth.X509CertPath;
@@ -38,17 +39,21 @@ public final class PrincipalSerializer {
private static final int PSK_HOSTNAME_LENGTH_BITS = 16;
private static final int PSK_IDENTITY_LENGTH_BITS = 16;
+ private static final int APPLICATION_PRINCIPAL_NAME_LENGTH_BITS = 16;
private PrincipalSerializer() {
}
/**
- * Serializes a principal to a byte array based on the plain text encoding defined in
- * RFC 5077, Section 4.
+ * Serializes a principal to a byte array based on the plain text encoding
+ * defined in RFC
+ * 5077, Section 4.
*
- * RFC 5077 does not explicitly define support for RawPublicKey based client authentication.
- * However, it supports the addition of arbitrary authentication mechanisms by extending
- * the ClientAuthenticationType which we do as follows:
+ * RFC 5077 does not explicitly define support for RawPublicKey based client
+ * authentication. However, it supports the addition of arbitrary
+ * authentication mechanisms by extending the
+ * ClientAuthenticationType which we do as follows:
+ *
*
* Californium doesn't support renegotiation at all, but RFC5746 requests to
* update to a minimal version of RFC 5746.
*
- * @see RFC 5746
+ * @see RFC
+ * 5746
*
* @since 3.8
*/
@@ -261,7 +263,7 @@ protected List
* Californium uses {@link #DTLS_MAX_CONNECTIONS} and
* {@link #DTLS_STALE_CONNECTION_THRESHOLD} in order to keep session as
* along as the resources are not required for fresh connections.
@@ -270,7 +272,7 @@ protected List
* After that period without exchanged messages, new messages will initiate
* a handshake. If possible a resumption/abbreviated handshake is used. Must
* not be used with {@link DtlsRole#SERVER_ONLY}. {@code 30s} is a common
@@ -299,8 +301,9 @@ protected List
* ECC calculations may be time intensive, especially for smaller
* micro-controllers without ecc-hardware support. The additional timeout
* prevents Californium from resending a flight too early. The extra time is
* used for the DTLS-client, if a ECDSA or ECDHE cipher suite is proposed,
* and for the DTLS-server, if a ECDSA or ECDHE cipher suite is selected.
- *
+ *
* This timeout is added to {@link #DTLS_RETRANSMISSION_TIMEOUT} and on each
* retransmission, the resulting time is doubled.
*/
@@ -363,27 +366,27 @@ protected List
*
* RFC 6347, Section 4.1.1.1, Page 12
- *
+ *
* In back-off mode, UDP datagrams of maximum 512 bytes or the negotiated
* records size, if that is smaller, are used. Each handshake message is
* placed in one dtls record, or more dtls records, if the handshake message
* is too large and must be fragmented. Beside of the CCS and FINISH dtls
* records, which send together in one UDP datagram, all other records are
* send in separate datagrams.
- *
+ *
* The {@link #DTLS_USE_MULTI_HANDSHAKE_MESSAGE_RECORDS} and
* {@link #DTLS_USE_MULTI_RECORD_MESSAGES} has precedence over the back-off
* definition.
- *
+ *
* Value {@code 0}, to disable it, {@code null}, for default of
* {@link #DTLS_MAX_RETRANSMISSIONS} / 2.
*/
public static final IntegerDefinition DTLS_RETRANSMISSION_BACKOFF = new IntegerDefinition(
- MODULE + "RETRANSMISSION_BACKOFF",
- "Number of flight-retransmissions before switching to backoff mode using single handshake messages in single record datagrams.",
+ MODULE + "RETRANSMISSION_BACKOFF", "Number of flight-retransmissions before switching to backoff mode "
+ + "using single handshake messages in single record datagrams.",
null, 0);
/**
@@ -399,8 +402,8 @@ protected List
+ * Note: Californium is only able to detect the MTU of local network
* interfaces. For the transmission, the PMTU (Path Maximum Transmission
* Unit) is required. Especially, if ip-tunnels are used, this value must be
* provided in order to consider a smaller PMTU.
@@ -486,17 +489,19 @@ protected List
* Limits maximum number of bytes sent in one transmission.
- *
- * Note: previous versions took the local link MTU without limits. That
- * results in possibly larger MTU, e.g. for localhost or some cloud nodes
- * using "jumbo frames". If a larger MTU is required to be detectable,
+ *
+ * Note: previous versions took the local link MTU without limits.
+ * That results in possibly larger MTU, e.g. for localhost or some cloud
+ * nodes using "jumbo frames". If a larger MTU is required to be detectable,
* please adjust this limit to the required value.
*
* @see #DEFAULT_MAX_TRANSMISSION_UNIT_LIMIT
@@ -508,11 +513,11 @@ protected List
* Note: if {@link #DTLS_ROLE} is {@link DtlsRole#SERVER_ONLY}, the
* specified default handshake mode is ignored and replaced by
* {@link DtlsEndpointContext#HANDSHAKE_MODE_NONE}.
- *
+ *
* Values are {@link DtlsEndpointContext#HANDSHAKE_MODE_NONE} or
* {@link DtlsEndpointContext#HANDSHAKE_MODE_AUTO}.
*/
@@ -550,8 +555,8 @@ protected List
* Californium uses the "sliding receive window" approach mentioned in
* RFC6347 4.1.2.6. Anti-Replay. That causes trouble, if some
@@ -704,7 +706,7 @@ protected List
* The configured value will be subtracted from to lower receive window
* boundary. A value of {@code -1} will set that calculated lower boundary
* to {@code 0}. Messages between lower receive window boundary and that
@@ -733,7 +735,7 @@ protected List
* Drop reorder records in order to protect from delay attacks, if no other
* means, maybe on application level, are available.
*
@@ -741,14 +743,13 @@ protected List
* Truncate certificate path according the received certificate authorities
* in the {@link CertificateRequest} for the client's
* {@link CertificateMessage}.
@@ -758,7 +759,7 @@ protected List
* Truncate certificate path according the available trusted certificates
* before validation.
*/
@@ -793,18 +794,18 @@ protected List
* To decrypt a message and calculate the MAC requires CPU. If a peer sends
* many messages with a broken MAC (maybe because the message is sent by an
* other peer with a spoofed source address), that may lower the overall
@@ -864,9 +863,9 @@ protected List
* A value of {@code 0} disables the MAC error filter.
- *
+ *
* tn time n, c=n counter with value
*
*
* Maximum number of MAC errors, before all messages are dropped for the
* {@link #DTLS_MAC_ERROR_FILTER_QUIET_TIME}. A value of {@code 0} disables
* the MAC error filter.
@@ -915,11 +914,12 @@ protected List
* Californium doesn't support renegotiation at all, but RFC5746 requests to
* update to a minimal version of RFC 5746.
*
- * @see RFC 5746
+ * @see RFC
+ * 5746
*
* @since 3.8
*/
@@ -932,13 +932,29 @@ protected List
+ * Used for certificate based handshakes without client certificates. If
+ * enabled, connections without authenticated clients are removed after this
+ * timeout, if not authorized using the {@link ApplicationAuthorizer}.
+ *
+ * @see ApplicationAuthorizer
+ * @since 4.0
+ */
+ public static final TimeDefinition DTLS_APPLICATION_AUTHORIZATION_TIMEOUT = new TimeDefinition(
+ MODULE + "APPLICATION_AUTHORIZATION_TIMEOUT",
+ "Timeout for application authorization of anonymous clients.\n0s to switch off.",
+ 0, TimeUnit.SECONDS);
+
public static final ModuleDefinitionsProvider DEFINITIONS = new ModuleDefinitionsProvider() {
@Override
@@ -1014,6 +1030,7 @@ public void applyDefinitions(Configuration config) {
config.set(DTLS_MAC_ERROR_FILTER_THRESHOLD, 0);
config.set(DTLS_SECURE_RENEGOTIATION, DEFAULT_SECURE_RENEGOTIATION);
config.set(DTLS_SUPPORT_KEY_MATERIAL_EXPORT, false);
+ config.set(DTLS_APPLICATION_AUTHORIZATION_TIMEOUT, 0, TimeUnit.SECONDS);
DefinitionUtils.verify(DtlsConfig.class, config);
}
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/config/DtlsConnectorConfig.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/config/DtlsConnectorConfig.java
index a333cb9c68..ad8f2c75d9 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/config/DtlsConnectorConfig.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/config/DtlsConnectorConfig.java
@@ -1539,11 +1539,19 @@ public DtlsConnectorConfig build() {
CertificateKeyAlgorithm.RSA)) {
ListUtils.addIfAbsent(keyAlgorithms, CertificateKeyAlgorithm.RSA);
}
- if (config.getConfiguration().get(DtlsConfig.DTLS_ROLE) == DtlsRole.CLIENT_ONLY) {
+ if (config.get(DtlsConfig.DTLS_ROLE) == DtlsRole.CLIENT_ONLY) {
ListUtils.addIfAbsent(keyAlgorithms, CertificateKeyAlgorithm.EC);
}
config.supportedCertificatekeyAlgorithms = keyAlgorithms;
}
+ if (config.get(DtlsConfig.DTLS_APPLICATION_AUTHORIZATION_TIMEOUT, TimeUnit.SECONDS) > 0) {
+ if (config.get(DtlsConfig.DTLS_ROLE) == DtlsRole.CLIENT_ONLY) {
+ throw new IllegalStateException("application authorization enabled, is not supported for client role!");
+ }
+ if (config.get(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE) == CertificateAuthenticationMode.NEEDED) {
+ throw new IllegalStateException("application authorization enabled, but client certificate needed!");
+ }
+ }
} else {
if (!config.supportedSignatureAlgorithms.isEmpty()) {
throw new IllegalStateException(
@@ -1555,6 +1563,9 @@ public DtlsConnectorConfig build() {
if (config.certificateVerifier != null) {
throw new IllegalStateException("certificate trust set, but no certificate based cipher suite!");
}
+ if (config.get(DtlsConfig.DTLS_APPLICATION_AUTHORIZATION_TIMEOUT, TimeUnit.SECONDS) > 0) {
+ throw new IllegalStateException("application authorization enabled, but no certificate based cipher suite!");
+ }
}
if (ecc) {
if (config.supportedGroups.isEmpty()) {
@@ -1716,7 +1727,7 @@ private void determineCipherSuitesFromConfig() {
if (config.certificateIdentityProvider != null || config.certificateVerifier != null) {
// certificate based cipher suites.
List
* Contains status information regarding
*
+ * Only used on server side during a period (twice the
+ * {@link CookieGenerator#COOKIE_LIFETIME_NANOS}) after successful
+ * handshakes. Prevents from processing retransmitted Client Hellos.
+ *
+ * Note: used outside of the serial-execution!
*
* @since 3.0
*/
@@ -103,7 +107,7 @@ public final class Connection {
/**
* Mark connection to require an abbreviated handshake.
- *
+ *
* Used to know when an abbreviated handshake should be initiated.
*/
private volatile boolean resumptionRequired;
@@ -124,6 +128,11 @@ public final class Connection {
private volatile SerialExecutor serialExecutor;
private InetSocketAddress peerAddress;
private InetSocketAddress router;
+ /**
+ * Connection ID.
+ *
+ * Local identifying connection ID (read connection ID).
+ */
private ConnectionId cid;
/**
* Data of this connection specific for the used {@link DatagramFilter}.
@@ -134,7 +143,7 @@ public final class Connection {
/**
* Root cause of alert.
- *
+ *
* For some case, the root cause may be hidden and replaced by a general
* cause when sending an alert message. This keeps the root cause for
* internal analysis.
@@ -162,8 +171,8 @@ public Connection(InetSocketAddress peerAddress) {
}
/**
- * Update connection state.
- *
+ * Updates the connection state.
+ *
* Calls {@link ConnectionListener#updateExecution(Connection)}.
*
* @since 2.4
@@ -176,7 +185,7 @@ public void updateConnectionState() {
}
/**
- * Set connector's context.
+ * Sets connector's context.
*
* @param executor executor to be used for {@link SerialExecutor}.
* @param listener connection listener.
@@ -219,6 +228,19 @@ public SerialExecutor getExecutor() {
return serialExecutor;
}
+ /**
+ * Creates task to execute provided task in serial execution.
+ *
+ * @param task task to be execute in serial executor
+ * @param force flag indicating, that the task should be executed, even if
+ * the serial executors are exhausted or shutdown.
+ * @return created task
+ * @since 4.0
+ */
+ public Runnable createTask(Runnable task, boolean force) {
+ return new ConnectionTask(task, force);
+ }
+
/**
* Checks, if the connection has a executing serial executor.
*
@@ -227,12 +249,55 @@ public SerialExecutor getExecutor() {
* executor is shutdown.
*/
public boolean isExecuting() {
- return serialExecutor != null && !serialExecutor.isShutdown();
+ SerialExecutor executor = serialExecutor;
+ return executor != null && !executor.isShutdown();
+ }
+
+ /**
+ * Executes a connection job.
+ *
+ * If the serial execution is available, use that to run the job. Otherwise
+ * drop it.
+ *
+ * @param job the job to execute
+ * @throws RejectedExecutionException if this task cannot be accepted for
+ * execution
+ * @throws NullPointerException if job is null
+ * @see #execute(Runnable, boolean)
+ * @since 4.0
+ */
+ public void execute(Runnable job) {
+ execute(job, false);
+ }
+
+ /**
+ * Executes a connection job.
+ *
+ * If the serial execution is available, use that to run the job. Otherwise
+ * run it directly, if parameter force is {@code true}, or drop it, if force
+ * is {@code false}.
+ *
+ * @param job the job to execute
+ * @param force {@code true} to force execution even if the serial execution
+ * is available is not available.
+ * @throws RejectedExecutionException if this task cannot be accepted for
+ * execution
+ * @throws NullPointerException if job is null
+ * @see #execute(Runnable)
+ * @since 4.0
+ */
+ public void execute(Runnable job, boolean force) {
+ SerialExecutor executor = serialExecutor;
+ if (executor != null) {
+ executor.execute(job, force);
+ } else if (force) {
+ job.run();
+ }
}
/**
* Shutdown executor and run all pending jobs.
- *
+ *
* The jobs are intended to check {@link #isExecuting()} in order to detect
* the shutdown.
*
@@ -250,7 +315,7 @@ public int shutdown() {
}
/**
- * Get session listener of connection.
+ * Gets session listener of connection.
*
* @return session listener.
*/
@@ -273,7 +338,7 @@ public boolean isActive() {
}
/**
- * Check, if this connection expects connection ID for incoming records.
+ * Checks, if this connection expects connection ID for incoming records.
*
* @return {@code true}, if connection ID is expected, {@code false},
* otherwise
@@ -284,18 +349,18 @@ public boolean expectCid() {
}
/**
- * Gets the connection id.
+ * Gets the local identifying connection ID.
*
- * @return the cid
+ * @return the local identifying connection ID.
*/
public ConnectionId getConnectionId() {
return cid;
}
/**
- * Sets the connection id.
+ * Sets the local identifying connection ID.
*
- * @param cid the connection id
+ * @param cid the local identifying connection ID
*/
public void setConnectionId(ConnectionId cid) {
this.cid = cid;
@@ -303,8 +368,8 @@ public void setConnectionId(ConnectionId cid) {
}
/**
- * Set filter data.
- *
+ * Sets filter data.
+ *
* Intended to be used by {@link DatagramFilter} implementations. The filter
* data is not persisted and considered to be short living.
*
@@ -316,8 +381,8 @@ public void setFilterData(Object filterData) {
}
/**
- * Get filter data.
- *
+ * Gets filter data.
+ *
* Intended to be used by {@link DatagramFilter} implementations. The filter
* data is not persisted and considered to be short living.
*
@@ -329,7 +394,7 @@ public Object getFilterData() {
}
/**
- * Get real time nanoseconds of last
+ * Gets real time nanoseconds of last
* {@link #updatePeerAddress(InetSocketAddress)}.
*
* @return real time nanoseconds
@@ -349,16 +414,15 @@ public InetSocketAddress getPeerAddress() {
}
/**
- * Update the address of this connection's peer.
- *
+ * Updates the address of this connection's peer.
+ *
* If the new address is {@code null}, an ongoing handshake is failed. A
* non-null address could only be applied, if the dtls context is
* established.
- *
- * Note: to keep track of the associated address in the connection store,
- * this method must not be called directly. It must be called by calling
- * {@link ConnectionStore#update(Connection, InetSocketAddress)}
- * or
+ *
+ * Note: to keep track of the associated address in the connection
+ * store, this method must not be called directly. It must be called by
+ * calling {@link ConnectionStore#update(Connection, InetSocketAddress)} or
* {@link ConnectionStore#remove(Connection, boolean)}.
*
* @param peerAddress the address of the peer
@@ -378,7 +442,8 @@ public void updatePeerAddress(InetSocketAddress peerAddress) {
if (pendingHandshaker != null) {
if (establishedDtlsContext == null
|| pendingHandshaker.getDtlsContext() != establishedDtlsContext) {
- // this will only call the listener, if no other cause was set before!
+ // this will only call the listener, if no other cause
+ // was set before!
pendingHandshaker.handshakeFailed(new IOException(
StringUtil.toDisplayString(previous) + " address reused during handshake!"));
}
@@ -391,7 +456,7 @@ public void updatePeerAddress(InetSocketAddress peerAddress) {
}
/**
- * Check, if the provided address is the peers address.
+ * Checks, if the provided address is the peers address.
*
* @param peerAddress provided peer address
* @return {@code true}, if the addresses are equal
@@ -429,7 +494,7 @@ public void setRouter(InetSocketAddress router) {
}
/**
- * Get endpoint context for writing messages.
+ * Gets endpoint context for writing messages.
*
* @param attributes initial attributes
* @return endpoint context for writing messages.
@@ -449,12 +514,11 @@ public DtlsEndpointContext getWriteContext(Attributes attributes) {
}
/**
- * Get endpoint context for reading messages.
+ * Gets endpoint context for reading messages.
*
* @param attributes initial attributes
* @param recordsPeer peer address of record. Only used, if connection has
* no {@link #peerAddress}.
- *
* @return endpoint context for reading messages.
* @since 3.0
*/
@@ -475,7 +539,7 @@ public DtlsEndpointContext getReadContext(Attributes attributes, InetSocketAddre
/**
* Gets the session containing the connection's current state.
- *
+ *
* This is the session of the {@link #establishedDtlsContext}, if not
* {@code null}, or the session negotiated in the {@link #ongoingHandshake}.
*
@@ -590,7 +654,7 @@ public boolean hasOngoingHandshake() {
}
/**
- * Check, if this connection belongs to double principal.
+ * Checks, if this connection belongs to double principal.
*
* @return {@code true}, if the principal has already a newer connection,
* {@code false}, if not.
@@ -601,7 +665,7 @@ public boolean isDouble() {
}
/**
- * Mark connection as double, if the principal has already a newer
+ * Marks connection as double, if the principal has already a newer
* connection.
*
* @since 3.5
@@ -611,27 +675,23 @@ public void setDouble() {
}
/**
- * Get system nanos of starting client hello.
+ * Gets system nanos of starting client hello.
*
- * @return system nanos, or {@code null}, if prevention is expired or not
- * used.
+ * @return system nanos, or {@code null}, if replay prevention is expired or
+ * not used.
* @since 3.0
*/
public Long getStartNanos() {
ClientHelloIdentifier start = this.startingHelloClient;
- if (start != null) {
- return start.nanos;
- } else {
- return null;
- }
+ return start == null ? null : start.nanos;
}
/**
* Checks whether this connection is started for the provided CLIENT_HELLO.
- *
+ *
* Use the random and message sequence number contained in the CLIENT_HELLO.
- *
- * Note: called outside of serial-execution and so requires external
+ *
+ * Note: called outside of serial-execution and so requires external
* synchronization!
*
* @param clientHello the message to check.
@@ -652,30 +712,28 @@ public boolean isStartedByClientHello(ClientHello clientHello) {
}
/**
- * Set starting CLIENT_HELLO.
- *
+ * Sets starting CLIENT_HELLO.
+ *
* Use the random and handshake message sequence number contained in the
- * CLIENT_HELLO. Removed, if when the handshake fails or with configurable
- * timeout after handshake completion.
- *
- * Note: called outside of serial-execution and so requires external
+ * CLIENT_HELLO to prevent processing retransmission. Removed, when the
+ * handshake fails or when the handshake completes, after a timeout of twice
+ * the {@link CookieGenerator#COOKIE_LIFETIME_NANOS}.
+ *
+ * Note: called outside of serial-execution and so requires external
* synchronization!
*
- * @param clientHello message which starts the connection.
+ * @param clientHello message which starts the connection. {@code null}, to
+ * remove this info after a timeout.
* @see #isStartedByClientHello(ClientHello)
*/
public void startByClientHello(ClientHello clientHello) {
- if (clientHello == null) {
- startingHelloClient = null;
- } else {
- startingHelloClient = new ClientHelloIdentifier(clientHello);
- }
+ startingHelloClient = ClientHelloIdentifier.create(clientHello);
}
/**
* Gets the DTLS context containing the connection's current state
* for the provided epoch.
- *
+ *
* This is the {@link #establishedDtlsContext}, if not {@code null} and the
* read epoch is matching. Or the DTLS context negotiated in the
* {@link #ongoingHandshake}, if not {@code null} and the read epoch is
@@ -705,7 +763,7 @@ public DTLSContext getDtlsContext(int readEpoch) {
/**
* Gets the DTLS context containing the connection's current state.
- *
+ *
* This is the {@link #establishedDtlsContext}, if not {@code null}, or the
* DTLS context negotiated in the {@link #ongoingHandshake}.
*
@@ -725,8 +783,8 @@ public DTLSContext getDtlsContext() {
}
/**
- * Reset DTLS context.
- *
+ * Resets DTLS context.
+ *
* Prepare connection for new handshake. Reset established DTLS context or
* resume session and remove resumption mark.
*
@@ -746,7 +804,7 @@ public void resetContext() {
}
/**
- * Check, if connection was closed.
+ * Checks, if connection was closed.
*
* @return {@code true}, if connection was closed, {@code false}, otherwise.
* @since 2.3
@@ -757,8 +815,8 @@ public boolean isClosed() {
}
/**
- * Close connection with record.
- *
+ * Closes connection with record.
+ *
* Mark session as closed. Received records with sequence numbers before
* will still be processed, others are dropped. No message will be send
* after this.
@@ -774,7 +832,7 @@ public void close(Record record) {
}
/**
- * Mark record as read in established DTLS context.
+ * Marks record as read in established DTLS context.
*
* @param record record to mark as read.
* @return {@code true}, if the record is newer than the current newest.
@@ -792,7 +850,7 @@ public boolean markRecordAsRead(Record record) {
/**
* Gets the root cause alert.
- *
+ *
* For some case, the root cause may be hidden and replaced by a general
* cause when sending an alert message. This keeps the root cause for
* internal analysis.
@@ -806,7 +864,7 @@ public AlertMessage getRootCauseAlert() {
/**
* Sets root cause alert.
- *
+ *
* For some case, the root cause may be hidden and replaced by a general
* cause when sending an alert message. This keeps the root cause for
* internal analysis.
@@ -826,7 +884,7 @@ public boolean setRootCause(AlertMessage rootCause) {
}
/**
- * Check, if resumption is required.
+ * Checks, if resumption is required.
*
* @return {@code true}, if an abbreviated handshake should be done next
* time a data will be sent on this connection.
@@ -836,13 +894,24 @@ public boolean isResumptionRequired() {
}
/**
- * Check, if the automatic session resumption should be triggered or is
+ * Forces an abbreviated handshake next time a data will be sent on
+ * this connection.
+ *
+ * @param resumptionRequired {@code true} to force abbreviated handshake.
+ */
+ public void setResumptionRequired(boolean resumptionRequired) {
+ this.resumptionRequired = resumptionRequired;
+ }
+
+ /**
+ * Checks, if the automatic session resumption should be triggered or is
* already required.
*
* @param autoResumptionTimeoutMillis auto resumption timeout in
* milliseconds. {@code null}, if auto resumption is not used.
* @return {@code true}, if the provided autoResumptionTimeoutMillis has
* expired without exchanging messages.
+ * @see #lastMessageNanos
*/
public boolean isAutoResumptionRequired(Long autoResumptionTimeoutMillis) {
if (!resumptionRequired && autoResumptionTimeoutMillis != null && establishedDtlsContext != null) {
@@ -856,18 +925,19 @@ public boolean isAutoResumptionRequired(Long autoResumptionTimeoutMillis) {
}
/**
- * Refresh auto resumption timeout.
- *
+ * Updates realtime nanoseconds of last message.
+ *
* Uses {@link ClockUtil#nanoRealtime()}.
*
* @see #lastMessageNanos
+ * @since 4.0 (was refreshAutoResumptionTime)
*/
- public void refreshAutoResumptionTime() {
+ public void updateLastMessageNanos() {
lastMessageNanos = ClockUtil.nanoRealtime();
}
/**
- * Get realtime nanoseconds of last message.
+ * Gets realtime nanoseconds of last message.
*
* @return realtime nanoseconds of last message
* @since 3.0
@@ -876,16 +946,6 @@ public long getLastMessageNanos() {
return lastMessageNanos;
}
- /**
- * Use to force an abbreviated handshake next time a data will be sent on
- * this connection.
- *
- * @param resumptionRequired true to force abbreviated handshake.
- */
- public void setResumptionRequired(boolean resumptionRequired) {
- this.resumptionRequired = resumptionRequired;
- }
-
@Override
public int hashCode() {
final int prime = 31;
@@ -970,7 +1030,7 @@ public String toString() {
/**
* Identifier of starting client hello.
- *
+ *
* Keeps random and handshake message sequence number to prevent from
* accidentally starting a handshake again.
*
@@ -1013,6 +1073,10 @@ private void write(DatagramWriter writer) {
writer.writeVarBytes(clientHelloRandom, Byte.SIZE);
writer.writeLong(nanos, Long.SIZE);
}
+
+ private static ClientHelloIdentifier create(ClientHello clientHello) {
+ return clientHello == null ? null : new ClientHelloIdentifier(clientHello);
+ }
}
private class ConnectionSessionListener implements SessionListener {
@@ -1071,16 +1135,53 @@ public void handshakeFlightRetransmitted(Handshaker handshaker, int flight) {
}
}
+ /**
+ * Connection task.
+ *
+ * Execute using the connection's serialExecutor.
+ *
+ * @since 4.0
+ */
+ private class ConnectionTask implements Runnable {
+ /**
+ * Task to execute in serial executor.
+ */
+ private final Runnable task;
+ /**
+ * Flag to force execution, if serial execution is exhausted or
+ * shutdown. The task is then executed in the context of this
+ * {@link Runnable}.
+ */
+ private final boolean force;
+
+ /**
+ * Creates connection task.
+ *
+ * @param task task to be execute in serial executor
+ * @param force flag indicating, that the task should be executed, even
+ * if the serial executors are exhausted or shutdown.
+ */
+ private ConnectionTask(Runnable task, boolean force) {
+ this.task = task;
+ this.force = force;
+ }
+
+ @Override
+ public void run() {
+ execute(task, force);
+ }
+ }
+
/**
* Version number for serialization.
*/
private static final int VERSION = 1;
/**
- * Write connection state.
- *
- * Note: the stream will contain not encrypted critical credentials. It is
- * required to protect this data before exporting it.
+ * Writes connection state.
+ *
+ * Note: the stream will contain not encrypted critical credentials.
+ * It is required to protect this data before exporting it.
*
* @param writer writer for connection state
* @return {@code true}, if connection is written, {@code false}, if not.
@@ -1110,7 +1211,7 @@ public boolean writeTo(DatagramWriter writer) {
}
/**
- * Read connection state.
+ * Reads connection state.
*
* @param reader reader with connection state.
* @param nanoShift adjusting shift for system time in nanoseconds.
@@ -1129,7 +1230,7 @@ public static Connection fromReader(DataStreamReader reader, long nanoShift) {
}
/**
- * Create instance from reader.
+ * Creates instance from reader.
*
* @param reader reader with connection state.
* @param nanoShift adjusting shift for system time in nanoseconds.
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/ConnectionId.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/ConnectionId.java
index 86338772f6..bdac83463b 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/ConnectionId.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/ConnectionId.java
@@ -28,7 +28,7 @@ public class ConnectionId extends Bytes {
public static final ConnectionId EMPTY = new ConnectionId(Bytes.EMPTY);
/**
- * Create connection id from bytes.
+ * Creates connection id from bytes.
*
* @param connectionId connectionId bytes
* @throws NullPointerException if connectionId is {@code null}
@@ -73,8 +73,8 @@ public static boolean useConnectionId(ConnectionIdGenerator generator) {
}
/**
- * Check, if provided cid is used for records.
- *
+ * Checks, if provided cid is used for records.
+ *
* Only none {@link ConnectionId#isEmpty()} cids are used for records.
*
* @param cid cid
@@ -85,4 +85,23 @@ public static boolean useConnectionId(ConnectionIdGenerator generator) {
public static boolean useConnectionId(ConnectionId cid) {
return cid != null && !cid.isEmpty();
}
+
+ /**
+ * Creates connection id from bytes.
+ *
+ * @param bytes bytes to create connection id
+ * @return create connection id or the provided bytes, if that is already of
+ * type {@link ConnectionId}.
+ * @since 4.0
+ */
+ public static ConnectionId create(Bytes bytes) {
+ if (bytes == null || bytes.isEmpty()) {
+ return null;
+ }
+ if (bytes instanceof ConnectionId) {
+ return (ConnectionId) bytes;
+ } else {
+ return new ConnectionId(bytes.getBytes());
+ }
+ }
}
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/DTLSSession.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/DTLSSession.java
index 25507baa0d..888a63f4e1 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/DTLSSession.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/DTLSSession.java
@@ -67,6 +67,7 @@
import org.eclipse.californium.scandium.auth.PrincipalSerializer;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
import org.eclipse.californium.scandium.dtls.cipher.PseudoRandomFunction;
+import org.eclipse.californium.scandium.dtls.cipher.RandomManager;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite.KeyExchangeAlgorithm;
import org.eclipse.californium.scandium.dtls.cipher.PseudoRandomFunction.Label;
import org.eclipse.californium.scandium.dtls.cipher.XECDHECryptography.SupportedGroup;
@@ -90,6 +91,11 @@ public final class DTLSSession implements Destroyable {
* An arbitrary byte sequence chosen by the server to identify this session.
*/
private SessionId sessionIdentifier = SessionId.emptySessionId();
+ /**
+ * An alternative transient ID, if the {@link DTLSSession#sessionIdentifier}
+ * is empty by intention.
+ */
+ private Bytes hostInternalIdentifier;
/**
* Protocol version.
@@ -268,7 +274,7 @@ public boolean isDestroyed() {
/**
* Gets this session's identifier.
*
- * @return the identifier or {@code null} if this session does not have an
+ * @return the identifier. May be empty, if this session does not have an
* identifier (yet).
*/
public SessionId getSessionIdentifier() {
@@ -295,6 +301,7 @@ void setSessionIdentifier(SessionId sessionIdentifier) {
SecretUtil.destroy(this.masterSecret);
this.masterSecret = null;
this.sessionIdentifier = sessionIdentifier;
+ this.hostInternalIdentifier = null;
} else {
throw new IllegalArgumentException("no new session identifier?");
}
@@ -424,8 +431,18 @@ void setSniSupported(boolean flag) {
* @param attributes attributes to add the entries
*/
public void addEndpointContext(MapBasedEndpointContext.Attributes attributes) {
- Bytes id = sessionIdentifier.isEmpty() ? new Bytes(("TIME:" + Long.toString(creationTime)).getBytes())
- : sessionIdentifier;
+ Bytes id = sessionIdentifier;
+ if (id.isEmpty()) {
+ if (hostInternalIdentifier == null) {
+ byte[] tag = ("TIME:" + Long.toString(creationTime)).getBytes();
+ int fill = 24 - tag.length;
+ if (fill > 0) {
+ tag = Bytes.concatenate(tag, Bytes.createBytes(RandomManager.currentSecureRandom(), fill));
+ }
+ hostInternalIdentifier= new Bytes(tag);
+ }
+ id = hostInternalIdentifier;
+ }
attributes.add(DtlsEndpointContext.KEY_SESSION_ID, id);
attributes.add(DtlsEndpointContext.KEY_CIPHER, cipherSuite.name());
if (extendedMasterSecret) {
@@ -834,7 +851,7 @@ void setPeerIdentity(Principal peerIdentity) {
@Override
public int hashCode() {
- return sessionIdentifier == null ? (int) creationTime : sessionIdentifier.hashCode();
+ return sessionIdentifier.isEmpty() ? (int) creationTime : sessionIdentifier.hashCode();
}
@Override
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/Handshaker.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/Handshaker.java
index 3b76a1a750..bd2efb01bd 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/Handshaker.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/Handshaker.java
@@ -91,14 +91,15 @@
import org.eclipse.californium.elements.RawData;
import org.eclipse.californium.elements.auth.AdditionalInfo;
+import org.eclipse.californium.elements.auth.ApplicationAuthorizer;
import org.eclipse.californium.elements.auth.ExtensiblePrincipal;
import org.eclipse.californium.elements.auth.PreSharedKeyIdentity;
import org.eclipse.californium.elements.auth.RawPublicKeyIdentity;
import org.eclipse.californium.elements.auth.X509CertPath;
+import org.eclipse.californium.elements.exception.MissingApplicationAuthorizationException;
import org.eclipse.californium.elements.util.Bytes;
import org.eclipse.californium.elements.util.ClockUtil;
import org.eclipse.californium.elements.util.NoPublicAPI;
-import org.eclipse.californium.elements.util.SerialExecutor;
import org.eclipse.californium.elements.util.StringUtil;
import org.eclipse.californium.scandium.auth.ApplicationLevelInfoSupplier;
import org.eclipse.californium.scandium.config.DtlsConfig;
@@ -208,6 +209,9 @@ public abstract class Handshaker implements Destroyable {
/** Timeout in nanoseconds to expire handshakes. */
private final long nanosExpireTimeout;
+ /** Timeout in milliseconds for application authorization. */
+ private final int applicationAuthorizationTimeout;
+
/** The current flight number. */
protected int flightNumber = 0;
/**
@@ -251,14 +255,21 @@ public abstract class Handshaker implements Destroyable {
private Runnable retransmitFlight;
/**
* Future of completion timeout of the last flight.
- *
+ *
* This future indicates, that the last flight of the handshake is pending.
* The last flight is not retransmitted by the retransmission timeout, it's
* retransmitted, if the other peer retransmits the flight before this.
- *
+ *
* If no data is received within that completion timeout, the handshake is
* completed without. The last flight is not longer kept, because the other
* peer is not longer considered to retransmit its flight before this.
+ *
+ * Since 4.0 a connection with anonymous client will be removed, if no
+ * application data is received. If application data is received, the
+ * {@link DtlsConfig#DTLS_APPLICATION_AUTHORIZATION_TIMEOUT} is used to
+ * ensure, that the application has authorized the client. If the
+ * application miss to authorize the anonymous client within that timeout,
+ * the connection is removed.
*
* @since 3.0
*/
@@ -453,9 +464,10 @@ public abstract class Handshaker implements Destroyable {
private boolean eccExpected;
private boolean changeCipherSuiteMessageExpected;
private boolean contextEstablished;
+ private boolean handshakeCompletedByApplicationData;
private boolean handshakeCompleted;
- private boolean handshakeAborted;
private boolean handshakeFailed;
+ private boolean removeConnection;
private boolean pskRequestPending;
private boolean certificateVerificationPending;
private boolean certificateIdentityPending;
@@ -540,6 +552,7 @@ protected Handshaker(long initialRecordSequenceNo, int initialMessageSeq, Record
this.retransmissionTimeout = config.getTimeAsInt(DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT, TimeUnit.MILLISECONDS);
this.maxRetransmissionTimeout = config.getTimeAsInt(DtlsConfig.DTLS_MAX_RETRANSMISSION_TIMEOUT, TimeUnit.MILLISECONDS);
this.additionalTimeoutForEcc = config.getTimeAsInt(DtlsConfig.DTLS_ADDITIONAL_ECC_TIMEOUT, TimeUnit.MILLISECONDS);
+ this.applicationAuthorizationTimeout = config.getTimeAsInt(DtlsConfig.DTLS_APPLICATION_AUTHORIZATION_TIMEOUT, TimeUnit.MILLISECONDS);
this.retransmissionRandomFactor = config.get(DtlsConfig.DTLS_RETRANSMISSION_INIT_RANDOM);
this.retransmissionTimeoutScale = config.get(DtlsConfig.DTLS_RETRANSMISSION_TIMEOUT_SCALE);
this.backOffRetransmission = config.getBackOffRetransmission();
@@ -852,7 +865,6 @@ private void processNextMessages(Record record) throws HandshakeException {
++bufferIndex;
}
if (epoch < context.getReadEpoch()) {
- final SerialExecutor serialExecutor = connection.getExecutor();
final List
+ * In {@link DtlsConfig#DTLS_APPLICATION_AUTHORIZATION_TIMEOUT} the registered
+ * listeners are called postponed for anonymous clients, when the
+ * {@link ApplicationAuthorizer} is called.
+ *
+ * @since 4.0
+ */
+ public final void handshakeCompletedWithApplicationData() {
+ if (!handshakeCompleted && !handshakeCompletedByApplicationData) {
+ handshakeCompletedByApplicationData = true;
+ if (applicationAuthorizationTimeout > 0) {
+ DTLSSession session = context.getSession();
+ if (session != null && session.getPeerIdentity() == null) {
+ if (timeoutLastFlight != null) {
+ timeoutLastFlight.cancel(false);
+ }
+ Runnable task = connection.createTask(() -> noApplicationAuthorization(false), false);
+ timeoutLastFlight = timer.schedule(task, applicationAuthorizationTimeout, TimeUnit.MILLISECONDS);
+ return;
+ }
+ }
+ handshakeCompleted();
+ }
+ }
+
/**
* Forward handshake completed to registered listeners.
*/
@@ -2225,6 +2210,7 @@ public final void handshakeCompleted() {
if (!handshakeCompleted) {
if (timeoutLastFlight != null) {
timeoutLastFlight.cancel(false);
+ timeoutLastFlight = null;
}
handshakeCompleted = true;
completePendingFlight();
@@ -2238,10 +2224,10 @@ public final void handshakeCompleted() {
/**
* Notifies all registered session listeners about a handshake failure.
- *
+ *
* Listeners are intended to remove the connection, if no session is
* established.
- *
+ *
* If {@link #setFailureCause(Throwable)} was called before, only calls with
* the same cause will notify the listeners. If
* {@link #setFailureCause(Throwable)} wasn't called before, sets the
@@ -2252,27 +2238,16 @@ public final void handshakeCompleted() {
* @see #handshakeAborted(Throwable)
*/
public final void handshakeFailed(Throwable cause) {
- if (this.cause == null) {
- this.cause = cause;
- }
- if (!handshakeFailed && this.cause == cause) {
- LOGGER.debug("handshake failed {}", connection, cause);
- handshakeFailed = true;
- completePendingFlight();
- for (SessionListener sessionListener : sessionListeners) {
- sessionListener.handshakeFailed(this, cause);
- }
- SecretUtil.destroy(context);
- SecretUtil.destroy(this);
- }
+ this.removeConnection = !connection.hasEstablishedDtlsContext();
+ failHandshake(cause);
}
/**
- * Abort handshake.
- *
+ * Aborts handshake.
+ *
* Notifies all registered session listeners about a handshake failure.
* Listeners are intended to keep the connection.
- *
+ *
* If {@link #setFailureCause(Throwable)} was called before, only calls with
* the same cause will notify the listeners. If
* {@link #setFailureCause(Throwable)} wasn't called before, sets the
@@ -2284,8 +2259,60 @@ public final void handshakeFailed(Throwable cause) {
* @since 2.1
*/
public final void handshakeAborted(Throwable cause) {
- this.handshakeAborted = true;
- handshakeFailed(cause);
+ this.removeConnection = false;
+ failHandshake(cause);
+ }
+
+ /**
+ * Handle missing application authorization.
+ *
+ * Listeners are intended to remove the connection.
+ *
+ * If {@link #setFailureCause(Throwable)} was called before, only calls with
+ * the same cause will notify the listeners. If
+ * {@link #setFailureCause(Throwable)} wasn't called before, sets the
+ * cause property to the given cause.
+ *
+ * @param rejected {@code true}, if rejected by the application, {@code false}, if authorization timed out.
+ * @since 4.0
+ */
+ public final void noApplicationAuthorization(boolean rejected) {
+ this.removeConnection = true;
+ failHandshake(new MissingApplicationAuthorizationException(rejected));
+ }
+
+ /**
+ * Fails handshake.
+ *
+ * If {@link #isRemovingConnection()} returns {@code true}, the listeners
+ * are intended to remove the connection.
+ *
+ * If {@link #setFailureCause(Throwable)} was called before, only calls with
+ * the same cause will notify the listeners. If
+ * {@link #setFailureCause(Throwable)} wasn't called before, sets the
+ * cause property to the given cause.
+ *
+ * @param cause The reason for the failure.
+ * @see #isRemovingConnection()
+ */
+ private final void failHandshake(Throwable cause) {
+ if (this.cause == null) {
+ this.cause = cause;
+ }
+ if (timeoutLastFlight != null) {
+ timeoutLastFlight.cancel(false);
+ timeoutLastFlight = null;
+ }
+ if (!handshakeFailed && this.cause == cause) {
+ LOGGER.debug("handshake failed {}", connection, cause);
+ handshakeFailed = true;
+ completePendingFlight();
+ for (SessionListener sessionListener : sessionListeners) {
+ sessionListener.handshakeFailed(this, cause);
+ }
+ SecretUtil.destroy(context);
+ SecretUtil.destroy(this);
+ }
}
/**
@@ -2355,16 +2382,17 @@ public boolean isPskRequestPending() {
}
/**
- * Check, if the connection must be removed.
- *
+ * Checks, if the connection must be removed.
+ *
* The connection must be removed, if {@link #handshakeFailed(Throwable)}
- * was called, and the connection has no established session.
+ * was called, and the connection has no established session or the
+ * application authorization for an anonymous client is missing.
*
* @return {@code true}, remove the connection, {@code false}, keep it.
* @since 2.1
*/
public boolean isRemovingConnection() {
- return !handshakeAborted && !connection.hasEstablishedDtlsContext();
+ return removeConnection;
}
/**
@@ -2583,15 +2611,14 @@ protected void ensureUndestroyed() {
*/
private void amendPeerPrincipal() {
- DTLSSession session = getSession();
- Principal peerIdentity = session.getPeerIdentity();
+ Principal peerIdentity = getPeerIdentity();
if (applicationLevelInfoSupplier != null && peerIdentity instanceof ExtensiblePrincipal) {
// amend the client principal with additional application level information
@SuppressWarnings("unchecked")
ExtensiblePrincipal extends Principal> extensibleClientIdentity = (ExtensiblePrincipal extends Principal>) peerIdentity;
AdditionalInfo additionalInfo = applicationLevelInfoSupplier.getInfo(peerIdentity, customArgument);
if (additionalInfo != null) {
- session.setPeerIdentity(extensibleClientIdentity.amend(additionalInfo));
+ getSession().setPeerIdentity(extensibleClientIdentity.amend(additionalInfo));
}
}
}
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/HelloHandshakeMessage.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/HelloHandshakeMessage.java
index e976d4ee00..19d7b82630 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/HelloHandshakeMessage.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/HelloHandshakeMessage.java
@@ -43,12 +43,11 @@ public abstract class HelloHandshakeMessage extends HandshakeMessage {
/** A generated random structure. */
protected final Random random;
- /** The ID of a session the client wishes to use for this connection. */
+ /** The ID of a session of this connection. */
protected final SessionId sessionId;
/**
- * Clients MAY request extended functionality from servers by sending data
- * in the extensions field.
+ * Client and server extensions.
*/
protected final HelloExtensions extensions = new HelloExtensions();
@@ -101,38 +100,41 @@ public String toString(int indent) {
StringBuilder sb = new StringBuilder();
sb.append(super.toString(indent));
String indentation = StringUtil.indentation(indent + 1);
- sb.append(indentation).append("Version: ").append(protocolVersion.getMajor()).append(", ").append(protocolVersion.getMinor()).append(StringUtil.lineSeparator());
+ sb.append(indentation).append("Version: ").append(protocolVersion.getMajor()).append(", ")
+ .append(protocolVersion.getMinor()).append(StringUtil.lineSeparator());
sb.append(indentation).append("Random:").append(StringUtil.lineSeparator());
sb.append(random.toString(indent + 2));
- sb.append(indentation).append("Session ID Length: ").append(sessionId.length()).append(" bytes").append(StringUtil.lineSeparator());
- if (sessionId.length() > 0) {
+ int len = sessionId.length();
+ sb.append(indentation).append("Session ID Length: ").append(len).append(" bytes")
+ .append(StringUtil.lineSeparator());
+ if (len > 0) {
sb.append(indentation).append("Session ID: ").append(sessionId).append(StringUtil.lineSeparator());
}
return sb.toString();
}
/**
- * Get protocol version.
+ * Gets the protocol version.
*
- * @return protocol version.
+ * @return the protocol version.
*/
public ProtocolVersion getProtocolVersion() {
return protocolVersion;
}
/**
- * Get client random
+ * Gets the client random
*
- * @return client random
+ * @return the client random
*/
public Random getRandom() {
return random;
}
/**
- * Get session id.
+ * Gets the session id.
*
- * @return session id. May be empty.
+ * @return the session id. May be empty.
* @see #hasSessionId()
*/
public SessionId getSessionId() {
@@ -150,18 +152,18 @@ public boolean hasSessionId() {
}
/**
- * Add hello extension.
+ * Adds hello extension.
*
- * @param extension hello extension to add
+ * @param extension the hello extension to add
*/
void addExtension(HelloExtension extension) {
extensions.addExtension(extension);
}
/**
- * Gets the client hello extensions the client has included in this message.
+ * Gets the hello extensions the peer has included in this message.
*
- * @return The extensions. May be empty, if no extensions are used.
+ * @return the extensions. May be empty, if no extensions are used.
*/
public HelloExtensions getExtensions() {
return extensions;
@@ -170,7 +172,7 @@ public HelloExtensions getExtensions() {
/**
* Gets the supported point formats.
*
- * @return the client's supported point formats extension if available,
+ * @return the supported point formats extension if available,
* otherwise {@code null}.
*/
public SupportedPointFormatsExtension getSupportedPointFormatsExtension() {
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/InMemoryConnectionStore.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/InMemoryConnectionStore.java
index ddf3278e2f..66c70a1f6d 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/InMemoryConnectionStore.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/InMemoryConnectionStore.java
@@ -31,12 +31,12 @@
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+import org.eclipse.californium.elements.auth.ExtensiblePrincipal;
import org.eclipse.californium.elements.util.ClockUtil;
import org.eclipse.californium.elements.util.DataStreamReader;
import org.eclipse.californium.elements.util.DatagramWriter;
import org.eclipse.californium.elements.util.FilteredLogger;
import org.eclipse.californium.elements.util.LeastRecentlyUpdatedCache;
-import org.eclipse.californium.elements.util.LeastRecentlyUpdatedCache.Timestamped;
import org.eclipse.californium.elements.util.SerialExecutor;
import org.eclipse.californium.elements.util.SerializationUtil;
import org.eclipse.californium.elements.util.StringUtil;
@@ -46,18 +46,16 @@
import org.slf4j.LoggerFactory;
/**
- * An in-memory {@code ConnectionStore} with a configurable maximum
- * capacity and support for evicting stale connections based on a least
- * recently update policy.
+ * An in-memory {@code ConnectionStore} with a configurable maximum capacity and
+ * support for evicting stale connections based on a least recently
+ * update policy.
*
* The store keeps track of the connections' last-access time automatically.
* Every time a verified record is received or a record is sent for a
* connection, the access-time is updated.
- *
* A connection can be successfully added to the store if any of the following
* conditions is met:
- *
* Insertion, lookup and removal of connections is done in O(log n).
- *
* Storing and reading to/from the store is thread safe.
- *
* Supports also a {@link SessionStore} implementation to keep sessions for
* longer or in a distribute system. If the connection store evicts a connection
@@ -86,14 +81,15 @@
* in the session store. Therefore the session store requires a own, independent
* cleanup for stale sessions. If a connection is removed by a critical ALERT,
* the session get's removed also from the session store.
- *
* Use {@code 0} or negative delays for test with synchronous blocking
* behaviour. And positive delays for test with asynchronous none-blocking
* behaviour.
*
- * @since 4.0 (Renamed AsyncNewAdvancedCertificateVerifier into AsyncCertificateVerifier)
+ * @since 4.0 (Renamed AsyncNewAdvancedCertificateVerifier into
+ * AsyncCertificateVerifier)
*/
public class AsyncCertificateVerifier extends StaticCertificateVerifier {
+
/**
* @since 3.10
*/
@@ -55,7 +57,8 @@ public class AsyncCertificateVerifier extends StaticCertificateVerifier {
/**
* Thread factory.
*/
- private static final NamedThreadFactory THREAD_FACTORY = new DaemonThreadFactory("AsyncCertVerifier#", NamedThreadFactory.SCANDIUM_THREAD_GROUP);
+ private static final NamedThreadFactory THREAD_FACTORY = new DaemonThreadFactory("AsyncCertVerifier#",
+ NamedThreadFactory.SCANDIUM_THREAD_GROUP);
/**
* Executor for asynchronous behaviour.
*/
@@ -73,8 +76,8 @@ public class AsyncCertificateVerifier extends StaticCertificateVerifier {
*/
private HandshakeResultHandler resultHandler;
- public AsyncCertificateVerifier(X509Certificate[] trustedCertificates,
- RawPublicKeyIdentity[] trustedRPKs, List
+ * Mainly contains integration test cases verifying the correct interaction
+ * between a client and a server during handshakes with and without SNI.
+ */
+@RunWith(Parameterized.class)
+@Category(Medium.class)
+public class ApplicationAuthorizationTest {
+
+ @ClassRule
+ public static DtlsNetworkRule network = new DtlsNetworkRule(DtlsNetworkRule.Mode.DIRECT,
+ DtlsNetworkRule.Mode.NATIVE);
+
+ @ClassRule
+ public static ThreadsRule cleanup = new ThreadsRule();
+
+ private static final int CLIENT_CONNECTION_STORE_CAPACITY = 5;
+
+ @Rule
+ public TestTimeRule time = new TestTimeRule();
+
+ @Rule
+ public TestNameLoggerRule names = new TestNameLoggerRule();
+ @Rule
+ public LoggingRule logging = new LoggingRule();
+
+ enum Mode {
+ NONE, AUTHORIZE, REJECT
+ }
+
+ @Parameter(0)
+ public Mode mode;
+
+ @Parameters(name = "mode {0}")
+ public static Iterable
*
* enum {
@@ -86,6 +91,8 @@ public static void serialize(final Principal principal, final DatagramWriter wri
throw new NullPointerException("Writer must not be null");
} else if (principal == null) {
writer.writeByte(ClientAuthenticationType.ANONYMOUS.code);
+ } else if (principal instanceof ApplicationPrincipal) {
+ serializeApplicationPrincipal((ApplicationPrincipal) principal, writer);
} else if (principal instanceof PreSharedKeyIdentity) {
serializeIdentity((PreSharedKeyIdentity) principal, writer);
} else if (principal instanceof RawPublicKeyIdentity) {
@@ -119,13 +126,23 @@ private static void serializeCertChain(final X509CertPath principal, final Datag
writer.writeBytes(principal.toByteArray());
}
+ private static void serializeApplicationPrincipal(final ApplicationPrincipal principal,
+ final DatagramWriter writer) {
+ writer.writeByte(ClientAuthenticationType.APPLICATION.code);
+ writer.writeByte(principal.isAnonymous() ? (byte) 1 : (byte) 0);
+ SerializationUtil.write(writer, principal.getName(), APPLICATION_PRINCIPAL_NAME_LENGTH_BITS);
+ }
+
/**
* Deserializes a principal from its byte array representation.
*
* @param reader The reader containing the byte array.
- * @return The principal object or {@code null} if the reader does not contain a supported principal type.
- * @throws GeneralSecurityException if the reader contains a raw public key principal that could not be recreated.
- * @throws IllegalArgumentException if the reader contains an unsupported ClientAuthenticationType.
+ * @return The principal object or {@code null} if the reader does not
+ * contain a supported principal type.
+ * @throws GeneralSecurityException if the reader contains a raw public key
+ * principal that could not be recreated.
+ * @throws IllegalArgumentException if the reader contains an unsupported
+ * ClientAuthenticationType.
*/
public static Principal deserialize(final DatagramReader reader) throws GeneralSecurityException {
if (reader == null) {
@@ -133,11 +150,13 @@ public static Principal deserialize(final DatagramReader reader) throws GeneralS
}
byte code = reader.readNextByte();
ClientAuthenticationType type = ClientAuthenticationType.fromCode(code);
- switch(type) {
+ switch (type) {
case CERT:
return deserializeCertChain(reader);
case PSK:
return deserializeIdentity(reader);
+ case APPLICATION:
+ return deserializeApplicationPrincipal(reader);
case RPK:
return deserializeSubjectInfo(reader);
default:
@@ -169,12 +188,20 @@ private static RawPublicKeyIdentity deserializeSubjectInfo(final DatagramReader
return new RawPublicKeyIdentity(subjectInfo);
}
+ private static ApplicationPrincipal deserializeApplicationPrincipal(final DatagramReader reader)
+ throws GeneralSecurityException {
+ byte code = reader.readNextByte();
+ String name = SerializationUtil.readString(reader, APPLICATION_PRINCIPAL_NAME_LENGTH_BITS);
+ if (ApplicationPrincipal.ANONYMOUS.getName().equals(name)) {
+ return ApplicationPrincipal.ANONYMOUS;
+ } else {
+ return new ApplicationPrincipal(name, code == 1);
+ }
+ }
+
private enum ClientAuthenticationType {
- ANONYMOUS((byte) 0x00),
- CERT((byte) 0x01),
- PSK((byte) 0x02),
- RPK((byte) 0xff);
+ ANONYMOUS((byte) 0x00), CERT((byte) 0x01), PSK((byte) 0x02), APPLICATION((byte) 0xfe), RPK((byte) 0xff);
private byte code;
diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/config/DtlsConfig.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/config/DtlsConfig.java
index 2dbf62c0cd..7c2a166e42 100644
--- a/scandium-core/src/main/java/org/eclipse/californium/scandium/config/DtlsConfig.java
+++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/config/DtlsConfig.java
@@ -22,6 +22,7 @@
import java.util.concurrent.TimeUnit;
import org.eclipse.californium.elements.DtlsEndpointContext;
+import org.eclipse.californium.elements.auth.ApplicationAuthorizer;
import org.eclipse.californium.elements.config.BasicListDefinition;
import org.eclipse.californium.elements.config.BooleanDefinition;
import org.eclipse.californium.elements.config.CertificateAuthenticationMode;
@@ -80,11 +81,12 @@ public enum DtlsRole {
/**
* DTLS secure renegotiation.
- *
+ *
@@ -901,7 +900,7 @@ protected List
*
*