-
Notifications
You must be signed in to change notification settings - Fork 7
Generic remote attestation implementation #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| /* | ||
| * Copyright (c) 2024 Siemens AG | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
| * not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package com.siemens.pki.cmpclientcomponent.configuration; | ||
|
|
||
| import com.siemens.pki.verifieradapter.asn1.EvidenceBundle; | ||
| import com.siemens.pki.verifieradapter.asn1.EvidenceStatement; | ||
| import com.siemens.pki.verifieradapter.asn1.NonceResponseValue.NonceResponse; | ||
| import java.math.BigInteger; | ||
| import org.bouncycastle.asn1.x509.Certificate; | ||
|
|
||
| /** | ||
| * attestation specific configuration | ||
| * | ||
| */ | ||
| public interface ClientAttestationContext { | ||
|
|
||
| /** | ||
| * obtain evidence statement from attestation | ||
| * @param attestationNonce DER encoded {@link NonceResponse} | ||
| * @return DER encoded {@link EvidenceStatement} | ||
| */ | ||
| byte[] getEvidenceStatement(byte[] attestationNonce); | ||
|
|
||
| /** | ||
| * indicates which Verifier to request a nonce from | ||
| * @return hint or <code>null</code> | ||
| */ | ||
| default String getNonceRequestHint() { | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * indicates the required length of the requested nonce | ||
| * @return length or <code>null</code> | ||
| */ | ||
| default BigInteger getNonceRequestLen() { | ||
|
This conversation was marked as resolved.
|
||
| return null; | ||
| } | ||
| /** | ||
| * indicates which Evidence type to request a nonce for | ||
| * @return OID formatted string or <code>null</code> | ||
| */ | ||
| default String getNonceRequestType() { | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Siemens proprietary extension to carry additional data | ||
| * @return additional data or <code>null</code> | ||
| */ | ||
| default byte[] getNonceRequestVendorextension() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider getNonceRequestVendorExtension (camelCase) instead of getNonceRequestVendorextension. |
||
| return null; | ||
| } | ||
| /** | ||
| * get certs to include in {@link EvidenceBundle} | ||
| * @return certs | ||
| */ | ||
| default Certificate[] getEvidenceBundleCerts() { | ||
| return null; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -144,7 +144,7 @@ private void validateResponse(final PKIMessage response) throws BaseCmpException | |
| private final ValidatorAndProtector nestedValidatorAndProtector; | ||
|
|
||
| /** | ||
| * @param certProfile certificate profile to be used for enrollment. | ||
| * @param certProfile certificate profile to be used for enrollment or | ||
| * <code>null</code> if no certificate profile | ||
| * should be used. | ||
| * | ||
|
|
@@ -184,6 +184,21 @@ PKIMessage buildFurtherRequest(final PKIMessage formerResponse, final PKIBody re | |
| false); | ||
| } | ||
|
|
||
| PKIMessage buildFurtherRequest( | ||
| final PKIMessage formerResponse, | ||
| final PKIBody requestBody, | ||
| final boolean withImplicitConfirm, | ||
| final int pvno) | ||
|
Comment on lines
+187
to
+191
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it is simpler to keep at least part of these argument in some state fields and take them from there?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Holding (transaction) states global and non final makes it hard to implement concurrency and slows down the VM. So I would keep everything local and final as much as possible. |
||
| throws Exception { | ||
| final PKIHeader formerResponseHeader = formerResponse.getHeader(); | ||
| return buildRequest( | ||
| requestBody, | ||
| formerResponseHeader.getTransactionID(), | ||
| formerResponseHeader.getSenderNonce(), | ||
| pvno, | ||
| withImplicitConfirm); | ||
| } | ||
|
|
||
| PKIMessage buildInitialRequest(final PKIBody requestBody, final boolean withImplicitConfirm) throws Exception { | ||
| return buildInitialRequest(requestBody, withImplicitConfirm, DEFAULT_PVNO); | ||
| } | ||
|
|
@@ -297,6 +312,10 @@ PKIBody sendReceiveInitialBody(final PKIBody body, final boolean withImplicitCon | |
| .getBody(); | ||
| } | ||
|
|
||
| PKIMessage sendReceiveInitialMessage(final PKIBody body) throws Exception { | ||
| return sendReceiveValidateMessage(buildInitialRequest(body, false), body.getType()); | ||
| } | ||
|
|
||
| PKIMessage sendReceiveValidateMessage(PKIMessage request, final int firstRequestType) throws Exception { | ||
| if (nestedValidatorAndProtector != null) { | ||
| request = nestedValidatorAndProtector | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,8 +20,10 @@ | |
| import static com.siemens.pki.cmpracomponent.util.NullUtil.defaultIfNull; | ||
| import static com.siemens.pki.cmpracomponent.util.NullUtil.ifNotNull; | ||
|
|
||
| import com.siemens.pki.cmpclientcomponent.configuration.ClientAttestationContext; | ||
| import com.siemens.pki.cmpclientcomponent.configuration.ClientContext; | ||
| import com.siemens.pki.cmpclientcomponent.configuration.EnrollmentContext; | ||
| import com.siemens.pki.cmpclientcomponent.configuration.EnrollmentContext.TemplateExtension; | ||
| import com.siemens.pki.cmpclientcomponent.configuration.RevocationContext; | ||
| import com.siemens.pki.cmpracomponent.configuration.CmpMessageInterface; | ||
| import com.siemens.pki.cmpracomponent.configuration.CrlUpdateRetrievalHandler; | ||
|
|
@@ -40,6 +42,13 @@ | |
| import com.siemens.pki.cmpracomponent.protection.ProtectionProvider; | ||
| import com.siemens.pki.cmpracomponent.protection.SignatureBasedProtection; | ||
| import com.siemens.pki.cmpracomponent.util.MessageDumper; | ||
| import com.siemens.pki.verifieradapter.asn1.AttestationObjectIdentifiers; | ||
| import com.siemens.pki.verifieradapter.asn1.EvidenceBundle; | ||
| import com.siemens.pki.verifieradapter.asn1.EvidenceStatement; | ||
| import com.siemens.pki.verifieradapter.asn1.NonceRequestValue; | ||
| import com.siemens.pki.verifieradapter.asn1.NonceRequestValue.NonceRequest; | ||
| import com.siemens.pki.verifieradapter.asn1.NonceResponseValue; | ||
| import com.siemens.pki.verifieradapter.asn1.NonceResponseValue.NonceResponse; | ||
| import java.io.IOException; | ||
| import java.security.KeyPair; | ||
| import java.security.PrivateKey; | ||
|
|
@@ -146,7 +155,7 @@ public interface EnrollmentResult { | |
| /** | ||
| * ctor | ||
| * | ||
| * @param certProfile certificate profile to be used for enrollment. | ||
| * @param certProfile certificate profile to be used for enrollment or | ||
| * <code>null</code> if no certificate profile | ||
| * should be used. | ||
| * | ||
|
|
@@ -409,6 +418,51 @@ private boolean grantsImplicitConfirm(final PKIMessage msg) { | |
| public EnrollmentResult invokeEnrollment() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is around 200 lines of code with a high complexity. |
||
|
|
||
| try { | ||
| PKIMessage ratNonceResponse = null; | ||
| EvidenceBundle evidenceBundle = null; | ||
| final ClientAttestationContext attestationContext = clientContext.getAttestationContext(); | ||
| if (attestationContext != null) { | ||
| NonceRequest nonceRequest = new NonceRequest( | ||
| attestationContext.getNonceRequestLen(), | ||
| attestationContext.getNonceRequestType(), | ||
| attestationContext.getNonceRequestHint(), | ||
| attestationContext.getNonceRequestVendorextension()); | ||
| NonceRequestValue nonceRequestValue = new NonceRequestValue(new NonceRequest[] {nonceRequest}); | ||
|
Comment on lines
+425
to
+430
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So here you generate a request for a single nonce.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs change of |
||
| ratNonceResponse = requestHandler.sendReceiveInitialMessage(new PKIBody( | ||
| PKIBody.TYPE_GEN_MSG, | ||
| new GenMsgContent(new InfoTypeAndValue( | ||
| AttestationObjectIdentifiers.id_it_NonceRequest, nonceRequestValue)))); | ||
| final PKIBody ratNonceResponseBody = ratNonceResponse.getBody(); | ||
| if (ratNonceResponseBody.getType() != PKIBody.TYPE_GEN_REP) { | ||
| logUnexpectedResponse(ratNonceResponseBody); | ||
| return null; | ||
| } | ||
|
|
||
| final GenRepContent content = (GenRepContent) ratNonceResponseBody.getContent(); | ||
| final InfoTypeAndValue[] itav = content.toInfoTypeAndValueArray(); | ||
| if (itav != null) { | ||
| for (final InfoTypeAndValue aktitav : itav) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should throw an error if not getting exactly one ITAV |
||
| if (AttestationObjectIdentifiers.id_it_NonceResponse.equals(aktitav.getInfoType())) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Otherwise should throw an error
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its about robustness -> Ignore, what you are unable to handle. |
||
| final ASN1Encodable infoValue = aktitav.getInfoValue(); | ||
| if (infoValue == null) { | ||
| LOGGER.error("no RAT nonce received"); | ||
| return null; | ||
| } | ||
| final NonceResponseValue ratNonce = NonceResponseValue.getInstance(infoValue); | ||
| NonceResponse[] nonceResponses = ratNonce.getNonceResponse(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should check that the number of nonces obtains equals the number requested, which so far is always 1.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Robustness. |
||
| EvidenceStatement[] evidenceStatements = new EvidenceStatement[nonceResponses.length]; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the number of evidence statements always follow the number of nonce responses? |
||
| for (int i = 0; i < nonceResponses.length; i++) { | ||
| NonceResponse aktnonce = nonceResponses[i]; | ||
| evidenceStatements[i] = EvidenceStatement.getInstance( | ||
| attestationContext.getEvidenceStatement(aktnonce.getEncoded())); | ||
| } | ||
| evidenceBundle = | ||
| new EvidenceBundle(evidenceStatements, attestationContext.getEvidenceBundleCerts()); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| final EnrollmentContext enrollmentContext = clientContext.getEnrollmentContext(); | ||
| final KeyPair certificateKeypair = enrollmentContext.getCertificateKeypair(); | ||
|
|
||
|
|
@@ -459,16 +513,25 @@ public EnrollmentResult invokeEnrollment() { | |
| case PKIBody.TYPE_CERT_REQ: | ||
| case PKIBody.TYPE_INIT_REQ: { | ||
| final String subject = enrollmentContext.getSubject(); | ||
| final Extension[] arrayOfExtensions = | ||
| ifNotNull(enrollmentContext.getExtensions(), exts -> exts.stream() | ||
| .map(ext -> new Extension( | ||
| new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ext.getValue())) | ||
| .toArray(Extension[]::new)); | ||
| final Extensions extensions = ifNotNull(arrayOfExtensions, Extensions::new); | ||
| final List<Extension> extensions = new ArrayList<>(); | ||
|
|
||
| final List<TemplateExtension> extensionsFromConfig = enrollmentContext.getExtensions(); | ||
| if (extensionsFromConfig != null) { | ||
| extensionsFromConfig.stream() | ||
| .map(ext -> new Extension( | ||
| new ASN1ObjectIdentifier(ext.getId()), ext.isCritical(), ext.getValue())) | ||
| .forEach(extensions::add); | ||
| } | ||
| if (evidenceBundle != null) { | ||
| extensions.add( | ||
| Extension.create(AttestationObjectIdentifiers.id_aa_evidence, false, evidenceBundle)); | ||
| } | ||
| final CertTemplateBuilder ctb = new CertTemplateBuilder() | ||
| .setSubject(ifNotNull(subject, X500Name::new)) | ||
| .setPublicKey(enrolledPublicKeyInfo) | ||
| .setExtensions(extensions); | ||
| .setPublicKey(enrolledPublicKeyInfo); | ||
| if (!extensions.isEmpty()) { | ||
| ctb.setExtensions(new Extensions(extensions.toArray(new Extension[extensions.size()]))); | ||
| } | ||
| requestBody = PkiMessageGenerator.generateIrCrKurBody( | ||
| enrollmentType, ctb.build(), null, enrolledPrivateKey); | ||
| pvno = enrolledPrivateKey == null ? PKIHeader.CMP_2021 : PKIHeader.CMP_2000; | ||
|
|
@@ -478,9 +541,13 @@ public EnrollmentResult invokeEnrollment() { | |
| LOGGER.error("EnrollmentType must be 0(ir), 2(cr), 7(kur) or 4(p10cr)"); | ||
| return null; | ||
| } | ||
| final PKIMessage responseMessage = requestHandler.sendReceiveValidateMessage( | ||
| requestHandler.buildInitialRequest(requestBody, enrollmentContext.getRequestImplictConfirm(), pvno), | ||
| enrollmentType); | ||
| final PKIMessage enrollmentRequestMessage = ratNonceResponse == null | ||
| ? requestHandler.buildInitialRequest( | ||
| requestBody, enrollmentContext.getRequestImplictConfirm(), pvno) | ||
| : requestHandler.buildFurtherRequest( | ||
| ratNonceResponse, requestBody, enrollmentContext.getRequestImplictConfirm(), pvno); | ||
| final PKIMessage responseMessage = | ||
| requestHandler.sendReceiveValidateMessage(enrollmentRequestMessage, enrollmentType); | ||
| final PKIBody responseBody = responseMessage.getBody(); | ||
| final int responseMessageType = responseBody.getType(); | ||
| if (enrollmentType == PKIBody.TYPE_P10_CERT_REQ) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* | ||
| * Copyright (c) 2023 Siemens AG | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); you may | ||
| * not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package com.siemens.pki.cmpracomponent.configuration; | ||
|
|
||
| /** | ||
| * support message handler supporting Remote Attestation Procedures nonce genm | ||
| * requests, see https://datatracker.ietf.org/doc/draft-ietf-rats-reference-interaction-models/ , | ||
| * and https://github.com/veraison/docs/blob/main/api/challenge-response/README.md | ||
| */ | ||
| public interface GetFreshRatNonceHandler extends SupportMessageHandlerInterface { | ||
| // up to now only a placeholder | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason, in https://datatracker.ietf.org/doc/draft-ietf-lamps-csr-attestation/ this meanwhile has been renamed to
AttestationBundle.