1 /* 2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package com.ohos.hapsigntool.profile; 17 18 import com.google.gson.JsonObject; 19 import com.ohos.hapsigntool.error.CustomException; 20 import com.ohos.hapsigntool.error.ERROR; 21 import com.ohos.hapsigntool.error.VerifyException; 22 import com.ohos.hapsigntool.hap.verify.VerifyUtils; 23 import com.ohos.hapsigntool.profile.model.VerificationResult; 24 import com.ohos.hapsigntool.utils.CertChainUtils; 25 import com.ohos.hapsigntool.utils.CertUtils; 26 import com.ohos.hapsigntool.utils.FileUtils; 27 import com.ohos.hapsigntool.utils.ValidateUtils; 28 import org.apache.logging.log4j.LogManager; 29 import org.apache.logging.log4j.Logger; 30 import org.bouncycastle.asn1.ASN1Encodable; 31 import org.bouncycastle.asn1.ASN1Set; 32 import org.bouncycastle.asn1.cms.Attribute; 33 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 34 import org.bouncycastle.asn1.x509.Time; 35 import org.bouncycastle.cert.X509CertificateHolder; 36 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 37 import org.bouncycastle.cms.CMSException; 38 import org.bouncycastle.cms.CMSSignedData; 39 import org.bouncycastle.cms.SignerId; 40 import org.bouncycastle.cms.SignerInformation; 41 import org.bouncycastle.cms.SignerInformationStore; 42 import org.bouncycastle.util.Store; 43 44 import javax.security.auth.x500.X500Principal; 45 import java.io.IOException; 46 import java.nio.charset.StandardCharsets; 47 import java.security.InvalidKeyException; 48 import java.security.NoSuchAlgorithmException; 49 import java.security.Signature; 50 import java.security.SignatureException; 51 import java.security.cert.CertificateException; 52 import java.security.cert.X509Certificate; 53 import java.time.LocalDateTime; 54 import java.time.ZoneId; 55 import java.util.ArrayList; 56 import java.util.Collection; 57 import java.util.Iterator; 58 import java.util.List; 59 import java.util.Date; 60 61 /** 62 * Signed provision profile verifier. 63 * 64 * @since 2021/12/28 65 */ 66 public class VerifyHelper implements IProvisionVerifier { 67 /** 68 * LOGGER. 69 */ 70 private static final Logger LOGGER = LogManager.getLogger(VerifyHelper.class); 71 72 /** 73 * Signed provision profile verifier. 74 */ VerifyHelper()75 public VerifyHelper() {} 76 77 /** 78 * Checked signed data with public key. 79 * 80 * @param cert public key 81 * @param signedData signed data with private key 82 * @param unsignedData unsigned data 83 * @param algorithm algorithm 84 */ verifySignature(X509Certificate cert, byte[] signedData, byte[] unsignedData, String algorithm)85 public static void verifySignature(X509Certificate cert, byte[] signedData, byte[] unsignedData, String algorithm) { 86 try { 87 Signature signature = Signature.getInstance(algorithm); 88 signature.initVerify(cert); 89 signature.update(unsignedData); 90 ValidateUtils.throwIfNotMatches(signature.verify(signedData), ERROR.SIGN_ERROR, "Signature not matched!"); 91 } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException exception) { 92 LOGGER.debug(exception.getMessage(), exception); 93 CustomException.throwException(ERROR.SIGN_ERROR, "Failed to verify signature: " + exception.getMessage()); 94 } 95 } 96 97 /** 98 * Convert store collection to list. 99 * 100 * @param certificates certificates from cmsSignedData 101 * @return List<X509Certificate> 102 */ certStoreToCertList(Store<X509CertificateHolder> certificates)103 public static List<X509Certificate> certStoreToCertList(Store<X509CertificateHolder> certificates) { 104 String errorMsg = "Verify failed, not found cert chain"; 105 JcaX509CertificateConverter converter = new JcaX509CertificateConverter(); 106 ValidateUtils.throwIfMatches(certificates == null, ERROR.VERIFY_ERROR, errorMsg); 107 Collection<X509CertificateHolder> matches = certificates.getMatches(null); 108 ValidateUtils.throwIfMatches(matches == null || !matches.iterator().hasNext(), 109 ERROR.VERIFY_ERROR, errorMsg); 110 111 Iterator<X509CertificateHolder> iterator = matches.iterator(); 112 List<X509Certificate> certificateList = new ArrayList<>(); 113 try { 114 while (iterator.hasNext()) { 115 X509CertificateHolder next = iterator.next(); 116 certificateList.add(converter.getCertificate(next)); 117 } 118 } catch (CertificateException exception) { 119 LOGGER.debug(exception.getMessage(), exception); 120 CustomException.throwException(ERROR.VERIFY_ERROR, errorMsg); 121 } 122 ValidateUtils.throwIfMatches(certificateList.size() == 0, ERROR.VERIFY_ERROR, errorMsg); 123 return certificateList; 124 } 125 126 /** 127 * verify p7b content. 128 * 129 * @param p7b signed p7b content 130 * @return result 131 * @throws VerifyException verify p7b failed 132 */ 133 @Override verify(byte[] p7b)134 public VerificationResult verify(byte[] p7b) throws VerifyException { 135 VerificationResult result = new VerificationResult(); 136 137 try { 138 CMSSignedData cmsSignedData = this.verifyPkcs(p7b); 139 List<X509Certificate> certificates = certStoreToCertList(cmsSignedData.getCertificates()); 140 CertUtils.sortCertificateChain(certificates); 141 142 SignerInformationStore signerInfos = cmsSignedData.getSignerInfos(); 143 Collection<SignerInformation> signers = signerInfos.getSigners(); 144 145 for (SignerInformation signer : signers) { 146 SignerId sid = signer.getSID(); 147 Date signTime = getSignTime(signer); 148 149 X500Principal principal = new X500Principal(sid.getIssuer().getEncoded()); 150 CertChainUtils.verifyCertChain(certificates, principal, sid.getSerialNumber(), 151 certificates.get(certificates.size() - 1), signTime); 152 } 153 154 result.setContent(FileUtils.GSON.fromJson(new String((byte[]) (cmsSignedData 155 .getSignedContent().getContent()), StandardCharsets.UTF_8), JsonObject.class)); 156 result.setMessage("OK"); 157 result.setVerifiedPassed(true); 158 return result; 159 } catch (CustomException | IOException exception) { 160 LOGGER.debug(exception.getMessage(), exception); 161 result.setMessage(exception.getMessage()); 162 result.setVerifiedPassed(false); 163 return result; 164 } 165 } 166 getSignTime(SignerInformation signer)167 Date getSignTime(SignerInformation signer) { 168 Date signTime = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()); 169 170 Attribute attribute = signer.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_signingTime); 171 172 if (attribute == null) { 173 LOGGER.warn("sign information does not include signTime"); 174 return signTime; 175 } 176 177 ASN1Set attrValues = attribute.getAttrValues(); 178 if (attrValues.size() == 0) { 179 LOGGER.warn("get sign time false, use local datetime verify profile cert chain"); 180 return signTime; 181 } 182 183 ASN1Encodable objectAt = attrValues.getObjectAt(0); 184 signTime = Time.getInstance(objectAt).getDate(); 185 186 return signTime; 187 } 188 verifyPkcs(byte[] p7b)189 CMSSignedData verifyPkcs(byte[] p7b) { 190 CMSSignedData cmsSignedData = null; 191 try { 192 cmsSignedData = new CMSSignedData(p7b); 193 boolean verifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); 194 ValidateUtils.throwIfNotMatches(verifyResult, ERROR.VERIFY_ERROR, 195 "Failed to verify BC signatures"); 196 return cmsSignedData; 197 } catch (CMSException exception) { 198 LOGGER.debug(exception.getMessage(), exception); 199 CustomException.throwException(ERROR.VERIFY_ERROR, "Failed to verify BC signatures: " 200 + exception.getMessage()); 201 } 202 return cmsSignedData; 203 } 204 } 205