1 /* 2 * Copyright (c) 2021-2023 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.api; 17 18 import com.ohos.hapsigntool.api.model.Options; 19 import com.ohos.hapsigntool.cert.CertTools; 20 import com.ohos.hapsigntool.error.CustomException; 21 import com.ohos.hapsigntool.error.ERROR; 22 import com.ohos.hapsigntool.error.VerifyException; 23 import com.ohos.hapsigntool.hap.provider.LocalJKSSignProvider; 24 import com.ohos.hapsigntool.hap.provider.RemoteSignProvider; 25 import com.ohos.hapsigntool.hap.provider.SignProvider; 26 import com.ohos.hapsigntool.hap.verify.VerifyElf; 27 import com.ohos.hapsigntool.hap.verify.VerifyHap; 28 import com.ohos.hapsigntool.profile.ProfileSignTool; 29 import com.ohos.hapsigntool.profile.VerifyHelper; 30 import com.ohos.hapsigntool.profile.model.VerificationResult; 31 import com.ohos.hapsigntool.utils.CertUtils; 32 import com.ohos.hapsigntool.utils.FileUtils; 33 import com.ohos.hapsigntool.utils.ParamConstants; 34 import com.ohos.hapsigntool.utils.ProfileUtils; 35 import com.ohos.hapsigntool.utils.StringUtils; 36 37 import org.apache.logging.log4j.LogManager; 38 import org.apache.logging.log4j.Logger; 39 import org.bouncycastle.jce.provider.BouncyCastleProvider; 40 41 import java.io.File; 42 import java.io.IOException; 43 import java.nio.charset.StandardCharsets; 44 import java.security.KeyPair; 45 import java.security.Security; 46 import java.security.cert.CertificateException; 47 import java.security.cert.X509Certificate; 48 import java.util.ArrayList; 49 import java.util.Arrays; 50 import java.util.List; 51 52 /** 53 * Main entry of lib. 54 * 55 * @since 2021/12/28 56 */ 57 public class SignToolServiceImpl implements ServiceApi { 58 /** 59 * App signing Capabilty Bytes. 60 */ 61 private static final byte[] APP_SIGNING_CAPABILITY = {0x30, 0x06, 0x02, 0x01, 0x01, 0x0A, 0x01, 0x00}; 62 63 /** 64 * Profile signing Capabilty Bytes. 65 */ 66 private static final byte[] PROFILE_SIGNING_CAPABILITY = {0x30, 0x06, 0x02, 0x01, 0x01, 0x0A, 0x01, 0x01}; 67 68 /** 69 * Logger. 70 */ 71 private static final Logger logger = LogManager.getLogger(ServiceApi.class); 72 73 static { Security.addProvider(new BouncyCastleProvider())74 Security.addProvider(new BouncyCastleProvider()); 75 } 76 77 /** 78 * Generate keyStore. 79 * 80 * @param options options 81 * @return Generate or not 82 */ 83 @Override generateKeyStore(Options options)84 public boolean generateKeyStore(Options options) { 85 LocalizationAdapter adapter = new LocalizationAdapter(options); 86 adapter.errorOnExist(options.getString(Options.KEY_ALIAS)); 87 KeyPair keyPair = adapter.getAliasKey(true); 88 adapter.releasePwd(); 89 return keyPair != null; 90 } 91 92 /** 93 * Generate csr. 94 * 95 * @param options options 96 * @return Generate or not 97 */ 98 @Override generateCsr(Options options)99 public boolean generateCsr(Options options) { 100 LocalizationAdapter adapter = new LocalizationAdapter(options); 101 KeyPair keyPair = adapter.getAliasKey(false); 102 adapter.releasePwd(); 103 byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); 104 if (csr == null) { 105 return false; 106 } 107 String csrContent = CertUtils.toCsrTemplate(csr); 108 outputString(csrContent, adapter.getOutFile()); 109 return true; 110 } 111 112 /** 113 * Generate cert. 114 * 115 * @param options options 116 * @return Generate or not 117 */ 118 @Override generateCert(Options options)119 public boolean generateCert(Options options) { 120 LocalizationAdapter adapter = new LocalizationAdapter(options); 121 adapter.errorIfNotExist(adapter.getOptions().getString(Options.KEY_ALIAS)); 122 String issuerAlias = adapter.getOptions().getString(Options.ISSUER_KEY_ALIAS); 123 adapter.errorIfNotExist(issuerAlias); 124 125 KeyPair subjectKeyPair = adapter.getAliasKey(false); 126 if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { 127 adapter.setKeyStoreHelper(null); 128 adapter.setIssuerKeyStoreFile(true); 129 } 130 KeyPair rootKeyPair = adapter.getIssuerAliasKey(); 131 adapter.releasePwd(); 132 byte[] csr = CertTools.generateCsr(subjectKeyPair, adapter.getSignAlg(), adapter.getSubject()); 133 134 X509Certificate cert = CertTools.generateCert(rootKeyPair, csr, adapter); 135 return outputCert(cert, adapter.getOutFile()); 136 } 137 138 /** 139 * Generate CA. 140 * 141 * @param options options 142 * @return Generate or not 143 */ 144 @Override generateCA(Options options)145 public boolean generateCA(Options options) { 146 LocalizationAdapter adapter = new LocalizationAdapter(options); 147 boolean isEmpty = StringUtils.isEmpty(options.getString(Options.ISSUER_KEY_ALIAS)); 148 KeyPair subKey = adapter.getAliasKey(true); 149 KeyPair rootKey; 150 String ksFile = options.getString(Options.KEY_STORE_FILE); 151 String iksFile = options.getString(Options.ISSUER_KEY_STORE_FILE); 152 if (isEmpty) { 153 if (!StringUtils.isEmpty(iksFile) && !ksFile.equals(iksFile)) { 154 CustomException.throwException(ERROR.WRITE_FILE_ERROR, 155 String.format("Parameter '%s' and parameter '%s' are inconsistent", ksFile, iksFile)); 156 } 157 if (options.containsKey(Options.ISSUER_KEY_STORE_RIGHTS)) { 158 boolean isEqual = Arrays.equals(options.getChars(Options.KEY_STORE_RIGHTS), 159 options.getChars(Options.ISSUER_KEY_STORE_RIGHTS)); 160 if (!isEqual) { 161 CustomException.throwException(ERROR.WRITE_FILE_ERROR, 162 String.format("Parameter '%s' and parameter '%s' are inconsistent", 163 Options.KEY_STORE_RIGHTS, Options.ISSUER_KEY_STORE_RIGHTS)); 164 } 165 } 166 rootKey = subKey; 167 } else { 168 if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { 169 FileUtils.validFileType(options.getString(Options.ISSUER_KEY_STORE_FILE), "p12", "jks"); 170 adapter.setKeyStoreHelper(null); 171 adapter.setIssuerKeyStoreFile(true); 172 } 173 rootKey = adapter.getIssuerAliasKey(); 174 } 175 adapter.releasePwd(); 176 177 byte[] csr = CertTools.generateCsr(subKey, adapter.getSignAlg(), adapter.getSubject()); 178 X509Certificate cert; 179 if (isEmpty) { 180 cert = CertTools.generateRootCaCert(rootKey, csr, adapter); 181 } else { 182 cert = CertTools.generateSubCert(rootKey, csr, adapter); 183 } 184 return outputCert(cert, adapter.getOutFile()); 185 } 186 187 /** 188 * Generate app cert. 189 * 190 * @param options options 191 * @return Generate or not 192 */ 193 @Override generateAppCert(Options options)194 public boolean generateAppCert(Options options) { 195 LocalizationAdapter adapter = new LocalizationAdapter(options); 196 KeyPair keyPair = adapter.getAliasKey(false); 197 if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { 198 adapter.setKeyStoreHelper(null); 199 adapter.setIssuerKeyStoreFile(true); 200 } 201 KeyPair issueKeyPair = adapter.getIssuerAliasKey(); 202 adapter.releasePwd(); 203 204 byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); 205 X509Certificate cert = CertTools.generateEndCert(issueKeyPair, csr, adapter, APP_SIGNING_CAPABILITY); 206 return getOutputCert(adapter, cert); 207 } 208 209 /** 210 * Generate profile cert. 211 * 212 * @param options options 213 * @return Generate or not 214 */ 215 @Override generateProfileCert(Options options)216 public boolean generateProfileCert(Options options) { 217 LocalizationAdapter adapter = new LocalizationAdapter(options); 218 KeyPair keyPair = adapter.getAliasKey(false); 219 if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { 220 adapter.setKeyStoreHelper(null); 221 adapter.setIssuerKeyStoreFile(true); 222 } 223 KeyPair issueKeyPair = adapter.getIssuerAliasKey(); 224 adapter.releasePwd(); 225 226 byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); 227 X509Certificate cert = CertTools.generateEndCert(issueKeyPair, csr, adapter, PROFILE_SIGNING_CAPABILITY); 228 return getOutputCert(adapter, cert); 229 } 230 getOutputCert(LocalizationAdapter adapter, X509Certificate cert)231 private boolean getOutputCert(LocalizationAdapter adapter, X509Certificate cert) { 232 if (adapter.isOutFormChain()) { 233 List<X509Certificate> certificates = new ArrayList<>(); 234 certificates.add(cert); 235 certificates.add(adapter.getSubCaCertFile()); 236 certificates.add(adapter.getCaCertFile()); 237 return outputCertChain(certificates, adapter.getOutFile()); 238 } else { 239 return outputCert(cert, adapter.getOutFile()); 240 } 241 } 242 243 /** 244 * Sign for profile. 245 * 246 * @param options options 247 * @return Sign or not 248 */ 249 @Override signProfile(Options options)250 public boolean signProfile(Options options) { 251 boolean isSuccess; 252 try { 253 LocalizationAdapter adapter = new LocalizationAdapter(options); 254 byte[] provisionContent = ProfileUtils.getProvisionContent(new File(adapter.getInFile())); 255 byte[] p7b = ProfileSignTool.generateP7b(adapter, provisionContent); 256 FileUtils.write(p7b, new File(adapter.getOutFile())); 257 isSuccess = true; 258 } catch (IOException exception) { 259 logger.debug(exception.getMessage(), exception); 260 logger.error(exception.getMessage()); 261 isSuccess = false; 262 } 263 return isSuccess; 264 } 265 266 /** 267 * Verify profile. 268 * 269 * @param options options 270 * @return Verify or not 271 */ 272 @Override verifyProfile(Options options)273 public boolean verifyProfile(Options options) { 274 boolean isSign; 275 try { 276 LocalizationAdapter adapter = new LocalizationAdapter(options); 277 VerifyHelper verifyHelper = new VerifyHelper(); 278 byte[] p7b = FileUtils.readFile(new File(adapter.getInFile())); 279 VerificationResult verificationResult = verifyHelper.verify(p7b); 280 isSign = verificationResult.isVerifiedPassed(); 281 if (!isSign) { 282 logger.error(verificationResult.getMessage()); 283 } 284 outputString(FileUtils.GSON_PRETTY_PRINT.toJson(verificationResult), adapter.getOutFile()); 285 } catch (IOException exception) { 286 logger.debug(exception.getMessage(), exception); 287 logger.error(exception.getMessage()); 288 isSign = false; 289 } catch (VerifyException e) { 290 CustomException.throwException(ERROR.VERIFY_ERROR, "Verify Profile Failed! " + e.getMessage()); 291 isSign = false; 292 } 293 return isSign; 294 } 295 296 /** 297 * Sign for hap. 298 * 299 * @param options options 300 * @return Sign or not 301 */ 302 @Override signHap(Options options)303 public boolean signHap(Options options) { 304 String mode = options.getString(Options.MODE); 305 // sign online or locally 306 SignProvider signProvider; 307 if ("localSign".equalsIgnoreCase(mode)) { 308 signProvider = new LocalJKSSignProvider(); 309 } else if ("remoteSign".equalsIgnoreCase(mode)) { 310 signProvider = new RemoteSignProvider(); 311 } else { 312 logger.info("Resign mode. But not implement yet"); 313 return false; 314 } 315 316 // The type of file is bin or hap 317 String inForm = options.getString(Options.IN_FORM, "zip"); 318 if ("zip".equalsIgnoreCase(inForm)) { 319 return signProvider.sign(options); 320 } else if ("elf".equalsIgnoreCase(inForm)) { 321 return signProvider.signElf(options); 322 } else { 323 return signProvider.signBin(options); 324 } 325 } 326 327 @Override verifyHap(Options options)328 public boolean verifyHap(Options options) { 329 if ("zip".equals(options.getOrDefault(ParamConstants.PARAM_IN_FORM, "zip"))) { 330 VerifyHap hapVerify = new VerifyHap(); 331 return hapVerify.verify(options); 332 } else { 333 VerifyElf verifyElf = new VerifyElf(); 334 return verifyElf.verify(options); 335 } 336 } 337 338 /** 339 * Output string, save into file or print. 340 * 341 * @param content String Content 342 * @param file file 343 */ outputString(String content, String file)344 public void outputString(String content, String file) { 345 if (StringUtils.isEmpty(file)) { 346 logger.info(content); 347 } else { 348 try { 349 FileUtils.write(content.getBytes(StandardCharsets.UTF_8), new File(file)); 350 } catch (IOException exception) { 351 logger.debug(exception.getMessage(), exception); 352 CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); 353 } 354 } 355 } 356 357 /** 358 * Output certificate. 359 * 360 * @param certificate certificate 361 * @param file file 362 * @return Whether to output certificate 363 */ outputCert(X509Certificate certificate, String file)364 public boolean outputCert(X509Certificate certificate, String file) { 365 try { 366 if (StringUtils.isEmpty(file)) { 367 logger.info(CertUtils.generateCertificateInCer(certificate)); 368 } else { 369 FileUtils.write(CertUtils.generateCertificateInCer(certificate).getBytes(StandardCharsets.UTF_8), 370 new File(file)); 371 } 372 return true; 373 } catch (CertificateException | IOException exception) { 374 logger.debug(exception.getMessage(), exception); 375 CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); 376 return false; 377 } 378 } 379 380 /** 381 * Output certificate in certificates list. 382 * 383 * @param certificates certificates 384 * @param file file 385 * @return Whether to output certificate in certificates list 386 */ outputCertChain(List<X509Certificate> certificates, String file)387 public boolean outputCertChain(List<X509Certificate> certificates, String file) { 388 try { 389 StringBuilder stringBuilder = new StringBuilder(); 390 for (X509Certificate cert : certificates) { 391 stringBuilder.append(CertUtils.generateCertificateInCer(cert)); 392 } 393 if (StringUtils.isEmpty(file)) { 394 logger.info(stringBuilder.toString()); 395 } else { 396 FileUtils.write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8), new File(file)); 397 } 398 return true; 399 } catch (CertificateException | IOException exception) { 400 logger.debug(exception.getMessage(), exception); 401 CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); 402 return false; 403 } 404 } 405 } 406