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