Skip to content

Commit bd060cd

Browse files
committed
Add tests and incorporate review comments
Signed-off-by: Kartheeswaran Kalidass <kartheeswaran.kalidass@bosch.io>
1 parent 142b98a commit bd060cd

6 files changed

Lines changed: 241 additions & 141 deletions

File tree

services/device-registry-base/src/main/java/org/eclipse/hono/deviceregistry/service/credentials/AbstractCredentialsManagementService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.eclipse.hono.service.management.credentials.CredentialsManagementService;
3838
import org.eclipse.hono.service.management.credentials.PasswordCredential;
3939
import org.eclipse.hono.service.management.credentials.X509CertificateCredential;
40+
import org.eclipse.hono.service.management.credentials.X509CertificateCredentialWithGeneratedAuthId;
4041
import org.eclipse.hono.service.management.tenant.Tenant;
4142
import org.eclipse.hono.util.Futures;
4243
import org.eclipse.hono.util.Lifecycle;
@@ -289,7 +290,8 @@ private static Future<List<CommonCredential>> applyAuthIdTemplateForX509Certific
289290
final List<CommonCredential> creds = credentials.stream()
290291
.map(cred -> {
291292
if (cred instanceof X509CertificateCredential) {
292-
return ((X509CertificateCredential) cred).applyAuthIdTemplate(tenant);
293+
return X509CertificateCredentialWithGeneratedAuthId.applyAuthIdTemplate(
294+
(X509CertificateCredential) cred, tenant);
293295
}
294296
return cred;
295297
})

services/device-registry-base/src/main/java/org/eclipse/hono/service/management/credentials/X509CertificateCredential.java

Lines changed: 12 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,11 @@
2424

2525
import javax.security.auth.x500.X500Principal;
2626

27-
import org.eclipse.hono.service.management.tenant.Tenant;
28-
import org.eclipse.hono.util.IdentityTemplate;
2927
import org.eclipse.hono.util.RegistryManagementConstants;
3028
import org.eclipse.hono.util.Strings;
3129

