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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
22 package org.onap.oom.certservice.cmpv2client.validation;
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;
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;
53 public final class CmpResponseValidationHelper {
55 private static final Logger LOG = LoggerFactory.getLogger(CmpResponseValidationHelper.class);
57 private CmpResponseValidationHelper() {
61 * Verifies the signature of the response message using our public key
63 * @param respPkiMessage PKIMessage we wish to verify signature for
64 * @param pk public key used to verify signature.
65 * @throws CmpClientException
67 static void verifySignature(PKIMessage respPkiMessage, PublicKey pk)
68 throws CmpClientException {
69 final byte[] protBytes = getProtectedBytes(respPkiMessage);
70 final DERBitString derBitString = (DERBitString) respPkiMessage.getProtection();
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
82 | SignatureException e) {
83 CmpClientException clientException =
84 new CmpClientException("Signature Verification failed", e);
85 LOG.error("Signature Verification failed", e);
86 throw clientException;
91 * verify the password based protection within the response message
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
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)) {
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");
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;
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.");
129 LOG.debug("No Implicit confirm in Response");
132 LOG.debug("No general Info in header of response, cannot verify implicit confirm");
137 * Create a base key to use for verifying the PasswordBasedMac on a PKIMessage
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
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);
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();
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);
171 private static ASN1ObjectIdentifier getImplicitConfirm(InfoTypeAndValue[] info) {
172 return info[0].getInfoType();
176 * Get cryptographical Mac we can use to decrypt our PKIMessage
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
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);
195 mac.update(protectedBytes, 0, protectedBytes.length);
200 * Converts the header and the body of a PKIMessage to an ASN1Encodable and returns the as a byte
203 * @param msg PKIMessage to get protected bytes from
204 * @return the PKIMessage's header and body in byte array
206 private static byte[] getProtectedBytes(PKIMessage msg) throws CmpClientException {
207 return CmpUtil.generateProtectedBytes(msg.getHeader(), msg.getBody());