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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.aaf.certservice.cmpv2client.impl;
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;
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.aaf.certservice.cmpv2client.exceptions.CmpClientException;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
57 public final class CmpResponseValidationHelper {
59 private static final Logger LOG = LoggerFactory.getLogger(CmpResponseValidationHelper.class);
61 private CmpResponseValidationHelper() {
65 * Create a base key to use for verifying the PasswordBasedMac on a PKIMessage
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
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);
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();
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);
100 * Verifies the signature of the response message using our public key
102 * @param respPkiMessage PKIMessage we wish to verify signature for
103 * @param pk public key used to verify signature.
104 * @throws CmpClientException
106 public static void verifySignature(PKIMessage respPkiMessage, PublicKey pk)
107 throws CmpClientException {
108 final byte[] protBytes = getProtectedBytes(respPkiMessage);
109 final DERBitString derBitString = respPkiMessage.getProtection();
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;
130 * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
133 * @param msg PKIMessage to get protected bytes from
134 * @return the PKIMessage's header and body in byte array
136 public static byte[] getProtectedBytes(PKIMessage msg) throws CmpClientException {
137 return getProtectedBytes(msg.getHeader(), msg.getBody());
141 * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
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
148 public static byte[] getProtectedBytes(PKIHeader header, PKIBody body) throws CmpClientException {
150 ASN1EncodableVector encodableVector = new ASN1EncodableVector();
151 encodableVector.add(header);
152 encodableVector.add(body);
153 ASN1Encodable protectedPart = new DERSequence(encodableVector);
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;
169 * verify the password based protection within the response message
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
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)) {
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");
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;
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.");
207 LOG.debug("No Implicit confirm in Response");
210 LOG.debug("No general Info in header of response, cannot verify implicit confirm");
214 public static ASN1ObjectIdentifier getImplicitConfirm(InfoTypeAndValue[] info) {
215 return info[0].getInfoType();
219 * Get cryptographical Mac we can use to decrypt our PKIMessage
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
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);
238 mac.update(protectedBytes, 0, protectedBytes.length);