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.SignToolErrMsg; 22 import com.ohos.hapsigntool.error.VerifyException; 23 import com.ohos.hapsigntool.hap.verify.VerifyUtils; 24 import com.ohos.hapsigntool.profile.model.VerificationResult; 25 import com.ohos.hapsigntool.utils.CertChainUtils; 26 import com.ohos.hapsigntool.utils.CertUtils; 27 import com.ohos.hapsigntool.utils.FileUtils; 28 import com.ohos.hapsigntool.utils.LogUtils; 29 import com.ohos.hapsigntool.utils.ValidateUtils; 30 31 import org.bouncycastle.asn1.ASN1Encodable; 32 import org.bouncycastle.asn1.ASN1Set; 33 import org.bouncycastle.asn1.cms.Attribute; 34 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 35 import org.bouncycastle.asn1.x509.Time; 36 import org.bouncycastle.cert.X509CertificateHolder; 37 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; 38 import org.bouncycastle.cms.CMSException; 39 import org.bouncycastle.cms.CMSSignedData; 40 import org.bouncycastle.cms.SignerId; 41 import org.bouncycastle.cms.SignerInformation; 42 import org.bouncycastle.cms.SignerInformationStore; 43 import org.bouncycastle.util.Store; 44 45 import javax.security.auth.x500.X500Principal; 46 import java.io.IOException; 47 import java.nio.charset.StandardCharsets; 48 import java.security.InvalidKeyException; 49 import java.security.NoSuchAlgorithmException; 50 import java.security.Signature; 51 import java.security.SignatureException; 52 import java.security.cert.CertificateException; 53 import java.security.cert.X509Certificate; 54 import java.time.LocalDateTime; 55 import java.time.ZoneId; 56 import java.util.ArrayList; 57 import java.util.Collection; 58 import java.util.Iterator; 59 import java.util.List; 60 import java.util.Date; 61 62 /** 63 * Signed provision profile verifier. 64 * 65 * @since 2021/12/28 66 */ 67 public class VerifyHelper implements IProvisionVerifier { 68 /** 69 * LOGGER. 70 */ 71 private static final LogUtils LOGGER = new LogUtils(VerifyHelper.class); 72 73 /** 74 * Signed provision profile verifier. 75 */ VerifyHelper()76 public VerifyHelper() {} 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, 92 SignToolErrMsg.SIGNATURE_NOT_MATCHED.toString()); 93 } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException exception) { 94 LOGGER.debug(exception.getMessage(), exception); 95 CustomException.throwException(ERROR.SIGN_ERROR, SignToolErrMsg.VERIFY_FAILED 96 .toString(exception.getMessage())); 97 } 98 } 99 100 /** 101 * Convert store collection to list. 102 * 103 * @param certificates certificates from cmsSignedData 104 * @return List<X509Certificate> 105 */ certStoreToCertList(Store<X509CertificateHolder> certificates)106 public static List<X509Certificate> certStoreToCertList(Store<X509CertificateHolder> certificates) { 107 String errorMsg = "Verify failed, not found cert chain"; 108 JcaX509CertificateConverter converter = new JcaX509CertificateConverter(); 109 ValidateUtils.throwIfMatches(certificates == null, ERROR.VERIFY_ERROR, SignToolErrMsg.VERIFY_FAILED 110 .toString(errorMsg)); 111 Collection<X509CertificateHolder> matches = certificates.getMatches(null); 112 ValidateUtils.throwIfMatches(matches == null || !matches.iterator().hasNext(), 113 ERROR.VERIFY_ERROR, SignToolErrMsg.VERIFY_FAILED.toString(errorMsg)); 114 115 Iterator<X509CertificateHolder> iterator = matches.iterator(); 116 List<X509Certificate> certificateList = new ArrayList<>(); 117 try { 118 while (iterator.hasNext()) { 119 X509CertificateHolder next = iterator.next(); 120 certificateList.add(converter.getCertificate(next)); 121 } 122 } catch (CertificateException exception) { 123 LOGGER.debug(exception.getMessage(), exception); 124 CustomException.throwException(ERROR.VERIFY_ERROR, SignToolErrMsg.VERIFY_FAILED.toString(errorMsg)); 125 } 126 ValidateUtils.throwIfMatches(certificateList.size() == 0, 127 ERROR.VERIFY_ERROR, SignToolErrMsg.VERIFY_FAILED.toString(errorMsg)); 128 return certificateList; 129 } 130 131 /** 132 * verify p7b content. 133 * 134 * @param p7b signed p7b content 135 * @return result 136 * @throws VerifyException verify p7b failed 137 */ 138 @Override verify(byte[] p7b)139 public VerificationResult verify(byte[] p7b) throws VerifyException { 140 VerificationResult result = new VerificationResult(); 141 142 try { 143 CMSSignedData cmsSignedData = this.verifyPkcs(p7b); 144 List<X509Certificate> certificates = certStoreToCertList(cmsSignedData.getCertificates()); 145 CertUtils.sortCertificateChain(certificates); 146 147 SignerInformationStore signerInfos = cmsSignedData.getSignerInfos(); 148 Collection<SignerInformation> signers = signerInfos.getSigners(); 149 150 for (SignerInformation signer : signers) { 151 SignerId sid = signer.getSID(); 152 Date signTime = getSignTime(signer); 153 154 X500Principal principal = new X500Principal(sid.getIssuer().getEncoded()); 155 CertChainUtils.verifyCertChain(certificates, principal, sid.getSerialNumber(), 156 certificates.get(certificates.size() - 1), signTime); 157 } 158 159 result.setContent(FileUtils.GSON.fromJson(new String((byte[]) (cmsSignedData 160 .getSignedContent().getContent()), StandardCharsets.UTF_8), JsonObject.class)); 161 result.setMessage("OK"); 162 result.setVerifiedPassed(true); 163 return result; 164 } catch (CustomException | IOException exception) { 165 LOGGER.debug(exception.getMessage(), exception); 166 result.setMessage(exception.getMessage()); 167 result.setVerifiedPassed(false); 168 return result; 169 } 170 } 171 getSignTime(SignerInformation signer)172 Date getSignTime(SignerInformation signer) { 173 Date signTime = Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()); 174 175 Attribute attribute = signer.getSignedAttributes().get(PKCSObjectIdentifiers.pkcs_9_at_signingTime); 176 177 if (attribute == null) { 178 LOGGER.warn("sign information does not include signTime"); 179 return signTime; 180 } 181 182 ASN1Set attrValues = attribute.getAttrValues(); 183 if (attrValues.size() == 0) { 184 LOGGER.warn("get sign time false, use local datetime verify profile cert chain"); 185 return signTime; 186 } 187 188 ASN1Encodable objectAt = attrValues.getObjectAt(0); 189 signTime = Time.getInstance(objectAt).getDate(); 190 191 return signTime; 192 } 193 verifyPkcs(byte[] p7b)194 CMSSignedData verifyPkcs(byte[] p7b) { 195 CMSSignedData cmsSignedData = null; 196 try { 197 cmsSignedData = new CMSSignedData(p7b); 198 boolean verifyResult = VerifyUtils.verifyCmsSignedData(cmsSignedData); 199 ValidateUtils.throwIfNotMatches(verifyResult, ERROR.VERIFY_ERROR, 200 SignToolErrMsg.VERIFY_FAILED.toString("Failed to verify BC signatures")); 201 return cmsSignedData; 202 } catch (CMSException exception) { 203 LOGGER.debug(exception.getMessage(), exception); 204 CustomException.throwException(ERROR.VERIFY_ERROR, SignToolErrMsg.VERIFY_FAILED 205 .toString("Failed to verify BC signatures" + exception.getMessage())); 206 } 207 return cmsSignedData; 208 } 209 } 210