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