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 getPostProcessInterceptors() { return null; } + @Override + public ApplicationAuthorizer getApplicationAuthorizer() { + return null; + } + } } diff --git a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/BaseServer.java b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/BaseServer.java index 3885626267..f89feee521 100644 --- a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/BaseServer.java +++ b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/BaseServer.java @@ -41,15 +41,17 @@ import org.eclipse.californium.cloud.util.DeviceProvisioningConsumer; import org.eclipse.californium.cloud.util.ResourceStore; 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.HealthStatisticLogger; import org.eclipse.californium.core.observe.ObserveStatisticLogger; import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.elements.EndpointContextMatcher; -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.Configuration.DefinitionsProvider; import org.eclipse.californium.elements.config.IntegerDefinition; @@ -62,9 +64,9 @@ import org.eclipse.californium.elements.util.ExecutorsUtil; import org.eclipse.californium.elements.util.NamedThreadFactory; import org.eclipse.californium.elements.util.NetworkInterfacesUtil; -import org.eclipse.californium.elements.util.ProtocolScheduledExecutorService; import org.eclipse.californium.elements.util.NetworkInterfacesUtil.InetAddressFilter; import org.eclipse.californium.elements.util.NetworkInterfacesUtil.SimpleInetAddressFilter; +import org.eclipse.californium.elements.util.ProtocolScheduledExecutorService; import org.eclipse.californium.elements.util.SslContextUtil.Credentials; import org.eclipse.californium.elements.util.StringUtil; import org.eclipse.californium.elements.util.SystemResourceMonitors; @@ -654,10 +656,11 @@ public void addEndpoints(ServerConfig cliArguments) { } // Context matcher - EndpointContextMatcher customContextMatcher = null; - if (MatcherMode.PRINCIPAL == config.get(CoapConfig.RESPONSE_MATCHING)) { - customContextMatcher = new PrincipalEndpointContextMatcher(true); - } + boolean applicationAuthentication = config.get(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE) != CertificateAuthenticationMode.NEEDED && + config.get(DtlsConfig.DTLS_APPLICATION_AUTHORIZATION_TIMEOUT, TimeUnit.SECONDS) > 0; + + EndpointContextMatcher customContextMatcher = EndpointContextMatcherFactory.create(CoAP.PROTOCOL_DTLS, + applicationAuthentication, config); // explore network interfaces Collection localAddresses; diff --git a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/resources/ProtectedCoapResource.java b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/resources/ProtectedCoapResource.java index 830051e86c..95707c94a8 100644 --- a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/resources/ProtectedCoapResource.java +++ b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/resources/ProtectedCoapResource.java @@ -160,9 +160,12 @@ protected ResponseCode checkPermission(Exchange exchange) { final PrincipalInfo info = getPrincipalInfo(exchange); if (info == null) { return UNAUTHORIZED; - } - if (!allowed(info.type)) { - return FORBIDDEN; + } else if (!allowed(info.type)) { + if (info.type == Type.ANONYMOUS_DEVICE) { + return UNAUTHORIZED; + } else { + return FORBIDDEN; + } } return checkOperationPermission(info, exchange, exchange.getRequest().getCode().write); } diff --git a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/ApplicationAnonymous.java b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/ApplicationAnonymous.java new file mode 100644 index 0000000000..9cf54aa59d --- /dev/null +++ b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/ApplicationAnonymous.java @@ -0,0 +1,58 @@ +/******************************************************************************** + * 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.cloud.util; + +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.californium.cloud.util.PrincipalInfo.Type; +import org.eclipse.californium.elements.auth.AdditionalInfo; +import org.eclipse.californium.elements.auth.ApplicationPrincipal; + +/** + * Application level anonymous. + * + * @since 4.0 + */ +public class ApplicationAnonymous { + + public static final PrincipalInfo ANONYMOUS_INFO = new PrincipalInfo(ApplicationPrincipal.ANONYMOUS.getName(), + ApplicationPrincipal.ANONYMOUS.getName(), Type.ANONYMOUS_DEVICE); + + public static final PrincipalInfo APPL_AUTH_INFO = new PrincipalInfo(ApplicationPrincipal.ANONYMOUS.getName(), + ApplicationPrincipal.ANONYMOUS.getName(), Type.APPL_AUTH_DEVICE); + + /** + * Application anonymous principal. + */ + public static final ApplicationPrincipal APPL_AUTH_PRINCIPAL; + + static { + Map info = new HashMap<>(); + info.put(PrincipalInfo.INFO_NAME, ApplicationPrincipal.ANONYMOUS.getName()); + info.put(PrincipalInfo.INFO_PROVIDER, new PrincipalInfoProvider() { + + @Override + public PrincipalInfo getPrincipalInfo(Principal principal) { + if (ApplicationPrincipal.ANONYMOUS.equals(principal)) { + return APPL_AUTH_INFO; + } + return null; + } + }); + APPL_AUTH_PRINCIPAL = ApplicationPrincipal.ANONYMOUS.amend(AdditionalInfo.from(info)); + } +} diff --git a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/DeviceManager.java b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/DeviceManager.java index ece627b659..9072c1475c 100644 --- a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/DeviceManager.java +++ b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/DeviceManager.java @@ -388,12 +388,12 @@ public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerN LOGGER.info("Certificate validation failed: Raw public key is not trusted"); AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE); return new CertificateVerificationResult(cid, - new HandshakeException("Raw public key is not trusted!", alert), null); + new HandshakeException("Raw public key is not trusted!", alert)); } else if (info.isEmpty()) { LOGGER.info("Certificate validation failed: Raw public key is banned"); AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE); return new CertificateVerificationResult(cid, - new HandshakeException("Raw public key is banned!", alert), null); + new HandshakeException("Raw public key is banned!", alert)); } else { return new CertificateVerificationResult(cid, publicKey, info); } @@ -408,7 +408,7 @@ public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerN LOGGER.debug("Certificate validation failed: key usage doesn't match"); AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE); return new CertificateVerificationResult(cid, - new HandshakeException("Key Usage doesn't match!", alert), null); + new HandshakeException("Key Usage doesn't match!", alert)); } X509Certificate[] trustedCertificates = getTrustedCertificates(); if (trustedCertificates == null || trustedCertificates.length == 0) { @@ -439,12 +439,12 @@ public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerN LOGGER.info("Certificate validation failed: x509 certificate is not trusted"); AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE); return new CertificateVerificationResult(cid, - new HandshakeException("x509 certificate is not trusted!", alert), null); + new HandshakeException("x509 certificate is not trusted!", alert)); } else if (info.isEmpty()) { LOGGER.info("{}Certificate validation failed: x509 certificate is banned", role); AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE); return new CertificateVerificationResult(cid, - new HandshakeException(role + "x509 certificate is banned!", alert), null); + new HandshakeException(role + "x509 certificate is banned!", alert)); } else { return new CertificateVerificationResult(cid, certChain, info); } @@ -458,8 +458,7 @@ public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerN if (cause instanceof CertificateExpiredException) { LOGGER.debug("Certificate expired: {}", cause.getMessage()); AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.CERTIFICATE_EXPIRED); - return new CertificateVerificationResult(cid, new HandshakeException("Certificate expired", alert), - null); + return new CertificateVerificationResult(cid, new HandshakeException("Certificate expired", alert)); } else if (cause != null) { LOGGER.debug("Certificate validation failed: {}/{}", e.getMessage(), cause.getMessage()); } else { @@ -467,7 +466,7 @@ public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerN } AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.BAD_CERTIFICATE); return new CertificateVerificationResult(cid, - new HandshakeException("Certificate chain could not be validated", alert, e), null); + new HandshakeException("Certificate chain could not be validated", alert, e)); } catch (GeneralSecurityException e) { if (LOGGER.isTraceEnabled()) { LOGGER.trace("Certificate validation failed", e); @@ -476,7 +475,7 @@ public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerN } AlertMessage alert = new AlertMessage(AlertLevel.FATAL, AlertDescription.DECRYPT_ERROR); return new CertificateVerificationResult(cid, - new HandshakeException("Certificate chain could not be validated", alert, e), null); + new HandshakeException("Certificate chain could not be validated", alert, e)); } } diff --git a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/PrincipalInfo.java b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/PrincipalInfo.java index 581be2b15c..bb45845caf 100644 --- a/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/PrincipalInfo.java +++ b/demo-apps/cf-cloud-demo-server/src/main/java/org/eclipse/californium/cloud/util/PrincipalInfo.java @@ -43,6 +43,14 @@ public class PrincipalInfo { */ public enum Type { + /** + * Device principal info for anonymous device. + */ + ANONYMOUS_DEVICE("anon"), + /** + * Device principal info for application authorized device. + */ + APPL_AUTH_DEVICE("appl_auth"), /** * Device principal info. */ @@ -122,6 +130,15 @@ public static Type valueOfShortName(String shortName) { * @param type type of principal */ public PrincipalInfo(String group, String name, Type type) { + if (group == null) { + throw new NullPointerException("group must not be null!"); + } + if (name == null) { + throw new NullPointerException("name must not be null!"); + } + if (type == null) { + throw new NullPointerException("type must not be null!"); + } this.name = name; this.group = group; this.type = type; @@ -132,6 +149,35 @@ public String toString() { return name + " (" + group + "," + type.getShortName() + ")"; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + group.hashCode(); + result = prime * result + name.hashCode(); + result = prime * result + type.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PrincipalInfo other = (PrincipalInfo) obj; + if (!group.equals(other.group)) { + return false; + } else if (!name.equals(other.name)) { + return false; + } else if (type != other.type) { + return false; + } + return true; + } + /** * Get principal info. *