3230
import com.fasterxml.jackson.annotation.JsonCreator;
3331
import com.fasterxml.jackson.annotation.JsonCreator.Mode;
34-
import com.fasterxml.jackson.annotation.JsonGetter;
3532
import com.fasterxml.jackson.annotation.JsonIgnore;
3633
import com.fasterxml.jackson.annotation.JsonInclude;
3734
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -81,7 +78,7 @@ protected X509CertificateCredential(final String authId, final String generatedA
8178
* @param generatedAuthId the authentication identifier generated by applying the <em>auth-id-template</em>
8279
* from the tenant's trust anchor to the client certificate's subject DN.
8380
* @param secrets The credential's secret(s).
84-
* @throws NullPointerException if subject DN and secrets are {@code null}.
81+
* @throws NullPointerException if subject DN or secrets is {@code null}.
8582
*/
8683
private X509CertificateCredential(final String subjectDN, final String issuerDN, final String generatedAuthId,
8784
final List<X509CertificateSecret> secrets) {
@@ -107,7 +104,7 @@ private X509CertificateCredential(final String subjectDN, final String issuerDN,
107104
* @param generatedAuthId the authentication identifier generated by applying the <em>auth-id-template</em>
108105
* from the tenant's trust anchor to the client certificate's subject DN.
109106
* @param secrets The credential's secret(s).
110-
* @throws NullPointerException if certificate bytes, subject DN and secrets are {@code null}.
107+
* @throws NullPointerException if certificate bytes and either subject DN or secrets are {@code null}.
111108
* @throws IllegalArgumentException if the given byte array cannot be decoded into an X.509 certificate or if
112109
* the given subject and issuer DNs are not a valid X.500 distinguished name or if
113110
* secrets is empty.
@@ -164,7 +161,7 @@ public static X509CertificateCredential fromSubjectDn(
164161
* @param generatedAuthId the authentication identifier generated by applying the <em>auth-id-template</em>
165162
* from the tenant's trust anchor to the client certificate's subject DN.
166163
* @param secrets The credential's secret(s).
167-
* @throws NullPointerException if any of the parameters are {@code null}.
164+
* @throws NullPointerException if subject DN or secrets is {@code null}.
168165
* @throws IllegalArgumentException if the given subject DN or issuer DN is not a valid X.500
169166
* distinguished name or if secrets is empty.
170167
* @return The credentials.
@@ -262,38 +259,6 @@ public final X509CertificateCredential setSecrets(final List<X509CertificateSecr
262259
return this;
263260
}
264261

265-
/**
266-
* Applies the <em>auth-id-template</em> from the tenant's trust anchor to the client certificate's subject DN.
267-
* <p>
268-
* It is only applicable, if a template is configured.
269-
*
270-
* @param tenant The tenant information.
271-
* @return the credential with the generated authentication identifier.
272-
* @throws NullPointerException if the tenant is {@code null}.
273-
*/
274-
@JsonIgnore
275-
public final X509CertificateCredential applyAuthIdTemplate(final Tenant tenant) {
276-
Objects.requireNonNull(tenant, "tenant information must not be null");
277-
278-
final String generatedAuthId = Optional.ofNullable(issuerDN)
279-
.flatMap(tenant::getAuthIdTemplate)
280-
.map(IdentityTemplate::new)
281-
.map(t -> t.apply(getAuthId()))
282-
.orElse(null);
283-
final X509CertificateCredential credential = new X509CertificateCredential(getAuthId(),
284-
generatedAuthId, secrets) {
285-
286-
@Override
287-
@JsonGetter(value = RegistryManagementConstants.FIELD_GENERATED_AUTH_ID)
288-
protected String getGeneratedAuthId() {
289-
return super.getGeneratedAuthId();
290-
}
291-
};
292-
293-
copyCommonAttributesTo(credential);
294-
return credential;
295-
}
296-
297262
/**
298263
* Overrides the authentication identifier with the generated authentication identifier,
299264
* if the generated one is not {@code null}.
@@ -310,6 +275,15 @@ public final X509CertificateCredential overrideAuthIdWithGeneratedAuthId() {
310275
return credential;
311276
}
312277

278+
/**
279+
* Gets the issuer DN of the client certificate.
280+
*
281+
* @return The issuer DN of the client certificate or {@code null}.
282+
*/
283+
protected final String getIssuerDN() {
284+
return issuerDN;
285+
}
286+
313287
/**
314288
* Gets the authentication identifier generated by applying the <em>auth-id-template</em>
315289
* from the tenant's trust anchor to the client certificate's subject DN.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2021 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Eclipse Public License 2.0 which is available at
9+
* http://www.eclipse.org/legal/epl-2.0
10+
*
11+
* SPDX-License-Identifier: EPL-2.0
12+
*******************************************************************************/
13+
package org.eclipse.hono.service.management.credentials;
14+
15+
import java.util.List;
16+
import java.util.Objects;
17+
import java.util.Optional;
18+
19+
import org.eclipse.hono.service.management.tenant.Tenant;
20+
import org.eclipse.hono.util.IdentityTemplate;
21+
import org.eclipse.hono.util.RegistryManagementConstants;
22+
23+
import com.fasterxml.jackson.annotation.JsonGetter;
24+
import com.fasterxml.jackson.annotation.JsonIgnore;
25+
26+
/**
27+
* An extended {@link X509CertificateCredential} to handle the generated authentication identifier.
28+
*/
29+
public class X509CertificateCredentialWithGeneratedAuthId extends X509CertificateCredential {
30+
31+
/**
32+
* Creates a new credentials object from the given authentication identifier, generated authentication
33+
* identifier and secrets.
34+
*
35+
* @param authId The authentication identifier.
36+
* @param generatedAuthId the authentication identifier generated by applying the <em>auth-id-template</em>
37+
* from the tenant's trust anchor to the client certificate's subject DN.
38+
* @param secrets The credential's secret(s).
39+
* @throws NullPointerException if authentication identifier or secrets is {@code null}.
40+
*/
41+
private X509CertificateCredentialWithGeneratedAuthId(final String authId, final String generatedAuthId,
42+
final List<X509CertificateSecret> secrets) {
43+
super(authId, generatedAuthId, secrets);
44+
}
45+
46+
/**
47+
* Gets the authentication identifier generated by applying the <em>auth-id-template</em> from the tenant's
48+
* trust anchor to the client certificate's subject DN.
49+
*
50+
* @return The generated authentication identifier or {@code null}.
51+
*/
52+
@JsonGetter(value = RegistryManagementConstants.FIELD_GENERATED_AUTH_ID)
53+
@Override
54+
public final String getGeneratedAuthId() {
55+
return super.getGeneratedAuthId();
56+
}
57+
58+
/**
59+
* Applies the <em>auth-id-template</em> from the tenant's trust anchor to the client certificate's subject DN.
60+
* <p>
61+
* It is only applicable, if a template is configured.
62+
*
63+
* @param credential The x509 certificate credential.
64+
* @param tenant The tenant information.
65+
* @return the credential with generated authentication identifier.
66+
* @throws NullPointerException if the tenant is {@code null}.
67+
*/
68+
@JsonIgnore
69+
public static X509CertificateCredentialWithGeneratedAuthId applyAuthIdTemplate(
70+
final X509CertificateCredential credential, final Tenant tenant) {
71+
Objects.requireNonNull(credential, "credential must not be null");
72+
Objects.requireNonNull(tenant, "tenant information must not be null");
73+
74+
final String generatedAuthId = Optional.ofNullable(credential.getIssuerDN())
75+
.flatMap(tenant::getAuthIdTemplate)
76+
.map(IdentityTemplate::new)
77+
.map(t -> t.apply(credential.getAuthId()))
78+
.orElse(null);
79+
final X509CertificateCredentialWithGeneratedAuthId credWithId = new X509CertificateCredentialWithGeneratedAuthId(
80+
credential.getAuthId(), generatedAuthId, credential.getSecrets());
81+
82+
credWithId.setComment(credential.getComment());
83+
credWithId.setEnabled(credential.isEnabled());
84+
credWithId.setExtensions(credential.getExtensions());
85+
86+
return credWithId;
87+
}
88+
}

services/device-registry-base/src/test/java/org/eclipse/hono/service/management/credentials/CredentialsTest.java

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,19 @@
3131
import java.security.cert.CertificateFactory;
3232
import java.security.cert.X509Certificate;
3333
import java.time.Instant;
34+
import java.time.temporal.ChronoUnit;
3435
import java.util.ArrayList;
3536
import java.util.Base64;
3637
import java.util.List;
38+
import java.util.UUID;
3739
import java.util.function.Function;
3840
import java.util.regex.Pattern;
3941
import java.util.stream.Stream;
4042

4143
import javax.security.auth.x500.X500Principal;
4244

45+
import org.eclipse.hono.service.management.tenant.Tenant;
46+
import org.eclipse.hono.service.management.tenant.TrustedCertificateAuthority;
4347
import org.eclipse.hono.util.RegistryManagementConstants;
4448
import org.junit.jupiter.api.BeforeEach;
4549
import org.junit.jupiter.api.Test;
@@ -242,18 +246,21 @@ public void testDecodePskCredential() {
242246
}
243247

244248
/**
245-
* Test encoding an X.509 secret.
249+
* Test encoding an X.509 credential.
246250
*/
247251
@Test
248252
public void testEncodeX509Credential() {
249253

250254
final X509CertificateSecret x509Secret = new X509CertificateSecret();
251255
addCommonProperties(x509Secret);
252-
var credential = X509CertificateCredential.fromSubjectDn("CN=foo, O=bar", List.of(x509Secret));
256+
var credential = X509CertificateCredential.fromSubjectDn("CN=foo, O=bar", "CN=test, O=bar", "foo-bar",
257+
List.of(x509Secret));
253258

254259
JsonObject json = JsonObject.mapFrom(credential);
255260
assertEquals("x509-cert", json.getString(RegistryManagementConstants.FIELD_TYPE));
256261
assertEquals("CN=foo,O=bar", json.getString(RegistryManagementConstants.FIELD_AUTH_ID));
262+
assertEquals(false, json.containsKey(RegistryManagementConstants.FIELD_GENERATED_AUTH_ID));
263+
assertEquals(false, json.containsKey(RegistryManagementConstants.FIELD_ISSUER_DN));
257264
JsonObject secret = json.getJsonArray(RegistryManagementConstants.FIELD_SECRETS).getJsonObject(0);
258265
assertCommonSecretProperties(secret);
259266

@@ -295,6 +302,106 @@ public void testDecodeX509CredentialFromSubjectDn() {
295302
assertCommonSecretProperties(secret);
296303
}
297304

305+
/**
306+
* Verifies that a JSON object containing an auth-id, generated-auth-id and a secret can be decoded into an X.509
307+
* credential.
308+
*/
309+
@Test
310+
void testDecodeX509CredentialWithGeneratedAuthId() {
311+
final JsonObject jsonCredential = new JsonObject()
312+
.put(RegistryManagementConstants.FIELD_TYPE, RegistryManagementConstants.SECRETS_TYPE_X509_CERT)
313+
.put(RegistryManagementConstants.FIELD_AUTH_ID, "CN=Acme")
314+
.put(RegistryManagementConstants.FIELD_GENERATED_AUTH_ID, "Acme")
315+
.put(RegistryManagementConstants.FIELD_COMMENT, "comment")
316+
.put(RegistryManagementConstants.FIELD_ENABLED, true)
317+
.put(RegistryManagementConstants.FIELD_SECRETS, new JsonArray()
318+
.add(new JsonObject()
319+
.put(RegistryManagementConstants.FIELD_ENABLED, true)
320+
.put(RegistryManagementConstants.FIELD_SECRETS_NOT_BEFORE, NOT_BEFORE_STRING)
321+
.put(RegistryManagementConstants.FIELD_SECRETS_NOT_AFTER, NOT_AFTER_STRING)
322+
.put(RegistryManagementConstants.FIELD_SECRETS_COMMENT, SECRET_COMMENT)));
323+
324+
final X509CertificateCredential credential = jsonCredential.mapTo(X509CertificateCredential.class);
325+
326+
assertEquals("CN=Acme", credential.getAuthId());
327+
assertEquals("Acme", credential.getGeneratedAuthId());
328+
assertEquals("comment", credential.getComment());
329+
assertTrue(credential.isEnabled());
330+
assertEquals(1, credential.getSecrets().size());
331+
332+
final X509CertificateSecret secret = credential.getSecrets().get(0);
333+
assertCommonSecretProperties(secret);
334+
}
335+
336+
/**
337+
* Verifies an X.509 credential obtained by overriding the authId with the generated authId.
338+
*/
339+
@Test
340+
void testX509CredentialObtainedByOverridingAuthId() {
341+
final String subjectDn = "CN=foo, O=bar";
342+
final String generatedAuthId = "foo-bar";
343+
final X509CertificateSecret x509Secret = new X509CertificateSecret();
344+
addCommonProperties(x509Secret);
345+
final var credential = X509CertificateCredential.fromSubjectDn(subjectDn, null, generatedAuthId,
346+
List.of(x509Secret));
347+
final X509CertificateCredential credentialWithOverriddenAuthId = credential.overrideAuthIdWithGeneratedAuthId();
348+
349+
assertEquals(generatedAuthId, credentialWithOverriddenAuthId.getAuthId());
350+
assertTrue(credentialWithOverriddenAuthId.isEnabled());
351+
assertEquals(1, credentialWithOverriddenAuthId.getSecrets().size());
352+
353+
final X509CertificateSecret secret = credentialWithOverriddenAuthId.getSecrets().get(0);
354+
assertCommonSecretProperties(secret);
355+
}
356+
357+
/**
358+
* Verifies an X.509 credential obtained by applying the given auth-id-template
359+
* to the client certificate's subject DN.
360+
*/
361+
@Test
362+
void testX509CredentialWithGeneratedAuthIdObtainedByApplyingAuthIdTemplate() {
363+
final String commonName = UUID.randomUUID().toString();
364+
final String issuerDn = "CN=testBase,OU=Hono,O=Eclipse";
365+
final String subjectDN = String.format("CN=%s,OU=Hono,O=Eclipse", commonName);
366+
final String authIdTemplate = "auth-{{subject-cn}}-{{subject-ou}}-{{subject-o}}";
367+
final String generatedAuthId = String.format("auth-%s-Hono-Eclipse", commonName);
368+
369+
final var trustedCa = new TrustedCertificateAuthority()
370+
.setSubjectDn(issuerDn)
371+
.setPublicKey("NOTAKEY".getBytes(StandardCharsets.UTF_8))
372+
.setAuthIdTemplate(authIdTemplate)
373+
.setNotBefore(Instant.now().minus(1, ChronoUnit.DAYS))
374+
.setNotAfter(Instant.now().plus(2, ChronoUnit.DAYS));
375+
final var tenant = new Tenant().setTrustedCertificateAuthorities(List.of(trustedCa));
376+
final X509CertificateSecret x509Secret = new X509CertificateSecret();
377+
addCommonProperties(x509Secret);
378+
final var credential = X509CertificateCredential.fromSubjectDn(subjectDN, issuerDn, null, List.of(x509Secret));
379+
credential.setEnabled(true)
380+
.setComment("comment");
381+
382+
final X509CertificateCredentialWithGeneratedAuthId credentialWithGeneratedAuthId = X509CertificateCredentialWithGeneratedAuthId
383+
.applyAuthIdTemplate(credential, tenant);
384+
385+
assertEquals(subjectDN, credentialWithGeneratedAuthId.getAuthId());
386+
assertEquals(generatedAuthId, credentialWithGeneratedAuthId.getGeneratedAuthId());
387+
assertEquals("comment", credentialWithGeneratedAuthId.getComment());
388+
assertTrue(credentialWithGeneratedAuthId.isEnabled());
389+
assertEquals(1, credentialWithGeneratedAuthId.getSecrets().size());
390+
391+
final X509CertificateSecret secret = credentialWithGeneratedAuthId.getSecrets().get(0);
392+
assertCommonSecretProperties(secret);
393+
394+
final JsonObject json = JsonObject.mapFrom(credentialWithGeneratedAuthId);
395+
396+
assertEquals("x509-cert", json.getString(RegistryManagementConstants.FIELD_TYPE));
397+
assertEquals(subjectDN, json.getString(RegistryManagementConstants.FIELD_AUTH_ID));
398+
assertEquals(generatedAuthId, json.getString(RegistryManagementConstants.FIELD_GENERATED_AUTH_ID));
399+
assertEquals("comment", json.getString(RegistryManagementConstants.FIELD_COMMENT));
400+
401+
final JsonObject secretJson = json.getJsonArray(RegistryManagementConstants.FIELD_SECRETS).getJsonObject(0);
402+
assertCommonSecretProperties(secretJson);
403+
}
404+
298405
/**
299406
* Verifies that a JSON object containing a client certificate can be decoded into an X.509 credential.
300407
*

0 commit comments

Comments
 (0)