Skip to content

Commit 166ff71

Browse files
committed
implement generic RAT based on OCTET String
1 parent d6b53f8 commit 166ff71

22 files changed

+1811
-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: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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.EvidenceBundle;
21+
import com.siemens.pki.verifieradapter.asn1.EvidenceStatement;
22+
import com.siemens.pki.verifieradapter.asn1.NonceResponseValue.NonceResponse;
23+
import java.math.BigInteger;
24+
import org.bouncycastle.asn1.x509.Certificate;
25+
26+
/**
27+
* attestation specific configuration
28+
*
29+
*/
30+
public interface ClientAttestationContext {
31+
32+
/**
33+
* obtain evidence statement from attestation
34+
* @param attestationNonce DER encoded {@link NonceResponse}
35+
* @return DER encoded {@link EvidenceStatement}
36+
*/
37+
byte[] getEvidenceStatement(byte[] attestationNonce);
38+
39+
/**
40+
* indicates which Verifier to request a nonce from
41+
* @return hint or <code>null</code>
42+
*/
43+
default String getNonceRequestHint() {
44+
return null;
45+
}
46+
47+
/**
48+
* indicates the required length of the requested nonce
49+
* @return length or <code>null</code>
50+
*/
51+
default BigInteger getNonceRequestLen() {
52+
return null;
53+
}
54+
/**
55+
* indicates which Evidence type to request a nonce for
56+
* @return OID formatted string or <code>null</code>
57+
*/
58+
default String getNonceRequestType() {
59+
return null;
60+
}
61+
/**
62+
* get certs to include in {@link EvidenceBundle}
63+
* @return certs
64+
*/
65+
default Certificate[] getEvidenceBundleCerts() {
66+
return null;
67+
}
68+
}

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: 77 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,50 @@ 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 =
459+
new EvidenceBundle(evidenceStatements, attestationContext.getEvidenceBundleCerts());
460+
break;
461+
}
462+
}
463+
}
464+
}
412465
final EnrollmentContext enrollmentContext = clientContext.getEnrollmentContext();
413466
final KeyPair certificateKeypair = enrollmentContext.getCertificateKeypair();
414467

@@ -459,16 +512,25 @@ public EnrollmentResult invokeEnrollment() {
459512
case PKIBody.TYPE_CERT_REQ:
460513
case PKIBody.TYPE_INIT_REQ: {
461514
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);
515+
final List<Extension> extensions = new ArrayList<>();
516+
517+
final List<TemplateExtension> extensionsFromConfig = enrollmentContext.getExtensions();
518+
if (extensionsFromConfig != null) {
519+
extensionsFromConfig.stream()
520+
.map(ext -> new Extension(
521+
new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ext.getValue()))
522+
.forEach(extensions::add);
523+
}
524+
if (evidenceBundle != null) {
525+
extensions.add(
526+
Extension.create(AttestationObjectIdentifiers.id_aa_evidence, false, evidenceBundle));
527+
}
468528
final CertTemplateBuilder ctb = new CertTemplateBuilder()
469529
.setSubject(ifNotNull(subject, X500Name::new))
470-
.setPublicKey(enrolledPublicKeyInfo)
471-
.setExtensions(extensions);
530+
.setPublicKey(enrolledPublicKeyInfo);
531+
if (!extensions.isEmpty()) {
532+
ctb.setExtensions(new Extensions(extensions.toArray(new Extension[extensions.size()])));
533+
}
472534
requestBody = PkiMessageGenerator.generateIrCrKurBody(
473535
enrollmentType, ctb.build(), null, enrolledPrivateKey);
474536
pvno = enrolledPrivateKey == null ? PKIHeader.CMP_2021 : PKIHeader.CMP_2000;
@@ -478,9 +540,13 @@ public EnrollmentResult invokeEnrollment() {
478540
LOGGER.error("EnrollmentType must be 0(ir), 2(cr), 7(kur) or 4(p10cr)");
479541
return null;
480542
}
481-
final PKIMessage responseMessage = requestHandler.sendReceiveValidateMessage(
482-
requestHandler.buildInitialRequest(requestBody, enrollmentContext.getRequestImplictConfirm(), pvno),
483-
enrollmentType);
543+
final PKIMessage enrollmentRequestMessage = ratNonceResponse == null
544+
? requestHandler.buildInitialRequest(
545+
requestBody, enrollmentContext.getRequestImplictConfirm(), pvno)
546+
: requestHandler.buildFurtherRequest(
547+
ratNonceResponse, requestBody, enrollmentContext.getRequestImplictConfirm(), pvno);
548+
final PKIMessage responseMessage =
549+
requestHandler.sendReceiveValidateMessage(enrollmentRequestMessage, enrollmentType);
484550
final PKIBody responseBody = responseMessage.getBody();
485551
final int responseMessageType = responseBody.getType();
486552
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+
}

0 commit comments

Comments
 (0)