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.hap.verify.VerifyUtils; 22 import com.ohos.hapsigntool.profile.model.VerificationResult; 23 import com.ohos.hapsigntool.utils.CertChainUtils; 24 import com.ohos.hapsigntool.utils.CertUtils; 25 import com.ohos.hapsigntool.utils.FileUtils; 26 import com.ohos.hapsigntool.utils.ValidateUtils; 27 import org.apache.logging.log4j.LogManager; 28 import org.apache.logging.log4j.Logger; 29 import org.bouncycastle.asn1.ASN1Encodable; 30 import org.bouncycastle.asn1.ASN1Set; 31 import org.bouncycastle.asn1.cms.Attribute; 32 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 33 import org.bouncycastle.asn1.x509.Time; 34 import org.bouncycastle.cert.X509CertificateHolder; 35 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 36 import org.bouncycastle.cms.CMSException; 37 import org.bouncycastle.cms.CMSSignedData; 38 import org.bouncycastle.cms.SignerId; 39 import org.bouncycastle.cms.SignerInformation; 40 import org.bouncycastle.cms.SignerInformationStore; 41 import org.bouncycastle.util.Store; 42 43 import javax.security.auth.x500.X500Principal; 44 import java.io.IOException; 45 import java.nio.charset.StandardCharsets; 46 import java.security.InvalidKeyException; 47 import java.security.NoSuchAlgorithmException; 48 import java.security.Signature; 49 import java.security.SignatureException; 50 import java.security.cert.CertificateException; 51 import java.security.cert.X509Certificate; 52 import java.time.LocalDateTime; 53 import java.time.ZoneId; 54 import java.util.ArrayList; 55 import java.util.Collection; 56 import java.util.Iterator; 57 import java.util.List; 58 import java.util.Date; 59 60 /** 61 * Signed provision profile verifier. 62 * 63 * @since 2021/12/28 64 */ 65 public class VerifyHelper implements IProvisionVerifier { 66 /** 67 * LOGGER. 68 */ 69 private static final Logger LOGGER = LogManager.getLogger(VerifyHelper.class); 70 71 /** 72 * Signed provision profile verifier. 73 */ VerifyHelper()74 public VerifyHelper() { 75 // Empty constructor 76 } 77 78 /** 79 * Checked signed data with public key. 80 * 81 * @param cert public key 82 * @param signedData signed data with private key 83 * @param unsignedData unsigned data 84 * @param algorithm algorithm 85 */ verifySignature(X509Certificate cert, byte[] signedData, byte[] unsignedData, String algorithm)86 public static void verifySignature(X509Certificate cert, byte[] signedData, byte[] unsignedData, String algorithm) { 87 try { 88 Signature signature = Signature.getInstance(algorithm); 89 signature.initVerify(cert); 90 signature.update(unsignedData); 91 ValidateUtils.throwIfNotMatches(signature.verify(signedData), ERROR.SIGN_ERROR, "Signature not matched!"); 92 } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException exception) { 93 LOGGER.debug(exception.getMessage(), exception); 94 CustomException.throwException(ERROR.SIGN_ERROR, "Failed to verify signature: " + exception.getMessage()); 95 } 96 } 97 98 /** 99 * Convert store collection to list. 100 * 101 * @param certificates certificates from cmsSignedData 102 * @return List<X509Certificate> 103 */ certStoreToCertList(Store<X509CertificateHolder> certificates)104 public static List<X509Certificate> certStoreToCertList(Store<X509CertificateHolder> certificates) { 105 String errorMsg = "Verify failed, not found cert chain"; 106 JcaX509CertificateConverter converter = new JcaX509CertificateConverter(); 107 ValidateUtils.throwIfMatches(certificates == null, ERROR.VERIFY_ERROR, errorMsg); 108 Collection<X509CertificateHolder> matches = certificates.getMatches(null); 109 ValidateUtils.throwIfMatches(matches == null || !matches.iterator().hasNext(), 110 ERROR.VERIFY_ERROR, errorMsg); 111 112 Iterator<X509CertificateHolder> iterator = matches.iterator(); 113 List<X509Certificate> certificateList = new ArrayList<>(); 114 try { 115 while (iterator.hasNext()) { 116 X509CertificateHolder next = iterator.next(); 117 certificateList.add(converter.getCertificate(next)); 118 } 119 } catch (CertificateException exception) { 120 LOGGER.debug(exception.getMessage(), exception); 121 CustomException.throwException(ERROR.VERIFY_ERROR, errorMsg); 122 } 123 ValidateUtils.throwIfMatches(certificateList.size() == 0, ERROR.VERIFY_ERROR, errorMsg); 124 return certificateList; 125 } 126 127 /** 128 * verify p7b content. 129 * 130 * @param p7b signed p7b content 131 * @return result 132 */ 133 @Override verify(byte[] p7b)134 public VerificationResult verify(byte[] p7b) { 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