Skip to content

Commit 86b8402

Browse files
committed
implement generic RAT based on OCTET String
1 parent d6b53f8 commit 86b8402

22 files changed

+1764
-17
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,24 @@ at `target/site/apidocs/com/siemens/pki/cmpracomponent/main/CmpRaComponent.html`
332332
# Acknowledgements
333333

334334
This work was partly funded by the German Federal Ministry of Education and Research in the project Quoryptan through grant number **16KIS2033**.
335+
336+
# Support for Remote Attestation in Certificate Signing Requests (CSRs)
337+
338+
This branch supports the internet drafts [Use of Remote Attestation with Certification Signing Requests](https://datatracker.ietf.org/doc/draft-ietf-lamps-csr-attestation/) and [Nonce-based Freshness for Remote Attestation in Certificate Signing Requests (CSRs) for the Certification Management Protocol (CMP) and for Enrollment over Secure Transport (EST) draft-ietf-lamps-attestation-freshness](https://datatracker.ietf.org/doc/draft-ietf-lamps-attestation-freshness/).
339+
340+
## Design
341+
342+
The [RA configuration interface](src/main/java/com/siemens/pki/cmpracomponent/configuration/Configuration.java) allows to
343+
register an [`com.siemens.pki.cmpracomponent.configuration.RatVerifierAdapter`](src\main\java\com\siemens\pki\cmpracomponent\configuration\RatVerifierAdapter.java) interface to an external Verifier. This interface is called
344+
by a modified [`com.siemens.pki.cmpracomponent.msgprocessing.ServiceImplementation`](src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/ServiceImplementation.java) to obtain a fresh RAT nonce via
345+
GENM/GENREP and also by the [`com.siemens.pki.cmpracomponent.msgprocessing.RaDownstream`](src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/RaDownstream.java) to process the subsequent CRMF template.
346+
347+
The [Client configuration interface](./src/main/java/com/siemens/pki/cmpclientcomponent/configuration/ClientContext.java) allows to register an [`com.siemens.pki.cmpclientcomponent.configuration.ClientAttestationContext`](src/main/java/com/siemens/pki/cmpclientcomponent/configuration/ClientAttestationContext.java) interface to an external Attester.
348+
349+
The package [`com.siemens.pki.verifieradapter.asn1`](src/main/java/com/siemens/pki/verifieradapter/asn1) supports some ASN.1 definitons from [Use of Remote Attestation with Certification Signing Requests](https://datatracker.ietf.org/doc/draft-ietf-lamps-csr-attestation/).
350+
351+
## Test case
352+
353+
The [`com.siemens.pki.cmpclientcomponent.test.TestCrWithRAT`](src/test/java/com/siemens/pki/cmpclientcomponent/test/TestCrWithRAT.java) shows setup and execution of a RAT sequence.
354+
355+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2024 Siemens AG
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
* not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
package com.siemens.pki.cmpclientcomponent.configuration;
19+
20+
import com.siemens.pki.verifieradapter.asn1.EvidenceStatement;
21+
import com.siemens.pki.verifieradapter.asn1.NonceResponseValue.NonceResponse;
22+
import java.math.BigInteger;
23+
24+
/**
25+
* attestation specific configuration
26+
*
27+
*/
28+
public interface ClientAttestationContext {
29+
30+
/**
31+
* obtain evidence statement from attestation
32+
* @param attestationNonce DER encoded {@link NonceResponse}
33+
* @return DER encoded {@link EvidenceStatement}
34+
*/
35+
byte[] getEvidenceStatement(byte[] attestationNonce);
36+
37+
/**
38+
* indicates which Verifier to request a nonce from
39+
* @return hint or <code>null</code>
40+
*/
41+
default String getNonceRequestHint() {
42+
return null;
43+
}
44+
45+
/**
46+
* indicates the required length of the requested nonce
47+
* @return length or <code>null</code>
48+
*/
49+
default BigInteger getNonceRequestLen() {
50+
return null;
51+
}
52+
/**
53+
* indicates which Evidence type to request a nonce for
54+
* @return OID formatted string or <code>null</code>
55+
*/
56+
default String getNonceRequestType() {
57+
return null;
58+
}
59+
}

src/main/java/com/siemens/pki/cmpclientcomponent/configuration/ClientContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@
2323
*/
2424
public interface ClientContext {
2525

26+
/**
27+
* get remote attestation specific configuration
28+
* @return remote attestation specific configuration
29+
* or <code>null</code> is no remote attestation shall
30+
* be used
31+
*/
32+
default ClientAttestationContext getAttestationContext() {
33+
return null;
34+
}
35+
2636
/**
2737
* get enrollment specific configuration
2838
*

src/main/java/com/siemens/pki/cmpclientcomponent/main/ClientRequestHandler.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,21 @@ PKIMessage buildFurtherRequest(final PKIMessage formerResponse, final PKIBody re
184184
false);
185185
}
186186

187+
PKIMessage buildFurtherRequest(
188+
final PKIMessage formerResponse,
189+
final PKIBody requestBody,
190+
final boolean withImplicitConfirm,
191+
final int pvno)
192+
throws Exception {
193+
final PKIHeader formerResponseHeader = formerResponse.getHeader();
194+
return buildRequest(
195+
requestBody,
196+
formerResponseHeader.getTransactionID(),
197+
formerResponseHeader.getSenderNonce(),
198+
pvno,
199+
withImplicitConfirm);
200+
}
201+
187202
PKIMessage buildInitialRequest(final PKIBody requestBody, final boolean withImplicitConfirm) throws Exception {
188203
return buildInitialRequest(requestBody, withImplicitConfirm, DEFAULT_PVNO);
189204
}
@@ -297,6 +312,10 @@ PKIBody sendReceiveInitialBody(final PKIBody body, final boolean withImplicitCon
297312
.getBody();
298313
}
299314

315+
PKIMessage sendReceiveInitialMessage(final PKIBody body) throws Exception {
316+
return sendReceiveValidateMessage(buildInitialRequest(body, false), body.getType());
317+
}
318+
300319
PKIMessage sendReceiveValidateMessage(PKIMessage request, final int firstRequestType) throws Exception {
301320
if (nestedValidatorAndProtector != null) {
302321
request = nestedValidatorAndProtector

src/main/java/com/siemens/pki/cmpclientcomponent/main/CmpClient.java

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import static com.siemens.pki.cmpracomponent.util.NullUtil.defaultIfNull;
2121
import static com.siemens.pki.cmpracomponent.util.NullUtil.ifNotNull;
2222

23+
import com.siemens.pki.cmpclientcomponent.configuration.ClientAttestationContext;
2324
import com.siemens.pki.cmpclientcomponent.configuration.ClientContext;
2425
import com.siemens.pki.cmpclientcomponent.configuration.EnrollmentContext;
26+
import com.siemens.pki.cmpclientcomponent.configuration.EnrollmentContext.TemplateExtension;
2527
import com.siemens.pki.cmpclientcomponent.configuration.RevocationContext;
2628
import com.siemens.pki.cmpracomponent.configuration.CmpMessageInterface;
2729
import com.siemens.pki.cmpracomponent.configuration.CrlUpdateRetrievalHandler;
@@ -40,6 +42,13 @@
4042
import com.siemens.pki.cmpracomponent.protection.ProtectionProvider;
4143
import com.siemens.pki.cmpracomponent.protection.SignatureBasedProtection;
4244
import com.siemens.pki.cmpracomponent.util.MessageDumper;
45+
import com.siemens.pki.verifieradapter.asn1.AttestationObjectIdentifiers;
46+
import com.siemens.pki.verifieradapter.asn1.EvidenceBundle;
47+
import com.siemens.pki.verifieradapter.asn1.EvidenceStatement;
48+
import com.siemens.pki.verifieradapter.asn1.NonceRequestValue;
49+
import com.siemens.pki.verifieradapter.asn1.NonceRequestValue.NonceRequest;
50+
import com.siemens.pki.verifieradapter.asn1.NonceResponseValue;
51+
import com.siemens.pki.verifieradapter.asn1.NonceResponseValue.NonceResponse;
4352
import java.io.IOException;
4453
import java.security.KeyPair;
4554
import java.security.PrivateKey;
@@ -409,6 +418,49 @@ private boolean grantsImplicitConfirm(final PKIMessage msg) {
409418
public EnrollmentResult invokeEnrollment() {
410419

411420
try {
421+
PKIMessage ratNonceResponse = null;
422+
EvidenceBundle evidenceBundle = null;
423+
final ClientAttestationContext attestationContext = clientContext.getAttestationContext();
424+
if (attestationContext != null) {
425+
NonceRequest nonceRequest = new NonceRequest(
426+
attestationContext.getNonceRequestLen(),
427+
attestationContext.getNonceRequestType(),
428+
attestationContext.getNonceRequestHint());
429+
NonceRequestValue nonceRequestValue = new NonceRequestValue(new NonceRequest[] {nonceRequest});
430+
ratNonceResponse = requestHandler.sendReceiveInitialMessage(new PKIBody(
431+
PKIBody.TYPE_GEN_MSG,
432+
new GenMsgContent(new InfoTypeAndValue(
433+
AttestationObjectIdentifiers.id_it_NonceRequest, nonceRequestValue))));
434+
final PKIBody ratNonceResponseBody = ratNonceResponse.getBody();
435+
if (ratNonceResponseBody.getType() != PKIBody.TYPE_GEN_REP) {
436+
logUnexpectedResponse(ratNonceResponseBody);
437+
return null;
438+
}
439+
440+
final GenRepContent content = (GenRepContent) ratNonceResponseBody.getContent();
441+
final InfoTypeAndValue[] itav = content.toInfoTypeAndValueArray();
442+
if (itav != null) {
443+
for (final InfoTypeAndValue aktitav : itav) {
444+
if (AttestationObjectIdentifiers.id_it_NonceResponse.equals(aktitav.getInfoType())) {
445+
final ASN1Encodable infoValue = aktitav.getInfoValue();
446+
if (infoValue == null) {
447+
LOGGER.error("no RAT nonce received");
448+
return null;
449+
}
450+
final NonceResponseValue ratNonce = NonceResponseValue.getInstance(infoValue);
451+
NonceResponse[] nonceResponses = ratNonce.getNonceResponse();
452+
EvidenceStatement[] evidenceStatements = new EvidenceStatement[nonceResponses.length];
453+
for (int i = 0; i < nonceResponses.length; i++) {
454+
NonceResponse aktnonce = nonceResponses[i];
455+
evidenceStatements[i] = EvidenceStatement.getInstance(
456+
attestationContext.getEvidenceStatement(aktnonce.getEncoded()));
457+
}
458+
evidenceBundle = new EvidenceBundle(evidenceStatements, null);
459+
break;
460+
}
461+
}
462+
}
463+
}
412464
final EnrollmentContext enrollmentContext = clientContext.getEnrollmentContext();
413465
final KeyPair certificateKeypair = enrollmentContext.getCertificateKeypair();
414466

@@ -459,16 +511,25 @@ public EnrollmentResult invokeEnrollment() {
459511
case PKIBody.TYPE_CERT_REQ:
460512
case PKIBody.TYPE_INIT_REQ: {
461513
final String subject = enrollmentContext.getSubject();
462-
final Extension[] arrayOfExtensions =
463-
ifNotNull(enrollmentContext.getExtensions(), exts -> exts.stream()
464-
.map(ext -> new Extension(
465-
new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ext.getValue()))
466-
.toArray(Extension[]::new));
467-
final Extensions extensions = ifNotNull(arrayOfExtensions, Extensions::new);
514+
final List<Extension> extensions = new ArrayList<>();
515+
516+
final List<TemplateExtension> extensionsFromConfig = enrollmentContext.getExtensions();
517+
if (extensionsFromConfig != null) {
518+
extensionsFromConfig.stream()
519+
.map(ext -> new Extension(
520+
new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ext.getValue()))
521+
.forEach(extensions::add);
522+
}
523+
if (evidenceBundle != null) {
524+
extensions.add(
525+
Extension.create(AttestationObjectIdentifiers.id_aa_evidence, false, evidenceBundle));
526+
}
468527
final CertTemplateBuilder ctb = new CertTemplateBuilder()
469528
.setSubject(ifNotNull(subject, X500Name::new))
470-
.setPublicKey(enrolledPublicKeyInfo)
471-
.setExtensions(extensions);
529+
.setPublicKey(enrolledPublicKeyInfo);
530+
if (!extensions.isEmpty()) {
531+
ctb.setExtensions(new Extensions(extensions.toArray(new Extension[extensions.size()])));
532+
}
472533
requestBody = PkiMessageGenerator.generateIrCrKurBody(
473534
enrollmentType, ctb.build(), null, enrolledPrivateKey);
474535
pvno = enrolledPrivateKey == null ? PKIHeader.CMP_2021 : PKIHeader.CMP_2000;
@@ -478,9 +539,13 @@ public EnrollmentResult invokeEnrollment() {
478539
LOGGER.error("EnrollmentType must be 0(ir), 2(cr), 7(kur) or 4(p10cr)");
479540
return null;
480541
}
481-
final PKIMessage responseMessage = requestHandler.sendReceiveValidateMessage(
482-
requestHandler.buildInitialRequest(requestBody, enrollmentContext.getRequestImplictConfirm(), pvno),
483-
enrollmentType);
542+
final PKIMessage enrollmentRequestMessage = ratNonceResponse == null
543+
? requestHandler.buildInitialRequest(
544+
requestBody, enrollmentContext.getRequestImplictConfirm(), pvno)
545+
: requestHandler.buildFurtherRequest(
546+
ratNonceResponse, requestBody, enrollmentContext.getRequestImplictConfirm(), pvno);
547+
final PKIMessage responseMessage =
548+
requestHandler.sendReceiveValidateMessage(enrollmentRequestMessage, enrollmentType);
484549
final PKIBody responseBody = responseMessage.getBody();
485550
final int responseMessageType = responseBody.getType();
486551
if (enrollmentType == PKIBody.TYPE_P10_CERT_REQ) {

src/main/java/com/siemens/pki/cmpracomponent/configuration/Configuration.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,19 @@ public interface Configuration {
8989
*/
9090
InventoryInterface getInventory(String certProfile, int bodyType);
9191

92+
/**
93+
* optionally access function to external remote attestation verify adapter
94+
*
95+
* @param certProfile certificate profile extracted from the CMP request header
96+
* generalInfo field or <code>null</code> if no certificate
97+
* profile was specified
98+
* @param bodyType request/response PKI Message Body type
99+
* @return external RatVerifierAdapter or <code>null</code>
100+
*/
101+
default RatVerifierAdapter getVerifierAdapter(String certProfile, int bodyType) {
102+
return null;
103+
}
104+
92105
/**
93106
* provide a persistence implementation
94107
*
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) 2023 Siemens AG
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
* not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
package com.siemens.pki.cmpracomponent.configuration;
19+
20+
/**
21+
* support message handler supporting Remote Attestation Procedures nonce genm
22+
* requests, see https://datatracker.ietf.org/doc/draft-ietf-rats-reference-interaction-models/ ,
23+
* and https://github.com/veraison/docs/blob/main/api/challenge-response/README.md
24+
*/
25+
public interface GetFreshRatNonceHandler extends SupportMessageHandlerInterface {
26+
// up to now only a placeholder
27+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2025 Siemens AG
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may
5+
* not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
* SPDX-License-Identifier: Apache-2.0
17+
*/
18+
package com.siemens.pki.cmpracomponent.configuration;
19+
20+
import com.siemens.pki.verifieradapter.asn1.AttestationResult;
21+
import com.siemens.pki.verifieradapter.asn1.EvidenceStatement;
22+
import java.math.BigInteger;
23+
24+
/**
25+
* adapter to remote attestation verfier
26+
*/
27+
public interface RatVerifierAdapter {
28+
29+
/**
30+
* turn evidence into verification result
31+
* @param transactionId current CMP transactionId, used to map related calls to getFreshRatNonce and processRatVerification
32+
* @param evidence evidence provided by EE as DER encoded {@link EvidenceStatement}
33+
* @return verification result as DER encoded {@link AttestationResult}
34+
* @throws Exception in case of error
35+
*/
36+
byte[] processRatVerification(byte[] transactionId, byte[] evidence) throws Exception;
37+
38+
interface NonceResponseRet {
39+
40+
/**
41+
* retuns the nonce of length len provided by the Verifier indicated with hint
42+
* @return nonce
43+
*/
44+
byte[] getNonce();
45+
46+
/**
47+
* indicates how long in seconds the Verifier considers the nonce valid
48+
* @return time in seconds or <code>null</code>
49+
*/
50+
default Integer getExpiry() {
51+
return null;
52+
}
53+
54+
/**
55+
* indicates which Verifier to request a nonce from
56+
* @return hint or <code>null</code>
57+
*/
58+
default String getHint() {
59+
return null;
60+
}
61+
62+
/**
63+
* indicates which Evidence type to request a nonce for
64+
* @return OID or <code>null</code>
65+
*/
66+
default String getType() {
67+
return null;
68+
}
69+
;
70+
}
71+
72+
/**
73+
* Generate nonce
74+
* @param transactionId current CMP transactionId, used to map related calls to getFreshRatNonce and processRatVerification
75+
* @param len the required length of the requested nonce, maybe <code>null</code>
76+
* @param type indicates which Evidence type to request a nonce for, OID as string or <code>null</code>
77+
* @param hint indicates which Verifier to request a nonce from, maybe <code>null</code>
78+
* @param encodedNonceRequest encoded NonceRequest containing len, type and hint
79+
* @return fresh BER encoded NonceResponse
80+
*/
81+
NonceResponseRet generateNonce(
82+
byte[] transactionId, BigInteger len, String type, String hint, byte[] encodedNonceRequest)
83+
throws Exception;
84+
}

0 commit comments

Comments
 (0)