diff options
author | EmmettCox <emmett.cox@est.tech> | 2020-02-27 17:19:47 +0000 |
---|---|---|
committer | EmmettCox <emmett.cox@est.tech> | 2020-03-04 09:16:08 +0000 |
commit | 9b682503a32af10dd6335c897e73e0e63f688210 (patch) | |
tree | 1fc962bc35fd15c55de9c29c0bab7f04fd521b54 /certService | |
parent | 7dc94694ec75ce738ecb2e98508c57eadc1d81fa (diff) |
Authenticate response from CMP server
Issue-ID: AAF-1037
Signed-off-by: EmmettCox <emmett.cox@est.tech>
Change-Id: I6f52627a169359067ddd928d1e895e8d6237c7b5
Diffstat (limited to 'certService')
11 files changed, 417 insertions, 48 deletions
diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java index 7655b025..e9f7a483 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/external/CSRMeta.java @@ -48,7 +48,7 @@ public class CSRMeta { private X500Name name; private X500Name issuerName; private Certificate certificate; - private SecureRandom random = new SecureRandom(); + private String senderKid; public CSRMeta(List<RDN> rdns) { this.rdns = rdns; @@ -188,6 +188,14 @@ public class CSRMeta { CaUrl = caUrl; } + public String senderKid() { + return senderKid; + } + + public void senderKid(String senderKid) { + this.senderKid = senderKid; + } + public String issuerCn() { return issuerCn; } diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java index 7dacfc80..29bd671d 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpClientImpl.java @@ -20,9 +20,13 @@ package org.onap.aaf.certservice.cmpv2client.impl; +import java.security.PublicKey; import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.checkIfCmpResponseContainsError; import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.getCertfromByteArray; import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseHelper.verifyAndReturnCertChainAndTrustSTore; +import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.checkImplicitConfirm; +import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifyPasswordBasedProtection; +import static org.onap.aaf.certservice.cmpv2client.impl.CmpResponseValidationHelper.verifySignature; import java.io.IOException; import java.security.cert.CertificateParsingException; @@ -38,7 +42,9 @@ import org.bouncycastle.asn1.cmp.CMPCertificate; import org.bouncycastle.asn1.cmp.CertRepMessage; import org.bouncycastle.asn1.cmp.CertResponse; import org.bouncycastle.asn1.cmp.PKIBody; +import org.bouncycastle.asn1.cmp.PKIHeader; import org.bouncycastle.asn1.cmp.PKIMessage; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException; import org.onap.aaf.certservice.cmpv2client.api.CmpClient; import org.onap.aaf.certservice.cmpv2client.external.CSRMeta; @@ -51,7 +57,7 @@ import org.slf4j.LoggerFactory; */ public class CmpClientImpl implements CmpClient { - private final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class); + private static final Logger LOG = LoggerFactory.getLogger(CmpClientImpl.class); private final CloseableHttpClient httpClient; private static final String DEFAULT_PROFILE = "RA"; @@ -82,6 +88,7 @@ public class CmpClientImpl implements CmpClient { .with(CreateCertRequest::setNotBefore, notBefore) .with(CreateCertRequest::setNotAfter, notAfter) .with(CreateCertRequest::setInitAuthPassword, csrMeta.password()) + .with(CreateCertRequest::setSenderKid, csrMeta.senderKid()) .build(); final PKIMessage pkiMessage = certRequest.generateCertReq(); @@ -96,6 +103,45 @@ public class CmpClientImpl implements CmpClient { return createCertificate(caName, profile, csrMeta, csr, null, null); } + private void checkCmpResponse( + final PKIMessage respPkiMessage, final PublicKey publicKey, final String initAuthPassword) + throws CmpClientException { + final PKIHeader header = respPkiMessage.getHeader(); + final AlgorithmIdentifier protectionAlgo = header.getProtectionAlg(); + verifySignatureWithPublicKey(respPkiMessage, publicKey); + verifyProtectionWithProtectionAlgo(respPkiMessage, initAuthPassword, header, protectionAlgo); + } + + private void verifySignatureWithPublicKey(PKIMessage respPkiMessage, PublicKey publicKey) + throws CmpClientException { + if (Objects.nonNull(publicKey)) { + LOG.debug("Verifying signature of the response."); + verifySignature(respPkiMessage, publicKey); + } else { + LOG.error("Public Key is not available, therefore cannot verify signature"); + throw new CmpClientException( + "Public Key is not available, therefore cannot verify signature"); + } + } + + private void verifyProtectionWithProtectionAlgo( + PKIMessage respPkiMessage, + String initAuthPassword, + PKIHeader header, + AlgorithmIdentifier protectionAlgo) + throws CmpClientException { + if (Objects.nonNull(protectionAlgo)) { + LOG.debug("Verifying PasswordBased Protection of the Response."); + verifyPasswordBasedProtection(respPkiMessage, initAuthPassword, protectionAlgo); + checkImplicitConfirm(header); + } else { + LOG.error( + "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm"); + throw new CmpClientException( + "Protection Algorithm is not available when expecting PBE protected response containing protection algorithm"); + } + } + private List<List<X509Certificate>> checkCmpCertRepMessage(final PKIMessage respPkiMessage) throws CmpClientException { final PKIBody pkiBody = respPkiMessage.getBody(); @@ -128,10 +174,11 @@ public class CmpClientImpl implements CmpClient { certResponse.getCertifiedKeyPair().getCertOrEncCert().getCertificate(); final Optional<X509Certificate> leafCertificate = getCertfromByteArray(cmpCertificate.getEncoded(), X509Certificate.class); - ArrayList<X509Certificate> certChain = new ArrayList<>(); - ArrayList<X509Certificate> trustStore = new ArrayList<>(); - return verifyAndReturnCertChainAndTrustSTore( - respPkiMessage, certRepMessage, leafCertificate.get(), certChain, trustStore); + if (leafCertificate.isPresent()) { + return verifyAndReturnCertChainAndTrustSTore( + respPkiMessage, certRepMessage, leafCertificate.get()); + } + return Collections.emptyList(); } private CertResponse getCertificateResponseContainingNewCertificate( @@ -184,8 +231,9 @@ public class CmpClientImpl implements CmpClient { final byte[] respBytes = cmpv2HttpClient.postRequest(pkiMessage, csrMeta.caUrl(), caName); try { final PKIMessage respPkiMessage = PKIMessage.getInstance(respBytes); - LOG.info("Recieved response from Server"); + LOG.info("Received response from Server"); checkIfCmpResponseContainsError(respPkiMessage); + checkCmpResponse(respPkiMessage, csrMeta.keypair().getPublic(), csrMeta.password()); return checkCmpCertRepMessage(respPkiMessage); } catch (IllegalArgumentException iae) { CmpClientException cmpClientException = diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java index 8fdee2ed..60d91c5f 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseHelper.java @@ -56,7 +56,9 @@ import org.slf4j.LoggerFactory; public class CmpResponseHelper { - private static final Logger LOG = LoggerFactory.getLogger(CmpMessageHelper.class); + private static final Logger LOG = LoggerFactory.getLogger(CmpResponseHelper.class); + + private CmpResponseHelper() {} public static void checkIfCmpResponseContainsError(PKIMessage respPkiMessage) throws CmpClientException { @@ -192,7 +194,7 @@ public class CmpResponseHelper { CertPathValidator.getInstance("PKIX", BouncyCastleProvider.PROVIDER_NAME); PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) cpv.validate(cp, params); if (LOG.isDebugEnabled()) { - LOG.debug("Certificate verify result:{} ", result.toString()); + LOG.debug("Certificate verify result:{} ", result); } } @@ -276,25 +278,23 @@ public class CmpResponseHelper { * @param respPkiMessage PKIMessage that may contain extra certs used for certchain * @param certRepMessage CertRepMessage that should contain rootCA for certchain * @param leafCertificate certificate returned from our original Cert Request - * @param certChain Array of certificates to be used for KeyStore - * @param trustStore Array of certificates to be used for TrustStore * @return list of two lists, CertChain and TrustStore * @throws CertificateParsingException thrown if error occurs while parsing certificate * @throws IOException thrown if IOException occurs while parsing certificate * @throws CmpClientException thrown if error occurs during the verification of the certChain */ public static List<List<X509Certificate>> verifyAndReturnCertChainAndTrustSTore( - PKIMessage respPkiMessage, - CertRepMessage certRepMessage, - X509Certificate leafCertificate, - ArrayList<X509Certificate> certChain, - ArrayList<X509Certificate> trustStore) + PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCertificate) throws CertificateParsingException, IOException, CmpClientException { + List<X509Certificate> certChain = + addExtraCertsToChain(respPkiMessage, certRepMessage, leafCertificate); List<String> certNames = getNamesOfCerts(certChain); LOG.debug("Verifying the following certificates in the cert chain: {}", certNames); - certChain.add(leafCertificate); - addExtraCertsToChain(respPkiMessage, certRepMessage, certChain); verify(certChain); + ArrayList<X509Certificate> trustStore = new ArrayList<>(); + final int rootCaIndex = certChain.size() - 1; + trustStore.add(certChain.get(rootCaIndex)); + certChain.remove(rootCaIndex); List<List<X509Certificate>> listOfArray = new ArrayList<>(); listOfArray.add(certChain); listOfArray.add(trustStore); @@ -303,7 +303,7 @@ public class CmpResponseHelper { public static List<String> getNamesOfCerts(List<X509Certificate> certChain) { List<String> certNames = new ArrayList<>(); - certChain.forEach((cert) -> certNames.add(cert.getSubjectDN().getName())); + certChain.forEach(cert -> certNames.add(cert.getSubjectDN().getName())); return certNames; } @@ -312,30 +312,56 @@ public class CmpResponseHelper { * * @param respPkiMessage PKIMessage that may contain extra certs used for certchain * @param certRepMessage CertRepMessage that should contain rootCA for certchain - * @param certChain Array of certificates to be used for KeyStore + * @param leafCert certificate at top of certChain. * @throws CertificateParsingException thrown if error occurs while parsing certificate * @throws IOException thrown if IOException occurs while parsing certificate * @throws CmpClientException thrown if there are errors creating CertificateFactory */ - public static void addExtraCertsToChain( - PKIMessage respPkiMessage, - CertRepMessage certRepMessage, - ArrayList<X509Certificate> certChain) + public static List<X509Certificate> addExtraCertsToChain( + PKIMessage respPkiMessage, CertRepMessage certRepMessage, X509Certificate leafCert) throws CertificateParsingException, IOException, CmpClientException { + List<X509Certificate> certChain = new ArrayList<>(); + certChain.add(leafCert); if (respPkiMessage.getExtraCerts() != null) { final CMPCertificate[] extraCerts = respPkiMessage.getExtraCerts(); for (CMPCertificate cmpCert : extraCerts) { Optional<X509Certificate> cert = getCertfromByteArray(cmpCert.getEncoded(), X509Certificate.class); - LOG.debug("Adding certificate {} to cert chain", cert.get().getSubjectDN().getName()); - certChain.add(cert.get()); + certChain = + ifCertPresent( + certChain, + cert, + "Adding certificate from extra certs {} to cert chain", + "Couldn't add certificate from extra certs, certificate wasn't an X509Certificate"); + return certChain; } } else { final CMPCertificate respCmpCaCert = getRootCa(certRepMessage); Optional<X509Certificate> cert = getCertfromByteArray(respCmpCaCert.getEncoded(), X509Certificate.class); - LOG.debug("Adding certificate {} to TrustStore", cert.get().getSubjectDN().getName()); + certChain = + ifCertPresent( + certChain, + cert, + "Adding certificate from CaPubs {} to TrustStore", + "Couldn't add certificate from CaPubs, certificate wasn't an X509Certificate"); + return certChain; + } + return Collections.emptyList(); + } + + public static List<X509Certificate> ifCertPresent( + List<X509Certificate> certChain, + Optional<X509Certificate> cert, + String certPresentString, + String certUnavailableString) { + if (cert.isPresent()) { + LOG.debug(certPresentString, cert.get().getSubjectDN().getName()); certChain.add(cert.get()); + return certChain; + } else { + LOG.debug(certUnavailableString); + return certChain; } } diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseValidationHelper.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseValidationHelper.java new file mode 100644 index 00000000..8191c69d --- /dev/null +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpResponseValidationHelper.java @@ -0,0 +1,238 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2020 Nordix Foundation. + * ================================================================================ + * 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 + * ============LICENSE_END========================================================= + */ + +package org.onap.aaf.certservice.cmpv2client.impl; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.util.Arrays; +import java.util.Objects; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.bouncycastle.asn1.ASN1Encodable; +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.DERBitString; +import org.bouncycastle.asn1.DEROutputStream; +import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers; +import org.bouncycastle.asn1.cmp.InfoTypeAndValue; +import org.bouncycastle.asn1.cmp.PBMParameter; +import org.bouncycastle.asn1.cmp.PKIBody; +import org.bouncycastle.asn1.cmp.PKIHeader; +import org.bouncycastle.asn1.cmp.PKIMessage; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; +import org.bouncycastle.asn1.x509.AlgorithmIdentifier; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class CmpResponseValidationHelper { + + private static final Logger LOG = LoggerFactory.getLogger(CmpResponseValidationHelper.class); + + private CmpResponseValidationHelper() {} + + /** + * Create a base key to use for verifying the PasswordBasedMac on a PKIMessage + * + * @param pbmParamSeq parameters recieved in PKIMessage used with password + * @param initAuthPassword password used to decrypt the basekey + * @return bytes representing the basekey + * @throws CmpClientException thrown if algorithem exceptions occur for the message digest + */ + public static byte[] getBaseKeyFromPbmParameters( + PBMParameter pbmParamSeq, String initAuthPassword) throws CmpClientException { + final int iterationCount = pbmParamSeq.getIterationCount().getPositiveValue().intValue(); + LOG.info("Iteration count is: {}", iterationCount); + final AlgorithmIdentifier owfAlg = pbmParamSeq.getOwf(); + LOG.info("One Way Function type is: {}", owfAlg.getAlgorithm().getId()); + final byte[] salt = pbmParamSeq.getSalt().getOctets(); + final byte[] raSecret = initAuthPassword != null ? initAuthPassword.getBytes() : new byte[0]; + byte[] basekey = new byte[raSecret.length + salt.length]; + System.arraycopy(raSecret, 0, basekey, 0, raSecret.length); + System.arraycopy(salt, 0, basekey, raSecret.length, salt.length); + try { + final MessageDigest messageDigest = + MessageDigest.getInstance( + owfAlg.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME); + for (int i = 0; i < iterationCount; i++) { + basekey = messageDigest.digest(basekey); + messageDigest.reset(); + } + } catch (NoSuchAlgorithmException | NoSuchProviderException ex) { + LOG.error("ProtectionBytes don't match passwordBasedProtection, authentication failed"); + throw new CmpClientException( + "ProtectionBytes don't match passwordBasedProtection, authentication failed", ex); + } + return basekey; + } + + /** + * Verifies the signature of the response message using our public key + * + * @param respPkiMessage PKIMessage we wish to verify signature for + * @param pk public key used to verify signature. + * @throws CmpClientException + */ + public static void verifySignature(PKIMessage respPkiMessage, PublicKey pk) + throws CmpClientException { + final byte[] protBytes = getProtectedBytes(respPkiMessage); + final DERBitString derBitString = respPkiMessage.getProtection(); + try { + final Signature signature = + Signature.getInstance( + PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), + BouncyCastleProvider.PROVIDER_NAME); + signature.initVerify(pk); + signature.update(protBytes); + signature.verify(derBitString.getBytes()); + } catch (NoSuchAlgorithmException + | NoSuchProviderException + | InvalidKeyException + | SignatureException e) { + CmpClientException clientException = + new CmpClientException("Signature Verification failed", e); + LOG.error("Signature Verification failed", e); + throw clientException; + } + } + /** + * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte + * array + * + * @param msg PKIMessage to get protected bytes from + * @return the PKIMessage's header and body in byte array + */ + public static byte[] getProtectedBytes(PKIMessage msg) throws CmpClientException { + return getProtectedBytes(msg.getHeader(), msg.getBody()); + } + + /** + * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte + * array + * + * @param header PKIHeader to be converted + * @param body PKIMessage to be converted + * @return the PKIMessage's header and body in byte array + */ + public static byte[] getProtectedBytes(PKIHeader header, PKIBody body) throws CmpClientException { + byte[] res; + ASN1EncodableVector v = new ASN1EncodableVector(); + v.add(header); + v.add(body); + ASN1Encodable protectedPart = new DERSequence(v); + try { + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + DEROutputStream out = new DEROutputStream(bao); + out.writeObject(protectedPart); + res = bao.toByteArray(); + } catch (IOException ioe) { + CmpClientException cmpClientException = + new CmpClientException("Error occured while getting protected bytes", ioe); + LOG.error("Error occured while getting protected bytes", ioe); + throw cmpClientException; + } + return res; + } + + /** + * verify the password based protection within the response message + * + * @param respPkiMessage PKIMessage we want to verify password based protection for + * @param initAuthPassword password used to decrypt protection + * @param protectionAlgo protection algorithm we can use to decrypt protection + * @throws CmpClientException + */ + public static void verifyPasswordBasedProtection( + PKIMessage respPkiMessage, String initAuthPassword, AlgorithmIdentifier protectionAlgo) + throws CmpClientException { + final byte[] protectedBytes = getProtectedBytes(respPkiMessage); + final PBMParameter pbmParamSeq = PBMParameter.getInstance(protectionAlgo.getParameters()); + if (Objects.nonNull(pbmParamSeq)) { + try { + byte[] basekey = getBaseKeyFromPbmParameters(pbmParamSeq, initAuthPassword); + final Mac mac = getMac(protectedBytes, pbmParamSeq, basekey); + final byte[] outBytes = mac.doFinal(); + final byte[] protectionBytes = respPkiMessage.getProtection().getBytes(); + if (!Arrays.equals(outBytes, protectionBytes)) { + LOG.error("protectionBytes don't match passwordBasedProtection, authentication failed"); + throw new CmpClientException( + "protectionBytes don't match passwordBasedProtection, authentication failed"); + } + } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException ex) { + CmpClientException cmpClientException = + new CmpClientException("Error while validating CMP response ", ex); + LOG.error("Error while validating CMP response ", ex); + throw cmpClientException; + } + } + } + + public static void checkImplicitConfirm(PKIHeader header) { + InfoTypeAndValue[] infos = header.getGeneralInfo(); + if (Objects.nonNull(infos)) { + if (CMPObjectIdentifiers.it_implicitConfirm.equals(getImplicitConfirm(infos))) { + LOG.info("Implicit Confirm on certificate from server."); + } else { + LOG.debug("No Implicit confirm in Response"); + } + } else { + LOG.debug("No general Info in header of response, cannot verify implicit confirm"); + } + } + + public static ASN1ObjectIdentifier getImplicitConfirm(InfoTypeAndValue[] info) { + return info[0].getInfoType(); + } + + /** + * Get cryptographical Mac we can use to decrypt our PKIMessage + * + * @param protectedBytes Protected bytes representing the PKIMessage + * @param pbmParamSeq Parameters used to decrypt PKIMessage, including mac algorithm used + * @param basekey Key used alongside mac Oid to create secret key for decrypting PKIMessage + * @return Mac that's ready to return decrypted bytes + * @throws NoSuchAlgorithmException Possibly thrown trying to get mac instance + * @throws NoSuchProviderException Possibly thrown trying to get mac instance + * @throws InvalidKeyException Possibly thrown trying to initialize mac using secretkey + */ + public static Mac getMac(byte[] protectedBytes, PBMParameter pbmParamSeq, byte[] basekey) + throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException { + final AlgorithmIdentifier macAlg = pbmParamSeq.getMac(); + LOG.info("Mac type is: {}", macAlg.getAlgorithm().getId()); + final String macOid = macAlg.getAlgorithm().getId(); + final Mac mac = Mac.getInstance(macOid, BouncyCastleProvider.PROVIDER_NAME); + final SecretKey key = new SecretKeySpec(basekey, macOid); + mac.init(key); + mac.reset(); + mac.update(protectedBytes, 0, protectedBytes.length); + return mac; + } +} diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java index 86935135..1bda4ac1 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CmpUtil.java @@ -31,6 +31,8 @@ import org.bouncycastle.asn1.ASN1GeneralizedTime; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.DEROutputStream; import org.bouncycastle.asn1.DERSequence; +import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers; +import org.bouncycastle.asn1.cmp.InfoTypeAndValue; import org.bouncycastle.asn1.cmp.PKIBody; import org.bouncycastle.asn1.cmp.PKIHeader; import org.bouncycastle.asn1.cmp.PKIHeaderBuilder; @@ -129,7 +131,7 @@ public final class CmpUtil { * @return PKIHeaderBuilder */ static PKIHeader generatePkiHeader( - X500Name subjectDn, X500Name issuerDn, AlgorithmIdentifier protectionAlg) { + X500Name subjectDn, X500Name issuerDn, AlgorithmIdentifier protectionAlg, String senderKid) { LOGGER.info("Generating a Pki Header Builder"); PKIHeaderBuilder pkiHeaderBuilder = new PKIHeaderBuilder( @@ -139,6 +141,8 @@ public final class CmpUtil { pkiHeaderBuilder.setSenderNonce(new DEROctetString(createRandomBytes())); pkiHeaderBuilder.setTransactionID(new DEROctetString(createRandomBytes())); pkiHeaderBuilder.setProtectionAlg(protectionAlg); + pkiHeaderBuilder.setGeneralInfo(new InfoTypeAndValue(CMPObjectIdentifiers.it_implicitConfirm)); + pkiHeaderBuilder.setSenderKID(new DEROctetString(senderKid.getBytes())); return pkiHeaderBuilder.build(); } diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java index db86a1e3..682e410d 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/Cmpv2HttpClient.java @@ -54,7 +54,7 @@ class Cmpv2HttpClient { * @param pkiMessage PKIMessage to send to server * @param urlString url for the server we're sending request * @param caName name of CA server - * @return + * @return PKIMessage received from CMPServer * @throws CmpClientException thrown if problems with connecting or parsing response to server */ public byte[] postRequest( @@ -73,7 +73,8 @@ class Cmpv2HttpClient { return byteArrOutputStream.toByteArray(); } catch (IOException ioe) { CmpClientException cmpClientException = - new CmpClientException(String.format("IOException error while trying to connect CA %s",caName), ioe); + new CmpClientException( + String.format("IOException error while trying to connect CA %s", caName), ioe); LOG.error("IOException error {}, while trying to connect CA {}", ioe.getMessage(), caName); throw cmpClientException; } diff --git a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java index 25943321..b185c92a 100644 --- a/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java +++ b/certService/src/main/java/org/onap/aaf/certservice/cmpv2client/impl/CreateCertRequest.java @@ -55,6 +55,7 @@ class CreateCertRequest { private Date notBefore; private Date notAfter; private String initAuthPassword; + private String senderKid; private static final int ITERATIONS = createRandomInt(5000); private static final byte[] SALT = createRandomBytes(); @@ -88,6 +89,10 @@ class CreateCertRequest { this.initAuthPassword = initAuthPassword; } + public void setSenderKid(String senderKid) { + this.senderKid = senderKid; + } + /** * Method to create {@link PKIMessage} from {@link CertRequest},{@link ProofOfPossession}, {@link * CertReqMsg}, {@link CertReqMessages}, {@link PKIHeader} and {@link PKIBody}. @@ -118,8 +123,11 @@ class CreateCertRequest { final PKIHeader pkiHeader = generatePkiHeader( - subjectDn, issuerDn, CmpMessageHelper.protectionAlgoIdentifier(ITERATIONS, SALT)); - final PKIBody pkiBody = new PKIBody(PKIBody.TYPE_CERT_REQ, certReqMessages); + subjectDn, + issuerDn, + CmpMessageHelper.protectionAlgoIdentifier(ITERATIONS, SALT), + senderKid); + final PKIBody pkiBody = new PKIBody(PKIBody.TYPE_INIT_REQ, certReqMessages); return CmpMessageHelper.protectPkiMessage( pkiHeader, pkiBody, initAuthPassword, ITERATIONS, SALT); diff --git a/certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java b/certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java index 26cf7e2d..713a2d00 100644 --- a/certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java +++ b/certService/src/test/java/org/onap/aaf/certservice/cmpv2Client/Cmpv2ClientTest.java @@ -27,12 +27,18 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.Security; import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -77,11 +83,13 @@ class Cmpv2ClientTest { private static ArrayList<RDN> rdns; @BeforeEach - void setUp() throws NoSuchProviderException, NoSuchAlgorithmException { + void setUp() + throws NoSuchProviderException, NoSuchAlgorithmException, IOException, + InvalidKeySpecException { KeyPairGenerator keyGenerator; keyGenerator = KeyPairGenerator.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME); keyGenerator.initialize(2048); - keyPair = keyGenerator.generateKeyPair(); + keyPair = LoadKeyPair(); rdns = new ArrayList<>(); try { rdns.add(new RDN("O=CommonCompany")); @@ -91,6 +99,27 @@ class Cmpv2ClientTest { initMocks(this); } + public KeyPair LoadKeyPair() + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, + NoSuchProviderException { + + final InputStream privateInputStream = this.getClass().getResourceAsStream("/privateKey"); + final InputStream publicInputStream = this.getClass().getResourceAsStream("/publicKey"); + BufferedInputStream bis = new BufferedInputStream(privateInputStream); + byte[] privateBytes = IOUtils.toByteArray(bis); + bis = new BufferedInputStream(publicInputStream); + byte[] publicBytes = IOUtils.toByteArray(bis); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA", BouncyCastleProvider.PROVIDER_NAME); + X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicBytes); + PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); + + PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateBytes); + PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); + + return new KeyPair(publicKey, privateKey); + } + @Test void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr() throws Exception { @@ -103,8 +132,9 @@ class Cmpv2ClientTest { "CN=ManagementCA", "CommonName.com", "CommonName@cn.com", - "password", + "mypassword", "http://127.0.0.1/ejbca/publicweb/cmp/cmp", + "senderKID", beforeDate, afterDate); when(httpClient.execute(any())).thenReturn(httpResponse); @@ -133,8 +163,9 @@ class Cmpv2ClientTest { } @Test - void shouldReturnValidPkiMessageWhenCreateCertificateRequestMessageMethodCalledWithValidCsr2() - throws Exception { + void + shouldThrowCmpClientExceptionWhenCreateCertificateRequestMessageMethodCalledWithWrongProtectedBytesInResponse() + throws Exception { // given Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00"); Date afterDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2020/11/11 12:00:00"); @@ -146,35 +177,35 @@ class Cmpv2ClientTest { "CommonName@cn.com", "password", "http://127.0.0.1/ejbca/publicweb/cmp/cmp", + "senderKID", beforeDate, afterDate); when(httpClient.execute(any())).thenReturn(httpResponse); when(httpResponse.getEntity()).thenReturn(httpEntity); try (final InputStream is = - this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile"); + this.getClass().getResourceAsStream("/ReturnedSuccessPKIMessageWithCertificateFile"); BufferedInputStream bis = new BufferedInputStream(is)) { byte[] ba = IOUtils.toByteArray(bis); doAnswer( - invocation -> { - OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0]; - os.write(ba); - return null; - }) + invocation -> { + OutputStream os = (ByteArrayOutputStream) invocation.getArguments()[0]; + os.write(ba); + return null; + }) .when(httpEntity) .writeTo(any(OutputStream.class)); } CmpClientImpl cmpClient = spy(new CmpClientImpl(httpClient)); - // when - List<List<X509Certificate>> cmpClientResult = - cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter); // then - assertNotNull(cmpClientResult); + Assertions.assertThrows( + CmpClientException.class, + () -> cmpClient.createCertificate("data", "RA", csrMeta, cert, notBefore, notAfter)); } @Test - void shouldReturnCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword() + void shouldThrowCmpClientExceptionWithPkiErrorExceptionWhenCmpClientCalledWithBadPassword() throws Exception { // given Date beforeDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").parse("2019/11/11 12:00:00"); @@ -187,6 +218,7 @@ class Cmpv2ClientTest { "CommonName@cn.com", "password", "http://127.0.0.1/ejbca/publicweb/cmp/cmp", + "senderKID", beforeDate, afterDate); when(httpClient.execute(any())).thenReturn(httpResponse); @@ -228,6 +260,7 @@ class Cmpv2ClientTest { "CommonName@cn.com", "password", "http://127.0.0.1/ejbca/publicweb/cmp/cmp", + "senderKID", beforeDate, afterDate); CmpClientImpl cmpClient = new CmpClientImpl(httpClient); @@ -251,6 +284,7 @@ class Cmpv2ClientTest { "Common@cn.com", "myPassword", "http://127.0.0.1/ejbca/publicweb/cmp/cmpTest", + "sender", beforeDate, afterDate); when(httpClient.execute(any())).thenThrow(IOException.class); @@ -269,6 +303,7 @@ class Cmpv2ClientTest { String email, String password, String externalCaUrl, + String senderKid, Date notBefore, Date notAfter) { csrMeta = new CSRMeta(rdns); @@ -280,6 +315,7 @@ class Cmpv2ClientTest { when(kpg.generateKeyPair()).thenReturn(keyPair); csrMeta.keypair(); csrMeta.caUrl(externalCaUrl); + csrMeta.senderKid(senderKid); this.notBefore = notBefore; this.notAfter = notAfter; diff --git a/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile b/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile Binary files differindex 94cc3461..e4a1d7b9 100644 --- a/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile +++ b/certService/src/test/resources/ReturnedSuccessPKIMessageWithCertificateFile diff --git a/certService/src/test/resources/privateKey b/certService/src/test/resources/privateKey Binary files differnew file mode 100644 index 00000000..216714c9 --- /dev/null +++ b/certService/src/test/resources/privateKey diff --git a/certService/src/test/resources/publicKey b/certService/src/test/resources/publicKey Binary files differnew file mode 100644 index 00000000..e5c63be8 --- /dev/null +++ b/certService/src/test/resources/publicKey |