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.google.gson.JsonParser; 19 import com.ohos.hapsigntool.entity.Options; 20 import com.ohos.hapsigntool.adapter.LocalizationAdapter; 21 import com.ohos.hapsigntool.error.CustomException; 22 import com.ohos.hapsigntool.error.ERROR; 23 import com.ohos.hapsigntool.error.SignToolErrMsg; 24 import com.ohos.hapsigntool.error.VerifyException; 25 import com.ohos.hapsigntool.hap.provider.LocalJKSSignProvider; 26 import com.ohos.hapsigntool.hap.provider.RemoteSignProvider; 27 import com.ohos.hapsigntool.hap.provider.SignProvider; 28 import com.ohos.hapsigntool.hap.verify.VerifyElf; 29 import com.ohos.hapsigntool.hap.verify.VerifyHap; 30 import com.ohos.hapsigntool.profile.ProfileSignTool; 31 import com.ohos.hapsigntool.profile.VerifyHelper; 32 import com.ohos.hapsigntool.profile.model.Provision; 33 import com.ohos.hapsigntool.profile.model.VerificationResult; 34 import com.ohos.hapsigntool.utils.CertUtils; 35 import com.ohos.hapsigntool.utils.FileUtils; 36 import com.ohos.hapsigntool.entity.ParamConstants; 37 import com.ohos.hapsigntool.utils.LogUtils; 38 import com.ohos.hapsigntool.utils.StringUtils; 39 40 import org.bouncycastle.jce.provider.BouncyCastleProvider; 41 42 import java.io.File; 43 import java.io.IOException; 44 import java.nio.charset.StandardCharsets; 45 import java.security.KeyPair; 46 import java.security.Security; 47 import java.security.cert.CertificateException; 48 import java.security.cert.X509Certificate; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.List; 52 53 /** 54 * Main entry of lib. 55 * 56 * @since 2021/12/28 57 */ 58 public class SignToolServiceImpl implements ServiceApi { 59 /** 60 * App signing Capabilty Bytes. 61 */ 62 private static final byte[] APP_SIGNING_CAPABILITY = {0x30, 0x06, 0x02, 0x01, 0x01, 0x0A, 0x01, 0x00}; 63 64 /** 65 * Profile signing Capabilty Bytes. 66 */ 67 private static final byte[] PROFILE_SIGNING_CAPABILITY = {0x30, 0x06, 0x02, 0x01, 0x01, 0x0A, 0x01, 0x01}; 68 69 /** 70 * Logger. 71 */ 72 private static final LogUtils LOGGER = new LogUtils(ServiceApi.class); 73 74 static { Security.addProvider(new BouncyCastleProvider())75 Security.addProvider(new BouncyCastleProvider()); 76 } 77 78 /** 79 * Generate keyStore. 80 * 81 * @param options options 82 * @return Generate or not 83 */ 84 @Override generateKeyStore(Options options)85 public boolean generateKeyStore(Options options) { 86 LocalizationAdapter adapter = new LocalizationAdapter(options); 87 adapter.errorOnExist(options.getString(Options.KEY_ALIAS)); 88 KeyPair keyPair = adapter.getAliasKey(true); 89 adapter.releasePwd(); 90 return keyPair != null; 91 } 92 93 /** 94 * Generate csr. 95 * 96 * @param options options 97 * @return Generate or not 98 */ 99 @Override generateCsr(Options options)100 public boolean generateCsr(Options options) { 101 LocalizationAdapter adapter = new LocalizationAdapter(options); 102 KeyPair keyPair = adapter.getAliasKey(false); 103 adapter.releasePwd(); 104 byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); 105 if (csr == null) { 106 return false; 107 } 108 String csrContent = CertUtils.toCsrTemplate(csr); 109 outputString(csrContent, adapter.getOutFile()); 110 return true; 111 } 112 113 /** 114 * Generate cert. 115 * 116 * @param options options 117 * @return Generate or not 118 */ 119 @Override generateCert(Options options)120 public boolean generateCert(Options options) { 121 LocalizationAdapter adapter = new LocalizationAdapter(options); 122 adapter.errorIfNotExist(adapter.getOptions().getString(Options.KEY_ALIAS)); 123 String issuerAlias = adapter.getOptions().getString(Options.ISSUER_KEY_ALIAS); 124 adapter.errorIfNotExist(issuerAlias); 125 126 KeyPair subjectKeyPair = adapter.getAliasKey(false); 127 if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { 128 adapter.setKeyStoreHelper(null); 129 adapter.setIssuerKeyStoreFile(true); 130 } 131 KeyPair rootKeyPair = adapter.getIssuerAliasKey(); 132 adapter.releasePwd(); 133 byte[] csr = CertTools.generateCsr(subjectKeyPair, adapter.getSignAlg(), adapter.getSubject()); 134 135 X509Certificate cert = CertTools.generateCert(rootKeyPair, csr, adapter); 136 return outputCert(cert, adapter.getOutFile()); 137 } 138 139 /** 140 * Generate CA. 141 * 142 * @param options options 143 * @return Generate or not 144 */ 145 @Override generateCA(Options options)146 public boolean generateCA(Options options) { 147 LocalizationAdapter adapter = new LocalizationAdapter(options); 148 boolean isEmpty = StringUtils.isEmpty(options.getString(Options.ISSUER_KEY_ALIAS)); 149 KeyPair subKey = adapter.getAliasKey(true); 150 KeyPair rootKey; 151 String ksFile = options.getString(Options.KEY_STORE_FILE); 152 String iksFile = options.getString(Options.ISSUER_KEY_STORE_FILE); 153 if (isEmpty) { 154 if (!StringUtils.isEmpty(iksFile) && !ksFile.equals(iksFile)) { 155 CustomException.throwException(ERROR.WRITE_FILE_ERROR, SignToolErrMsg.GENERATE_CA_FAILED 156 .toString(ksFile, iksFile)); 157 } 158 if (options.containsKey(Options.ISSUER_KEY_STORE_RIGHTS)) { 159 boolean isEqual = Arrays.equals(options.getChars(Options.KEY_STORE_RIGHTS), 160 options.getChars(Options.ISSUER_KEY_STORE_RIGHTS)); 161 if (!isEqual) { 162 CustomException.throwException(ERROR.WRITE_FILE_ERROR, SignToolErrMsg.GENERATE_CA_FAILED 163 .toString(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 = 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, SignToolErrMsg.VERIFY_PROFILE_FAILED 291 .toString(e.getMessage())); 292 isSign = false; 293 } 294 return isSign; 295 } 296 297 /** 298 * Sign for hap. 299 * 300 * @param options options 301 * @return Sign or not 302 */ 303 @Override signHap(Options options)304 public boolean signHap(Options options) { 305 String mode = options.getString(Options.MODE); 306 // sign online or locally 307 SignProvider signProvider; 308 if ("localSign".equalsIgnoreCase(mode)) { 309 signProvider = new LocalJKSSignProvider(); 310 } else if ("remoteSign".equalsIgnoreCase(mode)) { 311 signProvider = new RemoteSignProvider(); 312 } else { 313 LOGGER.info("Resign mode. But not implement yet"); 314 return false; 315 } 316 317 // The type of file is bin or hap 318 String inForm = options.getString(Options.IN_FORM, "zip"); 319 if ("zip".equalsIgnoreCase(inForm)) { 320 return signProvider.sign(options); 321 } else if ("elf".equalsIgnoreCase(inForm)) { 322 return signProvider.signElf(options); 323 } else { 324 return signProvider.signBin(options); 325 } 326 } 327 328 @Override verifyHap(Options options)329 public boolean verifyHap(Options options) { 330 if ("zip".equals(options.getOrDefault(ParamConstants.PARAM_IN_FORM, "zip"))) { 331 VerifyHap hapVerify = new VerifyHap(); 332 return hapVerify.verify(options); 333 } else { 334 VerifyElf verifyElf = new VerifyElf(); 335 return verifyElf.verify(options); 336 } 337 } 338 339 /** 340 * Output string, save into file or print. 341 * 342 * @param content String Content 343 * @param file file 344 */ outputString(String content, String file)345 public void outputString(String content, String file) { 346 if (StringUtils.isEmpty(file)) { 347 LOGGER.info(content); 348 } else { 349 try { 350 FileUtils.write(content.getBytes(StandardCharsets.UTF_8), new File(file)); 351 } catch (IOException exception) { 352 LOGGER.debug(exception.getMessage(), exception); 353 CustomException.throwException(ERROR.WRITE_FILE_ERROR, SignToolErrMsg.FILE_WRITE_FAILED 354 .toString(exception.getMessage())); 355 } 356 } 357 } 358 359 /** 360 * Output certificate. 361 * 362 * @param certificate certificate 363 * @param file file 364 * @return Whether to output certificate 365 */ outputCert(X509Certificate certificate, String file)366 public boolean outputCert(X509Certificate certificate, String file) { 367 try { 368 if (StringUtils.isEmpty(file)) { 369 LOGGER.info(CertUtils.generateCertificateInCer(certificate)); 370 } else { 371 FileUtils.write(CertUtils.generateCertificateInCer(certificate).getBytes(StandardCharsets.UTF_8), 372 new File(file)); 373 } 374 return true; 375 } catch (CertificateException | IOException exception) { 376 LOGGER.debug(exception.getMessage(), exception); 377 CustomException.throwException(ERROR.WRITE_FILE_ERROR, SignToolErrMsg.FILE_WRITE_FAILED 378 .toString(exception.getMessage())); 379 return false; 380 } 381 } 382 383 /** 384 * Output certificate in certificates list. 385 * 386 * @param certificates certificates 387 * @param file file 388 * @return Whether to output certificate in certificates list 389 */ outputCertChain(List<X509Certificate> certificates, String file)390 public boolean outputCertChain(List<X509Certificate> certificates, String file) { 391 try { 392 StringBuilder stringBuilder = new StringBuilder(); 393 for (X509Certificate cert : certificates) { 394 stringBuilder.append(CertUtils.generateCertificateInCer(cert)); 395 } 396 if (StringUtils.isEmpty(file)) { 397 LOGGER.info(stringBuilder.toString()); 398 } else { 399 FileUtils.write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8), new File(file)); 400 } 401 return true; 402 } catch (CertificateException | IOException exception) { 403 LOGGER.debug(exception.getMessage(), exception); 404 CustomException.throwException(ERROR.WRITE_FILE_ERROR, SignToolErrMsg.FILE_WRITE_FAILED 405 .toString(exception.getMessage())); 406 return false; 407 } 408 } 409 410 /** 411 * Get provision content. 412 * 413 * @param input input provision profile 414 * @return file data 415 * @throws IOException IOException 416 */ getProvisionContent(File input)417 public static byte[] getProvisionContent(File input) throws IOException { 418 byte[] bytes = FileUtils.readFile(input); 419 String json = JsonParser.parseString(new String(bytes, StandardCharsets.UTF_8)).toString(); 420 Provision provision = FileUtils.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), Provision.class); 421 Provision.enforceValid(provision); 422 return json.getBytes(StandardCharsets.UTF_8); 423 } 424 } 425