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 // Empty constructor 77 } 78 79 /** 80 * Checked signed data with public key. 81 * 82 * @param cert public key 83 * @param signedData signed data with private key 84 * @param unsignedData unsigned data 85 * @param algorithm algorithm 86 */ verifySignature(X509Certificate cert, byte[] signedData, byte[] unsignedData, String algorithm)87 public static void verifySignature(X509Certificate cert, byte[] signedData, byte[] unsignedData, String algorithm) { 88 try { 89 Signature signature = Signature.getInstance(algorithm); 90 signature.initVerify(cert); 91 signature.update(unsignedData); 92 ValidateUtils.throwIfNotMatches(signature.verify(signedData), ERROR.SIGN_ERROR, "Signature not matched!"); 93 } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException exception) { 94 LOGGER.debug(exception.getMessage(), exception); 95 CustomException.throwException(ERROR.SIGN_ERROR, "Failed to verify signature: " + exception.getMessage()); 96 } 97 } 98 99 /** 100 * Convert store collection to list. 101 * 102 * @param certificates certificates from cmsSignedData 103 * @return List<X509Certificate> 104 */ certStoreToCertList(Store<X509CertificateHolder> certificates)105 public static List<X509Certificate> certStoreToCertList(Store<X509CertificateHolder> certificates) { 106 String errorMsg = "Verify failed, not found cert chain"; 107 JcaX509CertificateConverter converter = new JcaX509CertificateConverter(); 108 ValidateUtils.throwIfMatches(certificates == null, ERROR.VERIFY_ERROR, errorMsg); 109 Collection<X509CertificateHolder> matches = certificates.getMatches(null); 110 ValidateUtils.throwIfMatches(matches == null || !matches.iterator().hasNext(), 111 ERROR.VERIFY_ERROR, errorMsg); 112 113 Iterator<X509CertificateHolder> iterator = matches.iterator(); 114 List<X509Certificate> certificateList = new ArrayList<>(); 115 try { 116 while (iterator.hasNext()) { 117 X509CertificateHolder next = iterator.next(); 118 certificateList.add(converter.getCertificate(next)); 119 } 120 } catch (CertificateException exception) { 121 LOGGER.debug(exception.getMessage(), exception); 122 CustomException.throwException(ERROR.VERIFY_ERROR, errorMsg); 123 } 124 ValidateUtils.throwIfMatches(certificateList.size() == 0, ERROR.VERIFY_ERROR, errorMsg); 125 return certificateList; 126 } 127 128 /** 129 * verify p7b content. 130 * 131 * @param p7b signed p7b content 132 * @return result 133 * @throws VerifyException verify p7b failed 134 */ 135 @Override verify(byte[] p7b)136 public VerificationResult verify(byte[] p7b) throws VerifyException { 137 VerificationResult result = new VerificationResult(); 138 139 try { 140 CMSSignedData cmsSignedData = this.verifyPkcs(p7b); 141 List<X509Certificate> certificates = certStoreToCertList(cmsSignedData.getCertificates()); 142 CertUtils.sortCertificateChain(certificates); 143 144 SignerInformationStore signerInfos = cmsSignedData.getSignerInfos(); 145 Collection<SignerInformation> signers = signerInfos.getSigners(); 146 147 for (SignerInformation signer : signers) { 148 SignerId sid = signer.getSID(); 149 Date signTime = getSignTime(signer); 150 151 X500Principal principal = new X500Principal(sid.getIssuer().getEncoded()); 152 CertChainUtils.verifyCertChain(certificates, principal, sid.getSerialNumber(), 153 certificates.get(certificates.size() - 1), signTime); 154 } 155 156 result.setContent(FileUtils.GSON.fromJson(new String((byte[]) (cmsSignedData 157 .getSignedContent().getContent()), StandardCharsets.UTF_8), JsonObject.class)); 158 result.setMessage("OK"); 159 result.setVerifiedPassed(true); 160 return result; 161 } catch (CustomException | IOException exception) { 162 LOGGER.debug(exception.getMessage(), exception); 163 result.setMessage(exception.getMessage()); 164 result.setVerifiedPassed(false); 165 return result; 166 } 167 } 168 getSignTime(SignerInformation signer)169 Date getSignTime(SignerInformation signer) { 170 Date signTime = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()); 171 172 Attribute attribute = signer.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_signingTime); 173 174 if (attribute == null) { 175 LOGGER.warn("sign information does not include signTime"); 176 return signTime; 177 } 178 179 ASN1Set attrValues = attribute.getAttrValues(); 180 if (attrValues.size() == 0) { 181 LOGGER.warn("get sign time false, use local datetime verify profile cert chain"); 182 return signTime; 183 } 184 185 ASN1Encodable objectAt = attrValues.getObjectAt(0); 186 signTime = Time.getInstance(objectAt).getDate(); 187 188 return signTime; 189 } 190 verifyPkcs(byte[] p7b)191 CMSSignedData verifyPkcs(byte[] p7b) { 192 CMSSignedData cmsSignedData = null; 193 try { 194 cmsSignedData = new CMSSignedData(p7b); 195 boolean verifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); 196 ValidateUtils.throwIfNotMatches(verifyResult, ERROR.VERIFY_ERROR, 197 "Failed to verify BC signatures"); 198 return cmsSignedData; 199 } catch (CMSException exception) { 200 LOGGER.debug(exception.getMessage(), exception); 201 CustomException.throwException(ERROR.VERIFY_ERROR, "Failed to verify BC signatures: " 202 + exception.getMessage()); 203 } 204 return cmsSignedData; 205 } 206 } 207