c699b11004988fbc41041278243c72592184b0e6
[oom/platform/cert-service.git] / certService / src / main / java / org / onap / oom / certservice / cmpv2client / impl / CmpResponseValidationHelper.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 java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.security.InvalidKeyException;
26 import java.security.MessageDigest;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.NoSuchProviderException;
29 import java.security.PublicKey;
30 import java.security.Signature;
31 import java.security.SignatureException;
32 import java.util.Arrays;
33 import java.util.Objects;
34 import javax.crypto.Mac;
35 import javax.crypto.SecretKey;
36 import javax.crypto.spec.SecretKeySpec;
37
38 import org.bouncycastle.asn1.ASN1Encodable;
39 import org.bouncycastle.asn1.ASN1EncodableVector;
40 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
41 import org.bouncycastle.asn1.DERBitString;
42 import org.bouncycastle.asn1.DEROutputStream;
43 import org.bouncycastle.asn1.DERSequence;
44 import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers;
45 import org.bouncycastle.asn1.cmp.InfoTypeAndValue;
46 import org.bouncycastle.asn1.cmp.PBMParameter;
47 import org.bouncycastle.asn1.cmp.PKIBody;
48 import org.bouncycastle.asn1.cmp.PKIHeader;
49 import org.bouncycastle.asn1.cmp.PKIMessage;
50 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
51 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
52 import org.bouncycastle.jce.provider.BouncyCastleProvider;
53 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 public final class CmpResponseValidationHelper {
58
59     private static final Logger LOG = LoggerFactory.getLogger(CmpResponseValidationHelper.class);
60
61     private CmpResponseValidationHelper() {
62     }
63
64     /**
65      * Create a base key to use for verifying the PasswordBasedMac on a PKIMessage
66      *
67      * @param pbmParamSeq      parameters recieved in PKIMessage used with password
68      * @param initAuthPassword password used to decrypt the basekey
69      * @return bytes representing the basekey
70      * @throws CmpClientException thrown if algorithem exceptions occur for the message digest
71      */
72     public static byte[] getBaseKeyFromPbmParameters(
73             PBMParameter pbmParamSeq, String initAuthPassword) throws CmpClientException {
74         final int iterationCount = pbmParamSeq.getIterationCount().getPositiveValue().intValue();
75         LOG.info("Iteration count is: {}", iterationCount);
76         final AlgorithmIdentifier owfAlg = pbmParamSeq.getOwf();
77         LOG.info("One Way Function type is: {}", owfAlg.getAlgorithm().getId());
78         final byte[] salt = pbmParamSeq.getSalt().getOctets();
79         final byte[] raSecret = initAuthPassword != null ? initAuthPassword.getBytes() : new byte[0];
80         byte[] basekey = new byte[raSecret.length + salt.length];
81         System.arraycopy(raSecret, 0, basekey, 0, raSecret.length);
82         System.arraycopy(salt, 0, basekey, raSecret.length, salt.length);
83         try {
84             final MessageDigest messageDigest =
85                     MessageDigest.getInstance(
86                             owfAlg.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
87             for (int i = 0; i < iterationCount; i++) {
88                 basekey = messageDigest.digest(basekey);
89                 messageDigest.reset();
90             }
91         } catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
92             LOG.error("ProtectionBytes don't match passwordBasedProtection, authentication failed");
93             throw new CmpClientException(
94                     "ProtectionBytes don't match passwordBasedProtection, authentication failed", ex);
95         }
96         return basekey;
97     }
98
99     /**
100      * Verifies the signature of the response message using our public key
101      *
102      * @param respPkiMessage PKIMessage we wish to verify signature for
103      * @param pk             public key used to verify signature.
104      * @throws CmpClientException
105      */
106     public static void verifySignature(PKIMessage respPkiMessage, PublicKey pk)
107             throws CmpClientException {
108         final byte[] protBytes = getProtectedBytes(respPkiMessage);
109         final DERBitString derBitString = respPkiMessage.getProtection();
110         try {
111             final Signature signature =
112                     Signature.getInstance(
113                             PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(),
114                             BouncyCastleProvider.PROVIDER_NAME);
115             signature.initVerify(pk);
116             signature.update(protBytes);
117             signature.verify(derBitString.getBytes());
118         } catch (NoSuchAlgorithmException
119                 | NoSuchProviderException
120                 | InvalidKeyException
121                 | SignatureException e) {
122             CmpClientException clientException =
123                     new CmpClientException("Signature Verification failed", e);
124             LOG.error("Signature Verification failed", e);
125             throw clientException;
126         }
127     }
128
129     /**
130      * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
131      * array
132      *
133      * @param msg PKIMessage to get protected bytes from
134      * @return the PKIMessage's header and body in byte array
135      */
136     public static byte[] getProtectedBytes(PKIMessage msg) throws CmpClientException {
137         return getProtectedBytes(msg.getHeader(), msg.getBody());
138     }
139
140     /**
141      * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
142      * array
143      *
144      * @param header PKIHeader to be converted
145      * @param body   PKIMessage to be converted
146      * @return the PKIMessage's header and body in byte array
147      */
148     public static byte[] getProtectedBytes(PKIHeader header, PKIBody body) throws CmpClientException {
149         byte[] res;
150         ASN1EncodableVector encodableVector = new ASN1EncodableVector();
151         encodableVector.add(header);
152         encodableVector.add(body);
153         ASN1Encodable protectedPart = new DERSequence(encodableVector);
154         try {
155             ByteArrayOutputStream bao = new ByteArrayOutputStream();
156             DEROutputStream out = new DEROutputStream(bao);
157             out.writeObject(protectedPart);
158             res = bao.toByteArray();
159         } catch (IOException ioe) {
160             CmpClientException cmpClientException =
161                     new CmpClientException("Error occured while getting protected bytes", ioe);
162             LOG.error("Error occured while getting protected bytes", ioe);
163             throw cmpClientException;
164         }
165         return res;
166     }
167
168     /**
169      * verify the password based protection within the response message
170      *
171      * @param respPkiMessage   PKIMessage we want to verify password based protection for
172      * @param initAuthPassword password used to decrypt protection
173      * @param protectionAlgo   protection algorithm we can use to decrypt protection
174      * @throws CmpClientException
175      */
176     public static void verifyPasswordBasedProtection(
177             PKIMessage respPkiMessage, String initAuthPassword, AlgorithmIdentifier protectionAlgo)
178             throws CmpClientException {
179         final byte[] protectedBytes = getProtectedBytes(respPkiMessage);
180         final PBMParameter pbmParamSeq = PBMParameter.getInstance(protectionAlgo.getParameters());
181         if (Objects.nonNull(pbmParamSeq)) {
182             try {
183                 byte[] basekey = getBaseKeyFromPbmParameters(pbmParamSeq, initAuthPassword);
184                 final Mac mac = getMac(protectedBytes, pbmParamSeq, basekey);
185                 final byte[] outBytes = mac.doFinal();
186                 final byte[] protectionBytes = respPkiMessage.getProtection().getBytes();
187                 if (!Arrays.equals(outBytes, protectionBytes)) {
188                     LOG.error("protectionBytes don't match passwordBasedProtection, authentication failed");
189                     throw new CmpClientException(
190                             "protectionBytes don't match passwordBasedProtection, authentication failed");
191                 }
192             } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException ex) {
193                 CmpClientException cmpClientException =
194                         new CmpClientException("Error while validating CMP response ", ex);
195                 LOG.error("Error while validating CMP response ", ex);
196                 throw cmpClientException;
197             }
198         }
199     }
200
201     public static void checkImplicitConfirm(PKIHeader header) {
202         InfoTypeAndValue[] infos = header.getGeneralInfo();
203         if (Objects.nonNull(infos)) {
204             if (CMPObjectIdentifiers.it_implicitConfirm.equals(getImplicitConfirm(infos))) {
205                 LOG.info("Implicit Confirm on certificate from server.");
206             } else {
207                 LOG.debug("No Implicit confirm in Response");
208             }
209         } else {
210             LOG.debug("No general Info in header of response, cannot verify implicit confirm");
211         }
212     }
213
214     public static ASN1ObjectIdentifier getImplicitConfirm(InfoTypeAndValue[] info) {
215         return info[0].getInfoType();
216     }
217
218     /**
219      * Get cryptographical Mac we can use to decrypt our PKIMessage
220      *
221      * @param protectedBytes Protected bytes representing the PKIMessage
222      * @param pbmParamSeq    Parameters used to decrypt PKIMessage, including mac algorithm used
223      * @param basekey        Key used alongside mac Oid to create secret key for decrypting PKIMessage
224      * @return Mac that's ready to return decrypted bytes
225      * @throws NoSuchAlgorithmException Possibly thrown trying to get mac instance
226      * @throws NoSuchProviderException  Possibly thrown trying to get mac instance
227      * @throws InvalidKeyException      Possibly thrown trying to initialize mac using secretkey
228      */
229     public static Mac getMac(byte[] protectedBytes, PBMParameter pbmParamSeq, byte[] basekey)
230             throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {
231         final AlgorithmIdentifier macAlg = pbmParamSeq.getMac();
232         LOG.info("Mac type is: {}", macAlg.getAlgorithm().getId());
233         final String macOid = macAlg.getAlgorithm().getId();
234         final Mac mac = Mac.getInstance(macOid, BouncyCastleProvider.PROVIDER_NAME);
235         final SecretKey key = new SecretKeySpec(basekey, macOid);
236         mac.init(key);
237         mac.reset();
238         mac.update(protectedBytes, 0, protectedBytes.length);
239         return mac;
240     }
241 }