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;
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;
56 public final class CmpResponseValidationHelper {
58 private static final Logger LOG = LoggerFactory.getLogger(CmpResponseValidationHelper.class);
60 private CmpResponseValidationHelper() {}
63 * Create a base key to use for verifying the PasswordBasedMac on a PKIMessage
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
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);
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();
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);
98 * Verifies the signature of the response message using our public key
100 * @param respPkiMessage PKIMessage we wish to verify signature for
101 * @param pk public key used to verify signature.
102 * @throws CmpClientException
104 public static void verifySignature(PKIMessage respPkiMessage, PublicKey pk)
105 throws CmpClientException {
106 final byte[] protBytes = getProtectedBytes(respPkiMessage);
107 final DERBitString derBitString = respPkiMessage.getProtection();
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;
127 * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
130 * @param msg PKIMessage to get protected bytes from
131 * @return the PKIMessage's header and body in byte array
133 public static byte[] getProtectedBytes(PKIMessage msg) throws CmpClientException {
134 return getProtectedBytes(msg.getHeader(), msg.getBody());
138 * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
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
145 public static byte[] getProtectedBytes(PKIHeader header, PKIBody body) throws CmpClientException {
147 ASN1EncodableVector v = new ASN1EncodableVector();
150 ASN1Encodable protectedPart = new DERSequence(v);
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;
166 * verify the password based protection within the response message
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
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)) {
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");
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;
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.");
204 LOG.debug("No Implicit confirm in Response");
207 LOG.debug("No general Info in header of response, cannot verify implicit confirm");
211 public static ASN1ObjectIdentifier getImplicitConfirm(InfoTypeAndValue[] info) {
212 return info[0].getInfoType();
216 * Get cryptographical Mac we can use to decrypt our PKIMessage
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
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);
235 mac.update(protectedBytes, 0, protectedBytes.length);