@@ -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 extensiblePrincipal = (ExtensiblePrincipal) 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 results) { }; if (forward && httpForwardConfigurationProvider != null) { - final HttpForwardConfiguration configuration = httpForwardConfigurationProvider.getConfiguration(domain, - info.name); + final HttpForwardConfiguration configuration = httpForwardConfigurationProvider.getConfiguration(info); if (configuration != null && configuration.isValid()) { String serviceName = configuration.getServiceName(); HttpForwardService service = HttpForwardServiceManager.getService(serviceName); diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/resources/S3ProxyResource.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/resources/S3ProxyResource.java index 6c3d25c4a9..7334381886 100644 --- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/resources/S3ProxyResource.java +++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/resources/S3ProxyResource.java @@ -36,6 +36,7 @@ import org.eclipse.californium.cloud.s3.proxy.S3ProxyRequest; import org.eclipse.californium.cloud.s3.util.DomainPrincipalInfo; import org.eclipse.californium.cloud.util.PrincipalInfo; +import org.eclipse.californium.cloud.util.PrincipalInfo.Type; import org.eclipse.californium.core.coap.CoAP.ResponseCode; import org.eclipse.californium.core.coap.Option; import org.eclipse.californium.core.coap.Request; @@ -81,7 +82,7 @@ public class S3ProxyResource extends ProtectedCoapResource { * @param s3Clients S3 clients */ public S3ProxyResource(String name, int pathStartIndex, Configuration config, S3ProxyClientProvider s3Clients) { - super(name); + super(name, Type.DEVICE, Type.ANONYMOUS_DEVICE, Type.APPL_AUTH_DEVICE); if (s3Clients == null) { throw new NullPointerException("s3client must not be null!"); } diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainApplicationAnonymous.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainApplicationAnonymous.java new file mode 100644 index 0000000000..85e0852c3b --- /dev/null +++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainApplicationAnonymous.java @@ -0,0 +1,63 @@ +/******************************************************************************** + * 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.cloud.s3.util; + +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.californium.cloud.util.PrincipalInfo; +import org.eclipse.californium.cloud.util.PrincipalInfo.Type; +import org.eclipse.californium.elements.auth.AdditionalInfo; +import org.eclipse.californium.elements.auth.ApplicationPrincipal; + +/** + * Application level anonymous. + * + * @since 4.0 + */ +public class DomainApplicationAnonymous { + + public static final DomainPrincipalInfo ANONYMOUS_INFO = new DomainPrincipalInfo( + ApplicationPrincipal.ANONYMOUS.getName(), ApplicationPrincipal.ANONYMOUS.getName(), + ApplicationPrincipal.ANONYMOUS.getName(), Type.ANONYMOUS_DEVICE); + + public static final DomainPrincipalInfo APPL_AUTH_INFO = new DomainPrincipalInfo( + ApplicationPrincipal.ANONYMOUS.getName(), ApplicationPrincipal.ANONYMOUS.getName(), + ApplicationPrincipal.ANONYMOUS.getName(), Type.APPL_AUTH_DEVICE); + + /** + * Application anonymous principal. + */ + public static final ApplicationPrincipal APPL_AUTH_PRINCIPAL; + + static { + + Map info = new HashMap<>(); + info.put(PrincipalInfo.INFO_NAME, ApplicationPrincipal.ANONYMOUS.getName()); + info.put(DomainPrincipalInfo.INFO_DOMAIN, ApplicationPrincipal.ANONYMOUS.getName()); + info.put(PrincipalInfo.INFO_PROVIDER, new DomainPrincipalInfoProvider() { + + @Override + public DomainPrincipalInfo getPrincipalInfo(Principal principal) { + if (ApplicationPrincipal.ANONYMOUS.equals(principal)) { + return APPL_AUTH_INFO; + } + return null; + } + }); + APPL_AUTH_PRINCIPAL = ApplicationPrincipal.ANONYMOUS.amend(AdditionalInfo.from(info)); + } +} diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainDeviceManager.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainDeviceManager.java index ac1725aeb8..9dff24a0f2 100644 --- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainDeviceManager.java +++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainDeviceManager.java @@ -237,10 +237,10 @@ public Set getGroup(String domain, String group) { } @Override - public HttpForwardConfiguration getConfiguration(String domain, String name) { - ResourceStore resource = domains.get(domain); + public HttpForwardConfiguration getConfiguration(DomainPrincipalInfo principalInfo) { + ResourceStore resource = domains.get(principalInfo.domain); if (resource != null) { - Device device = resource.getResource().get(name); + Device device = resource.getResource().get(principalInfo.name); if (device != null) { try { return BasicHttpForwardConfiguration.create(device.customFields); diff --git a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainPrincipalInfo.java b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainPrincipalInfo.java index b9ebc3dd13..8d0e05d8a9 100644 --- a/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainPrincipalInfo.java +++ b/demo-apps/cf-s3-proxy-server/src/main/java/org/eclipse/californium/cloud/s3/util/DomainPrincipalInfo.java @@ -59,6 +59,38 @@ public String toString() { return name + "@" + domain + " (" + group + "," + type.getShortName() + ")"; } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + domain.hashCode(); + result = prime * result + group.hashCode(); + result = prime * result + name.hashCode(); + result = prime * result + type.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DomainPrincipalInfo other = (DomainPrincipalInfo) obj; + if (!domain.equals(other.domain)) { + return false; + } else if (!group.equals(other.group)) { + return false; + } else if (!name.equals(other.name)) { + return false; + } else if (type != other.type) { + return false; + } + return true; + } + /** * Gets principal info. *

@@ -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 extensiblePrincipal = (ExtensiblePrincipal) 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 domainConfigFields = HttpForwardServiceManager.getDomainConfigFields(); + if (domainConfigFields != null) { + Map fields = new HashMap<>(); + for (String field : domainConfigFields) { + String value = domainDefinition.get(section, field); + fields.put(field, value); + } + try { + httpForwardingConfiguration = BasicHttpForwardConfiguration.create(fields); + if (httpForwardingConfiguration != null) { + LOGGER.info("{}: http forward {}, {}", name, httpForwardingConfiguration.getDestination(), + httpForwardingConfiguration.getDeviceIdentityMode()); + } + } catch (URISyntaxException e) { + LOGGER.warn("Failed to configure http forward '{}' for domain {}.", e.getInput(), section); + } + } } } if (web != null && webDomain == null) { webDomain = domains.get(web); } this.webDomain = webDomain; + this.anonymousHttpForwardingConfiguration = httpForwardingConfiguration; } /** @@ -459,8 +485,12 @@ public String remove(String domainName, String section, String name) { } @Override - public HttpForwardConfiguration getConfiguration(String domainName, String name) { - Domain domain = domains.get(domainName); + public HttpForwardConfiguration getConfiguration(DomainPrincipalInfo principalInfo) { + if (DomainApplicationAnonymous.ANONYMOUS_INFO.equals(principalInfo) + || DomainApplicationAnonymous.APPL_AUTH_INFO.equals(principalInfo)) { + return anonymousHttpForwardingConfiguration; + } + Domain domain = domains.get(principalInfo.domain); if (domain != null) { return domain.httpForwardingConfiguration; } diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/EndpointContextMatcher.java b/element-connector/src/main/java/org/eclipse/californium/elements/EndpointContextMatcher.java index de278d486b..614a48c06b 100644 --- a/element-connector/src/main/java/org/eclipse/californium/elements/EndpointContextMatcher.java +++ b/element-connector/src/main/java/org/eclipse/californium/elements/EndpointContextMatcher.java @@ -26,13 +26,6 @@ */ public interface EndpointContextMatcher extends EndpointIdentityResolver { - /** - * Return matcher name. Used for logging. - * - * @return name of strategy. - */ - String getName(); - /** * Check, if responses is related to the request. * diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/EndpointIdentityResolver.java b/element-connector/src/main/java/org/eclipse/californium/elements/EndpointIdentityResolver.java index 6c37f1840e..ebb43b53cb 100644 --- a/element-connector/src/main/java/org/eclipse/californium/elements/EndpointIdentityResolver.java +++ b/element-connector/src/main/java/org/eclipse/californium/elements/EndpointIdentityResolver.java @@ -32,6 +32,7 @@ public interface EndpointIdentityResolver { * * @param context endpoint context * @return endpoint identity object. + * @throws IllegalArgumentException if context doesn't contain an identity */ Object getEndpointIdentity(EndpointContext context); diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/PrincipalAndAnonymousEndpointContextMatcher.java b/element-connector/src/main/java/org/eclipse/californium/elements/PrincipalAndAnonymousEndpointContextMatcher.java new file mode 100644 index 0000000000..e86d6585bc --- /dev/null +++ b/element-connector/src/main/java/org/eclipse/californium/elements/PrincipalAndAnonymousEndpointContextMatcher.java @@ -0,0 +1,122 @@ +/******************************************************************************** + * 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 java.security.Principal; + +import org.eclipse.californium.elements.auth.ExtensiblePrincipal; +import org.eclipse.californium.elements.util.Bytes; + +/** + * Principal based endpoint context matcher. + *

+ * 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 authorize(EndpointContext context, ApplicationPrincipal principal); + + /** + * Reject authorization. + * + * @param context endpoint context to reject authorization. + * @return future completes with removing the connection. + */ + Future rejectAuthorization(EndpointContext context); + +} diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/auth/ApplicationPrincipal.java b/element-connector/src/main/java/org/eclipse/californium/elements/auth/ApplicationPrincipal.java new file mode 100644 index 0000000000..d704bda464 --- /dev/null +++ b/element-connector/src/main/java/org/eclipse/californium/elements/auth/ApplicationPrincipal.java @@ -0,0 +1,106 @@ +/******************************************************************************** + * 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.util.Objects; + +/** + * Application level principal. + * + * @since 4.0 + */ +public class ApplicationPrincipal extends AbstractExtensiblePrincipal { + + /** + * Anonymous principal. + */ + public static final ApplicationPrincipal ANONYMOUS = new ApplicationPrincipal("anonymous", true); + + /** + * Principal's name. + */ + private final String name; + + /** + * Mark for anonymous principals. + */ + private final boolean anonymous; + + /** + * Creates an application principal. + * + * @param name name of principal + * @param anonymous {@code true} for anonymous principal. + */ + public ApplicationPrincipal(String name, boolean anonymous) { + this.name = name; + this.anonymous = anonymous; + } + + /** + * Creates an application principal. + * + * @param name name + * @param anonymous {@code true} for anonymous principal. + * @param additionalInformation additional information + */ + private ApplicationPrincipal(String name, boolean anonymous, AdditionalInfo additionalInformation) { + super(additionalInformation); + this.name = name; + this.anonymous = anonymous; + } + + @Override + public ApplicationPrincipal amend(AdditionalInfo additionalInfo) { + return new ApplicationPrincipal(name, anonymous, additionalInfo); + } + + @Override + public String getName() { + return name; + } + + @Override + public String toString() { + return "Application Prinicpal [" + name + "]"; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if (obj == null) { + return false; + } else if (getClass() != obj.getClass()) { + return false; + } + ApplicationPrincipal other = (ApplicationPrincipal) obj; + if (anonymous != other.anonymous) { + return false; + } + return Objects.equals(name, other.name); + } + + @Override + public boolean isAnonymous() { + return anonymous; + } + +} diff --git a/element-connector/src/main/java/org/eclipse/californium/elements/auth/ExtensiblePrincipal.java b/element-connector/src/main/java/org/eclipse/californium/elements/auth/ExtensiblePrincipal.java index a6cfb98b5e..8546bc4f5e 100644 --- a/element-connector/src/main/java/org/eclipse/californium/elements/auth/ExtensiblePrincipal.java +++ b/element-connector/src/main/java/org/eclipse/californium/elements/auth/ExtensiblePrincipal.java @@ -14,7 +14,6 @@ * Bosch Software Innovations GmbH - initial creation *******************************************************************************/ - package org.eclipse.californium.elements.auth; import java.security.Principal; @@ -27,10 +26,11 @@ public interface ExtensiblePrincipal extends Principal { /** - * Creates a shallow copy of this principal which contains additional information. + * Creates a shallow copy of this principal which contains additional + * information. *

- * 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 extends Principal { * The map will be empty if no additional information is available. */ AdditionalInfo getExtendedInfo(); + + /** + * Checks, if principal is anonymous. + *

+ * 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 list) { } else { LOGGER.info("Threads {} : {}{}", description, thread.getName(), mark); } - if (LOGGER.isTraceEnabled()) { + if (LOGGER.isDebugEnabled()) { StackTraceElement[] stackTrace = thread.getStackTrace(); for (StackTraceElement trace : stackTrace) { - LOGGER.trace(" {}", trace); + LOGGER.debug(" {}", trace); } } } else { @@ -351,7 +351,7 @@ protected void initialize() { protected void shutdown() { int hooks = cleanup.size(); if (hooks > 0) { - LOGGER.debug("{} shutdown hooks", hooks); + LOGGER.trace("{} shutdown hooks", hooks); for (Runnable hook : cleanup) { try { hook.run(); @@ -363,7 +363,7 @@ protected void shutdown() { hooks = shutdown.size(); if (hooks > 0) { try { - LOGGER.debug("{} shutdown executor services", hooks); + LOGGER.trace("{} shutdown executor services", hooks); ExecutorsUtil.shutdownExecutorGracefully(1000, shutdown.toArray(new ExecutorService[hooks])); } catch (RuntimeException ex) { LOGGER.warn("shutdown failed!", ex); diff --git a/element-connector/src/test/java/org/eclipse/californium/elements/util/Asn1DerDecoderTest.java b/element-connector/src/test/java/org/eclipse/californium/elements/util/Asn1DerDecoderTest.java index 769a3a3f7a..9163c3d198 100644 --- a/element-connector/src/test/java/org/eclipse/californium/elements/util/Asn1DerDecoderTest.java +++ b/element-connector/src/test/java/org/eclipse/californium/elements/util/Asn1DerDecoderTest.java @@ -39,12 +39,15 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Verifies behavior of {@link Asn1DerDecoder}. */ @Category(Small.class) public class Asn1DerDecoderTest { + private static final Logger LOGGER = LoggerFactory.getLogger(Asn1DerDecoderTest.class); /** * DH subject public key, ASN.1 DER / Base64 encoded. @@ -331,9 +334,9 @@ public void testBrokenEcdsa() throws IOException, GeneralSecurityException { boolean valid = signature.verify(ghost); if (valid) { broken = true; - System.err.println(info + " is vulnerable for ECDSA R := 0, CVE-2022-21449!"); + LOGGER.warn("{} is vulnerable for ECDSA R := 0, CVE-2022-21449!", info); } else { - System.out.println(info + " is not vulnerable for ECDSA R := 0, CVE-2022-21449!"); + LOGGER.info("{} is not vulnerable for ECDSA R := 0, CVE-2022-21449!", info); } try { Asn1DerDecoder.checkEcDsaSignature(ghost, keys.getPublicKey()); @@ -358,14 +361,14 @@ public void testBrokenEcdsa() throws IOException, GeneralSecurityException { try { valid = signature.verify(ghost2); } catch (SignatureException e) { - System.out.println(info + ", possible: " + e.getMessage()); + LOGGER.info("{}, possible: {}", info, e.getMessage()); valid = false; } if (valid) { broken = true; - System.err.println(info + " is vulnerable for ECDSA R := N, CVE-2022-21449!"); + LOGGER.warn("{} is vulnerable for ECDSA R := N, CVE-2022-21449!", info); } else { - System.out.println(info + " is not vulnerable for ECDSA R := N, CVE-2022-21449!"); + LOGGER.info("{} is not vulnerable for ECDSA R := N, CVE-2022-21449!", info); } try { Asn1DerDecoder.checkEcDsaSignature(ghost2, keys.getPublicKey()); diff --git a/element-connector/src/test/java/org/eclipse/californium/elements/util/PersistentComponentUtilTest.java b/element-connector/src/test/java/org/eclipse/californium/elements/util/PersistentComponentUtilTest.java index ae0f220f3c..5691453ff7 100644 --- a/element-connector/src/test/java/org/eclipse/californium/elements/util/PersistentComponentUtilTest.java +++ b/element-connector/src/test/java/org/eclipse/californium/elements/util/PersistentComponentUtilTest.java @@ -80,6 +80,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); @@ -92,6 +93,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())); @@ -117,6 +119,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); @@ -130,6 +133,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())); @@ -143,6 +147,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())); @@ -182,7 +187,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/element-connector/src/test/java/org/eclipse/californium/elements/util/TestConditionTools.java b/element-connector/src/test/java/org/eclipse/californium/elements/util/TestConditionTools.java index 9a0011f3a3..03905c64d2 100644 --- a/element-connector/src/test/java/org/eclipse/californium/elements/util/TestConditionTools.java +++ b/element-connector/src/test/java/org/eclipse/californium/elements/util/TestConditionTools.java @@ -17,6 +17,7 @@ package org.eclipse.californium.elements.util; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import java.util.concurrent.TimeUnit; @@ -36,7 +37,7 @@ private TestConditionTools() { /** * Wait for condition to come {@code true}. - * + *

* 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 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 listOut = handshaker.takeDeferredApplicationData(); @@ -712,7 +729,9 @@ public void handshakeFailed(Handshaker handshaker, Throwable error) { // failure after established (last FINISH), // but before completed (first data) if (error instanceof ConnectionEvictedException) { - LOGGER.debug("Handshake with [{}] never get APPLICATION_DATA", peerAddress, error); + LOGGER.debug("Handshake with [{}] never get APPLICATION_DATA", peerAddress); + } else if (error instanceof MissingApplicationAuthorizationException) { + LOGGER.debug("Handshake with [{}] never authorized by application", peerAddress); } else { LOGGER.warn("Handshake with [{}] failed after session was established!", peerAddress, error); @@ -840,20 +859,13 @@ private final void contextEstablished(Handshaker handshaker) { try { final Connection connection = handshaker.getConnection(); connectionStore.putEstablishedSession(connection); - final SerialExecutor serialExecutor = connection.getExecutor(); List listOut = handshaker.takeDeferredApplicationData(); if (!listOut.isEmpty()) { LOGGER.trace("DTLS context with [{}] established, now process deferred {} outgoing messages", handshaker.getPeerAddress(), listOut.size()); for (RawData message : listOut) { final RawData rawData = message; - serialExecutor.execute(new Runnable() { - - @Override - public void run() { - sendMessage(rawData, connection); - } - }); + connection.execute(() -> sendMessage(rawData, connection)); } } List listIn = handshaker.takeDeferredRecordsOfNextEpoch(); @@ -862,13 +874,7 @@ public void run() { handshaker.getPeerAddress(), listIn.size()); for (Record message : listIn) { final Record record = message; - serialExecutor.execute(new Runnable() { - - @Override - public void run() { - processRecord(record, connection); - } - }); + connection.execute(() -> processRecord(record, connection)); } } } catch (RejectedExecutionException ex) { @@ -893,57 +899,65 @@ private long calculateRecentHandshakeExpires() { /** * Cleanup recent handshakes. - * + *

* 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 iterator = recentHandshakes.iterator(); - while (running.get()) { - if (!iterator.hasNext()) { - break; + * @return number of remove recent handshakes. {@code -1}, if execution is + * already pending. + * @since 4.0 (added return value) + */ + protected int cleanupRecentHandshakes(int calls) { + int count = -1; + if (recentHandshakesCleanup.compareAndSet(false, true)) { + count = 0; + long time = ClockUtil.nanoRealtime(); + int size = recentHandshakesCounter.get(); + boolean full = (calls % 8) == 0; + String qualifier = full ? " (full)" : ""; + try { + boolean started = false; + int loop = 0; + int log = Math.max(10000, size / 5); + long expires = calculateRecentHandshakeExpires(); + Iterator iterator = recentHandshakes.iterator(); + while (running.get()) { + if (!iterator.hasNext()) { + break; + } + Connection connection = iterator.next(); + if ((loop++ % log) == 0) { + started = true; + LOGGER.trace("{} recent handshakes, cleaning up {} - {}", size, count, + connection.getConnectionId()); + } + Long startNanos = connection.getStartNanos(); + if (startNanos == null || (expires - startNanos) >= 0) { + connection.startByClientHello(null); + size = recentHandshakesCounter.decrementAndGet(); + iterator.remove(); + ++count; + } else if (!full) { + break; + } } - Connection connection = iterator.next(); - if ((loop++ % log) == 0) { - started = true; - LOGGER.trace("{} recent handshakes, cleaning up {} - {}", size, count, - connection.getConnectionId()); - } - Long startNanos = connection.getStartNanos(); - if (startNanos == null || (expires - startNanos) >= 0) { - connection.startByClientHello(null); - size = recentHandshakesCounter.decrementAndGet(); - iterator.remove(); - ++count; - } else if (!full) { - break; + if (started) { + time = ClockUtil.nanoRealtime() - time; + LOGGER.debug("{} left recent handshakes, {} removed in {}ms{}!", size, count, + TimeUnit.NANOSECONDS.toMillis(time), qualifier); } - } - if (started) { + } catch (Throwable ex) { time = ClockUtil.nanoRealtime() - time; - LOGGER.debug("{} left recent handshakes, {} removed in {}ms{}!", size, count, - TimeUnit.NANOSECONDS.toMillis(time), qualifier); + LOGGER.error("{} recent handshakes, cleanup failed after {} in {}ms{}!", size, count, + TimeUnit.NANOSECONDS.toMillis(time), qualifier, ex); + } finally { + recentHandshakesCleanup.set(false); + } + if (running.get()) { + connectionStore.shrink(calls, running); } - } catch (Throwable ex) { - time = ClockUtil.nanoRealtime() - time; - LOGGER.error("{} recent handshakes, cleanup failed after {} in {}ms{}!", size, count, - TimeUnit.NANOSECONDS.toMillis(time), qualifier, ex); - } - if (running.get() ) { - connectionStore.shrink(calls, running); } + return count; } /** @@ -986,7 +1000,7 @@ public final void setExecutor(ProtocolScheduledExecutorService executor) { } } } - if (change ) { + if (change) { connectionStore.setExecutor(null); } } @@ -1003,14 +1017,7 @@ public final void setExecutor(ProtocolScheduledExecutorService executor) { public final void close(InetSocketAddress peerAddress) { final Connection connection = getConnection(peerAddress, null, false); if (connection != null && connection.hasEstablishedDtlsContext()) { - SerialExecutor serialExecutor = connection.getExecutor(); - serialExecutor.execute(new Runnable() { - - @Override - public void run() { - closeConnection(connection); - } - }); + connection.execute(() -> closeConnection(connection)); } } @@ -1122,8 +1129,8 @@ protected void init(InetSocketAddress bindAddress, DatagramSocket socket, Intege if (lengthCode != null) { // reduce inbound buffer size accordingly inboundDatagramBufferSize = lengthCode.length() + MAX_CIPHERTEXT_EXPANSION - // 12 bytes DTLS handshake message headers, - // 13 bytes DTLS record headers + // 12 bytes DTLS handshake message headers, + // 13 bytes DTLS record headers + Record.DTLS_HANDSHAKE_HEADER_LENGTH; } @@ -1185,8 +1192,8 @@ protected void init(InetSocketAddress bindAddress, DatagramSocket socket, Intege if (executorService == null) { int threadCount = config.get(DtlsConfig.DTLS_CONNECTOR_THREAD_COUNT); - executorService = ExecutorsUtil.newProtocolScheduledThreadPool(threadCount, new DaemonThreadFactory( - "DTLS-Worker-" + addr + "#", NamedThreadFactory.SCANDIUM_THREAD_GROUP)); //$NON-NLS-1$ + executorService = ExecutorsUtil.newProtocolScheduledThreadPool(threadCount, + new DaemonThreadFactory("DTLS-Worker-" + addr + "#", NamedThreadFactory.SCANDIUM_THREAD_GROUP)); //$NON-NLS-1$ connectionStore.setExecutor(executorService); this.hasInternalExecutor = true; } @@ -1271,11 +1278,11 @@ public void work() throws Exception { TimeUnit.MILLISECONDS); // check either for interval or DtlsHealthExtended long intervalMillis = healthStatusIntervalMillis; - // schedule more frequent updates for updating the number of - // connections in the DtlsHealth - if (healthStatusIntervalMillis == 0 || healthStatusIntervalMillis > 2000) { - intervalMillis = 2000; - } + // schedule more frequent updates for updating + // the number of connections in the DtlsHealth + if (healthStatusIntervalMillis == 0 || healthStatusIntervalMillis > 2000) { + intervalMillis = 2000; + } if (intervalMillis > 0) { statusLogger = executorService.scheduleBackgroundAtFixedRate(new Runnable() { @@ -1356,6 +1363,7 @@ public final void forceResumeAllSessions() { */ public final void clearConnectionState() { connectionStore.clear(); + clearRecentHandshakes(); } private final DatagramSocket getSocket() { @@ -1427,12 +1435,11 @@ public void stop() { * Destroys the connector. *

* 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. *

*/ @Override @@ -1477,10 +1484,6 @@ public int load(InputStream in, long deltaNanos) throws IOException { return res; } - public boolean restoreConnection(Connection connection) { - return connectionStore.restore(connection); - } - /** * Start to terminate connections related to the provided principals. * @@ -1592,22 +1595,18 @@ private void nextForEach(final Iterator iterator, final Predicate { + boolean done = true; + try { + if (!result.isStopped() && !handler.test(next)) { + done = false; + nextForEach(iterator, handler, result); + } + } catch (Exception exception) { + result.failed(exception); + } finally { + if (done) { + result.done(); } } }); @@ -2284,7 +2283,7 @@ private boolean updateConnectionAddress(Record record, Connection connection) { final Handshaker ongoingHandshake = connection.getOngoingHandshake(); if (ongoingHandshake != null) { // the handshake has been completed successfully - ongoingHandshake.handshakeCompleted(); + ongoingHandshake.handshakeCompletedWithApplicationData(); } if (connectionListener != null) { if (connectionListener.onConnectionUpdatesSequenceNumbers(connection, false)) { @@ -2422,13 +2421,9 @@ private void processNewClientHello(final Record record) { } if (connection != null) { try { - connection.getExecutor().execute(new Runnable() { - - @Override - public void run() { - if (running.get()) { - processClientHello(record, connection); - } + connection.execute(() -> { + if (running.get()) { + processClientHello(record, connection); } }); } catch (RejectedExecutionException e) { @@ -2464,7 +2459,8 @@ public void run() { * @param peerAddress other peer's address * @param clientHello received new client hello * @param executor executor for the connection - * @return connection to process the new client hello + * @return connection to process the new client hello, or {@code null}, to + * drop the client hello. * @since 3.5 */ private Connection getConnectionForNewClientHello(InetSocketAddress peerAddress, ClientHello clientHello, @@ -2489,13 +2485,9 @@ private Connection getConnectionForNewClientHello(InetSocketAddress peerAddress, final DtlsException cause = new DtlsException( "Received new CLIENT_HELLO from " + StringUtil.toDisplayString(peerAddress)); try { - connection.getExecutor().execute(new Runnable() { - - @Override - public void run() { - if (running.get()) { - handshaker.handshakeFailed(cause); - } + connection.execute(() -> { + if (running.get()) { + handshaker.handshakeFailed(cause); } }); } catch (RejectedExecutionException ex) { @@ -2558,8 +2550,8 @@ private void processClientHello(Record record, Connection connection) { // At this point the client has demonstrated reachability by // completing a cookie exchange. So start a new handshake // (see section 4.2.8 of RFC 6347 (DTLS 1.2)) - handshaker = new ServerHandshaker(record.getSequenceNumber(), clientHello.getMessageSeq(), this, executorService, - connection, config); + handshaker = new ServerHandshaker(record.getSequenceNumber(), clientHello.getMessageSeq(), this, + executorService, connection, config); } initializeHandshaker(handshaker); handshaker.processMessage(record); @@ -2869,7 +2861,8 @@ public void send(final RawData message) { if (create) { create = !getEffectiveHandshakeMode(message).equals(DtlsEndpointContext.HANDSHAKE_MODE_NONE); } - connection = getConnection(message.getInetSocketAddress(), null, create); + final ConnectionId cid = create ? null : getConnectionIdFromEndpointContext(message.getEndpointContext()); + connection = getConnection(message.getInetSocketAddress(), cid, create); if (connection == null) { if (create) { error = new IllegalStateException("connection store is exhausted!"); @@ -3058,7 +3051,8 @@ private void sendMessageWithoutSession(final RawData message, final Connection c String hostname = message.getEndpointContext().getVirtualHost(); // no session with peer established nor handshaker started yet, // create new empty session & start handshake - ClientHandshaker clientHandshaker = new ClientHandshaker(hostname, this, executorService, connection, config, false); + ClientHandshaker clientHandshaker = new ClientHandshaker(hostname, this, executorService, connection, + config, false); initializeHandshaker(clientHandshaker); message.onConnecting(); clientHandshaker.addApplicationDataForDeferredProcessing(message); @@ -3148,7 +3142,8 @@ private void sendMessageWithSession(final RawData message, final Connection conn // https://tools.ietf.org/html/rfc5246#section-7.4.1.3 newHandshaker = new ClientHandshaker(hostname, this, executorService, connection, config, probing); } else { - newHandshaker = new ResumingClientHandshaker(resume, this, executorService, connection, config, probing); + newHandshaker = new ResumingClientHandshaker(resume, this, executorService, connection, config, + probing); } if (probing) { // Only reset the resumption trigger, but keep the session @@ -3722,4 +3717,64 @@ public String toString() { return getProtocol() + "-" + StringUtil.toString(getAddress()); } + @Override + public Future authorize(final EndpointContext context, final ApplicationPrincipal principal) { + if (principal == null) { + throw new NullPointerException("Principal must not be null!"); + } + final ConnectionId cid = getConnectionIdFromEndpointContext(context); + final Connection connection = getConnection(context.getPeerAddress(), cid, false); + final CompletableFuture future = new CompletableFuture<>(); + if (connection != null) { + connection.execute(() -> { + Principal identity = connection.getEstablishedPeerIdentity(); + if (identity == null) { + connection.setEstablishedPeerIdentity(principal); + connectionStore.putEstablishedSession(connection); + } + Handshaker handshaker = connection.getOngoingHandshake(); + if (handshaker != null) { + handshaker.handshakeCompleted(); + } + future.complete(identity == null || principal.equals(identity)); + }); + } else { + future.complete(false); + } + return future; + } + + @Override + public Future rejectAuthorization(EndpointContext context) { + final ConnectionId cid = getConnectionIdFromEndpointContext(context); + final Connection connection = getConnection(context.getPeerAddress(), cid, false); + final CompletableFuture future = new CompletableFuture<>(); + if (connection != null) { + connection.execute(() -> { + if (datagramFilter != null) { + datagramFilter.onApplicationAuthorizationRejected(connection); + } + final Handshaker handshaker = connection.getOngoingHandshake(); + if (handshaker != null) { + handshaker.noApplicationAuthorization(true); + } else { + if (health != null) { + health.applicationAuthorizationRejected(true); + } + connectionStore.remove(connection, true); + } + future.complete(null); + }); + } else { + future.complete(null); + } + return future; + } + + private ConnectionId getConnectionIdFromEndpointContext(EndpointContext context) { + if (context == null) { + throw new NullPointerException("EndpointContext must not be null!"); + } + return ConnectionId.create(context.get(DtlsEndpointContext.KEY_READ_CONNECTION_ID)); + } } diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/DatagramFilter.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/DatagramFilter.java index b90f2a6afc..d87fb1b81a 100644 --- a/scandium-core/src/main/java/org/eclipse/californium/scandium/DatagramFilter.java +++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/DatagramFilter.java @@ -76,4 +76,12 @@ public interface DatagramFilter { */ void onDrop(Record record); + /** + * Called, when the application rejected to authorize an anonymous client. + * + * @param connection rejected connection + * @since 4.0 + */ + void onApplicationAuthorizationRejected(Connection connection); + } diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsDatagramFilter.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsDatagramFilter.java index c9d87a4d42..cf34d3f05d 100644 --- a/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsDatagramFilter.java +++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/DtlsDatagramFilter.java @@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.californium.elements.config.Configuration; +import org.eclipse.californium.elements.util.ClockUtil; import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.dtls.Connection; import org.eclipse.californium.scandium.dtls.ContentType; @@ -26,9 +27,9 @@ import org.eclipse.californium.scandium.dtls.Record; /** - * Filter valid DTLS incoming datagrams. - * - * Use an advanced MAC error filter. + * Filter valid incoming DTLS datagrams. + *

+ * 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: + * *

 	 * 
 	 * 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.
-	 * 
+	 * 

* 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 parseValue(String value) { /** * DTLS session timeout. Currently not supported! - * + *

* 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 parseValue(String value) { "DTLS session timeout. Currently not supported.", 1L, TimeUnit.HOURS); /** * DTLS auto handshake timeout. - * + *

* 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 parseValue(String value) { * */ public static final IntegerDefinition DTLS_CONNECTION_ID_LENGTH = new IntegerDefinition( - MODULE + "CONNECTION_ID_LENGTH", - "DTLS connection ID length. default, -1 disables, 0 enables support without active use of CID.", 6, -1); + MODULE + "CONNECTION_ID_LENGTH", "DTLS connection ID length. default, -1 disables, " + + "0 enables support without active use of CID.", + 6, -1); /** * If {@link #DTLS_CONNECTION_ID_LENGTH} enables the use of a connection id, @@ -341,13 +344,13 @@ protected List parseValue(String value) { /** * Specify the additional initial DTLS retransmission timeout, when the * other peer is expected to perform ECC calculations. - * + *

* 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 parseValue(String value) { /** * Specify the number of DTLS retransmissions before the attempt to transmit * a flight in back-off mode. - * + *

* * 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 parseValue(String value) { * of next flight, not waiting for the last. */ public static final BooleanDefinition DTLS_USE_EARLY_STOP_RETRANSMISSION = new BooleanDefinition( - MODULE + "USE_EARLY_STOP_RETRANSMISSION", - "Stop retransmission on receiving the first message of the next flight, not waiting for the last message.", + MODULE + "USE_EARLY_STOP_RETRANSMISSION", "Stop retransmission on receiving the first message of the next " + + "flight, not waiting for the last message.", true); /** @@ -416,7 +419,7 @@ protected List parseValue(String value) { * Specify the maximum fragment length. * * @see RFC 6066, Section 4 + * "_blank">RFC 6066, Section 4 */ public static final EnumDefinition DTLS_MAX_FRAGMENT_LENGTH = new EnumDefinition<>( MODULE + "MAX_FRAGMENT_SIZE", "DTLS maximum fragment length (RFC 6066).", Length.values()); @@ -426,8 +429,8 @@ protected List parseValue(String value) { */ public static final IntegerDefinition DTLS_MAX_FRAGMENTED_HANDSHAKE_MESSAGE_LENGTH = new IntegerDefinition( MODULE + "MAX_FRAGMENTED_HANDSHAKE_MESSAGE_LENGTH", - "DTLS maximum length of reassembled fragmented handshake message.\n" + - "Must be large enough for used certificates.", + "DTLS maximum length of reassembled fragmented handshake message.\n" + + "Must be large enough for used certificates.", DEFAULT_MAX_FRAGMENTED_HANDSHAKE_MESSAGE_LENGTH, 64); /** @@ -440,7 +443,7 @@ protected List parseValue(String value) { */ public static final BooleanDefinition DTLS_USE_MULTI_HANDSHAKE_MESSAGE_RECORDS = new BooleanDefinition( MODULE + "USE_MULTI_HANDSHAKE_MESSAGE_RECORDS", - "Use multiple handshake messages in DTLS records.\nNot all libraries may have implemented this!"); + "Use multiple handshake messages in DTLS records.\n" + "Not all libraries may have implemented this!"); /** * Specify the client's certificate authentication mode. @@ -477,8 +480,8 @@ protected List parseValue(String value) { /** * Specify the MTU (Maximum Transmission Unit). - * - * Note: Californium is only able to detect the MTU of local network + *

+ * 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 parseValue(String value) { * @see #DTLS_MAX_TRANSMISSION_UNIT_LIMIT */ public static final IntegerDefinition DTLS_MAX_TRANSMISSION_UNIT = new IntegerDefinition( - MODULE + "MAX_TRANSMISSION_UNIT", "DTLS MTU (Maximum Transmission Unit).\nMust be used, if the MTU of the local network doesn't apply, e.g. if ip-tunnels are used.", null, 64); + MODULE + "MAX_TRANSMISSION_UNIT", "DTLS MTU (Maximum Transmission Unit).\n" + + "Must be used, if the MTU of the local network doesn't apply, " + "e.g. if ip-tunnels are used.", + null, 64); /** * Specify a MTU (Maximum Transmission Unit) limit for (link local) auto * detection. - * + *

* 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 parseValue(String value) { /** * Specify default handshake mode. - * + *

* 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 parseValue(String value) { */ public static final TimeDefinition DTLS_STALE_CONNECTION_THRESHOLD = new TimeDefinition( MODULE + "STALE_CONNECTION_THRESHOLD", - "DTLS threshold for stale connections. Connections will only get removed for new ones, "+ - "if at least for that threshold no messages are exchanged using that connection.", + "DTLS threshold for stale connections. Connections will only get removed for new ones, " + + "if at least for that threshold no messages are exchanged using that connection.", DEFAULT_STALE_CONNECTION_TRESHOLD_SECONDS, TimeUnit.SECONDS); /** @@ -561,8 +566,7 @@ protected List parseValue(String value) { * @since 3.5 */ public static final IntegerDefinition DTLS_MAX_PENDING_OUTBOUND_JOBS = new IntegerDefinition( - MODULE + "MAX_PENDING_OUTBOUND_JOBS", - "Maximum number of jobs for outbound DTLS messages.", + MODULE + "MAX_PENDING_OUTBOUND_JOBS", "Maximum number of jobs for outbound DTLS messages.", DEFAULT_MAX_PENDING_OUTBOUND_JOBS, 64); /** @@ -572,8 +576,7 @@ protected List parseValue(String value) { * @since 3.5 */ public static final IntegerDefinition DTLS_MAX_PENDING_INBOUND_JOBS = new IntegerDefinition( - MODULE + "MAX_PENDING_INBOUND_JOBS", - "Maximum number of jobs for inbound DTLS messages.", + MODULE + "MAX_PENDING_INBOUND_JOBS", "Maximum number of jobs for inbound DTLS messages.", DEFAULT_MAX_PENDING_INBOUND_JOBS, 64); /** * Specify the number of pending handshake result jobs that can be queued @@ -582,8 +585,7 @@ protected List parseValue(String value) { * @since 3.5 */ public static final IntegerDefinition DTLS_MAX_PENDING_HANDSHAKE_RESULT_JOBS = new IntegerDefinition( - MODULE + "MAX_PENDING_HANDSHAKE_RESULT_JOBS", - "Maximum number of jobs for DTLS handshake results.", + MODULE + "MAX_PENDING_HANDSHAKE_RESULT_JOBS", "Maximum number of jobs for DTLS handshake results.", DEFAULT_MAX_PENDING_HANDSHAKE_RESULT_JOBS, 64); /** @@ -597,8 +599,8 @@ protected List parseValue(String value) { */ public static final IntegerDefinition DTLS_MAX_DEFERRED_OUTBOUND_APPLICATION_MESSAGES = new IntegerDefinition( MODULE + "MAX_DEFERRED_OUTBOUND_APPLICATION_MESSAGES", - "DTLS maximum deferred outbound application messages.", - DEFAULT_MAX_DEFERRED_OUTBOUND_APPLICATION_MESSAGES, 0); + "DTLS maximum deferred outbound application messages.", DEFAULT_MAX_DEFERRED_OUTBOUND_APPLICATION_MESSAGES, + 0); /** * Specify maximum size of deferred processed incoming records. * @@ -656,7 +658,7 @@ protected List parseValue(String value) { * {@link org.eclipse.californium.elements.EndpointContext}. * * @see RFC 6066, Section 3 + * "_blank">RFC 6066, Section 3 */ public static final BooleanDefinition DTLS_USE_SERVER_NAME_INDICATION = new BooleanDefinition( MODULE + "USE_SERVER_NAME_INDICATION", "DTLS use server name indication.", false); @@ -665,7 +667,7 @@ protected List parseValue(String value) { * Defines the usage of the "extend master secret" extension. * * @see RFC - * 7627 + * 7627 */ public static final EnumDefinition DTLS_EXTENDED_MASTER_SECRET_MODE = new EnumDefinition<>( MODULE + "EXTENDED_MASTER_SECRET_MODE", "DTLS extended master secret mode.", @@ -695,7 +697,7 @@ protected List parseValue(String value) { /** * Use disabled window for anti replay filter. - * + *

* 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 parseValue(String value) { * to discard such records, this values defines a "disabled window", that * allows record to pass the filter, even if the records are too old for the * current receive window. - * + *

* 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 parseValue(String value) { /** * Only process newer records based on epoch/sequence_number. - * + *

* 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 parseValue(String value) { */ public static final BooleanDefinition DTLS_USE_NEWER_RECORD_FILTER = new BooleanDefinition( MODULE + "USE_NEWER_FILTER", - "DTLS use newer record filter.\n" - + "Drop reordered records in order to protect from delay attacks,\n" + "DTLS use newer record filter.\n" + "Drop reordered records in order to protect from delay attacks,\n" + "if no other means, maybe on application level, are available.", false); /** * Use truncated certificate paths for client's certificate message. - * + *

* Truncate certificate path according the received certificate authorities * in the {@link CertificateRequest} for the client's * {@link CertificateMessage}. @@ -758,7 +759,7 @@ protected List parseValue(String value) { /** * Use truncated certificate paths for validation. - * + *

* Truncate certificate path according the available trusted certificates * before validation. */ @@ -793,18 +794,18 @@ protected List parseValue(String value) { */ public static final EnumListDefinition DTLS_PRESELECTED_CIPHER_SUITES = new EnumListDefinition<>( MODULE + "PRESELECTED_CIPHER_SUITES", - "List of preselected DTLS cipher-suites.\n" + - "If not recommended cipher suites are intended to be used, switch off DTLS_RECOMMENDED_CIPHER_SUITES_ONLY.\n" + - "The supported cipher suites are evaluated at runtime and may differ from the ones when creating this properties file.", + "List of preselected DTLS cipher-suites.\n" + + "If not recommended cipher suites are intended to be used, switch off DTLS_RECOMMENDED_CIPHER_SUITES_ONLY.\n" + + "The supported cipher suites are evaluated at runtime and may differ from the ones when creating this properties file.", CipherSuite.getCipherSuites(false, false)); /** * Select {@link CipherSuite}s. */ public static final EnumListDefinition DTLS_CIPHER_SUITES = new EnumListDefinition<>( MODULE + "CIPHER_SUITES", - "List of DTLS cipher-suites.\n" + - "If not recommended cipher suites are intended to be used, switch off DTLS_RECOMMENDED_CIPHER_SUITES_ONLY.\n" + - "The supported cipher suites are evaluated at runtime and may differ from the ones when creating this properties file.", + "List of DTLS cipher-suites.\n" + + "If not recommended cipher suites are intended to be used, switch off DTLS_RECOMMENDED_CIPHER_SUITES_ONLY.\n" + + "The supported cipher suites are evaluated at runtime and may differ from the ones when creating this properties file.", null, 1, CipherSuite.getCipherSuites(false, true)); /** * Select curves ({@link SupportedGroup}s). @@ -823,8 +824,8 @@ protected List parseValue(String value) { */ public static final EnumListDefinition DTLS_CERTIFICATE_KEY_ALGORITHMS = new EnumListDefinition<>( MODULE + "CERTIFICATE_KEY_ALGORITHMS", - "List of DTLS certificate key algorithms.\n" + - "On the client side used to select the default cipher-suites, on the server side to negotiate the client's certificate.", + "List of DTLS certificate key algorithms.\n" + + "On the client side used to select the default cipher-suites, on the server side to negotiate the client's certificate.", new CertificateKeyAlgorithm[] { CertificateKeyAlgorithm.EC, CipherSuite.CertificateKeyAlgorithm.RSA }); /** @@ -846,13 +847,11 @@ protected List parseValue(String value) { */ public static final BooleanDefinition DTLS_REMOVE_STALE_DOUBLE_PRINCIPALS = new BooleanDefinition( MODULE + "REMOVE_STALE_DOUBLE_PRINCIPALS", - "Remove stale double principals.\n" + - "Requires unique principals and a read-write-lock connection store.", - false); + "Remove stale double principals.\n" + "Requires unique principals.", false); /** * Quiet time for DTLS MAC error filter. - * + *

* 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 parseValue(String value) { * dropped before decryption in order to protect the CPU. This dropping last * for this quiet period and afterwards, the MAC error counter is reseted as * it is reseted, if no MAC error occurs for that time. - * + *

* A value of {@code 0} disables the MAC error filter. - * + *

* tn time n, c=n counter with value * *

@@ -901,7 +900,7 @@ protected List parseValue(String value) {
 
 	/**
 	 * Threshold for DTLS MAC error filter.
-	 * 
+	 * 

* 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 parseValue(String value) { /** * Specify the secure renegotiation mode. - * + *

* 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 parseValue(String value) { /** * Support key material export. * - * @see RFC 5705 + * @see RFC + * 5705 * * @since 3.10 */ public static final BooleanDefinition DTLS_SUPPORT_KEY_MATERIAL_EXPORT = new BooleanDefinition( MODULE + "SUPPORT_KEY_MATERIAL_EXPORT", "Support key material export according RFC5705.", false); + /** + * Application authorization timeout for anonymous clients. + *

+ * 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 keyAlgorithms = new ArrayList<>(); - if (config.getConfiguration().get(DtlsConfig.DTLS_ROLE) == DtlsRole.CLIENT_ONLY) { + if (config.get(DtlsConfig.DTLS_ROLE) == DtlsRole.CLIENT_ONLY) { if (config.supportedCertificatekeyAlgorithms.isEmpty()) { // clients may operate anonymous. therefore ensure, // EC is added in order to comply to RFC7252 diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/CertificateVerificationResult.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/CertificateVerificationResult.java index 26a20e448c..30039cc26d 100644 --- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/CertificateVerificationResult.java +++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/CertificateVerificationResult.java @@ -92,14 +92,11 @@ public CertificateVerificationResult(ConnectionId cid, PublicKey publicKey, Obje * * @param cid connection id * @param exception handshake exception. - * @param customArgument custom argument. May be {@code null}. Passed to - * {@link ApplicationLevelInfoSupplier} by the - * {@link Handshaker}, if a - * {@link ApplicationLevelInfoSupplier} is available. * @throws NullPointerException if cid or exception is {@code null}. + * @since 4.0 (removed customArgument) */ - public CertificateVerificationResult(ConnectionId cid, HandshakeException exception, Object customArgument) { - super(cid, customArgument); + public CertificateVerificationResult(ConnectionId cid, HandshakeException exception) { + super(cid, null); if (exception == null) { throw new NullPointerException("exception must not be null!"); } diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/Connection.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/Connection.java index 83ca66513f..083216b62c 100644 --- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/Connection.java +++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/Connection.java @@ -45,6 +45,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -61,6 +62,7 @@ import org.eclipse.californium.elements.util.SerializationUtil; import org.eclipse.californium.elements.util.StringUtil; import org.eclipse.californium.scandium.ConnectionListener; +import org.eclipse.californium.scandium.CookieGenerator; import org.eclipse.californium.scandium.DatagramFilter; import org.eclipse.californium.scandium.util.SecretUtil; import org.slf4j.Logger; @@ -68,7 +70,7 @@ /** * Information about the DTLS connection to a peer. - * + *

* Contains status information regarding *

    *
  • a potentially ongoing handshake with the peer
  • @@ -87,10 +89,12 @@ public final class Connection { /** * Identifier of the Client Hello used to start the handshake. - * - * Maybe {@code null}, for client side connections. - * - * Note: used outside of the serial-execution! + *

    + * 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 records = takeDeferredRecordsOfNextEpoch(); if (deferredIncomingRecordsSize > 0) { throw new HandshakeException( @@ -860,16 +872,10 @@ private void processNextMessages(Record record) throws HandshakeException { new AlertMessage(AlertLevel.FATAL, AlertDescription.INTERNAL_ERROR)); } for (Record deferredRecord : records) { - if (serialExecutor != null && !serialExecutor.isShutdown()) { + if (connection.isExecuting()) { try { final Record dRecord = deferredRecord; - serialExecutor.execute(new Runnable() { - - @Override - public void run() { - recordLayer.processRecord(dRecord, connection); - } - }); + connection.execute(() -> recordLayer.processRecord(dRecord, connection), false); continue; } catch (RejectedExecutionException ex) { LOGGER.debug("Execution rejected while processing record [type: {}, peer: {}]", @@ -1677,6 +1683,17 @@ public final ServerNames getServerNames() { return sniEnabled ? getSession().getServerNames() : null; } + /** + * Gets other peer's identity. + * + * @return other peer's identity, or {@code null}, if not available. + * @since 4.0 + */ + public final Principal getPeerIdentity() { + DTLSSession session = getSession(); + return session == null ? null : session.getPeerIdentity(); + } + /** * Gets the session this handshaker is used to establish. * @@ -1914,7 +1931,21 @@ public void completePendingFlight() { * @see #sendFlight(DTLSFlight) */ public void sendLastFlight(DTLSFlight flight) { - timeoutLastFlight = timer.schedule(new TimeoutCompletedTask(), nanosExpireTimeout, TimeUnit.NANOSECONDS); + Runnable task = connection.createTask(() -> { + Principal peerIdentity = getPeerIdentity(); + if (peerIdentity == null) { + // no peer, no data => remove connection + noApplicationAuthorization(false); + } else if (recordLayer.isRunning()) { + handshakeCompleted(); + } + }, false); + long timeout = nanosExpireTimeout; + if (applicationAuthorizationTimeout > 0 && getPeerIdentity() == null) { + // use application authorization timeout to wait for data. + timeout = TimeUnit.MILLISECONDS.toNanos(applicationAuthorizationTimeout); + } + timeoutLastFlight = timer.schedule(task, timeout, TimeUnit.NANOSECONDS); flight.setRetransmissionNeeded(false); sendFlight(flight); } @@ -1925,7 +1956,7 @@ public void sendLastFlight(DTLSFlight flight) { * @param flight flight to send * @see #sendFlight(DTLSFlight) */ - public void sendFlight(DTLSFlight flight) { + public void sendFlight(final DTLSFlight flight) { completePendingFlight(); try { int timeout = retransmissionTimeout; @@ -1951,7 +1982,7 @@ public void sendFlight(DTLSFlight flight) { recordLayer.sendFlight(datagrams); pendingFlight.set(flight); if (flight.isRetransmissionNeeded()) { - retransmitFlight = new TimeoutPeerTask(flight); + retransmitFlight = connection.createTask(() -> handleTimeout(flight), true); flight.scheduleRetransmission(timer, retransmitFlight); } int effectiveMessageSize = flight.getEffectiveMaxMessageSize(); @@ -2086,79 +2117,6 @@ private void handleTimeout(DTLSFlight flight) { } } - /** - * Peer related task for executing in serial executor. - * - * @since 2.4 - */ - 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; - /** - * Create peer 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() { - final SerialExecutor serialExecutor = connection.getExecutor(); - try { - serialExecutor.execute(task); - } catch (RejectedExecutionException e) { - LOGGER.debug("Execution rejected while execute task of peer: {}", connection.getPeerAddress(), e); - if (force) { - task.run(); - } - } - } - } - - /** - * Peer task calling the {@link #handleTimeout(DTLSFlight)}. - * - * @since 2.4 - */ - private class TimeoutPeerTask extends ConnectionTask { - - private TimeoutPeerTask(final DTLSFlight flight) { - super(new Runnable() { - @Override - public void run() { - handleTimeout(flight); - } - }, true); - } - } - - private class TimeoutCompletedTask extends ConnectionTask { - - private TimeoutCompletedTask() { - super(new Runnable() { - @Override - public void run() { - if (recordLayer.isRunning()) { - handshakeCompleted(); - } - } - }, false); - } - } - /** * Adds a listener to the list of listeners to be notified * about session life cycle events. @@ -2218,6 +2176,33 @@ protected final void contextEstablished() throws HandshakeException { } } + /** + * Report handshake completed by application data. + *

    + * 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 extensibleClientIdentity = (ExtensiblePrincipal) 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: - *

    *
      *
    • The store's remaining capacity is greater than zero.
    • *
    • The store contains at least one stale connection, i.e. a @@ -72,13 +70,10 @@ * key, one with the session id as key, and one with the principal as key. In * addition to that the store keeps a doubly-linked list of the connections in * update-time order. - *

      *

      * 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. - *

      * - * @since 4.0 (Rename InMemoryReadWriteLockConnectionStore into InMemoryConnectionStore) + * @since 4.0 (Rename InMemoryReadWriteLockConnectionStore into + * InMemoryConnectionStore) */ public class InMemoryConnectionStore implements ConnectionStore { private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryConnectionStore.class); - private static final FilteredLogger WARN_FILTER = new FilteredLogger(LOGGER.getName(), 3, TimeUnit.SECONDS.toNanos(10)); + private static final FilteredLogger WARN_FILTER = new FilteredLogger(LOGGER.getName(), 3, + TimeUnit.SECONDS.toNanos(10)); // extra cid bytes additionally to required bytes for small capacity. private static final int DEFAULT_SMALL_EXTRA_CID_LENGTH = 2; @@ -132,8 +128,7 @@ public class InMemoryConnectionStore implements ConnectionStore { * @param uniquePrincipals {@code true}, to limit stale connections by * unique principals, {@code false}, if not. */ - public InMemoryConnectionStore(int capacity, long threshold, SessionStore sessionStore, - boolean uniquePrincipals) { + public InMemoryConnectionStore(int capacity, long threshold, SessionStore sessionStore, boolean uniquePrincipals) { this.connections = new LeastRecentlyUpdatedCache<>(capacity, threshold, TimeUnit.SECONDS); this.connectionsByAddress = new ConcurrentHashMap<>(); this.connectionsByPrincipal = uniquePrincipals ? new ConcurrentHashMap() : null; @@ -148,31 +143,24 @@ public InMemoryConnectionStore(int capacity, long threshold, SessionStore sessio @Override public void onEviction(final Connection staleConnection) { - Runnable remove = new Runnable() { - - @Override - public void run() { - Handshaker handshaker = staleConnection.getOngoingHandshake(); - if (handshaker != null) { - handshaker.handshakeFailed(new ConnectionEvictedException("Evicted!")); - } - synchronized (InMemoryConnectionStore.this) { - removeByAddressConnections(staleConnection); - removeByEstablishedSessions(staleConnection.getEstablishedSessionIdentifier(), - staleConnection); - removeByPrincipal(staleConnection.getEstablishedPeerIdentity(), staleConnection); - ConnectionListener listener = connectionListener; - if (listener != null) { - listener.onConnectionRemoved(staleConnection); - } + staleConnection.execute(() -> { + Handshaker handshaker = staleConnection.getOngoingHandshake(); + if (handshaker != null) { + handshaker.handshakeFailed(new ConnectionEvictedException("Evicted!")); + } + connections.writeLock().lock(); + try { + removeByAddressConnections(staleConnection); + removeByEstablishedSessions(staleConnection.getEstablishedSessionIdentifier(), staleConnection); + removeByPrincipal(staleConnection.getEstablishedPeerIdentity(), staleConnection); + ConnectionListener listener = connectionListener; + if (listener != null) { + listener.onConnectionRemoved(staleConnection); } + } finally { + connections.writeLock().unlock(); } - }; - if (staleConnection.isExecuting()) { - staleConnection.getExecutor().execute(remove); - } else { - remove.run(); - } + }); } }); @@ -284,6 +272,7 @@ public boolean put(final Connection connection) { connections.writeLock().lock(); try { if (connections.put(connectionId, connection)) { + connection.updateLastMessageNanos(); if (LOGGER.isTraceEnabled()) { LOGGER.trace("{}connection: add {} (size {})", tag, connection, connections.size(), new Throwable("connection added!")); @@ -292,9 +281,7 @@ public boolean put(final Connection connection) { } addToAddressConnections(connection); if (session != null) { - if (session.getPeerIdentity() != null) { - addToPrincipalsConnections(session.getPeerIdentity(), connection, false); - } + addToPrincipalsConnections(session.getPeerIdentity(), connection, false); addToEstablishedConnections(session.getSessionIdentifier(), connection); } success = true; @@ -321,7 +308,7 @@ public boolean update(final Connection connection, InetSocketAddress newPeerAddr connections.writeLock().lock(); try { if (connections.update(connection.getConnectionId()) != null) { - connection.refreshAutoResumptionTime(); + connection.updateLastMessageNanos(); if (newPeerAddress == null) { LOGGER.debug("{}connection: {} updated usage!", tag, connection.getConnectionId()); } else if (!connection.equalsPeerAddress(newPeerAddress)) { @@ -369,12 +356,8 @@ public void putEstablishedSession(Connection connection) { if (principal != null || hasSessionId) { connections.writeLock().lock(); try { - if (principal != null) { - addToPrincipalsConnections(principal, connection, false); - } - if (hasSessionId) { - addToEstablishedConnections(sessionId, connection); - } + addToPrincipalsConnections(principal, connection, false); + addToEstablishedConnections(sessionId, connection); } finally { connections.writeLock().unlock(); } @@ -443,7 +426,9 @@ private Connection findLocally(final SessionId id) { } else { LOGGER.warn("{}connection {} lost session {}!", tag, connection.getConnectionId(), id); } - connections.update(connection.getConnectionId()); + if (connections.update(connection.getConnectionId()) != null) { + connection.updateLastMessageNanos(); + } } return connection; } @@ -500,19 +485,7 @@ private void shrink(AtomicBoolean running, boolean full) { } if (connection.isDouble()) { if (connections.isStale(connection.getConnectionId())) { - Runnable removeConnection = new Runnable() { - - @Override - public void run() { - LOGGER.trace("{}Remove connection from stale principals", tag); - remove(connection, false); - } - }; - if (connection.isExecuting()) { - connection.getExecutor().execute(removeConnection); - } else { - remove(connection, false); - } + connection.execute(() -> remove(connection, false)); ++count; } else if (!full) { break; @@ -644,27 +617,25 @@ private void addToAddressConnections(Connection connection) { if (peerAddress != null) { final Connection previous = connectionsByAddress.put(peerAddress, connection); if (previous != null && previous != connection) { - Runnable removeAddress = new Runnable() { - - @Override - public void run() { - if (previous.equalsPeerAddress(peerAddress)) { - previous.updatePeerAddress(null); - if (connectionsByEstablishedSession == null) { - if (!previous.expectCid()) { - remove(previous, false); - } - } - } - } - }; LOGGER.debug("{}connection: {} - {} added! {} removed from address.", tag, connection.getConnectionId(), StringUtil.toLog(peerAddress), previous.getConnectionId()); - if (previous.isExecuting()) { - previous.getExecutor().execute(removeAddress); - } else { - removeAddress.run(); - } + previous.execute(() -> { + if (previous.equalsPeerAddress(peerAddress)) { + previous.updatePeerAddress(null); + // remove anonymous previous connection from all stores. + // Connections without CID nor session ID are removed + // from internal stores. Connections without CID but + // with session ID are removed from internal stores, if + // an external session store is used to keep them for + // resumption. + boolean fullRemove = previous.getEstablishedPeerIdentity() == null; + boolean internalRemove = !previous.expectCid() && (connectionsByEstablishedSession == null + || previous.getEstablishedSessionIdentifier().isEmpty()); + if (fullRemove || internalRemove) { + remove(previous, fullRemove); + } + } + }); } else { LOGGER.debug("{}connection: {} - {} added!", tag, connection.getConnectionId(), StringUtil.toLog(peerAddress)); @@ -675,7 +646,7 @@ public void run() { } private boolean addToEstablishedConnections(SessionId sessionId, Connection connection) { - if (connectionsByEstablishedSession != null) { + if (connectionsByEstablishedSession != null && !sessionId.isEmpty()) { final Connection previous = connectionsByEstablishedSession.put(sessionId, connection); if (previous != null && previous != connection) { removePreviousConnection("session", previous); @@ -686,7 +657,12 @@ private boolean addToEstablishedConnections(SessionId sessionId, Connection conn } private boolean addToPrincipalsConnections(Principal principal, Connection connection, boolean removePrevious) { - if (connectionsByPrincipal != null) { + if (connectionsByPrincipal != null && principal != null) { + if (principal instanceof ExtensiblePrincipal) { + if (((ExtensiblePrincipal) principal).isAnonymous()) { + return false; + } + } final Connection previous = connectionsByPrincipal.put(principal, connection); if (previous != null && previous != connection) { if (removePrevious) { @@ -703,19 +679,10 @@ private boolean addToPrincipalsConnections(Principal principal, Connection conne } private void removePreviousConnection(final String cause, final Connection connection) { - Runnable removePreviousConnection = new Runnable() { - - @Override - public void run() { - LOGGER.debug("{}Remove connection from {}", tag, cause); - remove(connection, false); - } - }; - if (connection.isExecuting()) { - connection.getExecutor().execute(removePreviousConnection); - } else { - removePreviousConnection.run(); - } + connection.execute(() -> { + LOGGER.debug("{}Remove connection from {}", tag, cause); + remove(connection, false); + }, true); } @Override @@ -763,16 +730,16 @@ public int saveConnections(OutputStream out, long maxQuietPeriodInSeconds) throw long startNanos = ClockUtil.nanoRealtime(); boolean writeProgress = false; long progressNanos = startNanos; - Iterator> iterator = connections.timestampedIterator(); + Iterator iterator = connections.ascendingIterator(); while (iterator.hasNext()) { - Timestamped connection = iterator.next(); - long updateNanos = connection.getLastUpdate(); + Connection connection = iterator.next(); + long updateNanos = connection.getLastMessageNanos(); long quiet = TimeUnit.NANOSECONDS.toSeconds(startNanos - updateNanos); if (quiet > maxQuietPeriodInSeconds) { - LOGGER.trace("{}skip {} ts, {}s too quiet!", tag, updateNanos, quiet); + LOGGER.trace("{}skip {} ts, {}s too quiet! {}", tag, updateNanos, quiet, connection.getConnectionId()); } else { - LOGGER.trace("{}write {} ts, {}s ", tag, updateNanos, quiet); - if (connection.getValue().writeTo(writer)) { + LOGGER.trace("{}write {} ts, {}s {}", tag, updateNanos, quiet, connection.getConnectionId()); + if (connection.writeTo(writer)) { writer.writeTo(out); ++count; } else { @@ -816,8 +783,8 @@ public int loadConnections(InputStream in, long delta) throws IOException { restore = !connections.isStale(connection.getConnectionId()); } if (restore) { - LOGGER.trace("{}read {} ts, {}s", tag, lastUpdate, - TimeUnit.NANOSECONDS.toSeconds(startNanos - lastUpdate)); + LOGGER.trace("{}read {} ts, {}s {}", tag, lastUpdate, + TimeUnit.NANOSECONDS.toSeconds(startNanos - lastUpdate), connection.getConnectionId()); restore(connection); ++count; } @@ -857,10 +824,10 @@ public boolean restore(Connection connection) { try { if (connections.put(connectionId, connection, connection.getLastMessageNanos())) { if (LOGGER.isTraceEnabled()) { - LOGGER.trace("{}connection: add {} (size {})", tag, connection, connections.size(), - new Throwable("connection added!")); + LOGGER.trace("{}connection: restore {} (size {})", tag, connection, connections.size(), + new Throwable("connection restored!")); } else { - LOGGER.debug("{}connection: add {} (size {})", tag, connectionId, connections.size()); + LOGGER.debug("{}connection: restore {} (size {})", tag, connectionId, connections.size()); } addToAddressConnections(connection); if (!connection.isExecuting()) { diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/PskSecretResult.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/PskSecretResult.java index 709eef238b..42c38ad306 100644 --- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/PskSecretResult.java +++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/PskSecretResult.java @@ -64,12 +64,16 @@ public PskSecretResult(ConnectionId cid, PskPublicInformation pskIdentity, Secre * @param pskIdentity PSK identity * @param secret secret, {@code null}, if generation failed. Algorithm must * be "MAC" or "PSK". - * @param customArgument custom argument. May be {@code null}. Passed to + * @param customArgument custom argument. Must be {@code null}, if secret is + * {@code null}. May be {@code null}. Passed to * {@link ApplicationLevelInfoSupplier} by the - * {@link Handshaker}, if a - * {@link ApplicationLevelInfoSupplier} is available. - * @throws IllegalArgumentException if algorithm is neither "MAC" nor "PSK" + * {@link Handshaker}, if a {@link ApplicationLevelInfoSupplier} + * is available. + * @throws IllegalArgumentException if algorithm is neither "MAC" nor "PSK". + * Or when a custom argument is provided without a secret * @throws NullPointerException if cid or pskIdentity is {@code null} + * @since 4.0 (throws IllegalArgumentException, when a custom argument is + * provided without a secret) */ public PskSecretResult(ConnectionId cid, PskPublicInformation pskIdentity, SecretKey secret, Object customArgument) { @@ -84,6 +88,8 @@ public PskSecretResult(ConnectionId cid, PskPublicInformation pskIdentity, Secre "Secret must be either MAC for master secret, or PSK for secret key, but not " + algorithm + "!"); } + } else if (customArgument != null) { + throw new IllegalArgumentException("Custom argument must be null, if no secret is provided!"); } this.pskIdentity = pskIdentity; this.secret = secret; diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/cipher/DefaultCipherSuiteSelector.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/cipher/DefaultCipherSuiteSelector.java index 3463262d46..d7d1d27645 100644 --- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/cipher/DefaultCipherSuiteSelector.java +++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/cipher/DefaultCipherSuiteSelector.java @@ -123,10 +123,17 @@ protected boolean selectForCertificate(CipherSuiteParameters parameters, CipherS parameters.setCertificateMismatch(CertificateBasedMismatch.SERVER_CERT_TYPE); return false; } + CertificateType clientCertificateType = null; CertificateAuthenticationMode clientAuthentication = parameters.getClientAuthenticationMode(); - if (clientAuthentication.useCertificateRequest() && parameters.getClientCertTypes().isEmpty()) { - parameters.setCertificateMismatch(CertificateBasedMismatch.CLIENT_CERT_TYPE); - return false; + if (clientAuthentication.useCertificateRequest()) { + if (parameters.getClientCertTypes().isEmpty()) { + if (CertificateAuthenticationMode.NEEDED == clientAuthentication) { + parameters.setCertificateMismatch(CertificateBasedMismatch.CLIENT_CERT_TYPE); + return false; + } + } else { + clientCertificateType = parameters.getClientCertTypes().get(0); + } } if (parameters.getSignatures().isEmpty()) { parameters.setCertificateMismatch(CertificateBasedMismatch.SIGNATURE_ALGORITHMS); @@ -146,14 +153,14 @@ protected boolean selectForCertificate(CipherSuiteParameters parameters, CipherS parameters.setCertificateMismatch(CertificateBasedMismatch.CERTIFICATE_SIGNATURE_ALGORITHMS); return false; } - CertificateType certificateType = parameters.getServerCertTypes().get(0); - if (CertificateType.X_509.equals(certificateType)) { + CertificateType serverCertificateType = parameters.getServerCertTypes().get(0); + if (CertificateType.X_509.equals(serverCertificateType)) { if (parameters.getCertificateChain() == null) { throw new IllegalArgumentException("Certificate type x509 requires a certificate chain!"); } // check, if certificate chain is supported - boolean supported = SignatureAndHashAlgorithm - .isSignedWithSupportedAlgorithms(parameters.getSignatures(), parameters.getCertificateChain()); + boolean supported = SignatureAndHashAlgorithm.isSignedWithSupportedAlgorithms(parameters.getSignatures(), + parameters.getCertificateChain()); if (supported) { supported = SupportedGroup.isSupported(parameters.getSupportedGroups(), parameters.getCertificateChain()); @@ -163,21 +170,18 @@ protected boolean selectForCertificate(CipherSuiteParameters parameters, CipherS // contains unsupported signature hash algorithms or groups // (curves). if (parameters.getServerCertTypes().contains(CertificateType.RAW_PUBLIC_KEY)) { - certificateType = CertificateType.RAW_PUBLIC_KEY; + serverCertificateType = CertificateType.RAW_PUBLIC_KEY; } else { - parameters - .setCertificateMismatch(CertificateBasedMismatch.CERTIFICATE_PATH_SIGNATURE_ALGORITHMS); + parameters.setCertificateMismatch(CertificateBasedMismatch.CERTIFICATE_PATH_SIGNATURE_ALGORITHMS); return false; } } } parameters.select(cipherSuite); - parameters.selectServerCertificateType(certificateType); + parameters.selectServerCertificateType(serverCertificateType); parameters.selectSignatureAndHashAlgorithm(signatureAndHashAlgorithm); parameters.selectSupportedGroup(parameters.getSupportedGroups().get(0)); - certificateType = clientAuthentication.useCertificateRequest() ? parameters.getClientCertTypes().get(0) - : null; - parameters.selectClientCertificateType(certificateType); + parameters.selectClientCertificateType(clientCertificateType); return true; } } diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/x509/AsyncCertificateVerifier.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/x509/AsyncCertificateVerifier.java index 200244020d..61d2806b2f 100644 --- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/x509/AsyncCertificateVerifier.java +++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/x509/AsyncCertificateVerifier.java @@ -18,6 +18,7 @@ import java.net.InetSocketAddress; import java.security.PublicKey; import java.security.cert.CertPath; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.List; import java.util.concurrent.ScheduledExecutorService; @@ -37,16 +38,17 @@ import org.slf4j.LoggerFactory; /** - * Simple asynchronous test implementation of - * {@link CertificateVerifier}. + * Simple asynchronous test implementation of {@link CertificateVerifier}. *

      * 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 supportedCertificateTypes) { + public AsyncCertificateVerifier(X509Certificate[] trustedCertificates, RawPublicKeyIdentity[] trustedRPKs, + List supportedCertificateTypes) { super(trustedCertificates, trustedRPKs, supportedCertificateTypes); executorService = ExecutorsUtil.newSingleThreadScheduledExecutor(THREAD_FACTORY); // $NON-NLS-1$ } @@ -144,8 +147,7 @@ public void run() { } private void verifyCertificateAsynchronous(ConnectionId cid, ServerNames serverName, InetSocketAddress remotePeer, - boolean clientUsage, boolean verifySubject, boolean truncateCertificatePath, - CertificateMessage message) { + boolean clientUsage, boolean verifySubject, boolean truncateCertificatePath, CertificateMessage message) { CertificateVerificationResult result = super.verifyCertificate(cid, serverName, remotePeer, clientUsage, verifySubject, truncateCertificatePath, message); CertPath certPath = result.getCertificatePath(); @@ -174,6 +176,43 @@ public static Builder builder() { public static class Builder extends StaticCertificateVerifier.Builder { + @Override + public Builder setTrustedCertificates(Certificate... trustedCertificates) { + super.setTrustedCertificates(trustedCertificates); + return this; + } + + @Override + public Builder setTrustAllCertificates() { + super.setTrustAllCertificates(); + return this; + } + + @Override + public Builder setTrustedRPKs(RawPublicKeyIdentity... trustedRPKs) { + super.setTrustedRPKs(trustedRPKs); + return this; + } + + @Override + public Builder setTrustAllRPKs() { + super.setTrustAllRPKs(); + return this; + } + + @Override + public Builder setSupportedCertificateTypes(List supportedCertificateTypes) { + super.setSupportedCertificateTypes(supportedCertificateTypes); + return this; + } + + @Override + public Builder setUseEmptyAcceptedIssuers(boolean useEmptyAcceptedIssuers) { + super.setUseEmptyAcceptedIssuers(useEmptyAcceptedIssuers); + return this; + } + + @Override public AsyncCertificateVerifier build() { return new AsyncCertificateVerifier(trustedCertificates, trustedRPKs, supportedCertificateTypes); } diff --git a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/x509/StaticCertificateVerifier.java b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/x509/StaticCertificateVerifier.java index 016a58eab1..f1db3e942c 100644 --- a/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/x509/StaticCertificateVerifier.java +++ b/scandium-core/src/main/java/org/eclipse/californium/scandium/dtls/x509/StaticCertificateVerifier.java @@ -247,7 +247,7 @@ public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerN } } catch (HandshakeException e) { LOGGER.debug("Certificate validation failed!", e); - return new CertificateVerificationResult(cid, e, null); + return new CertificateVerificationResult(cid, e); } } diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/ApplicationAuthorizationTest.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/ApplicationAuthorizationTest.java new file mode 100644 index 0000000000..53165c757a --- /dev/null +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/ApplicationAuthorizationTest.java @@ -0,0 +1,307 @@ +/******************************************************************************* + * Copyright (c) 2018 - 2019 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v20.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Achim Kraus (Bosch Software Innovations GmbH) - initial creation + * Based on the original test + * in DTLSConnectorTest. + * Updated to use ConnectorHelper + ******************************************************************************/ +package org.eclipse.californium.scandium; + +import static org.eclipse.californium.scandium.ConnectorHelper.SERVERNAME; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.security.GeneralSecurityException; +import java.security.Principal; +import java.util.Arrays; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.eclipse.californium.elements.AddressEndpointContext; +import org.eclipse.californium.elements.EndpointContext; +import org.eclipse.californium.elements.RawData; +import org.eclipse.californium.elements.auth.ApplicationPrincipal; +import org.eclipse.californium.elements.category.Medium; +import org.eclipse.californium.elements.config.CertificateAuthenticationMode; +import org.eclipse.californium.elements.rule.LoggingRule; +import org.eclipse.californium.elements.rule.TestNameLoggerRule; +import org.eclipse.californium.elements.rule.TestTimeRule; +import org.eclipse.californium.elements.rule.ThreadsRule; +import org.eclipse.californium.elements.util.TestCondition; +import org.eclipse.californium.elements.util.TestConditionTools; +import org.eclipse.californium.scandium.ConnectorHelper.AlertCatcher; +import org.eclipse.californium.scandium.config.DtlsConfig; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.DtlsTestTools; +import org.eclipse.californium.scandium.dtls.x509.AsyncCertificateVerifier; +import org.eclipse.californium.scandium.rule.DtlsNetworkRule; +import org.junit.After; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +/** + * Verifies behavior of {@link DTLSConnector}. + *

      + * 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 setups() { + return Arrays.asList(Mode.values()); + } + + DtlsConnectorConfig.Builder serverBuilder; + ConnectorHelper serverHelper; + + AsyncCertificateVerifier serverVerifier; + + DtlsHealthLogger serverHealth; + + DtlsConnectorConfig.Builder clientBuilder; + DTLSConnector client; + AlertCatcher clientAlertCatcher; + AsyncCertificateVerifier clientVerifier; + DtlsConnectorConfig.Builder clientConfigBuilder; + + /** + * Sets up the fixture. + */ + @Before + public void setUp() { + + clientAlertCatcher = new AlertCatcher(); + + serverHelper = new ConnectorHelper(network); + serverBuilder = serverHelper.serverBuilder; + serverBuilder.set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.NONE) + .set(DtlsConfig.DTLS_APPLICATION_AUTHORIZATION_TIMEOUT, 1, TimeUnit.SECONDS); + + clientBuilder = DtlsConnectorConfig.builder(network.createClientTestConfig()); + } + + /** + * Destroys the server and client. + */ + @After + public void cleanUp() { + if (serverHelper != null && serverHelper.server != null) { + assertThat(serverHelper.server.isRunning(), is(true)); + try { + // wait until no pending jobs left + TestConditionTools.waitForCondition(6000, 100, TimeUnit.MILLISECONDS, new TestCondition() { + + @Override + public boolean isFulFilled() throws IllegalStateException { + return !serverHelper.server.updateHealth(); + } + }); + TestConditionTools.assertStatisticCounter("jobs left", serverHealth, "pending in jobs", is(0L)); + TestConditionTools.assertStatisticCounter("jobs left", serverHealth, "pending out jobs", is(0L)); + TestConditionTools.assertStatisticCounter("jobs left", serverHealth, "pending handshake jobs", is(0L)); + } catch (InterruptedException e) { + } + } + if (serverVerifier != null) { + serverVerifier.shutdown(); + serverVerifier = null; + } + if (serverHelper != null) { + if (serverHelper.server != null) { + serverHelper.server.stop(); + ConnectorHelper.assertReloadConnections("server", serverHelper.server); + } + serverHelper.destroyServer(); + serverHelper = null; + } + if (clientVerifier != null) { + clientVerifier.shutdown(); + clientVerifier = null; + } + if (client != null) { + client.stop(); + ConnectorHelper.assertReloadConnections("client", client); + client.destroy(); + client = null; + } + } + + private void startServer() throws IOException, GeneralSecurityException { + + DtlsConnectorConfig incompleteConfig = serverBuilder.getIncompleteConfig(); + + if (incompleteConfig.get(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE) != CertificateAuthenticationMode.NONE) { + if (incompleteConfig.getCertificateVerifier() == null) { + serverVerifier = AsyncCertificateVerifier.builder().setTrustAllCertificates().setTrustAllRPKs().build(); + serverBuilder.setCertificateVerifier(serverVerifier); + serverVerifier.setDelay(DtlsTestTools.DEFAULT_HANDSHAKE_RESULT_DELAY_MILLIS); + } + } + serverHealth = new DtlsHealthLogger("server"); + serverBuilder.setHealthHandler(serverHealth); + serverHelper.startServer(); + + } + + private DTLSSession startClientRpk(String hostname) throws Exception { + clientVerifier = AsyncCertificateVerifier.builder().setTrustAllRPKs().build(); + clientBuilder.setCertificateVerifier(clientVerifier); + return startClient(hostname); + } + + private DTLSSession startClientX509(String hostname) throws Exception { + if (clientBuilder.getIncompleteConfig().getCertificateVerifier() == null) { + clientVerifier = AsyncCertificateVerifier.builder().setTrustAllCertificates() + .build(); + clientBuilder.setCertificateVerifier(clientVerifier); + } + return startClient(hostname); + } + + private DTLSSession startClient(String hostname) throws Exception { + InetSocketAddress clientEndpoint = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); + clientBuilder.setAddress(clientEndpoint).setLoggingTag("client").set(DtlsConfig.DTLS_RECEIVER_THREAD_COUNT, 1) + .set(DtlsConfig.DTLS_CONNECTOR_THREAD_COUNT, 1) + .set(DtlsConfig.DTLS_MAX_CONNECTIONS, CLIENT_CONNECTION_STORE_CAPACITY); + + DtlsConnectorConfig clientConfig = clientBuilder.build(); + + client = serverHelper.createClient(clientConfig); + client.setAlertHandler(clientAlertCatcher); + RawData raw = RawData.outbound("Hello World".getBytes(), + new AddressEndpointContext(serverHelper.serverEndpoint, hostname, null), null, false); + serverHelper.givenAnEstablishedSession(client, raw, true); + final DTLSSession session = client.getSessionByAddress(serverHelper.serverEndpoint); + assertThat(session, is(notNullValue())); + return session; + } + + @Test + public void testRpkHandshakeApplicationAuthorized() throws Exception { + startServer(); + startClientRpk(null); + final EndpointContext endpointContext = serverHelper.serverRawDataProcessor.getClientEndpointContext(); + + // client's principal + Principal principal = endpointContext.getPeerIdentity(); + assertThat(principal, is(nullValue())); + + if (mode == Mode.AUTHORIZE) { + Future future = serverHelper.server.authorize(endpointContext, ApplicationPrincipal.ANONYMOUS); + assertThat(future.get(1000, TimeUnit.MILLISECONDS), is(true)); + principal = serverHelper.getServersClientIdentity(endpointContext); + assertThat(principal, is(ApplicationPrincipal.ANONYMOUS)); + // second authorization is rejected. + future = serverHelper.server.authorize(endpointContext, new ApplicationPrincipal("test", false)); + assertThat(future.get(1000, TimeUnit.MILLISECONDS), is(false)); + principal = serverHelper.getServersClientIdentity(endpointContext); + assertThat(principal, is(ApplicationPrincipal.ANONYMOUS)); + + } else if (mode == Mode.REJECT) { + Future future = serverHelper.server.rejectAuthorization(endpointContext); + future.get(1000, TimeUnit.MILLISECONDS); + TestConditionTools.assertStatisticCounter(serverHealth, "application rejected authorizations", is(1L), 500, + TimeUnit.MILLISECONDS); + } else if (mode == Mode.NONE) { + Thread.sleep(2000); + } + + // still available after recent handshake timeout + time.addTestTimeShift(CookieGenerator.COOKIE_LIFETIME_NANOS * 3, TimeUnit.NANOSECONDS); + + serverHelper.server.cleanupRecentHandshakes(0); +// assertThat(serverHelper.server.cleanupRecentHandshakes(0), is(1)); + if (mode == Mode.NONE) { + TestConditionTools.assertStatisticCounter(serverHealth, "application missing authorizations", is(1L), 500, + TimeUnit.MILLISECONDS); + } + serverHelper.getServerConnection(endpointContext, mode == Mode.AUTHORIZE); + } + + @Test + public void testX509HandshakeApplicationAuthorized() throws Exception { + startServer(); + startClientX509(SERVERNAME); + EndpointContext endpointContext = serverHelper.serverRawDataProcessor.getClientEndpointContext(); + + // client's principal + Principal principal = endpointContext.getPeerIdentity(); + assertThat(principal, is(nullValue())); + + if (mode == Mode.AUTHORIZE) { + Future future = serverHelper.server.authorize(endpointContext, ApplicationPrincipal.ANONYMOUS); + future.get(1000, TimeUnit.MILLISECONDS); + principal = serverHelper.getServersClientIdentity(endpointContext); + assertThat(principal, is(ApplicationPrincipal.ANONYMOUS)); + } else if (mode == Mode.REJECT) { + Future future = serverHelper.server.rejectAuthorization(endpointContext); + future.get(1000, TimeUnit.MILLISECONDS); + TestConditionTools.assertStatisticCounter(serverHealth, "application rejected authorizations", is(1L), 500, + TimeUnit.MILLISECONDS); + } else if (mode == Mode.NONE) { + Thread.sleep(2000); + } + + // still available after recent handshake timeout + time.addTestTimeShift(CookieGenerator.COOKIE_LIFETIME_NANOS * 3, TimeUnit.NANOSECONDS); + + serverHelper.server.cleanupRecentHandshakes(0); +// assertThat(serverHelper.server.cleanupRecentHandshakes(0), is(1)); + if (mode == Mode.NONE) { + TestConditionTools.assertStatisticCounter(serverHealth, "application missing authorizations", is(1L), 500, + TimeUnit.MILLISECONDS); + } + serverHelper.getServerConnection(endpointContext, mode == Mode.AUTHORIZE); + } +} diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/ConnectorHelper.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/ConnectorHelper.java index 3547f3b6bd..9e8f09ec3a 100644 --- a/scandium-core/src/test/java/org/eclipse/californium/scandium/ConnectorHelper.java +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/ConnectorHelper.java @@ -38,6 +38,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -129,7 +130,7 @@ public class ConnectorHelper { static final ThreadFactory TEST_UDP_THREAD_FACTORY = new TestThreadFactory("TEST-UDP-"); boolean useSessionStore; - DTLSConnector server; + DtlsTestConnector server; InetSocketAddress serverEndpoint; DebugConnectionStore serverConnectionStore; SessionStore serverSessionStore; @@ -145,7 +146,8 @@ public class ConnectorHelper { DtlsConnectorConfig.Builder serverBuilder; public ConnectorHelper(DtlsNetworkRule network) { - List list = new ArrayList<>(CipherSuite.getCertificateCipherSuites(false, CertificateKeyAlgorithm.EC)); + List list = new ArrayList<>( + CipherSuite.getCertificateCipherSuites(false, CertificateKeyAlgorithm.EC)); list.addAll(CipherSuite.getCipherSuitesByKeyExchangeAlgorithm(false, KeyExchangeAlgorithm.ECDHE_PSK, KeyExchangeAlgorithm.PSK)); serverPskStore = new MultiPskStore(); @@ -153,17 +155,15 @@ public ConnectorHelper(DtlsNetworkRule network) { serverPskStore.setKey(SCOPED_CLIENT_IDENTITY, SCOPED_CLIENT_IDENTITY_SECRET.getBytes(), SERVERNAME); serverBuilder = DtlsConnectorConfig.builder(network.createTestConfig()) - .set(DtlsConfig.DTLS_ROLE, DtlsRole.SERVER_ONLY) - .set(DtlsConfig.DTLS_RECEIVER_THREAD_COUNT, 1) + .set(DtlsConfig.DTLS_ROLE, DtlsRole.SERVER_ONLY).set(DtlsConfig.DTLS_RECEIVER_THREAD_COUNT, 1) .set(DtlsConfig.DTLS_CONNECTOR_THREAD_COUNT, 2) .set(DtlsConfig.DTLS_MAX_CONNECTIONS, SERVER_CONNECTION_STORE_CAPACITY) .set(DtlsConfig.DTLS_STALE_CONNECTION_THRESHOLD, 5, TimeUnit.MINUTES) - .setAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)) - .setPskStore(serverPskStore) + .setAddress(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)).setPskStore(serverPskStore) .setCertificateIdentityProvider(new SingleCertificateProvider(DtlsTestTools.getPrivateKey(), - DtlsTestTools.getServerCertificateChain(), CertificateType.RAW_PUBLIC_KEY, CertificateType.X_509)) - .set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, false) - .set(DtlsConfig.DTLS_CIPHER_SUITES, list) + DtlsTestTools.getServerCertificateChain(), CertificateType.RAW_PUBLIC_KEY, + CertificateType.X_509)) + .set(DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, false).set(DtlsConfig.DTLS_CIPHER_SUITES, list) .setLoggingTag("server"); } @@ -302,20 +302,17 @@ public DTLSConnector createClient(DtlsConnectorConfig configuration) { return new DtlsTestConnector(configuration); } - public DTLSConnector createClient(DtlsConnectorConfig configuration, - ConnectionStore connectionStore) { + public DTLSConnector createClient(DtlsConnectorConfig configuration, ConnectionStore connectionStore) { return new DtlsTestConnector(configuration, connectionStore); } - public static DtlsConnectorConfig.Builder newClientConfigBuilder(DtlsNetworkRule network) throws IOException, GeneralSecurityException { + public static DtlsConnectorConfig.Builder newClientConfigBuilder(DtlsNetworkRule network) + throws IOException, GeneralSecurityException { InetSocketAddress clientEndpoint = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); CertificateVerifier clientCertificateVerifier = StaticCertificateVerifier.builder() .setTrustedCertificates(DtlsTestTools.getTrustedCertificates()).setTrustAllRPKs().build(); - return DtlsConnectorConfig.builder(network.createTestConfig()) - .set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY) - .setLoggingTag("client") - .setAddress(clientEndpoint) - .set(DtlsConfig.DTLS_RECEIVER_THREAD_COUNT, 1) + return DtlsConnectorConfig.builder(network.createTestConfig()).set(DtlsConfig.DTLS_ROLE, DtlsRole.CLIENT_ONLY) + .setLoggingTag("client").setAddress(clientEndpoint).set(DtlsConfig.DTLS_RECEIVER_THREAD_COUNT, 1) .set(DtlsConfig.DTLS_CONNECTOR_THREAD_COUNT, 2) .setCertificateIdentityProvider(new SingleCertificateProvider(DtlsTestTools.getClientPrivateKey(), DtlsTestTools.getClientCertificateChain(), CertificateType.RAW_PUBLIC_KEY, @@ -323,29 +320,170 @@ public static DtlsConnectorConfig.Builder newClientConfigBuilder(DtlsNetworkRule .setCertificateVerifier(clientCertificateVerifier); } - public DTLSSession getEstablishedServerDtlsSession(InetSocketAddress address) { - DTLSSession establishedServerSession = getEstablishedServerDtlsContext(address).getSession(); - assertNotNull(establishedServerSession); + /** + * Gets server side client identity. + * + * @param address client address + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @return the client identity, or {@code null}, if not available. + * @see #assertExists(Object, boolean...) + * @since 4.0 + */ + public Principal getServersClientIdentity(InetSocketAddress address, boolean... exists) { + DTLSSession dtlsSession = getEstablishedServerDtlsSession(address, exists); + if (dtlsSession == null) + return null; + Principal principal = dtlsSession.getPeerIdentity(); + assertExists(principal, exists); + return principal; + } + + /** + * Gets server side client identity. + * + * @param context client context + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @return the client identity, or {@code null}, if not available. + * @see #assertExists(Object, boolean...) + * @since 4.0 + */ + public Principal getServersClientIdentity(EndpointContext context, boolean... exists) { + return getServersClientIdentity(context.getPeerAddress(), exists); + } + + /** + * Gets server side established dtl session. + * + * @param address client address + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @return the dtls session, or {@code null}, if not available. + * @see #assertExists(Object, boolean...) + * @since 4.0 (added parameter exists) + */ + public DTLSSession getEstablishedServerDtlsSession(InetSocketAddress address, boolean... exists) { + DTLSContext dtlsContext = getEstablishedServerDtlsContext(address, exists); + if (dtlsContext == null) + return null; + DTLSSession establishedServerSession = dtlsContext.getSession(); + assertExists(establishedServerSession, exists); return establishedServerSession; } - public DTLSContext getEstablishedServerDtlsContext(InetSocketAddress address) { - Connection con = serverConnectionStore.get(address); - assertNotNull(con); + /** + * Gets server side established dtl session. + * + * @param context client context + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @return the dtls session, or {@code null}, if not available. + * @see #assertExists(Object, boolean...) + * @since 4.0 + */ + public DTLSSession getEstablishedServerDtlsSession(EndpointContext context, boolean... exists) { + return getEstablishedServerDtlsSession(context.getPeerAddress(), exists); + } + + /** + * Gets server side established dtl session. + * + * @param address client address + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @return the dtls session, or {@code null}, if not available. + * @see #assertExists(Object, boolean...) + * @since 4.0 (added parameter exists) + */ + public DTLSContext getEstablishedServerDtlsContext(InetSocketAddress address, boolean... exists) { + Connection con = getServerConnection(address, exists); + if (con == null) + return null; DTLSContext establishedServerContext = con.getEstablishedDtlsContext(); - assertNotNull(establishedServerContext); + assertExists(establishedServerContext, exists); return establishedServerContext; } - public TestContext givenAnEstablishedSession(DTLSConnector client, boolean releaseSocket) - throws Exception { + /** + * Gets server side established dtl session. + * + * @param context client context + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @return the dtls session, or {@code null}, if not available. + * @see #assertExists(Object, boolean...) + * @since 4.0 + */ + public DTLSContext getEstablishedServerDtlsContext(EndpointContext context, boolean... exists) { + return getEstablishedServerDtlsContext(context.getPeerAddress(), exists); + } + + /** + * Gets server side connection. + * + * @param address client address + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @return the connection, or {@code null}, if not available. + * @see #assertExists(Object, boolean...) + * @since 4.0 + */ + public Connection getServerConnection(InetSocketAddress address, boolean... exists) { + Connection con = serverConnectionStore.get(address); + assertExists(con, exists); + return con; + } + + /** + * Gets server side connection. + * + * @param context client context + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @return the connection, or {@code null}, if not available. + * @see #assertExists(Object, boolean...) + * @since 4.0 + */ + public Connection getServerConnection(EndpointContext context, boolean... exists) { + return getServerConnection(context.getPeerAddress(), exists); + } + + /** + * Assert existence of object. + * + * @param object object to assert + * @param exists enable asserts for existence. none, don't assert, + * {@code true}, assert is not {@code null}, {@code false} assert + * is {@code null} + * @since 4.0 + */ + private void assertExists(Object object, boolean... exists) { + if (exists != null && exists.length == 1) { + if (exists[0]) { + assertNotNull(object); + } else { + assertNull(object); + } + } + } + + public TestContext givenAnEstablishedSession(DTLSConnector client, boolean releaseSocket) throws Exception { RawData raw = RawData.outbound("Hello World".getBytes(), new AddressEndpointContext(serverEndpoint), null, false); return givenAnEstablishedSession(client, raw, releaseSocket); } - public TestContext givenAnEstablishedSession(DTLSConnector client, RawData msgToSend, - boolean releaseSocket) throws Exception { + public TestContext givenAnEstablishedSession(DTLSConnector client, RawData msgToSend, boolean releaseSocket) + throws Exception { LatchDecrementingRawDataChannel clientChannel = new LatchDecrementingRawDataChannel(1); client.setRawDataReceiver(clientChannel); @@ -426,8 +564,8 @@ public static DebugConnectionStore createDebugConnectionStore(DtlsConnectorConfi public static DebugConnectionStore createDebugConnectionStore(Configuration configuration, SessionStore sessionStore) { return new DebugInMemoryConnectionStore(configuration.get(DtlsConfig.DTLS_MAX_CONNECTIONS), - configuration.get(DtlsConfig.DTLS_STALE_CONNECTION_THRESHOLD, TimeUnit.SECONDS), sessionStore, - configuration.get(DtlsConfig.DTLS_REMOVE_STALE_DOUBLE_PRINCIPALS)); + configuration.get(DtlsConfig.DTLS_STALE_CONNECTION_THRESHOLD, TimeUnit.SECONDS), sessionStore, + configuration.get(DtlsConfig.DTLS_REMOVE_STALE_DOUBLE_PRINCIPALS)); } public static class TestContext { @@ -547,7 +685,13 @@ public void receiveData(final RawData raw) { if (processor != null) { RawData response = processor.process(raw); if (response != null && connector != null) { - connector.send(response); + try { + connector.send(response); + } catch (IllegalStateException ex) { + if (connector.isRunning()) { + throw ex; + } + } } } } @@ -856,7 +1000,7 @@ private DatagramSocket getSocket() { } } - class DtlsTestConnector extends DTLSConnector { + public class DtlsTestConnector extends DTLSConnector { DtlsTestConnector(DtlsConnectorConfig configuration) { super(configuration); @@ -885,6 +1029,10 @@ protected void onInitializeHandshaker(Handshaker handshaker) { handshaker.addSessionListener(listener); sessionListenerMap.put(handshaker.getPeerAddress(), listener); } + + public int cleanupRecentHandshakes(int calls) { + return super.cleanupRecentHandshakes(calls); + } } public static interface BuilderSetup { @@ -1043,6 +1191,44 @@ public boolean onReceiving(Record record, Connection connection) { public boolean onMacError(Record record, Connection connection) { return true; } + + @Override + public void onApplicationAuthorizationRejected(Connection connection) { + + } + } + + public static class RejectCatcher extends Catcher implements DatagramFilter { + + @Override + public void onDrop(DatagramPacket packet) { + + } + + @Override + public void onDrop(Record record) { + + } + + @Override + public boolean onReceiving(DatagramPacket packet) { + return true; + } + + @Override + public boolean onReceiving(Record record, Connection connection) { + return true; + } + + @Override + public boolean onMacError(Record record, Connection connection) { + return true; + } + + @Override + public void onApplicationAuthorizationRejected(Connection connection) { + onEvent(connection); + } } } diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorAdvancedTest.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorAdvancedTest.java index 9c804e13c6..2eb55f4370 100644 --- a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorAdvancedTest.java +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorAdvancedTest.java @@ -389,7 +389,7 @@ public void setUp() throws Exception { verifyHandshakeResponses = 1; resumeHandshakeResponses = 1; - clientCertificateVerifier = (AsyncCertificateVerifier)AsyncCertificateVerifier.builder() + clientCertificateVerifier = AsyncCertificateVerifier.builder() .setTrustedCertificates(DtlsTestTools.getTrustedCertificates()) .setTrustAllRPKs() .build(); diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorHandshakeTest.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorHandshakeTest.java index c1f16755aa..597f3286a4 100644 --- a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorHandshakeTest.java +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorHandshakeTest.java @@ -457,7 +457,7 @@ private void startServer() throws IOException, GeneralSecurityException { } if (incompleteConfig.get(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE) != CertificateAuthenticationMode.NONE) { if (incompleteConfig.getCertificateVerifier() == null) { - serverVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier.builder() + serverVerifier = AsyncCertificateVerifier.builder() .setTrustAllCertificates().setTrustAllRPKs().build(); serverBuilder.setCertificateVerifier(serverVerifier); serverVerifier.setDelay(DtlsTestTools.DEFAULT_HANDSHAKE_RESULT_DELAY_MILLIS); @@ -480,7 +480,7 @@ private DTLSSession startClientPsk(String hostname) throws Exception { } private DTLSSession startClientRpk(String hostname) throws Exception { - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllRPKs().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); clientBuilder.setCertificateVerifier(clientCertificateVerifier); @@ -489,7 +489,7 @@ private DTLSSession startClientRpk(String hostname) throws Exception { private DTLSSession startClientX509(String hostname) throws Exception { if (clientBuilder.getIncompleteConfig().getCertificateVerifier() == null) { - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); clientBuilder.setCertificateVerifier(clientCertificateVerifier); @@ -786,7 +786,7 @@ public void testX509HandshakeWithWrongServernameClientWithSniAndServerWithSni() serverBuilder.set(DtlsConfig.DTLS_USE_SERVER_NAME_INDICATION, true); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); clientBuilder.set(DtlsConfig.DTLS_USE_SERVER_NAME_INDICATION, true) @@ -820,7 +820,7 @@ public void testX509HandshakeWithWrongServernameClientWithoutSniAndServerWithSni serverBuilder.set(DtlsConfig.DTLS_USE_SERVER_NAME_INDICATION, true); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); clientBuilder.setCertificateVerifier(clientCertificateVerifier) @@ -1119,7 +1119,7 @@ public void testX509ClientRsaCertificateChainHandshakeFailure() throws Exception serverBuilder.setAsList(DtlsConfig.DTLS_CERTIFICATE_KEY_ALGORITHMS, CertificateKeyAlgorithm.EC); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -1154,7 +1154,7 @@ public void testX509TrustServerCertificate() throws Exception { .setCertificateIdentityProvider(new SingleCertificateProvider(DtlsTestTools.getPrivateKey(), DtlsTestTools.getServerCertificateChain())); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustedCertificates(DtlsTestTools.getServerCertificateChain()[0]).build(); clientsCertificateVerifiers.add(clientCertificateVerifier); clientBuilder.setCertificateVerifier(clientCertificateVerifier); @@ -1755,7 +1755,7 @@ public void testRpkRsaHandshakeSingleProvider() throws Exception { .set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.WANTED); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllRPKs().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -1797,7 +1797,7 @@ public void testRpkRsaHandshakeKeyManagerProvider() throws Exception { .set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.WANTED); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllRPKs().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -1839,7 +1839,7 @@ public void testRpkRsaEcdsaMixedHandshakeKeyManagerProvider() throws Exception { .set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.WANTED); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllRPKs().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -1875,7 +1875,7 @@ public void testRpkRsaAnonymousHandshakeSingleProvider() throws Exception { .set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.WANTED); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllRPKs().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -1908,7 +1908,7 @@ public void testX509Ed25519Handshake() throws Exception { new KeyManagerCertificateProvider(DtlsTestTools.getDtlsServerKeyManager(), CertificateType.X_509)); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -1946,7 +1946,7 @@ public void testX509RsaHandshake() throws Exception { CertificateType.X_509)); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -1972,7 +1972,7 @@ public void testX509RsaHandshake() throws Exception { public void testX509HandshakeSignatureAlgorithmsExtensionSha256Ecdsa() throws Exception { startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -1995,7 +1995,7 @@ public void testX509HandshakeSignatureAlgorithmsExtensionSha384Ecdsa() throws Ex SignatureAndHashAlgorithm.SHA256_WITH_ECDSA); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2020,7 +2020,7 @@ public void testX509HandshakeFailingNoCommonSignatureAlgorithms() throws Excepti SignatureAndHashAlgorithm.SHA256_WITH_ECDSA); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2054,7 +2054,7 @@ public void testX509HandshakeFailingNoCommonSignatureAlgorithms() throws Excepti clientAlertCatcher.resetEvent(); client.destroy(); - clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier.builder() + clientCertificateVerifier = AsyncCertificateVerifier.builder() .setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2079,7 +2079,7 @@ public void testX509HandshakeFailingCertificateSignatureAlgorithm() throws Excep SignatureAndHashAlgorithm.SHA256_WITH_ECDSA); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2115,7 +2115,7 @@ public void testX509HandshakeFailingWrongClientCertificate() throws Exception { logging.setLoggingLevel("ERROR", SingleCertificateProvider.class); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2147,7 +2147,7 @@ public void testX509HandshakeFailingWrongClientCertificate() throws Exception { public void testX509HandshakeFailingExpiredClientCertificate() throws Exception { startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2180,7 +2180,7 @@ public void testX509HandshakeFailingExpiredClientCertificate() throws Exception public void testX509HandshakeFailingMissingClientCertificate() throws Exception { startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2212,7 +2212,7 @@ public void testX509HandshakeFailingNoCommonCurve() throws Exception { serverBuilder.set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.NONE); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2246,7 +2246,7 @@ public void testX509HandshakeFailingCertificateCurve() throws Exception { serverBuilder.set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.NONE); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllCertificates().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2317,7 +2317,7 @@ public void testDefaultHandshakeModeNone() throws Exception { serverBuilder.set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.WANTED); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllRPKs().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); @@ -2344,7 +2344,7 @@ public void testDefaultHandshakeModeAuto() throws Exception { serverBuilder.set(DtlsConfig.DTLS_CLIENT_AUTHENTICATION_MODE, CertificateAuthenticationMode.WANTED); startServer(); - AsyncCertificateVerifier clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier + AsyncCertificateVerifier clientCertificateVerifier = AsyncCertificateVerifier .builder().setTrustAllRPKs().build(); clientsCertificateVerifiers.add(clientCertificateVerifier); diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorResumeTest.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorResumeTest.java index ec077ed36f..a162439adb 100644 --- a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorResumeTest.java +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorResumeTest.java @@ -435,7 +435,7 @@ public AdditionalInfo getInfo(Principal clientIdentity, Object customArgument) { pskStore.setKey(SCOPED_CLIENT_IDENTITY, SCOPED_CLIENT_IDENTITY_SECRET.getBytes(), SERVERNAME); pskStore.setKey(SCOPED_CLIENT_IDENTITY, SCOPED_CLIENT_IDENTITY_SECRET.getBytes(), SERVERNAME_ALT); serverPskStore = new AsyncPskStore(pskStore); - serverCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier.builder() + serverCertificateVerifier = AsyncCertificateVerifier.builder() .setTrustedCertificates(DtlsTestTools.getTrustedCertificates()).setTrustAllRPKs().build(); serverResumptionVerifier = new AsyncResumptionVerifier(); @@ -466,7 +466,7 @@ public AdditionalInfo getInfo(Principal clientIdentity, Object customArgument) { clientInMemoryPskStore.addKnownPeer(serverHelper.serverEndpoint, SERVERNAME_ALT, SCOPED_CLIENT_IDENTITY, SCOPED_CLIENT_IDENTITY_SECRET.getBytes()); clientPskStore = new AsyncPskStore(clientInMemoryPskStore); - clientCertificateVerifier = (AsyncCertificateVerifier) AsyncCertificateVerifier.builder() + clientCertificateVerifier = AsyncCertificateVerifier.builder() .setTrustedCertificates(DtlsTestTools.getTrustedCertificates()).setTrustAllRPKs().build(); clientCertificateProvider = new AsyncCertificateProvider(clientPrivateKey, clientCertificateChain, CertificateType.RAW_PUBLIC_KEY, CertificateType.X_509); @@ -821,7 +821,7 @@ public void testConnectorResumesSessionFromExistingConnection() throws Exception assertThat(connection.getEstablishedSession().getSessionIdentifier(), is(sessionId)); assertClientIdentity(clientPrincipalType); - peer = serverHelper.getEstablishedServerDtlsSession(client.getAddress()).getPeerIdentity(); + peer = serverHelper.getServersClientIdentity(client.getAddress()); assertThat(peer, is(instanceOf(ExtensiblePrincipal.class))); principal = (ExtensiblePrincipal) peer; assertThat(principal.getExtendedInfo().get(KEY_DEVICE_ID, String.class), is(DEVICE_ID)); diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorStartStopTest.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorStartStopTest.java index dafdf9e482..2dfd933b94 100644 --- a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorStartStopTest.java +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSConnectorStartStopTest.java @@ -141,7 +141,7 @@ public void setUp() throws IOException, GeneralSecurityException { clientChannel = new LatchDecrementingRawDataChannel(); client.setRawDataReceiver(clientChannel); if (restoreClientConnection != null) { - client.restoreConnection(restoreClientConnection); + clientConnectionStore.restore(restoreClientConnection); restoreClientConnection = null; } } diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSEndpointContextTest.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSEndpointContextTest.java index 7b7bd5e93f..3464b9542a 100644 --- a/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSEndpointContextTest.java +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/DTLSEndpointContextTest.java @@ -287,7 +287,7 @@ private void assertEstablishedClientSession() { assertNotNull(establishedClientSession); } - private static class TestEndpointContextMatcher implements EndpointContextMatcher { + public static class TestEndpointContextMatcher implements EndpointContextMatcher { private final int count; private final CountDownLatch latchSendMatcher; diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/auth/PrincipalSerializerTest.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/auth/PrincipalSerializerTest.java index a7230dd113..1bd94aebcb 100644 --- a/scandium-core/src/test/java/org/eclipse/californium/scandium/auth/PrincipalSerializerTest.java +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/auth/PrincipalSerializerTest.java @@ -27,6 +27,7 @@ import java.security.PublicKey; import java.security.cert.Certificate; +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,7 +39,6 @@ import org.junit.Test; import org.junit.experimental.categories.Category; - /** * Verifies behavior of {@link PrincipalSerializer}. * @@ -53,9 +53,8 @@ public class PrincipalSerializerTest { * Creates a public key to be used in test cases. * * @throws GeneralSecurityException if the demo server certificate chain - * cannot be read. - * @throws IOException if the demo server certificate chain - * cannot be read. + * cannot be read. + * @throws IOException if the demo server certificate chain cannot be read. */ @BeforeClass public static void init() throws IOException, GeneralSecurityException { @@ -70,9 +69,9 @@ public static void init() throws IOException, GeneralSecurityException { } /** - * Verifies that a pre-shared key identity that has been serialized using the - * serialize method can be re-instantiated properly using the deserialize - * method. + * Verifies that a pre-shared key identity that has been serialized using + * the serialize method can be re-instantiated properly using the + * deserialize method. */ @Test public void testSerializedPSKIdentityCanBeDeserialized() { @@ -81,9 +80,9 @@ public void testSerializedPSKIdentityCanBeDeserialized() { } /** - * Verifies that a pre-shared key identity without a virtual host that has been - * serialized using the serialize method can be re-instantiated properly using - * the deserialize method. + * Verifies that a pre-shared key identity without a virtual host that has + * been serialized using the serialize method can be re-instantiated + * properly using the deserialize method. */ @Test public void testSerializedPSKIdentityWithoutHostCanBeDeserialized() { @@ -100,7 +99,8 @@ private static void testSerializedPSKIdentityCanBeDeserialized(PreSharedKeyIdent // THEN the resulting byte array can be used to re-instantiate // the identity - PreSharedKeyIdentity identity = (PreSharedKeyIdentity) PrincipalSerializer.deserialize(new DatagramReader(writer.toByteArray())); + PreSharedKeyIdentity identity = (PreSharedKeyIdentity) PrincipalSerializer + .deserialize(new DatagramReader(writer.toByteArray())); assertThat(identity, is(pskIdentity)); } catch (GeneralSecurityException e) { // should not happen @@ -109,9 +109,8 @@ private static void testSerializedPSKIdentityCanBeDeserialized(PreSharedKeyIdent } /** - * Verifies that a public key that has been serialized using the - * serialize method can be re-instantiated properly using the deserialize - * method. + * Verifies that a public key that has been serialized using the serialize + * method can be re-instantiated properly using the deserialize method. * * @throws GeneralSecurityException if the key cannot be deserialized. */ @@ -126,7 +125,8 @@ public void testSerializedRPKCanBeDeserialized() throws GeneralSecurityException // THEN the resulting byte array can be used to re-instantiate // the public key - RawPublicKeyIdentity identity = (RawPublicKeyIdentity) PrincipalSerializer.deserialize(new DatagramReader(writer.toByteArray())); + RawPublicKeyIdentity identity = (RawPublicKeyIdentity) PrincipalSerializer + .deserialize(new DatagramReader(writer.toByteArray())); assertThat(identity.getKey(), is(publicKey)); assertThat(identity.getKey().getAlgorithm(), is(publicKey.getAlgorithm())); } @@ -155,4 +155,56 @@ public void testSerializedX509CertPathCanBeDeserialized() throws GeneralSecurity assertThat(identity.getPath(), is(x509Identity.getPath())); } + /** + * Verifies that an anonymous application authorization has been serialized + * using the serialize method can be re-instantiated properly using the + * deserialize method. + * + * @throws GeneralSecurityException if the key cannot be deserialized. + */ + @Test + public void testSerializedAnonymousApplicationPrincipal() throws GeneralSecurityException { + + // WHEN serializing the application authorization to a byte array + DatagramWriter writer = new DatagramWriter(); + PrincipalSerializer.serialize(ApplicationPrincipal.ANONYMOUS, writer); + + // THEN the resulting byte array can be used to re-instantiate + ApplicationPrincipal identity = (ApplicationPrincipal) PrincipalSerializer + .deserialize(new DatagramReader(writer.toByteArray())); + assertThat(identity, is(ApplicationPrincipal.ANONYMOUS)); + } + + /** + * Verifies that an application authorization has been serialized using the + * serialize method can be re-instantiated properly using the deserialize + * method. + * + * @throws GeneralSecurityException if the key cannot be deserialized. + */ + @Test + public void testSerializedApplicationPrincipal() throws GeneralSecurityException { + + // WHEN serializing the application authorization to a byte array + DatagramWriter writer = new DatagramWriter(); + ApplicationPrincipal principal = new ApplicationPrincipal("me", false); + PrincipalSerializer.serialize(principal, writer); + + // THEN the resulting byte array can be used to re-instantiate + ApplicationPrincipal identity = (ApplicationPrincipal) PrincipalSerializer + .deserialize(new DatagramReader(writer.toByteArray())); + assertThat(identity, is(principal)); + + // WHEN serializing the application authorization to a byte array + writer = new DatagramWriter(); + principal = new ApplicationPrincipal("you", true); + PrincipalSerializer.serialize(principal, writer); + + // THEN the resulting byte array can be used to re-instantiate + identity = (ApplicationPrincipal) PrincipalSerializer + .deserialize(new DatagramReader(writer.toByteArray())); + assertThat(identity, is(principal)); + + } + } diff --git a/scandium-core/src/test/java/org/eclipse/californium/scandium/dtls/DebugInMemoryConnectionStore.java b/scandium-core/src/test/java/org/eclipse/californium/scandium/dtls/DebugInMemoryConnectionStore.java index 82022d5bc7..f59ff25741 100644 --- a/scandium-core/src/test/java/org/eclipse/californium/scandium/dtls/DebugInMemoryConnectionStore.java +++ b/scandium-core/src/test/java/org/eclipse/californium/scandium/dtls/DebugInMemoryConnectionStore.java @@ -96,8 +96,8 @@ public boolean dump(InetSocketAddress address) { */ private void dump(Connection connection) { if (connection.hasEstablishedDtlsContext()) { - LOG.info(" {}connection: {} - {} : {}", tag, connection.getConnectionId(), connection.getPeerAddress(), - connection.getEstablishedSession().getSessionIdentifier()); + LOG.info(" {}connection: {} - {} : {} {}", tag, connection.getConnectionId(), connection.getPeerAddress(), + connection.getEstablishedPeerIdentity(), connection.getEstablishedSession().getSessionIdentifier()); } else { LOG.info(" {}connection: {} - {}", tag, connection.getConnectionId(), connection.getPeerAddress()); }