2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2020 Nordix Foundation.
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.aaf.certservice.cmpv2client.impl;
23 import static org.onap.aaf.certservice.cmpv2client.impl.CmpUtil.generateProtectedBytes;
25 import java.io.ByteArrayOutputStream;
26 import java.io.IOException;
27 import java.security.InvalidKeyException;
28 import java.security.KeyPair;
29 import java.security.MessageDigest;
30 import java.security.NoSuchAlgorithmException;
31 import java.security.NoSuchProviderException;
32 import java.security.Signature;
33 import java.security.SignatureException;
34 import java.util.ArrayList;
35 import java.util.Date;
36 import java.util.List;
37 import javax.crypto.Mac;
38 import javax.crypto.SecretKey;
39 import javax.crypto.spec.SecretKeySpec;
41 import org.bouncycastle.asn1.ASN1EncodableVector;
42 import org.bouncycastle.asn1.ASN1Integer;
43 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
44 import org.bouncycastle.asn1.DERBitString;
45 import org.bouncycastle.asn1.DEROctetString;
46 import org.bouncycastle.asn1.DEROutputStream;
47 import org.bouncycastle.asn1.DERSequence;
48 import org.bouncycastle.asn1.DERTaggedObject;
49 import org.bouncycastle.asn1.cmp.PBMParameter;
50 import org.bouncycastle.asn1.cmp.PKIBody;
51 import org.bouncycastle.asn1.cmp.PKIHeader;
52 import org.bouncycastle.asn1.cmp.PKIMessage;
53 import org.bouncycastle.asn1.crmf.CertRequest;
54 import org.bouncycastle.asn1.crmf.OptionalValidity;
55 import org.bouncycastle.asn1.crmf.POPOSigningKey;
56 import org.bouncycastle.asn1.crmf.ProofOfPossession;
57 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
58 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
59 import org.bouncycastle.asn1.x509.Extension;
60 import org.bouncycastle.asn1.x509.Extensions;
61 import org.bouncycastle.asn1.x509.ExtensionsGenerator;
62 import org.bouncycastle.asn1.x509.GeneralName;
63 import org.bouncycastle.asn1.x509.GeneralNames;
64 import org.bouncycastle.asn1.x509.KeyUsage;
65 import org.bouncycastle.asn1.x509.Time;
66 import org.bouncycastle.jce.provider.BouncyCastleProvider;
67 import org.onap.aaf.certservice.cmpv2client.exceptions.CmpClientException;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
71 public final class CmpMessageHelper {
73 private static final Logger LOG = LoggerFactory.getLogger(CmpMessageHelper.class);
74 private static final AlgorithmIdentifier OWF_ALGORITHM =
75 new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.3.14.3.2.26"));
76 private static final AlgorithmIdentifier MAC_ALGORITHM =
77 new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.2.840.113549.2.9"));
78 private static final ASN1ObjectIdentifier PASSWORD_BASED_MAC =
79 new ASN1ObjectIdentifier("1.2.840.113533.7.66.13");
81 private CmpMessageHelper() {
85 * Creates an Optional Validity, which is used to specify how long the returned cert should be
88 * @param notBefore Date specifying certificate is not valid before this date.
89 * @param notAfter Date specifying certificate is not valid after this date.
90 * @return {@link OptionalValidity} that can be set for certificate on external CA.
92 public static OptionalValidity generateOptionalValidity(
93 final Date notBefore, final Date notAfter) {
94 LOG.info("Generating Optional Validity from Date objects");
95 ASN1EncodableVector optionalValidityV = new ASN1EncodableVector();
96 if (notBefore != null) {
97 Time nb = new Time(notBefore);
98 optionalValidityV.add(new DERTaggedObject(true, 0, nb));
100 if (notAfter != null) {
101 Time na = new Time(notAfter);
102 optionalValidityV.add(new DERTaggedObject(true, 1, na));
104 return OptionalValidity.getInstance(new DERSequence(optionalValidityV));
108 * Create Extensions from Subject Alternative Names.
110 * @return {@link Extensions}.
112 public static Extensions generateExtension(final List<String> sansList)
113 throws CmpClientException {
114 LOG.info("Generating Extensions from Subject Alternative Names");
115 final ExtensionsGenerator extGenerator = new ExtensionsGenerator();
116 final GeneralName[] sansGeneralNames = getGeneralNames(sansList);
119 final KeyUsage keyUsage =
121 KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.nonRepudiation);
122 extGenerator.addExtension(Extension.keyUsage, false, new DERBitString(keyUsage));
123 extGenerator.addExtension(
124 Extension.subjectAlternativeName, false, new GeneralNames(sansGeneralNames));
125 } catch (IOException ioe) {
126 CmpClientException cmpClientException =
127 new CmpClientException(
128 "Exception occurred while creating extensions for PKIMessage", ioe);
129 LOG.error("Exception occurred while creating extensions for PKIMessage");
130 throw cmpClientException;
132 return extGenerator.generate();
135 public static GeneralName[] getGeneralNames(List<String> sansList) {
136 final List<GeneralName> nameList = new ArrayList<>();
137 for (String san : sansList) {
138 nameList.add(new GeneralName(GeneralName.dNSName, san));
140 final GeneralName[] sansGeneralNames = new GeneralName[nameList.size()];
141 nameList.toArray(sansGeneralNames);
142 return sansGeneralNames;
146 * Method generates Proof-of-Possession (POP) of Private Key. To allow a CA/RA to properly
147 * validity binding between an End Entity and a Key Pair, the PKI Operations specified here make
148 * it possible for an End Entity to prove that it has possession of the Private Key corresponding
149 * to the Public Key for which a Certificate is requested.
151 * @param certRequest Certificate request that requires proof of possession
152 * @param keypair keypair associated with the subject sending the certificate request
153 * @return {@link ProofOfPossession}.
154 * @throws CmpClientException A general-purpose Cmp client exception.
156 public static ProofOfPossession generateProofOfPossession(
157 final CertRequest certRequest, final KeyPair keypair) throws CmpClientException {
158 ProofOfPossession proofOfPossession;
159 try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
160 final DEROutputStream derOutputStream = new DEROutputStream(byteArrayOutputStream);
161 derOutputStream.writeObject(certRequest);
163 byte[] popoProtectionBytes = byteArrayOutputStream.toByteArray();
164 final String sigalg = PKCSObjectIdentifiers.sha256WithRSAEncryption.getId();
165 final Signature signature = Signature.getInstance(sigalg, BouncyCastleProvider.PROVIDER_NAME);
166 signature.initSign(keypair.getPrivate());
167 signature.update(popoProtectionBytes);
168 DERBitString bs = new DERBitString(signature.sign());
171 new ProofOfPossession(
173 null, new AlgorithmIdentifier(new ASN1ObjectIdentifier(sigalg)), bs));
175 | NoSuchProviderException
176 | NoSuchAlgorithmException
177 | InvalidKeyException
178 | SignatureException ex) {
179 CmpClientException cmpClientException =
180 new CmpClientException(
181 "Exception occurred while creating proof of possession for PKIMessage", ex);
182 LOG.error("Exception occurred while creating proof of possession for PKIMessage");
183 throw cmpClientException;
185 return proofOfPossession;
189 * Generic code to create Algorithm Identifier for protection of PKIMessage.
191 * @return Algorithm Identifier
193 public static AlgorithmIdentifier protectionAlgoIdentifier(int iterations, byte[] salt) {
194 ASN1Integer iteration = new ASN1Integer(iterations);
195 DEROctetString derSalt = new DEROctetString(salt);
197 PBMParameter pp = new PBMParameter(derSalt, OWF_ALGORITHM, iteration, MAC_ALGORITHM);
198 return new AlgorithmIdentifier(PASSWORD_BASED_MAC, pp);
202 * Adds protection to the PKIMessage via a specified protection algorithm.
204 * @param password password used to authenticate PkiMessage with external CA
205 * @param pkiHeader Header of PKIMessage containing generic details for any PKIMessage
206 * @param pkiBody Body of PKIMessage containing specific details for certificate request
207 * @return Protected Pki Message
208 * @throws CmpClientException Wraps several exceptions into one general-purpose exception.
210 public static PKIMessage protectPkiMessage(
211 PKIHeader pkiHeader, PKIBody pkiBody, String password, int iterations, byte[] salt)
212 throws CmpClientException {
214 byte[] raSecret = password.getBytes();
215 byte[] basekey = new byte[raSecret.length + salt.length];
216 System.arraycopy(raSecret, 0, basekey, 0, raSecret.length);
217 System.arraycopy(salt, 0, basekey, raSecret.length, salt.length);
221 MessageDigest.getInstance(
222 OWF_ALGORITHM.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
223 for (int i = 0; i < iterations; i++) {
224 basekey = dig.digest(basekey);
227 byte[] protectedBytes = generateProtectedBytes(pkiHeader, pkiBody);
229 Mac.getInstance(MAC_ALGORITHM.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
230 SecretKey key = new SecretKeySpec(basekey, MAC_ALGORITHM.getAlgorithm().getId());
233 mac.update(protectedBytes, 0, protectedBytes.length);
235 } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException ex) {
236 CmpClientException cmpClientException =
237 new CmpClientException(
238 "Exception occurred while generating proof of possession for PKIMessage", ex);
239 LOG.error("Exception occured while generating the proof of possession for PKIMessage");
240 throw cmpClientException;
242 DERBitString bs = new DERBitString(out);
244 return new PKIMessage(pkiHeader, pkiBody, bs);