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.oom.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.oom.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);