From bd5db5444a30c04c2087cb439471c61953986ba3 Mon Sep 17 00:00:00 2001 From: Andreas Kretschmer <38779775+Akretsch@users.noreply.github.com> Date: Wed, 24 Jul 2024 10:34:12 +0200 Subject: [PATCH] again fix protection of NESTED responses (#114) --- CHANGELOG.md | 3 + pom.xml | 2 +- .../msggeneration/MsgOutputProtector.java | 18 +- .../msggeneration/PkiMessageGenerator.java | 16 +- .../msgprocessing/CmpRaUpstream.java | 12 +- .../msgprocessing/RaDownstream.java | 332 ++++++++---------- .../msgprocessing/ServiceImplementation.java | 2 +- .../msgvalidation/MessageBodyValidator.java | 21 +- .../protection/MacProtection.java | 5 +- .../protection/NoProtection.java | 5 +- .../protection/ProtectionProvider.java | 13 +- .../protection/SignatureBasedProtection.java | 5 +- ...tCentralKeyGenerationWithKeyAgreement.java | 106 ++++-- .../cmpclientcomponent/test/TestP10Cr.java | 2 +- .../test/TestRefusingInventory.java | 28 +- .../test/TestUpdateByInventory.java | 165 +++++++++ .../test/CkgOnlineEnrollmentTestcaseBase.java | 2 - .../test/TestMessageBodyValidator.java | 93 +++++ .../test/TestSupportMessages.java | 41 ++- .../test/framework/CmpCaMock.java | 1 - .../test/framework/ConfigurationFactory.java | 35 ++ .../test/framework/HeaderProviderForTest.java | 2 +- .../framework/TrustChainAndPrivateKey.java | 7 +- 23 files changed, 664 insertions(+), 252 deletions(-) create mode 100644 src/test/java/com/siemens/pki/cmpclientcomponent/test/TestUpdateByInventory.java create mode 100644 src/test/java/com/siemens/pki/cmpracomponent/test/TestMessageBodyValidator.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c6831f..7cbca55a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,3 +140,6 @@ feat: add logging while accessing configuration data fix: handling of NestedEndpointContext.isIncomingRecipientValid +### 4.1.4 (Jul 23 2024) + +fix: again fix protection of NESTED responses diff --git a/pom.xml b/pom.xml index 1adc8c55..c8ab98ac 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ com.siemens.pki CmpRaComponent jar - 4.1.3 + 4.1.4 UTF-8 . diff --git a/src/main/java/com/siemens/pki/cmpracomponent/msggeneration/MsgOutputProtector.java b/src/main/java/com/siemens/pki/cmpracomponent/msggeneration/MsgOutputProtector.java index 44c88d8d..514ba40c 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/msggeneration/MsgOutputProtector.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/msggeneration/MsgOutputProtector.java @@ -29,6 +29,7 @@ import com.siemens.pki.cmpracomponent.protection.ProtectionProvider; import com.siemens.pki.cmpracomponent.protection.ProtectionProviderFactory; import com.siemens.pki.cmpracomponent.util.ConfigLogger; +import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.Collections; @@ -123,9 +124,11 @@ public MsgOutputProtector(final NestedEndpointContext config, final String inter * @param headerProvider the header to use * @param body body of new message * @return new message - * @throws Exception in case of error + * @throws IOException in case of encoding problem + * @throws GeneralSecurityException in case of error */ - public PKIMessage createOutgoingMessage(final HeaderProvider headerProvider, PKIBody body) throws Exception { + public PKIMessage createOutgoingMessage(final HeaderProvider headerProvider, PKIBody body) + throws GeneralSecurityException, IOException { switch (reprotectMode) { case reprotect: case keep: @@ -145,9 +148,11 @@ public PKIMessage createOutgoingMessage(final HeaderProvider headerProvider, PKI * @param request request to answer * @param body body of new message * @return new message - * @throws Exception in case of error + * @throws GeneralSecurityException in case of error + * @throws IOException in case of encoding error */ - public PKIMessage generateAndProtectResponseTo(PKIMessage request, final PKIBody body) throws Exception { + public PKIMessage generateAndProtectResponseTo(PKIMessage request, final PKIBody body) + throws GeneralSecurityException, IOException { return stripRedundantExtraCerts(PkiMessageGenerator.generateAndProtectMessage( PkiMessageGenerator.buildRespondingHeaderProvider(request), protector, recipient, body, null)); } @@ -166,10 +171,11 @@ public ProtectionProvider getProtector() { * @param issuingChain trust chain of issued certificate to add to extracerts or * null * @return protected message ready to send - * @throws Exception in case of processing error + * @throws IOException in case of encoding problem + * @throws GeneralSecurityException in case of processing error */ public PKIMessage protectOutgoingMessage(final PKIMessage in, final List issuingChain) - throws Exception { + throws GeneralSecurityException, IOException { switch (reprotectMode) { case reprotect: return stripRedundantExtraCerts(PkiMessageGenerator.generateAndProtectMessage( diff --git a/src/main/java/com/siemens/pki/cmpracomponent/msggeneration/PkiMessageGenerator.java b/src/main/java/com/siemens/pki/cmpracomponent/msggeneration/PkiMessageGenerator.java index 470e5d73..74b3c2ea 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/msggeneration/PkiMessageGenerator.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/msggeneration/PkiMessageGenerator.java @@ -29,6 +29,7 @@ import com.siemens.pki.cmpracomponent.util.MessageDumper; import java.io.IOException; import java.math.BigInteger; +import java.security.GeneralSecurityException; import java.security.PrivateKey; import java.security.Signature; import java.util.Collections; @@ -257,7 +258,8 @@ public ASN1OctetString getTransactionID() { * @param issuingChain chain of enrolled certificate to append at the * extraCerts * @return a fully build and protected message - * @throws Exception in case of error + * @throws GeneralSecurityException in case of error + * @throws IOException in case of encoding error */ public static PKIMessage generateAndProtectMessage( final HeaderProvider headerProvider, @@ -265,7 +267,7 @@ public static PKIMessage generateAndProtectMessage( GeneralName newRecipient, final PKIBody body, final List issuingChain) - throws Exception { + throws GeneralSecurityException, IOException { synchronized (protectionProvider) { final GeneralName recipient = computeDefaultIfNull(newRecipient, headerProvider::getRecipient); final GeneralName sender = computeDefaultIfNull(protectionProvider.getSender(), headerProvider::getSender); @@ -422,11 +424,12 @@ public static PKIBody generateIpCpKupErrorBody(final int bodyType, final int fai * @param privateKey private key to build the POPO, if set to null, POPO is * set to raVerified * @return a IR, CR or KUR body - * @throws Exception in case of error + * @throws GeneralSecurityException in case of error + * @throws IOException in case of encoding error */ public static PKIBody generateIrCrKurBody( final int bodyType, final CertTemplate certTemplate, final Controls controls, final PrivateKey privateKey) - throws Exception { + throws GeneralSecurityException, IOException { final CertRequest certReq = new CertRequest(CERT_REQ_ID_0, certTemplate, controls); if (privateKey == null) { return new PKIBody(bodyType, new CertReqMessages(new CertReqMsg(certReq, new ProofOfPossession(), null))); @@ -542,10 +545,11 @@ public static PKIBody generateRrBody(final X500Name issuer, final ASN1Integer se * @param headerProvider PKI header * @param body message body * @return a fully build and not protected message - * @throws Exception in case of error + * @throws GeneralSecurityException in case of error + * @throws IOException in case of encoding error */ public static PKIMessage generateUnprotectMessage(final HeaderProvider headerProvider, final PKIBody body) - throws Exception { + throws GeneralSecurityException, IOException { return generateAndProtectMessage(headerProvider, ProtectionProvider.NO_PROTECTION, null, body, null); } diff --git a/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/CmpRaUpstream.java b/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/CmpRaUpstream.java index cdecce70..a1bb81d0 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/CmpRaUpstream.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/CmpRaUpstream.java @@ -32,6 +32,8 @@ import com.siemens.pki.cmpracomponent.persistency.PersistencyContextManager; import com.siemens.pki.cmpracomponent.util.CmpFuncEx; import com.siemens.pki.cmpracomponent.util.ConfigLogger; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -76,19 +78,17 @@ class CmpRaUpstream implements RaUpstream { * @param persistencyContextManager persistency interface * @param config specific configuration * @param upstreamExchange upstream function - * @throws Exception in case of error */ CmpRaUpstream( final PersistencyContextManager persistencyContextManager, final Configuration config, - final CmpFuncEx upstreamExchange) - throws Exception { + final CmpFuncEx upstreamExchange) { this.persistencyContextManager = persistencyContextManager; this.config = config; this.upstreamMsgHandler = upstreamExchange; } - void gotResponseAtUpstream(final PKIMessage responseMessage) throws Exception { + void gotResponseAtUpstream(final PKIMessage responseMessage) throws IOException, CmpProcessingException { final PersistencyContext persistencyContext = persistencyContextManager.loadPersistencyContext( responseMessage.getHeader().getTransactionID().getOctets()); if (persistencyContext == null) { @@ -162,7 +162,7 @@ public PKIMessage handleRequest(final PKIMessage in, final PersistencyContext pe PkiMessageGenerator.generateResponseBodyWithWaiting(sentMessage.getBody(), INTERFACE_NAME)); } // synchronous transfer - if (receivedMessage.getBody().getType() == PKIBody.TYPE_NESTED) { + if (receivedMessage.getBody().getType() == PKIBody.TYPE_NESTED && nestedEndpointContext != null) { final MessageHeaderValidator nestedHeaderValidator = new MessageHeaderValidator(NESTED_INTERFACE_NAME); nestedHeaderValidator.validate(receivedMessage); final ProtectionValidator nestedProtectionValidator = new ProtectionValidator( @@ -220,7 +220,7 @@ public PKIMessage handleRequest(final PKIMessage in, final PersistencyContext pe } private PKIMessage handlePollReq(final PKIMessage in, final PersistencyContext persistencyContext) - throws Exception { + throws BaseCmpException, GeneralSecurityException, IOException { final PKIMessage delayedResponse = persistencyContext.getPendingDelayedResponse(); if (delayedResponse != null) { final InputValidator inputValidator = new InputValidator( diff --git a/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/RaDownstream.java b/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/RaDownstream.java index 02ebd05b..765dfe21 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/RaDownstream.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/RaDownstream.java @@ -48,6 +48,7 @@ import com.siemens.pki.cmpracomponent.protection.SignatureBasedProtection; import com.siemens.pki.cmpracomponent.util.ConfigLogger; import com.siemens.pki.cmpracomponent.util.MessageDumper; +import com.siemens.pki.cmpracomponent.util.NullUtil.ExFunction; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.KeyFactory; @@ -56,13 +57,13 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; -import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.spec.X509EncodedKeySpec; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.List; +import java.util.function.Function; import java.util.stream.Collectors; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.ASN1Integer; @@ -107,6 +108,16 @@ */ class RaDownstream { + private static Function wrap(ExFunction checkedFunction) { + return t -> { + try { + return checkedFunction.apply(t); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } + private static final String INTERFACE_NAME = "downstream"; private static final String NESTED_INTERFACE_NAME = "nested " + INTERFACE_NAME; @@ -129,7 +140,6 @@ class RaDownstream { * @param config specific configuration * @param upstream related upstream interface handler * @param supportedmessagetypes - * @throws Exception in case of error */ RaDownstream( final PersistencyContextManager persistencyContextManager, @@ -147,7 +157,7 @@ protected CmsEncryptorBase buildEncryptor( final CkgContext ckgConfiguration, final int initialRequestType, final String interfaceName) - throws GeneralSecurityException, CmpProcessingException, CmpEnrollmentException { + throws GeneralSecurityException, BaseCmpException { final ASN1ObjectIdentifier protectingAlgOID = incomingRequest.getHeader().getProtectionAlg().getAlgorithm(); if (CMPObjectIdentifiers.passwordBasedMac.equals(protectingAlgOID) @@ -177,29 +187,10 @@ protected CmsEncryptorBase buildEncryptor( return new KeyTransportEncryptor(ckgConfiguration, recipientCert, initialRequestType, interfaceName); } - private MsgOutputProtector getOutputProtector(final PersistencyContext persistencyContext, final int bodyType) - throws Exception { - return new MsgOutputProtector( - ConfigLogger.log( - INTERFACE_NAME, - "Configuration.getDownstreamConfiguration", - config::getDownstreamConfiguration, - persistencyContext.getCertProfile(), - bodyType), - INTERFACE_NAME, - persistencyContext); - } - - /** - * special handling for CR, IR, KUR - * - * @param incomingCertificateRequest - * @param outputProtector - * @return handled message - * @throws Exception in case of error - */ + // special handling for CR, IR, KUR private PKIMessage handleCrmfCertificateRequest( - final PKIMessage incomingCertificateRequest, final PersistencyContext persistencyContext) throws Exception { + final PKIMessage incomingCertificateRequest, final PersistencyContext persistencyContext) + throws BaseCmpException, GeneralSecurityException, IOException { final PKIBody requestBody = incomingCertificateRequest.getBody(); final PKIBody body = requestBody; @@ -233,21 +224,17 @@ private PKIMessage handleCrmfCertificateRequest( } final String requesterDnFinal = requesterDn; final CertTemplate certTemplateFinal = certTemplate; + byte[] encodedTemplate = certTemplateFinal.getEncoded(); + byte[] encodedRequest = incomingCertificateRequest.getEncoded(); final CheckAndModifyResult checkResult = ConfigLogger.logOptional( INTERFACE_NAME, "InventoryInterface.checkAndModifyCertRequest(byte[], String, byte[], String, byte[])", - () -> { - try { - return inventory.checkAndModifyCertRequest( - persistencyContext.getTransactionId(), - requesterDnFinal, - certTemplateFinal.getEncoded(), - ifNotNull(certTemplateFinal.getSubject(), X500Name::toString), - incomingCertificateRequest.getEncoded()); - } catch (IOException | RuntimeException e) { - return null; - } - }); + () -> inventory.checkAndModifyCertRequest( + persistencyContext.getTransactionId(), + requesterDnFinal, + encodedTemplate, + ifNotNull(certTemplateFinal.getSubject(), X500Name::toString), + encodedRequest)); if (checkResult == null || !ConfigLogger.log(INTERFACE_NAME, "CheckAndModifyResult.isGranted()", checkResult::isGranted)) { @@ -395,7 +382,7 @@ PKIMessage handleInputMessage(final PKIMessage in) { try { try { byte[] transactionId = - ifNotNull(in, m -> m.getHeader().getTransactionID().getEncoded()); + ifNotNull(in, m -> m.getHeader().getTransactionID().getOctets()); if (transactionId == null) { MsgOutputProtector protector = new MsgOutputProtector( ConfigLogger.log( @@ -411,14 +398,31 @@ PKIMessage handleInputMessage(final PKIMessage in) { PkiMessageGenerator.generateErrorBody( PKIFailureInfo.badDataFormat, "transactionId missing")); } - persistencyContext = persistencyContextManager.loadCreatePersistencyContext( - in.getHeader().getTransactionID().getOctets()); + persistencyContext = persistencyContextManager.loadCreatePersistencyContext(transactionId); + final int inBodyType = in.getBody().getType(); + if (inBodyType == PKIBody.TYPE_NESTED) { + PersistencyContext nestedPersistencyContext = persistencyContext; + // suppress persistency update for NESTED messages + persistencyContext = null; + return handleNestedRequest(in, nestedPersistencyContext); + } + final InputValidator inputValidator = new InputValidator( + INTERFACE_NAME, + config::getDownstreamConfiguration, + config::isRaVerifiedAcceptable, + supportedMessageTypes, + persistencyContext); + inputValidator.validate(in); - PKIMessage responseFromUpstream = handleInputRequest(in, persistencyContext); + PKIMessage responseFromUpstream = handleValidatedRequest(in, persistencyContext); // apply downstream protection and nesting List issuingChain = null; responseBodyType = responseFromUpstream.getBody().getType(); switch (responseBodyType) { + case PKIBody.TYPE_NESTED: + // never nest a nested message + persistencyContext = null; + return responseFromUpstream; case PKIBody.TYPE_INIT_REP: case PKIBody.TYPE_CERT_REP: case PKIBody.TYPE_KEY_UPDATE_REP: @@ -432,8 +436,14 @@ PKIMessage handleInputMessage(final PKIMessage in) { break; default: } - - PKIMessage protectedResponse = getOutputProtector(persistencyContext, responseBodyType) + final CmpMessageInterface downstreamConfiguration = ConfigLogger.log( + INTERFACE_NAME, + "Configuration.getDownstreamConfiguration", + config::getDownstreamConfiguration, + ifNotNull(persistencyContext, PersistencyContext::getCertProfile), + responseBodyType); + PKIMessage protectedResponse = new MsgOutputProtector( + downstreamConfiguration, INTERFACE_NAME, persistencyContext) .protectOutgoingMessage( new PKIMessage( responseFromUpstream.getHeader(), @@ -441,16 +451,7 @@ PKIMessage handleInputMessage(final PKIMessage in) { responseFromUpstream.getProtection(), responseFromUpstream.getExtraCerts()), issuingChain); - if (responseBodyType == PKIBody.TYPE_NESTED) { - // never nest a nested message - return protectedResponse; - } - final CmpMessageInterface downstreamConfiguration = ConfigLogger.log( - INTERFACE_NAME, - "Configuration.getDownstreamConfiguration", - config::getDownstreamConfiguration, - null, - responseBodyType); + final NestedEndpointContext nestedEndpointContext = ConfigLogger.logOptional( INTERFACE_NAME, "CmpMessageInterface.getNestedEndpointContext()", @@ -465,11 +466,23 @@ PKIMessage handleInputMessage(final PKIMessage in) { new PKIBody(PKIBody.TYPE_NESTED, new PKIMessages(protectedResponse))); } catch (final BaseCmpException e) { final PKIBody errorBody = e.asErrorBody(); - return getOutputProtector(persistencyContext, responseBodyType) + final CmpMessageInterface downstreamConfiguration = ConfigLogger.log( + INTERFACE_NAME, + "Configuration.getDownstreamConfiguration", + config::getDownstreamConfiguration, + ifNotNull(persistencyContext, PersistencyContext::getCertProfile), + errorBody.getType()); + return new MsgOutputProtector(downstreamConfiguration, INTERFACE_NAME, persistencyContext) .generateAndProtectResponseTo(in, errorBody); } catch (final RuntimeException ex) { final PKIBody errorBody = new CmpProcessingException(INTERFACE_NAME, ex).asErrorBody(); - return getOutputProtector(persistencyContext, responseBodyType) + final CmpMessageInterface downstreamConfiguration = ConfigLogger.log( + INTERFACE_NAME, + "Configuration.getDownstreamConfiguration", + config::getDownstreamConfiguration, + ifNotNull(persistencyContext, PersistencyContext::getCertProfile), + errorBody.getType()); + return new MsgOutputProtector(downstreamConfiguration, INTERFACE_NAME, persistencyContext) .generateAndProtectResponseTo(in, errorBody); } finally { if (persistencyContext != null) { @@ -477,7 +490,7 @@ PKIMessage handleInputMessage(final PKIMessage in) { INTERFACE_NAME, "Configuration.getDownstreamTimeout", config::getDownstreamTimeout, - ifNotNull(persistencyContext, PersistencyContext::getCertProfile), + persistencyContext.getCertProfile(), responseBodyType); if (offset == 0) { offset = Integer.MAX_VALUE / 2; @@ -493,75 +506,63 @@ PKIMessage handleInputMessage(final PKIMessage in) { } } - private PKIMessage handleInputRequest(final PKIMessage in, final PersistencyContext persistencyContext) - throws Exception { - - final int inBodyType = in.getBody().getType(); - if (inBodyType == PKIBody.TYPE_NESTED) { - final CmpMessageInterface downstreamConfiguration = ConfigLogger.log( - INTERFACE_NAME, - "Configuration.getDownstreamConfiguration", - config::getDownstreamConfiguration, - null, - PKIBody.TYPE_NESTED); - final NestedEndpointContext nestedEndpointContext = ConfigLogger.logOptional( - INTERFACE_NAME, - "CmpMessageInterface.getNestedEndpointContext()", - downstreamConfiguration::getNestedEndpointContext); - if (nestedEndpointContext != null) { - final MessageHeaderValidator nestedHeaderValidator = new MessageHeaderValidator(NESTED_INTERFACE_NAME); - nestedHeaderValidator.validate(in); - final ProtectionValidator nestedProtectionValidator = new ProtectionValidator( - NESTED_INTERFACE_NAME, - ConfigLogger.logOptional( - NESTED_INTERFACE_NAME, - "NestedEndpointContext.getInputVerification()", - nestedEndpointContext::getInputVerification)); - nestedProtectionValidator.validate(in); - PKIHeader inHeader = in.getHeader(); - boolean isIncomingRecipientValid = ConfigLogger.log( - NESTED_INTERFACE_NAME, - "NestedEndpointContext.isIncomingRecipientValid()", - () -> nestedEndpointContext.isIncomingRecipientValid( - inHeader.getRecipient().getName().toString())); - if (!isIncomingRecipientValid) { - return upstreamHandler.handleRequest(in, persistencyContext); - } - final PKIMessage[] embeddedMessages = - PKIMessages.getInstance(in.getBody().getContent()).toPKIMessageArray(); - if (embeddedMessages == null || embeddedMessages.length == 0) { - throw new CmpProcessingException( - NESTED_INTERFACE_NAME, - PKIFailureInfo.badMessageCheck, - "no embedded messages inside NESTED message"); - } - // wrapped protection case - if (embeddedMessages.length == 1) { - return handleInputMessage(embeddedMessages[0]); - } - // batching - final PKIMessage[] responses = Arrays.stream(embeddedMessages) - .map(this::handleInputMessage) - .toArray(PKIMessage[]::new); - // batched responses needs to be wrapped in a new NESTED response - MsgOutputProtector nestedOutputProtector = - new MsgOutputProtector(nestedEndpointContext, INTERFACE_NAME); - return nestedOutputProtector.generateAndProtectResponseTo( - in, new PKIBody(PKIBody.TYPE_NESTED, new PKIMessages(responses))); - } - } - final InputValidator inputValidator = new InputValidator( + private PKIMessage handleNestedRequest(final PKIMessage in, final PersistencyContext persistencyContext) + throws BaseCmpException, GeneralSecurityException, IOException { + final CmpMessageInterface downstreamConfiguration = ConfigLogger.log( INTERFACE_NAME, + "Configuration.getDownstreamConfiguration", config::getDownstreamConfiguration, - config::isRaVerifiedAcceptable, - supportedMessageTypes, - persistencyContext); - inputValidator.validate(in); - return handleValidatedRequest(in, persistencyContext); + null, + PKIBody.TYPE_NESTED); + final NestedEndpointContext nestedEndpointContext = ConfigLogger.logOptional( + INTERFACE_NAME, + "CmpMessageInterface.getNestedEndpointContext()", + downstreamConfiguration::getNestedEndpointContext); + if (nestedEndpointContext == null) { + return upstreamHandler.handleRequest(in, persistencyContext); + } + final MessageHeaderValidator nestedHeaderValidator = new MessageHeaderValidator(NESTED_INTERFACE_NAME); + nestedHeaderValidator.validate(in); + final ProtectionValidator nestedProtectionValidator = new ProtectionValidator( + NESTED_INTERFACE_NAME, + ConfigLogger.logOptional( + NESTED_INTERFACE_NAME, + "NestedEndpointContext.getInputVerification()", + nestedEndpointContext::getInputVerification)); + nestedProtectionValidator.validate(in); + PKIHeader inHeader = in.getHeader(); + boolean isIncomingRecipientValid = ConfigLogger.log( + NESTED_INTERFACE_NAME, + "NestedEndpointContext.isIncomingRecipientValid()", + () -> nestedEndpointContext.isIncomingRecipientValid( + inHeader.getRecipient().getName().toString())); + if (!isIncomingRecipientValid) { + return upstreamHandler.handleRequest(in, persistencyContext); + } + final PKIMessage[] embeddedMessages = + PKIMessages.getInstance(in.getBody().getContent()).toPKIMessageArray(); + if (embeddedMessages == null || embeddedMessages.length == 0) { + throw new CmpProcessingException( + NESTED_INTERFACE_NAME, + PKIFailureInfo.badMessageCheck, + "no embedded messages inside NESTED message"); + } + // wrapped protection case + if (embeddedMessages.length == 1) { + return handleInputMessage(embeddedMessages[0]); + } + // batching + final PKIMessage[] responses = + Arrays.stream(embeddedMessages).map(this::handleInputMessage).toArray(PKIMessage[]::new); + // batched responses needs to be wrapped in a new NESTED response + MsgOutputProtector nestedOutputProtector = new MsgOutputProtector(nestedEndpointContext, INTERFACE_NAME); + return nestedOutputProtector.generateAndProtectResponseTo( + in, new PKIBody(PKIBody.TYPE_NESTED, new PKIMessages(responses))); } private PKIMessage handleP10CertificateRequest( - final PKIMessage incomingP10Request, final PersistencyContext persistencyContext) throws BaseCmpException { + final PKIMessage incomingP10Request, final PersistencyContext persistencyContext) + throws BaseCmpException, IOException { try { final PKIBody body = incomingP10Request.getBody(); persistencyContext.setRequestType(body.getType()); @@ -593,24 +594,18 @@ private PKIMessage handleP10CertificateRequest( requesterDn = sender.toString(); } } - final PKCS10CertificationRequest p10RequestFinal = p10Request; - final PKIMessage incomingP10RequestFinal = incomingP10Request; + final byte[] encodedP10Request = p10Request.getEncoded(); + final byte[] encodedIncomingP10Request = incomingP10Request.getEncoded(); final String requesterDnFinal = requesterDn; if (!ConfigLogger.log( INTERFACE_NAME, "InventoryInterface.checkP10CertRequest(byte[], String, byte[], String, byte[])", - () -> { - try { - return inventory.checkP10CertRequest( - persistencyContext.getTransactionId(), - requesterDnFinal, - p10RequestFinal.getEncoded(), - p10RequestFinal.getSubject().toString(), - incomingP10RequestFinal.getEncoded()); - } catch (final IOException e) { - throw new RuntimeException(e); - } - })) { + () -> inventory.checkP10CertRequest( + persistencyContext.getTransactionId(), + requesterDnFinal, + encodedP10Request, + p10Request.getSubject().toString(), + encodedIncomingP10Request))) { throw new CmpValidationException( INTERFACE_NAME, PKIFailureInfo.badCertTemplate, "request refused by external inventory"); } @@ -622,7 +617,7 @@ private PKIMessage handleP10CertificateRequest( } private PKIMessage handleRevocationRequest(PKIMessage incomingRequest, PersistencyContext persistencyContext) - throws BaseCmpException { + throws BaseCmpException, IOException { final PKIBody body = incomingRequest.getBody(); final int requestType = body.getType(); persistencyContext.setRequestType(requestType); @@ -635,25 +630,20 @@ private PKIMessage handleRevocationRequest(PKIMessage incomingRequest, Persisten if (inventory != null) { final CertTemplate revTemplate = ((RevReqContent) body.getContent()).toRevDetailsArray()[0].getCertDetails(); + final byte[] encodedIncomingRequest = incomingRequest.getEncoded(); if (!ConfigLogger.log( INTERFACE_NAME, "InventoryInterface.checkRevocationRequest(byte[], String, String, String, byte[])", - () -> { - try { - return inventory.checkRevocationRequest( - persistencyContext.getTransactionId(), - ifNotNull(incomingRequest.getHeader().getSender(), sender -> X500Name.getInstance( - sender.getName()) - .toString()), - ifNotNull(revTemplate, template -> template.getSerialNumber() - .toString()), - ifNotNull(revTemplate, template -> template.getIssuer() - .toString()), - incomingRequest.getEncoded()); - } catch (final IOException e) { - throw new RuntimeException(e); - } - })) { + () -> inventory.checkRevocationRequest( + persistencyContext.getTransactionId(), + ifNotNull(incomingRequest.getHeader().getSender(), sender -> X500Name.getInstance( + sender.getName()) + .toString()), + ifNotNull(revTemplate, template -> template.getSerialNumber() + .toString()), + ifNotNull(revTemplate, template -> template.getIssuer() + .toString()), + encodedIncomingRequest))) { throw new CmpValidationException( INTERFACE_NAME, PKIFailureInfo.badRequest, "request refused by external inventory"); } @@ -662,7 +652,8 @@ private PKIMessage handleRevocationRequest(PKIMessage incomingRequest, Persisten } private PKIMessage handleValidatedRequest( - final PKIMessage incomingRequest, final PersistencyContext persistencyContext) throws Exception { + final PKIMessage incomingRequest, final PersistencyContext persistencyContext) + throws BaseCmpException, IOException { // request pre processing // by default there is no pre processing PKIMessage preprocessedRequest = incomingRequest; @@ -794,13 +785,7 @@ private PKIMessage processCertResponse( } final List issuingChain = issuingChainAsX509.stream() .filter(x -> !x.equals(enrolledCertificateAsX509)) - .map(x -> { - try { - return CMPCertificate.getInstance(x.getEncoded()); - } catch (final CertificateEncodingException e) { - throw new RuntimeException(e); - } - }) + .map(wrap(x -> CMPCertificate.getInstance(x.getEncoded()))) .collect(Collectors.toList()); persistencyContext.setIssuingChain(issuingChain); @@ -812,27 +797,20 @@ private PKIMessage processCertResponse( persistencyContext.getCertProfile(), responseType); if (inventory != null) { + final byte[] encodedEnrolledCertificate = enrolledCertificate.getEncoded(); if (!ConfigLogger.log( INTERFACE_NAME, "InventoryInterface.learnEnrollmentResult(byte[], byte[], String, String, String)", - () -> { - try { - return inventory.learnEnrollmentResult( - persistencyContext.getTransactionId(), - enrolledCertificate.getEncoded(), - enrolledCertificateAsX509 - .getSerialNumber() - .toString(), - enrolledCertificateAsX509 - .getSubjectX500Principal() - .toString(), - enrolledCertificateAsX509 - .getIssuerX500Principal() - .toString()); - } catch (final IOException e) { - throw new RuntimeException(e); - } - })) { + () -> inventory.learnEnrollmentResult( + persistencyContext.getTransactionId(), + encodedEnrolledCertificate, + enrolledCertificateAsX509.getSerialNumber().toString(), + enrolledCertificateAsX509 + .getSubjectX500Principal() + .toString(), + enrolledCertificateAsX509 + .getIssuerX500Principal() + .toString()))) { throw new CmpEnrollmentException( incomingRequest.getBody().getType(), INTERFACE_NAME, diff --git a/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/ServiceImplementation.java b/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/ServiceImplementation.java index 2ff2c274..d3eae4e8 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/ServiceImplementation.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/msgprocessing/ServiceImplementation.java @@ -76,7 +76,7 @@ class ServiceImplementation { * @param config specific configuration * @throws Exception in case of error */ - ServiceImplementation(final Configuration config) throws Exception { + ServiceImplementation(final Configuration config) { this.config = config; } diff --git a/src/main/java/com/siemens/pki/cmpracomponent/msgvalidation/MessageBodyValidator.java b/src/main/java/com/siemens/pki/cmpracomponent/msgvalidation/MessageBodyValidator.java index c2aa925c..99f884ec 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/msgvalidation/MessageBodyValidator.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/msgvalidation/MessageBodyValidator.java @@ -189,15 +189,17 @@ private void assertValueNotNull(final Object value, final int failInfo, final St @Override public String validate(final PKIMessage message) throws BaseCmpException { try { - final ASN1GeneralizedTime messageTime = message.getHeader().getMessageTime(); - if (messageTime != null) { - final long diffInMillis = messageTime.getDate().getTime() - new Date().getTime(); - if (!ConfigLogger.log( - interfaceName, - "CmpMessageInterface.isMessageTimeDeviationAllowed(long)", - () -> cmpInterfaceConfig.isMessageTimeDeviationAllowed(diffInMillis / 1000L))) { - throw new CmpValidationException( - interfaceName, PKIFailureInfo.badTime, "message time out of allowed range"); + if (cmpInterfaceConfig != null) { + final ASN1GeneralizedTime messageTime = message.getHeader().getMessageTime(); + if (messageTime != null) { + final long diffInMillis = messageTime.getDate().getTime() - new Date().getTime(); + if (!ConfigLogger.log( + interfaceName, + "CmpMessageInterface.isMessageTimeDeviationAllowed(long)", + () -> cmpInterfaceConfig.isMessageTimeDeviationAllowed(diffInMillis / 1000L))) { + throw new CmpValidationException( + interfaceName, PKIFailureInfo.badTime, "message time out of allowed range"); + } } } @@ -301,6 +303,7 @@ private void validateCrmfCertReq( final CertRequest certReq = certReqMsg.getCertReq(); assertEnrollmentEqual(enrollmentType, certReq.getCertReqId(), ASN1INTEGER_0, CERT_REQ_ID_MUST_BE_0); final CertTemplate certTemplate = certReq.getCertTemplate(); + assertEnrollmentValueNotNull(enrollmentType, certTemplate, PKIFailureInfo.badCertTemplate, "cert template"); final int versionInTemplate = certTemplate.getVersion(); if (versionInTemplate != -1 && versionInTemplate != 2) { throw new CmpEnrollmentException( diff --git a/src/main/java/com/siemens/pki/cmpracomponent/protection/MacProtection.java b/src/main/java/com/siemens/pki/cmpracomponent/protection/MacProtection.java index c47c032a..0dbe205b 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/protection/MacProtection.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/protection/MacProtection.java @@ -23,6 +23,8 @@ import com.siemens.pki.cmpracomponent.configuration.SharedSecretCredentialContext; import com.siemens.pki.cmpracomponent.cryptoservices.WrappedMac; import com.siemens.pki.cmpracomponent.util.ConfigLogger; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.List; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.DERBitString; @@ -61,7 +63,8 @@ public AlgorithmIdentifier getProtectionAlg() { } @Override - public synchronized DERBitString getProtectionFor(final ProtectedPart protectedPart) throws Exception { + public synchronized DERBitString getProtectionFor(final ProtectedPart protectedPart) + throws GeneralSecurityException, IOException { return new DERBitString(protectingMac.calculateMac(protectedPart.getEncoded(ASN1Encoding.DER))); } diff --git a/src/main/java/com/siemens/pki/cmpracomponent/protection/NoProtection.java b/src/main/java/com/siemens/pki/cmpracomponent/protection/NoProtection.java index dead1cd4..ae0d3306 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/protection/NoProtection.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/protection/NoProtection.java @@ -17,6 +17,8 @@ */ package com.siemens.pki.cmpracomponent.protection; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.List; import org.bouncycastle.asn1.DERBitString; import org.bouncycastle.asn1.DEROctetString; @@ -46,7 +48,8 @@ public AlgorithmIdentifier getProtectionAlg() { } @Override - public DERBitString getProtectionFor(final ProtectedPart protectedPart) throws Exception { + public DERBitString getProtectionFor(final ProtectedPart protectedPart) + throws GeneralSecurityException, IOException { return null; } diff --git a/src/main/java/com/siemens/pki/cmpracomponent/protection/ProtectionProvider.java b/src/main/java/com/siemens/pki/cmpracomponent/protection/ProtectionProvider.java index e4dac948..6cbb23ef 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/protection/ProtectionProvider.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/protection/ProtectionProvider.java @@ -17,6 +17,9 @@ */ package com.siemens.pki.cmpracomponent.protection; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.cert.CertificateException; import java.util.List; import org.bouncycastle.asn1.DERBitString; import org.bouncycastle.asn1.DEROctetString; @@ -39,9 +42,10 @@ public interface ProtectionProvider { /** * get extra certs used for protection * @return extra certs used for protection - * @throws Exception in case of error + * @throws CertificateException in case of error + * @throws GeneralSecurityException in case of error */ - List getProtectingExtraCerts() throws Exception; + List getProtectingExtraCerts() throws GeneralSecurityException; /** * get protection algorithm @@ -54,9 +58,10 @@ public interface ProtectionProvider { * * @param protectedPart message part covered by protection * @return the protection string - * @throws Exception in case of error + * @throws IOException in case of encoding error + * @throws GeneralSecurityException in case of error */ - DERBitString getProtectionFor(ProtectedPart protectedPart) throws Exception; + DERBitString getProtectionFor(ProtectedPart protectedPart) throws GeneralSecurityException, IOException; /** * get sender to use for protected message diff --git a/src/main/java/com/siemens/pki/cmpracomponent/protection/SignatureBasedProtection.java b/src/main/java/com/siemens/pki/cmpracomponent/protection/SignatureBasedProtection.java index 3199130a..e2d2bef9 100644 --- a/src/main/java/com/siemens/pki/cmpracomponent/protection/SignatureBasedProtection.java +++ b/src/main/java/com/siemens/pki/cmpracomponent/protection/SignatureBasedProtection.java @@ -20,6 +20,8 @@ import com.siemens.pki.cmpracomponent.configuration.SignatureCredentialContext; import com.siemens.pki.cmpracomponent.cryptoservices.BaseCredentialService; import com.siemens.pki.cmpracomponent.cryptoservices.CertUtility; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.security.Signature; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; @@ -76,7 +78,8 @@ public AlgorithmIdentifier getProtectionAlg() { } @Override - public DERBitString getProtectionFor(final ProtectedPart protectedPart) throws Exception { + public DERBitString getProtectionFor(final ProtectedPart protectedPart) + throws GeneralSecurityException, IOException { final Signature sig = Signature.getInstance(getSignatureAlgorithmName()); sig.initSign(getPrivateKey()); sig.update(protectedPart.getEncoded(ASN1Encoding.DER)); diff --git a/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestCentralKeyGenerationWithKeyAgreement.java b/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestCentralKeyGenerationWithKeyAgreement.java index bd527b60..981c5b6a 100644 --- a/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestCentralKeyGenerationWithKeyAgreement.java +++ b/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestCentralKeyGenerationWithKeyAgreement.java @@ -41,13 +41,24 @@ import com.siemens.pki.cmpracomponent.test.framework.TestUtils; import com.siemens.pki.cmpracomponent.test.framework.TrustChainAndPrivateKey; import com.siemens.pki.cmpracomponent.util.MessageDumper; +import java.io.IOException; import java.math.BigInteger; +import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERBitString; +import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.cmp.PKIBody; +import org.bouncycastle.asn1.edec.EdECObjectIdentifiers; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.sec.SECObjectIdentifiers; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -67,52 +78,67 @@ public class TestCentralKeyGenerationWithKeyAgreement extends EnrollmentTestcase private static final Logger LOGGER = LoggerFactory.getLogger(TestCentralKeyGenerationWithKeyAgreement.class); public static Object[][] inputList = { + { + DEFAULT_KEY_AGREEMENT, + DEFAULT_KEY_ENCRYPTION, + X9ObjectIdentifiers.id_ecPublicKey.getId(), + SECObjectIdentifiers.secp256r1.getId() + }, + {DEFAULT_KEY_AGREEMENT, DEFAULT_KEY_ENCRYPTION, EdECObjectIdentifiers.id_Ed448.getId(), null}, // - {DEFAULT_KEY_AGREEMENT, DEFAULT_KEY_ENCRYPTION}, + {DEFAULT_KEY_AGREEMENT, DEFAULT_KEY_ENCRYPTION, EdECObjectIdentifiers.id_Ed25519.getId(), null}, // - {DEFAULT_KEY_AGREEMENT, "2.16.840.1.101.3.4.1.5"}, + {DEFAULT_KEY_AGREEMENT, DEFAULT_KEY_ENCRYPTION, PKCSObjectIdentifiers.rsaEncryption.getId(), null}, // - {DEFAULT_KEY_AGREEMENT, "2.16.840.1.101.3.4.1.25"}, + {DEFAULT_KEY_AGREEMENT, DEFAULT_KEY_ENCRYPTION, null, null}, // - {DEFAULT_KEY_AGREEMENT, "2.16.840.1.101.3.4.1.45"}, + {DEFAULT_KEY_AGREEMENT, "2.16.840.1.101.3.4.1.5", null, null}, // + {DEFAULT_KEY_AGREEMENT, "2.16.840.1.101.3.4.1.25", null, null}, // - {"1.3.132.1.11.0", DEFAULT_KEY_ENCRYPTION}, + {DEFAULT_KEY_AGREEMENT, "2.16.840.1.101.3.4.1.45", null, null}, // - {"1.3.132.1.11.1", DEFAULT_KEY_ENCRYPTION}, // - {"1.3.132.1.11.2", DEFAULT_KEY_ENCRYPTION}, + {"1.3.132.1.11.0", DEFAULT_KEY_ENCRYPTION, null, null}, // - {"1.3.132.1.11.3", DEFAULT_KEY_ENCRYPTION}, + {"1.3.132.1.11.1", DEFAULT_KEY_ENCRYPTION, null, null}, // + {"1.3.132.1.11.2", DEFAULT_KEY_ENCRYPTION, null, null}, // - {"1.3.132.1.14.0", DEFAULT_KEY_ENCRYPTION}, + {"1.3.132.1.11.3", DEFAULT_KEY_ENCRYPTION, null, null}, // - {"1.3.132.1.14.1", DEFAULT_KEY_ENCRYPTION}, // - {"1.3.132.1.14.2", DEFAULT_KEY_ENCRYPTION}, + {"1.3.132.1.14.0", DEFAULT_KEY_ENCRYPTION, null, null}, // - {"1.3.132.1.14.3", DEFAULT_KEY_ENCRYPTION}, + {"1.3.132.1.14.1", DEFAULT_KEY_ENCRYPTION, null, null}, // + {"1.3.132.1.14.2", DEFAULT_KEY_ENCRYPTION, null, null}, // - {"1.3.132.1.15.0", DEFAULT_KEY_ENCRYPTION}, + {"1.3.132.1.14.3", DEFAULT_KEY_ENCRYPTION, null, null}, // - {"1.3.132.1.15.1", DEFAULT_KEY_ENCRYPTION}, // - {"1.3.132.1.15.2", DEFAULT_KEY_ENCRYPTION}, + {"1.3.132.1.15.0", DEFAULT_KEY_ENCRYPTION, null, null}, // - {"1.3.132.1.15.3", DEFAULT_KEY_ENCRYPTION}, + {"1.3.132.1.15.1", DEFAULT_KEY_ENCRYPTION, null, null}, + // + {"1.3.132.1.15.2", DEFAULT_KEY_ENCRYPTION, null, null}, + // + {"1.3.132.1.15.3", DEFAULT_KEY_ENCRYPTION, null, null}, // // }; - @Parameters(name = "{index}: keyAgreement=>{0}, keyEncryption=>{1}") + @Parameters(name = "{index}: keyAgreement=>{0}, keyEncryption=>{1}, EnrollmentKeyType=>{2}") public static List data() { final List ret = new ArrayList<>(inputList.length); for (final Object[] aktInput : inputList) { final Object keyAgreement = aktInput[0]; final Object keyEncryption = aktInput[1]; - ret.add(new Object[] {keyAgreement, keyEncryption, keyAgreement, keyEncryption}); + final Object keyType = aktInput[2]; + final Object keyParam = aktInput[3]; + ret.add(new Object[] { + keyAgreement, keyEncryption, keyType, keyParam, keyAgreement, keyEncryption, keyType, keyParam + }); } return ret; } @@ -122,13 +148,53 @@ public static List data() { private TrustChainAndPrivateKey raCredentials; + private final KeyPair keyPair; + public TestCentralKeyGenerationWithKeyAgreement( final String keyAgreementAsString, final String keyEncryptionAsString, + final String keyTypeAsString, + final String keyParamAsString, final String keyAgreementOID, - final String keyEncryptionOID) { + final String keyEncryptionOID, + final String keyTypeOID, + final String keyParamOID) { this.keyAgreementAlg = keyAgreementOID; this.keyEncryptionAlg = keyEncryptionOID; + if (keyTypeOID != null) { + PublicKey pubKey = new PublicKey() { + private static final long serialVersionUID = 1L; + + @Override + public byte[] getEncoded() { + try { + return new DERSequence(new ASN1Encodable[] { + new AlgorithmIdentifier( + new ASN1ObjectIdentifier(keyTypeOID), + keyParamOID == null ? null : new ASN1ObjectIdentifier(keyParamOID)), + new DERBitString(new byte[0]) + }) + .getEncoded(); + } catch (IOException e) { + fail(e.getLocalizedMessage()); + return null; + } + } + + @Override + public String getAlgorithm() { + return null; + } + + @Override + public String getFormat() { + return null; + } + }; + this.keyPair = new KeyPair(pubKey, null); + } else { + this.keyPair = null; + } } private Configuration buildSignatureBasedDownstreamConfiguration() throws Exception { @@ -459,7 +525,7 @@ public void setUp() throws Exception { public void testCrWithKeyAgreement() throws Exception { final EnrollmentResult ret = getSignatureBasedCmpClient( "theCertProfileForOnlineEnrollment", - getClientContext(PKIBody.TYPE_CERT_REQ, null, null), + getClientContext(PKIBody.TYPE_CERT_REQ, keyPair, null), UPSTREAM_TRUST_PATH) .invokeEnrollment(); assertNotNull(ret); diff --git a/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestP10Cr.java b/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestP10Cr.java index 6217f076..07155806 100644 --- a/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestP10Cr.java +++ b/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestP10Cr.java @@ -136,7 +136,7 @@ public boolean isMessageTimeDeviationAllowed(final long deviation) { @Override public int getDownstreamTimeout(final String certProfile, final int bodyType) { - return 10; + return 0; } @Override diff --git a/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestRefusingInventory.java b/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestRefusingInventory.java index 0605d293..77edc2f2 100644 --- a/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestRefusingInventory.java +++ b/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestRefusingInventory.java @@ -64,13 +64,35 @@ public void testCr() throws Exception { assertNull(ret); } + @Test + public void testCrWithException() throws Exception { + final EnrollmentResult ret = getSignatureBasedCmpClient( + "refuseMeWithException", + getClientContext( + PKIBody.TYPE_CERT_REQ, + ConfigurationFactory.getKeyGenerator().generateKeyPair(), + null), + UPSTREAM_TRUST_PATH) + .invokeEnrollment(); + assertNull(ret); + } + + @Test + public void testRrRefuse() throws Exception { + testRr("refuseMeRr"); + } + + @Test + public void testRrException() throws Exception { + testRr("refuseMeWithExceptionRR"); + } + /** * Revoke a Valid Certificate * * @throws Exception */ - @Test - public void testRr() throws Exception { + private void testRr(String certProfile) throws Exception { final CmpClient crClient = getSignatureBasedCmpClient( "theCertProfileForOnlineEnrollment", getClientContext( @@ -159,7 +181,7 @@ public boolean isMessageTimeDeviationAllowed(final long deviation) { return deviation < 10; } }; - final CmpClient rrClient = new CmpClient("refuseMeRr", getUpstreamExchange(), rrUpstream, rrClientContext); + final CmpClient rrClient = new CmpClient(certProfile, getUpstreamExchange(), rrUpstream, rrClientContext); assertFalse(rrClient.invokeRevocation()); } } diff --git a/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestUpdateByInventory.java b/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestUpdateByInventory.java new file mode 100644 index 00000000..e7384e5b --- /dev/null +++ b/src/test/java/com/siemens/pki/cmpclientcomponent/test/TestUpdateByInventory.java @@ -0,0 +1,165 @@ +/* + * 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.cmpclientcomponent.test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import com.siemens.pki.cmpclientcomponent.configuration.ClientContext; +import com.siemens.pki.cmpclientcomponent.configuration.EnrollmentContext; +import com.siemens.pki.cmpclientcomponent.configuration.RevocationContext; +import com.siemens.pki.cmpclientcomponent.main.CmpClient; +import com.siemens.pki.cmpclientcomponent.main.CmpClient.EnrollmentResult; +import com.siemens.pki.cmpracomponent.configuration.CmpMessageInterface; +import com.siemens.pki.cmpracomponent.configuration.CredentialContext; +import com.siemens.pki.cmpracomponent.configuration.NestedEndpointContext; +import com.siemens.pki.cmpracomponent.configuration.SignatureCredentialContext; +import com.siemens.pki.cmpracomponent.configuration.VerificationContext; +import com.siemens.pki.cmpracomponent.test.framework.ConfigurationFactory; +import com.siemens.pki.cmpracomponent.test.framework.SignatureValidationCredentials; +import java.math.BigInteger; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import org.bouncycastle.asn1.cmp.PKIBody; +import org.junit.Before; +import org.junit.Test; + +public class TestUpdateByInventory extends EnrollmentTestcaseBase { + + private static final String UPSTREAM_TRUST_PATH = "credentials/CMP_CA_and_LRA_DOWNSTREAM_Root.pem"; + + @Before + public void setUp() throws Exception { + launchCmpCaAndRa(ConfigurationFactory.buildSignatureBasedDownstreamConfiguration()); + } + + @Test + public void testCr() throws Exception { + final EnrollmentResult ret = getSignatureBasedCmpClient( + "updateTemplateCr", + getClientContext( + PKIBody.TYPE_CERT_REQ, + ConfigurationFactory.getKeyGenerator().generateKeyPair(), + null), + UPSTREAM_TRUST_PATH) + .invokeEnrollment(); + assertNotNull(ret); + } + + /** + * Revoke a Valid Certificate + * + * @throws Exception + */ + @Test + public void testRr() throws Exception { + final CmpClient crClient = getSignatureBasedCmpClient( + "theCertProfileForOnlineEnrollment", + getClientContext( + PKIBody.TYPE_CERT_REQ, + ConfigurationFactory.getKeyGenerator().generateKeyPair(), + null), + UPSTREAM_TRUST_PATH); + + final EnrollmentResult crResult = crClient.invokeEnrollment(); + + final X509Certificate crEnrolledCertificate = crResult.getEnrolledCertificate(); + + final SignatureValidationCredentials enrollmentCredentials = getEnrollmentCredentials(); + + final ClientContext rrClientContext = new ClientContext() { + @Override + public EnrollmentContext getEnrollmentContext() { + fail("getEnrollmentContext"); + return null; + } + + @Override + public RevocationContext getRevocationContext() { + return new RevocationContext() { + + @Override + public String getIssuer() { + return crEnrolledCertificate.getIssuerX500Principal().getName(); + } + + @Override + public BigInteger getSerialNumber() { + return crEnrolledCertificate.getSerialNumber(); + } + }; + } + }; + + final CmpMessageInterface rrUpstream = new CmpMessageInterface() { + + @Override + public VerificationContext getInputVerification() { + return new SignatureValidationCredentials(UPSTREAM_TRUST_PATH, null); + } + + @Override + public NestedEndpointContext getNestedEndpointContext() { + return null; + } + + @Override + public CredentialContext getOutputCredentials() { + return new SignatureCredentialContext() { + + @Override + public List getCertificateChain() { + final List ret = new ArrayList<>(enrollmentCredentials.getAdditionalCerts()); + ret.add(0, crEnrolledCertificate); + return ret; + } + + @Override + public PrivateKey getPrivateKey() { + return crResult.getPrivateKey(); + } + }; + } + + @Override + public ReprotectMode getReprotectMode() { + return ReprotectMode.reprotect; + } + + @Override + public boolean getSuppressRedundantExtraCerts() { + return false; + } + + @Override + public boolean isCacheExtraCerts() { + return false; + } + + @Override + public boolean isMessageTimeDeviationAllowed(final long deviation) { + return deviation < 10; + } + }; + final CmpClient rrClient = new CmpClient("refuseMeRr", getUpstreamExchange(), rrUpstream, rrClientContext); + assertFalse(rrClient.invokeRevocation()); + } +} diff --git a/src/test/java/com/siemens/pki/cmpracomponent/test/CkgOnlineEnrollmentTestcaseBase.java b/src/test/java/com/siemens/pki/cmpracomponent/test/CkgOnlineEnrollmentTestcaseBase.java index 94b7e1a9..bb3738da 100644 --- a/src/test/java/com/siemens/pki/cmpracomponent/test/CkgOnlineEnrollmentTestcaseBase.java +++ b/src/test/java/com/siemens/pki/cmpracomponent/test/CkgOnlineEnrollmentTestcaseBase.java @@ -36,7 +36,6 @@ import java.security.KeyPair; import java.security.PrivateKey; import java.util.function.Function; -import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.cmp.CMPCertificate; import org.bouncycastle.asn1.cmp.CertRepMessage; import org.bouncycastle.asn1.cmp.CertResponse; @@ -77,7 +76,6 @@ public EnrollmentResult executeCrmfCertificateRequestWithoutKey( .setSubject(new X500Name("CN=Subject")); final CertTemplate template = ctb.build(); - CertTemplate.getInstance(template.getEncoded(ASN1Encoding.DER)); final PKIBody crBody = PkiMessageGenerator.generateIrCrKurBody(requestMesssageType, template, null, null); final PKIMessage cr = PkiMessageGenerator.generateAndProtectMessage( diff --git a/src/test/java/com/siemens/pki/cmpracomponent/test/TestMessageBodyValidator.java b/src/test/java/com/siemens/pki/cmpracomponent/test/TestMessageBodyValidator.java new file mode 100644 index 00000000..95e3be7a --- /dev/null +++ b/src/test/java/com/siemens/pki/cmpracomponent/test/TestMessageBodyValidator.java @@ -0,0 +1,93 @@ +/* + * 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.cmpracomponent.test; + +import com.siemens.pki.cmpracomponent.msgvalidation.BaseCmpException; +import com.siemens.pki.cmpracomponent.msgvalidation.CmpEnrollmentException; +import com.siemens.pki.cmpracomponent.msgvalidation.CmpValidationException; +import com.siemens.pki.cmpracomponent.msgvalidation.MessageBodyValidator; +import org.bouncycastle.asn1.ASN1Integer; +import org.bouncycastle.asn1.cmp.CertRepMessage; +import org.bouncycastle.asn1.cmp.CertResponse; +import org.bouncycastle.asn1.cmp.ErrorMsgContent; +import org.bouncycastle.asn1.cmp.PKIBody; +import org.bouncycastle.asn1.cmp.PKIFailureInfo; +import org.bouncycastle.asn1.cmp.PKIMessage; +import org.bouncycastle.asn1.cmp.PKIStatus; +import org.bouncycastle.asn1.cmp.PKIStatusInfo; +import org.bouncycastle.asn1.cmp.RevRepContentBuilder; +import org.bouncycastle.asn1.crmf.CertReqMessages; +import org.bouncycastle.asn1.crmf.CertReqMsg; +import org.bouncycastle.asn1.crmf.CertRequest; +import org.bouncycastle.asn1.crmf.CertTemplateBuilder; +import org.junit.Test; + +public class TestMessageBodyValidator { + + private final MessageBodyValidator validatorUnderTest = + new MessageBodyValidator("test validator", (x, y) -> true, null, null); + + @Test(expected = CmpEnrollmentException.class) + public void testWrongCertIdInRequest() throws BaseCmpException { + PKIBody bodyToTest = new PKIBody( + PKIBody.TYPE_CERT_REQ, + new CertReqMessages( + new CertReqMsg(new CertRequest(77, (new CertTemplateBuilder()).build(), null), null, null))); + validatorUnderTest.validate(new PKIMessage(null, bodyToTest)); + } + + @Test(expected = CmpValidationException.class) + public void testWrongCertIdInResponse() throws BaseCmpException { + PKIBody bodyToTest = new PKIBody(PKIBody.TYPE_CERT_REP, new CertRepMessage(null, new CertResponse[] { + new CertResponse(new ASN1Integer(77), new PKIStatusInfo(PKIStatus.rejection)) + })); + validatorUnderTest.validate(new PKIMessage(null, bodyToTest)); + } + + @Test(expected = CmpValidationException.class) + public void testTwoResponsesInResponse() throws BaseCmpException { + PKIBody bodyToTest = new PKIBody(PKIBody.TYPE_CERT_REP, new CertRepMessage(null, new CertResponse[] { + new CertResponse(new ASN1Integer(0), new PKIStatusInfo(PKIStatus.rejection)), + new CertResponse(new ASN1Integer(0), new PKIStatusInfo(PKIStatus.rejection)) + })); + validatorUnderTest.validate(new PKIMessage(null, bodyToTest)); + } + + @Test(expected = CmpEnrollmentException.class) + public void testMissingTemplatInRequeste() throws BaseCmpException { + PKIBody bodyToTest = new PKIBody( + PKIBody.TYPE_CERT_REQ, new CertReqMessages(new CertReqMsg(new CertRequest(0, null, null), null, null))); + validatorUnderTest.validate(new PKIMessage(null, bodyToTest)); + } + + @Test(expected = CmpValidationException.class) + public void testBrokenPkiStatusInError() throws BaseCmpException { + PKIBody bodyToTest = new PKIBody( + PKIBody.TYPE_ERROR, + new ErrorMsgContent(new PKIStatusInfo(PKIStatus.granted, null, new PKIFailureInfo(77)))); + validatorUnderTest.validate(new PKIMessage(null, bodyToTest)); + } + + @Test + public void testNegativeRevRep() throws BaseCmpException { + PKIBody bodyToTest = new PKIBody( + PKIBody.TYPE_REVOCATION_REP, + (new RevRepContentBuilder().add(new PKIStatusInfo(PKIStatus.rejection))).build()); + validatorUnderTest.validate(new PKIMessage(null, bodyToTest)); + } +} diff --git a/src/test/java/com/siemens/pki/cmpracomponent/test/TestSupportMessages.java b/src/test/java/com/siemens/pki/cmpracomponent/test/TestSupportMessages.java index 53ad5575..f3a3987c 100644 --- a/src/test/java/com/siemens/pki/cmpracomponent/test/TestSupportMessages.java +++ b/src/test/java/com/siemens/pki/cmpracomponent/test/TestSupportMessages.java @@ -31,6 +31,7 @@ import java.util.function.Function; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.cmp.*; @@ -81,16 +82,7 @@ null, new GeneralNames(new GeneralName(new X500Name("CN=distributionPoint")))), new HeaderProviderForTest("CrlUpdateRetrieval"), ConfigurationFactory.getEeSignaturebasedProtectionProvider(), genmBody); - if (LOGGER.isDebugEnabled()) { - // avoid unnecessary call of MessageDumper.dumpPkiMessage, if debug isn't - // enabled - LOGGER.debug("send" + MessageDumper.dumpPkiMessage(genm)); - } final PKIMessage genr = eeCmpClient.apply(genm); - if (LOGGER.isDebugEnabled()) { - // avoid unnecessary string processing, if debug isn't enabled - LOGGER.debug("got" + MessageDumper.dumpPkiMessage(genr)); - } assertEquals("message type", PKIBody.TYPE_GEN_REP, genr.getBody().getType()); final GenRepContent content = (GenRepContent) genr.getBody().getContent(); final InfoTypeAndValue[] itav = content.toInfoTypeAndValueArray(); @@ -139,6 +131,37 @@ public void testGetCaCerts() throws Exception { assertEquals("number of returned certificates", 20, value.size()); } + /* + * Get CA certificates without transaction Id + */ + @Test + public void testGetCaCertsWithoutTransactionId() throws Exception { + final ASN1ObjectIdentifier getCaCertOid = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.4.17"); + final PKIBody genmBody = + new PKIBody(PKIBody.TYPE_GEN_MSG, new GenMsgContent(new InfoTypeAndValue(getCaCertOid))); + final PKIMessage genm = PkiMessageGenerator.generateAndProtectMessage( + new HeaderProviderForTest("GetCaCertsCertProfile") { + @Override + public ASN1OctetString getTransactionID() { + return null; + } + }, + ConfigurationFactory.getEeSignaturebasedProtectionProvider(), + genmBody); + if (LOGGER.isDebugEnabled()) { + // avoid unnecessary call of MessageDumper.dumpPkiMessage, if debug isn't + // enabled + LOGGER.debug("send" + MessageDumper.dumpPkiMessage(genm)); + } + final PKIMessage genr = getEeClient().apply(genm); + if (LOGGER.isDebugEnabled()) { + // avoid unnecessary call of MessageDumper.dumpPkiMessage, if debug isn't + // enabled + LOGGER.debug("got" + MessageDumper.dumpPkiMessage(genr)); + } + assertEquals("message type", PKIBody.TYPE_ERROR, genr.getBody().getType()); + } + /* * Get Certificate Request Template */ diff --git a/src/test/java/com/siemens/pki/cmpracomponent/test/framework/CmpCaMock.java b/src/test/java/com/siemens/pki/cmpracomponent/test/framework/CmpCaMock.java index 0cd3b06b..d487165e 100644 --- a/src/test/java/com/siemens/pki/cmpracomponent/test/framework/CmpCaMock.java +++ b/src/test/java/com/siemens/pki/cmpracomponent/test/framework/CmpCaMock.java @@ -117,7 +117,6 @@ private CMPCertificate createCertificate( try { v3CertBldr.addExtension(extensionsFromTemplate.getExtension(oid)); } catch (final CertIOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } }); diff --git a/src/test/java/com/siemens/pki/cmpracomponent/test/framework/ConfigurationFactory.java b/src/test/java/com/siemens/pki/cmpracomponent/test/framework/ConfigurationFactory.java index 37a8066e..2b476cc9 100644 --- a/src/test/java/com/siemens/pki/cmpracomponent/test/framework/ConfigurationFactory.java +++ b/src/test/java/com/siemens/pki/cmpracomponent/test/framework/ConfigurationFactory.java @@ -436,6 +436,7 @@ public VerificationContext getInputVerification() { case "certProfileForKur": case "certProfileForRr": case "refuseMeRr": + case "refuseMeWithExceptionRR": return enrollmentTrust; } } @@ -508,6 +509,14 @@ public InventoryInterface getInventory(final String certProfile, final int bodyT certProfile, MessageDumper.msgTypeAsString(bodyType)); + if ("refuseMeWithException".equals(certProfile)) { + throw new RuntimeException("intended inventory config error"); + } + + if ("refuseMeWithExceptionRR".equals(certProfile)) { + throw new RuntimeException("intended inventory config error"); + } + if ("refuseMeCr".equals(certProfile)) { return new InventoryInterface() { @@ -534,6 +543,32 @@ public boolean isGranted() { }; } + if ("updateTemplateCr".equals(certProfile)) { + return new InventoryInterface() { + + @Override + public CheckAndModifyResult checkAndModifyCertRequest( + byte[] transactionID, + String requesterDn, + byte[] certTemplate, + String requestedSubjectDn, + byte[] pkiMessage) { + return new CheckAndModifyResult() { + + @Override + public byte[] getUpdatedCertTemplate() { + return certTemplate; + } + + @Override + public boolean isGranted() { + return true; + } + }; + } + }; + } + if ("refuseMeRr".equals(certProfile)) { return new InventoryInterface() { @Override diff --git a/src/test/java/com/siemens/pki/cmpracomponent/test/framework/HeaderProviderForTest.java b/src/test/java/com/siemens/pki/cmpracomponent/test/framework/HeaderProviderForTest.java index 7fb8440a..44a045ec 100644 --- a/src/test/java/com/siemens/pki/cmpracomponent/test/framework/HeaderProviderForTest.java +++ b/src/test/java/com/siemens/pki/cmpracomponent/test/framework/HeaderProviderForTest.java @@ -37,7 +37,7 @@ /** * */ -public final class HeaderProviderForTest implements HeaderProvider { +public class HeaderProviderForTest implements HeaderProvider { final ASN1OctetString transactionId; final ASN1OctetString senderNonce = new DEROctetString(CertUtility.generateRandomBytes(16)); diff --git a/src/test/java/com/siemens/pki/cmpracomponent/test/framework/TrustChainAndPrivateKey.java b/src/test/java/com/siemens/pki/cmpracomponent/test/framework/TrustChainAndPrivateKey.java index 458023b6..7ba37257 100644 --- a/src/test/java/com/siemens/pki/cmpracomponent/test/framework/TrustChainAndPrivateKey.java +++ b/src/test/java/com/siemens/pki/cmpracomponent/test/framework/TrustChainAndPrivateKey.java @@ -20,6 +20,8 @@ import com.siemens.pki.cmpracomponent.configuration.SignatureCredentialContext; import com.siemens.pki.cmpracomponent.cryptoservices.AlgorithmHelper; import com.siemens.pki.cmpracomponent.protection.ProtectionProvider; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyStore; import java.security.PrivateKey; @@ -91,7 +93,7 @@ public ProtectionProvider setEndEntityToProtect(final CMPCertificate certificate return new ProtectionProvider() { @Override - public List getProtectingExtraCerts() throws Exception { + public List getProtectingExtraCerts() throws GeneralSecurityException { final List ret = new ArrayList<>(trustChain.size()); ret.add(certificate); for (final X509Certificate aktCert : trustChain) { @@ -110,7 +112,8 @@ public AlgorithmIdentifier getProtectionAlg() { } @Override - public DERBitString getProtectionFor(final ProtectedPart protectedPart) throws Exception { + public DERBitString getProtectionFor(final ProtectedPart protectedPart) + throws GeneralSecurityException, IOException { final Signature sig = Signature.getInstance(AlgorithmHelper.getSigningAlgNameFromKey(privateKey)); sig.initSign(privateKey); sig.update(protectedPart.getEncoded(ASN1Encoding.DER));