Skip to content

Commit 2362218

Browse files
committed
implement generic RAT based on OCTET String
1 parent 1c493f3 commit 2362218

22 files changed

+1762
-19
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,3 +332,28 @@ 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.VerifierAdapter`](src/main/java/com/siemens/pki/verifieradapter/VerifierAdapter.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+
As an example implementation the REST interface to a [Veraison Attestation Verification Service](https://github.com/veraison) is provided at [`com.siemens.pki.verifieradapter.veraison.rest`](src/main/java/com/siemens/pki/verifieradapter/veraison/rest).
348+
349+
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.
350+
351+
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/).
352+
353+
## Test case
354+
355+
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. To execute the TC the verifier REST endpoint [`com.siemens.pki.verifieradapter.veraison.rest.RestConfig.DEFAULT_VERIFIER_BASE_PATH`](src/main/java/com/siemens/pki/verifieradapter/veraison/rest/RestConfig.java) needs to be adapted to the current network setup.
356+
357+
## Standalone Setup
358+
For a standalone setup look at [LightweightCmpRa](https://code.siemens.com/ct-rda-cst-ses-de/remote-attestation/base-functionality/lightweightcmpra/-/tree/RAT_integration?ref_type=heads).
359+
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@
2323
*/
2424
public interface ClientContext {
2525

26+
/**
27+
* get remote attestation specific configuration
28+
* @return remote attestation specific configuration
29+
*/
30+
default ClientAttestationContext getAttestationContext() {
31+
return null;
32+
}
33+
2634
/**
2735
* get enrollment specific configuration
2836
*

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: 84 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.ByteArrayInputStream;
4453
import java.io.IOException;
4554
import java.security.KeyPair;
@@ -90,6 +99,7 @@
9099
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
91100
import org.bouncycastle.asn1.x509.Time;
92101
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
102+
import org.bouncycastle.util.encoders.Hex;
93103
import org.slf4j.Logger;
94104
import org.slf4j.LoggerFactory;
95105

@@ -140,6 +150,7 @@ public interface EnrollmentResult {
140150

141151
/**
142152
* ctor
153+
*
143154
* @param certProfile certificate profile to be used for enrollment.
144155
* <code>null</code> if no certificate profile
145156
* should be used.
@@ -163,6 +174,12 @@ public CmpClient(
163174
this.clientContext = clientContext;
164175
}
165176

177+
private byte[] callAttester(byte[] ratNonce) {
178+
final String nonceAsHexstring = Hex.toHexString(ratNonce);
179+
LOGGER.debug("call attester with nonce " + nonceAsHexstring);
180+
return ("this would be the attestation for nonce " + nonceAsHexstring).getBytes();
181+
}
182+
166183
private ArrayList<X509Certificate> fetchCaCertificatesFromValue(final ASN1Encodable infoValue) {
167184
if (infoValue == null) {
168185
return null;
@@ -404,6 +421,49 @@ private boolean grantsImplicitConfirm(final PKIMessage msg) {
404421
public EnrollmentResult invokeEnrollment() {
405422

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

@@ -454,16 +514,25 @@ public EnrollmentResult invokeEnrollment() {
454514
case PKIBody.TYPE_CERT_REQ:
455515
case PKIBody.TYPE_INIT_REQ: {
456516
final String subject = enrollmentContext.getSubject();
457-
final Extension[] arrayOfExtensions =
458-
ifNotNull(enrollmentContext.getExtensions(), exts -> exts.stream()
459-
.map(ext -> new Extension(
460-
new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ext.getValue()))
461-
.toArray(Extension[]::new));
462-
final Extensions extensions = ifNotNull(arrayOfExtensions, Extensions::new);
517+
final List<Extension> extensions = new ArrayList<>();
518+
519+
final List<TemplateExtension> extensionsFromConfig = enrollmentContext.getExtensions();
520+
if (extensionsFromConfig != null) {
521+
extensionsFromConfig.stream()
522+
.map(ext -> new Extension(
523+
new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ext.getValue()))
524+
.forEach(extensions::add);
525+
}
526+
if (evidenceBundle != null) {
527+
extensions.add(
528+
Extension.create(AttestationObjectIdentifiers.id_aa_evidence, false, evidenceBundle));
529+
}
463530
final CertTemplateBuilder ctb = new CertTemplateBuilder()
464531
.setSubject(ifNotNull(subject, X500Name::new))
465-
.setPublicKey(enrolledPublicKeyInfo)
466-
.setExtensions(extensions);
532+
.setPublicKey(enrolledPublicKeyInfo);
533+
if (!extensions.isEmpty()) {
534+
ctb.setExtensions(new Extensions(extensions.toArray(new Extension[extensions.size()])));
535+
}
467536
requestBody = PkiMessageGenerator.generateIrCrKurBody(
468537
enrollmentType, ctb.build(), null, enrolledPrivateKey);
469538
pvno = enrolledPrivateKey == null ? PKIHeader.CMP_2021 : PKIHeader.CMP_2000;
@@ -473,9 +542,13 @@ public EnrollmentResult invokeEnrollment() {
473542
LOGGER.error("EnrollmentType must be 0(ir), 2(cr), 7(kur) or 4(p10cr)");
474543
return null;
475544
}
476-
final PKIMessage responseMessage = requestHandler.sendReceiveValidateMessage(
477-
requestHandler.buildInitialRequest(requestBody, enrollmentContext.getRequestImplictConfirm(), pvno),
478-
enrollmentType);
545+
final PKIMessage enrollmentRequestMessage = ratNonceResponse == null
546+
? requestHandler.buildInitialRequest(
547+
requestBody, enrollmentContext.getRequestImplictConfirm(), pvno)
548+
: requestHandler.buildFurtherRequest(
549+
ratNonceResponse, requestBody, enrollmentContext.getRequestImplictConfirm(), pvno);
550+
final PKIMessage responseMessage =
551+
requestHandler.sendReceiveValidateMessage(enrollmentRequestMessage, enrollmentType);
479552
final PKIBody responseBody = responseMessage.getBody();
480553
final int responseMessageType = responseBody.getType();
481554
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 VerifierAdapter or <code>null</code>
100+
*/
101+
default VerifierAdapter 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)