[OOM-CERT-SERVICE] Fix KeyUsage extention sent to CMPv2 server
[oom/platform/cert-service.git] / certService / src / main / java / org / onap / oom / certservice / cmpv2client / impl / CmpMessageHelper.java
1 /*-
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
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.oom.certservice.cmpv2client.impl;
22
23 import static org.onap.oom.certservice.cmpv2client.impl.CmpUtil.generateProtectedBytes;
24
25 import java.io.ByteArrayOutputStream;
26 import java.io.IOException;
27 import java.security.InvalidKeyException;
28 import java.security.Key;
29 import java.security.KeyPair;
30 import java.security.MessageDigest;
31 import java.security.NoSuchAlgorithmException;
32 import java.security.NoSuchProviderException;
33 import java.security.Signature;
34 import java.security.SignatureException;
35 import java.util.Date;
36 import javax.crypto.Mac;
37 import javax.crypto.SecretKey;
38 import javax.crypto.spec.SecretKeySpec;
39
40 import org.bouncycastle.asn1.ASN1EncodableVector;
41 import org.bouncycastle.asn1.ASN1Integer;
42 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
43 import org.bouncycastle.asn1.DERBitString;
44 import org.bouncycastle.asn1.DEROctetString;
45 import org.bouncycastle.asn1.DEROutputStream;
46 import org.bouncycastle.asn1.DERSequence;
47 import org.bouncycastle.asn1.DERTaggedObject;
48 import org.bouncycastle.asn1.cmp.PBMParameter;
49 import org.bouncycastle.asn1.cmp.PKIBody;
50 import org.bouncycastle.asn1.cmp.PKIHeader;
51 import org.bouncycastle.asn1.cmp.PKIMessage;
52 import org.bouncycastle.asn1.crmf.CertRequest;
53 import org.bouncycastle.asn1.crmf.OptionalValidity;
54 import org.bouncycastle.asn1.crmf.POPOSigningKey;
55 import org.bouncycastle.asn1.crmf.ProofOfPossession;
56 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
57 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
58 import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
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.KeyPurposeId;
65 import org.bouncycastle.asn1.x509.KeyUsage;
66 import org.bouncycastle.asn1.x509.Time;
67 import org.bouncycastle.jce.provider.BouncyCastleProvider;
68 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71
72 public final class CmpMessageHelper {
73
74     private static final Logger LOG = LoggerFactory.getLogger(CmpMessageHelper.class);
75     private static final AlgorithmIdentifier OWF_ALGORITHM =
76             new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.3.14.3.2.26"));
77     private static final AlgorithmIdentifier MAC_ALGORITHM =
78             new AlgorithmIdentifier(new ASN1ObjectIdentifier("1.3.6.1.5.5.8.1.2"));
79     private static final ASN1ObjectIdentifier PASSWORD_BASED_MAC =
80             new ASN1ObjectIdentifier("1.2.840.113533.7.66.13");
81     private static final boolean CRITICAL_FALSE = false;
82
83     private CmpMessageHelper() {
84     }
85
86     /**
87      * Creates an Optional Validity, which is used to specify how long the returned cert should be
88      * valid for.
89      *
90      * @param notBefore Date specifying certificate is not valid before this date.
91      * @param notAfter  Date specifying certificate is not valid after this date.
92      * @return {@link OptionalValidity} that can be set for certificate on external CA.
93      */
94     public static OptionalValidity generateOptionalValidity(
95             final Date notBefore, final Date notAfter) {
96         LOG.info("Generating Optional Validity from Date objects");
97         ASN1EncodableVector optionalValidityV = new ASN1EncodableVector();
98         if (notBefore != null) {
99             Time nb = new Time(notBefore);
100             optionalValidityV.add(new DERTaggedObject(true, 0, nb));
101         }
102         if (notAfter != null) {
103             Time na = new Time(notAfter);
104             optionalValidityV.add(new DERTaggedObject(true, 1, na));
105         }
106         return OptionalValidity.getInstance(new DERSequence(optionalValidityV));
107     }
108
109     /**
110      * Create Extensions from Subject Alternative Names.
111      *
112      * @return {@link Extensions}.
113      */
114     public static Extensions generateExtension(final GeneralName[] sansArray)
115             throws CmpClientException {
116         LOG.info("Generating Extensions from Subject Alternative Names");
117         final ExtensionsGenerator extGenerator = new ExtensionsGenerator();
118         try {
119             extGenerator.addExtension(Extension.keyUsage, CRITICAL_FALSE, getKeyUsage());
120             extGenerator.addExtension(Extension.extendedKeyUsage, CRITICAL_FALSE, getExtendedKeyUsage());
121             extGenerator.addExtension(
122                     Extension.subjectAlternativeName, CRITICAL_FALSE, new GeneralNames(sansArray));
123         } catch (IOException ioe) {
124             CmpClientException cmpClientException =
125                     new CmpClientException(
126                             "Exception occurred while creating extensions for PKIMessage", ioe);
127             LOG.error("Exception occurred while creating extensions for PKIMessage");
128             throw cmpClientException;
129         }
130         return extGenerator.generate();
131     }
132
133     /**
134      * Method generates Proof-of-Possession (POP) of Private Key. To allow a CA/RA to properly
135      * validity binding between an End Entity and a Key Pair, the PKI Operations specified here make
136      * it possible for an End Entity to prove that it has possession of the Private Key corresponding
137      * to the Public Key for which a Certificate is requested.
138      *
139      * @param certRequest Certificate request that requires proof of possession
140      * @param keypair     keypair associated with the subject sending the certificate request
141      * @return {@link ProofOfPossession}.
142      * @throws CmpClientException A general-purpose Cmp client exception.
143      */
144     public static ProofOfPossession generateProofOfPossession(
145             final CertRequest certRequest, final KeyPair keypair) throws CmpClientException {
146         ProofOfPossession proofOfPossession;
147         try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
148             final DEROutputStream derOutputStream = new DEROutputStream(byteArrayOutputStream);
149             derOutputStream.writeObject(certRequest);
150
151             byte[] popoProtectionBytes = byteArrayOutputStream.toByteArray();
152             final String sigalg = PKCSObjectIdentifiers.sha256WithRSAEncryption.getId();
153             final Signature signature = Signature.getInstance(sigalg, BouncyCastleProvider.PROVIDER_NAME);
154             signature.initSign(keypair.getPrivate());
155             signature.update(popoProtectionBytes);
156             DERBitString bs = new DERBitString(signature.sign());
157
158             proofOfPossession =
159                     new ProofOfPossession(
160                             new POPOSigningKey(
161                                     null, new AlgorithmIdentifier(new ASN1ObjectIdentifier(sigalg)), bs));
162         } catch (IOException
163                 | NoSuchProviderException
164                 | NoSuchAlgorithmException
165                 | InvalidKeyException
166                 | SignatureException ex) {
167             CmpClientException cmpClientException =
168                     new CmpClientException(
169                             "Exception occurred while creating proof of possession for PKIMessage", ex);
170             LOG.error("Exception occurred while creating proof of possession for PKIMessage");
171             throw cmpClientException;
172         }
173         return proofOfPossession;
174     }
175
176     /**
177      * Generic code to create Algorithm Identifier for protection of PKIMessage.
178      *
179      * @return Algorithm Identifier
180      */
181     public static AlgorithmIdentifier protectionAlgoIdentifier(int iterations, byte[] salt) {
182         ASN1Integer iteration = new ASN1Integer(iterations);
183         DEROctetString derSalt = new DEROctetString(salt);
184
185         PBMParameter pp = new PBMParameter(derSalt, OWF_ALGORITHM, iteration, MAC_ALGORITHM);
186         return new AlgorithmIdentifier(PASSWORD_BASED_MAC, pp);
187     }
188
189     /**
190      * Adds protection to the PKIMessage via a specified protection algorithm.
191      *
192      * @param password  password used to authenticate PkiMessage with external CA
193      * @param pkiHeader Header of PKIMessage containing generic details for any PKIMessage
194      * @param pkiBody   Body of PKIMessage containing specific details for certificate request
195      * @return Protected Pki Message
196      * @throws CmpClientException Wraps several exceptions into one general-purpose exception.
197      */
198     public static PKIMessage protectPkiMessage(
199             PKIHeader pkiHeader, PKIBody pkiBody, String password, int iterations, byte[] salt)
200             throws CmpClientException {
201
202         byte[] raSecret = password.getBytes();
203         byte[] basekey = new byte[raSecret.length + salt.length];
204         System.arraycopy(raSecret, 0, basekey, 0, raSecret.length);
205         System.arraycopy(salt, 0, basekey, raSecret.length, salt.length);
206         byte[] out;
207         try {
208             MessageDigest dig =
209                     MessageDigest.getInstance(
210                             OWF_ALGORITHM.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
211             for (int i = 0; i < iterations; i++) {
212                 basekey = dig.digest(basekey);
213                 dig.reset();
214             }
215             byte[] protectedBytes = generateProtectedBytes(pkiHeader, pkiBody);
216             Mac mac =
217                     Mac.getInstance(MAC_ALGORITHM.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
218             SecretKey key = new SecretKeySpec(basekey, MAC_ALGORITHM.getAlgorithm().getId());
219             mac.init(key);
220             mac.reset();
221             mac.update(protectedBytes, 0, protectedBytes.length);
222             out = mac.doFinal();
223         } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException ex) {
224             CmpClientException cmpClientException =
225                     new CmpClientException(
226                             "Exception occurred while generating proof of possession for PKIMessage", ex);
227             LOG.error("Exception occured while generating the proof of possession for PKIMessage");
228             throw cmpClientException;
229         }
230         DERBitString bs = new DERBitString(out);
231
232         return new PKIMessage(pkiHeader, pkiBody, bs);
233     }
234
235     private static KeyUsage getKeyUsage() {
236         return new KeyUsage(
237             KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.nonRepudiation);
238     }
239
240     private static ExtendedKeyUsage getExtendedKeyUsage() {
241         return new ExtendedKeyUsage(
242             new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth});
243     }
244 }