[OOM-CERT-SERVICE] Fix vulnerabilities for Kohn
[oom/platform/cert-service.git] / certService / src / main / java / org / onap / oom / certservice / cmpv2client / validation / CmpResponseValidationHelper.java
1 /*-
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2020 Nordix Foundation.
4  *  Copyright (C) 2021 Nokia.
5  * ================================================================================
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * SPDX-License-Identifier: Apache-2.0
19  * ============LICENSE_END=========================================================
20  */
21
22 package org.onap.oom.certservice.cmpv2client.validation;
23
24 import java.security.InvalidKeyException;
25 import java.security.MessageDigest;
26 import java.security.NoSuchAlgorithmException;
27 import java.security.NoSuchProviderException;
28 import java.security.PublicKey;
29 import java.security.Signature;
30 import java.security.SignatureException;
31 import java.util.Arrays;
32 import java.util.Objects;
33 import javax.crypto.Mac;
34 import javax.crypto.SecretKey;
35 import javax.crypto.spec.SecretKeySpec;
36
37 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
38 import org.bouncycastle.asn1.DERBitString;
39 import org.bouncycastle.asn1.ASN1BitString;
40 import org.bouncycastle.asn1.cmp.CMPObjectIdentifiers;
41 import org.bouncycastle.asn1.cmp.InfoTypeAndValue;
42 import org.bouncycastle.asn1.cmp.PBMParameter;
43 import org.bouncycastle.asn1.cmp.PKIHeader;
44 import org.bouncycastle.asn1.cmp.PKIMessage;
45 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
46 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
47 import org.bouncycastle.jce.provider.BouncyCastleProvider;
48 import org.onap.oom.certservice.cmpv2client.exceptions.CmpClientException;
49 import org.onap.oom.certservice.cmpv2client.impl.CmpUtil;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
52
53 public final class CmpResponseValidationHelper {
54
55     private static final Logger LOG = LoggerFactory.getLogger(CmpResponseValidationHelper.class);
56
57     private CmpResponseValidationHelper() {
58     }
59
60     /**
61      * Verifies the signature of the response message using our public key
62      *
63      * @param respPkiMessage PKIMessage we wish to verify signature for
64      * @param pk             public key used to verify signature.
65      * @throws CmpClientException
66      */
67     static void verifySignature(PKIMessage respPkiMessage, PublicKey pk)
68             throws CmpClientException {
69         final byte[] protBytes = getProtectedBytes(respPkiMessage);
70         final DERBitString derBitString = (DERBitString) respPkiMessage.getProtection();
71         try {
72             final Signature signature =
73                     Signature.getInstance(
74                             PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(),
75                             BouncyCastleProvider.PROVIDER_NAME);
76             signature.initVerify(pk);
77             signature.update(protBytes);
78             signature.verify(derBitString.getBytes());
79         } catch (NoSuchAlgorithmException
80                 | NoSuchProviderException
81                 | InvalidKeyException
82                 | SignatureException e) {
83             CmpClientException clientException =
84                     new CmpClientException("Signature Verification failed", e);
85             LOG.error("Signature Verification failed", e);
86             throw clientException;
87         }
88     }
89
90     /**
91      * verify the password based protection within the response message
92      *
93      * @param respPkiMessage   PKIMessage we want to verify password based protection for
94      * @param initAuthPassword password used to decrypt protection
95      * @param protectionAlgo   protection algorithm we can use to decrypt protection
96      * @throws CmpClientException
97      */
98     static void verifyPasswordBasedProtection(
99             PKIMessage respPkiMessage, String initAuthPassword, AlgorithmIdentifier protectionAlgo)
100             throws CmpClientException {
101         final byte[] protectedBytes = getProtectedBytes(respPkiMessage);
102         final PBMParameter pbmParamSeq = PBMParameter.getInstance(protectionAlgo.getParameters());
103         if (Objects.nonNull(pbmParamSeq)) {
104             try {
105                 byte[] basekey = getBaseKeyFromPbmParameters(pbmParamSeq, initAuthPassword);
106                 final Mac mac = getMac(protectedBytes, pbmParamSeq, basekey);
107                 final byte[] outBytes = mac.doFinal();
108                 final byte[] protectionBytes = respPkiMessage.getProtection().getBytes();
109                 if (!Arrays.equals(outBytes, protectionBytes)) {
110                     LOG.error("protectionBytes don't match passwordBasedProtection, authentication failed");
111                     throw new CmpClientException(
112                             "protectionBytes don't match passwordBasedProtection, authentication failed");
113                 }
114             } catch (NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException ex) {
115                 CmpClientException cmpClientException =
116                         new CmpClientException("Error while validating CMP response ", ex);
117                 LOG.error("Error while validating CMP response ", ex);
118                 throw cmpClientException;
119             }
120         }
121     }
122
123     static void checkImplicitConfirm(PKIHeader header) {
124         InfoTypeAndValue[] infos = header.getGeneralInfo();
125         if (Objects.nonNull(infos)) {
126             if (CMPObjectIdentifiers.it_implicitConfirm.equals(getImplicitConfirm(infos))) {
127                 LOG.info("Implicit Confirm on certificate from server.");
128             } else {
129                 LOG.debug("No Implicit confirm in Response");
130             }
131         } else {
132             LOG.debug("No general Info in header of response, cannot verify implicit confirm");
133         }
134     }
135
136     /**
137      * Create a base key to use for verifying the PasswordBasedMac on a PKIMessage
138      *
139      * @param pbmParamSeq      parameters recieved in PKIMessage used with password
140      * @param initAuthPassword password used to decrypt the basekey
141      * @return bytes representing the basekey
142      * @throws CmpClientException thrown if algorithem exceptions occur for the message digest
143      */
144     private static byte[] getBaseKeyFromPbmParameters(
145             PBMParameter pbmParamSeq, String initAuthPassword) throws CmpClientException {
146         final int iterationCount = pbmParamSeq.getIterationCount().getPositiveValue().intValue();
147         LOG.info("Iteration count is: {}", iterationCount);
148         final AlgorithmIdentifier owfAlg = pbmParamSeq.getOwf();
149         LOG.info("One Way Function type is: {}", owfAlg.getAlgorithm().getId());
150         final byte[] salt = pbmParamSeq.getSalt().getOctets();
151         final byte[] raSecret = initAuthPassword != null ? initAuthPassword.getBytes() : new byte[0];
152         byte[] basekey = new byte[raSecret.length + salt.length];
153         System.arraycopy(raSecret, 0, basekey, 0, raSecret.length);
154         System.arraycopy(salt, 0, basekey, raSecret.length, salt.length);
155         try {
156             final MessageDigest messageDigest =
157                     MessageDigest.getInstance(
158                             owfAlg.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
159             for (int i = 0; i < iterationCount; i++) {
160                 basekey = messageDigest.digest(basekey);
161                 messageDigest.reset();
162             }
163         } catch (NoSuchAlgorithmException | NoSuchProviderException ex) {
164             LOG.error("ProtectionBytes don't match passwordBasedProtection, authentication failed");
165             throw new CmpClientException(
166                     "ProtectionBytes don't match passwordBasedProtection, authentication failed", ex);
167         }
168         return basekey;
169     }
170
171     private static ASN1ObjectIdentifier getImplicitConfirm(InfoTypeAndValue[] info) {
172         return info[0].getInfoType();
173     }
174
175     /**
176      * Get cryptographical Mac we can use to decrypt our PKIMessage
177      *
178      * @param protectedBytes Protected bytes representing the PKIMessage
179      * @param pbmParamSeq    Parameters used to decrypt PKIMessage, including mac algorithm used
180      * @param basekey        Key used alongside mac Oid to create secret key for decrypting PKIMessage
181      * @return Mac that's ready to return decrypted bytes
182      * @throws NoSuchAlgorithmException Possibly thrown trying to get mac instance
183      * @throws NoSuchProviderException  Possibly thrown trying to get mac instance
184      * @throws InvalidKeyException      Possibly thrown trying to initialize mac using secretkey
185      */
186     private static Mac getMac(byte[] protectedBytes, PBMParameter pbmParamSeq, byte[] basekey)
187             throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {
188         final AlgorithmIdentifier macAlg = pbmParamSeq.getMac();
189         LOG.info("Mac type is: {}", macAlg.getAlgorithm().getId());
190         final String macOid = macAlg.getAlgorithm().getId();
191         final Mac mac = Mac.getInstance(macOid, BouncyCastleProvider.PROVIDER_NAME);
192         final SecretKey key = new SecretKeySpec(basekey, macOid);
193         mac.init(key);
194         mac.reset();
195         mac.update(protectedBytes, 0, protectedBytes.length);
196         return mac;
197     }
198
199     /**
200      * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
201      * array
202      *
203      * @param msg PKIMessage to get protected bytes from
204      * @return the PKIMessage's header and body in byte array
205      */
206     private static byte[] getProtectedBytes(PKIMessage msg) throws CmpClientException {
207         return CmpUtil.generateProtectedBytes(msg.getHeader(), msg.getBody());
208     }
209 }