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.VerifyException; 24 import com.ohos.hapsigntool.hap.provider.LocalJKSSignProvider; 25 import com.ohos.hapsigntool.hap.provider.RemoteSignProvider; 26 import com.ohos.hapsigntool.hap.provider.SignProvider; 27 import com.ohos.hapsigntool.hap.verify.VerifyElf; 28 import com.ohos.hapsigntool.hap.verify.VerifyHap; 29 import com.ohos.hapsigntool.profile.ProfileSignTool; 30 import com.ohos.hapsigntool.profile.VerifyHelper; 31 import com.ohos.hapsigntool.profile.model.Provision; 32 import com.ohos.hapsigntool.profile.model.VerificationResult; 33 import com.ohos.hapsigntool.utils.CertUtils; 34 import com.ohos.hapsigntool.utils.FileUtils; 35 import com.ohos.hapsigntool.entity.ParamConstants; 36 import com.ohos.hapsigntool.utils.StringUtils; 37 38 import org.apache.logging.log4j.LogManager; 39 import org.apache.logging.log4j.Logger; 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 Logger logger = LogManager.getLogger(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, 156 String.format("Parameter '%s' and parameter '%s' are inconsistent", 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, 163 String.format("Parameter '%s' and parameter '%s' are inconsistent", 164 Options.KEY_STORE_RIGHTS, Options.ISSUER_KEY_STORE_RIGHTS)); 165 } 166 } 167 rootKey = subKey; 168 } else { 169 if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { 170 FileUtils.validFileType(options.getString(Options.ISSUER_KEY_STORE_FILE), "p12", "jks"); 171 adapter.setKeyStoreHelper(null); 172 adapter.setIssuerKeyStoreFile(true); 173 } 174 rootKey = adapter.getIssuerAliasKey(); 175 } 176 adapter.releasePwd(); 177 178 byte[] csr = CertTools.generateCsr(subKey, adapter.getSignAlg(), adapter.getSubject()); 179 X509Certificate cert; 180 if (isEmpty) { 181 cert = CertTools.generateRootCaCert(rootKey, csr, adapter); 182 } else { 183 cert = CertTools.generateSubCert(rootKey, csr, adapter); 184 } 185 return outputCert(cert, adapter.getOutFile()); 186 } 187 188 /** 189 * Generate app cert. 190 * 191 * @param options options 192 * @return Generate or not 193 */ 194 @Override generateAppCert(Options options)195 public boolean generateAppCert(Options options) { 196 LocalizationAdapter adapter = new LocalizationAdapter(options); 197 KeyPair keyPair = adapter.getAliasKey(false); 198 if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { 199 adapter.setKeyStoreHelper(null); 200 adapter.setIssuerKeyStoreFile(true); 201 } 202 KeyPair issueKeyPair = adapter.getIssuerAliasKey(); 203 adapter.releasePwd(); 204 205 byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); 206 X509Certificate cert = CertTools.generateEndCert(issueKeyPair, csr, adapter, APP_SIGNING_CAPABILITY); 207 return getOutputCert(adapter, cert); 208 } 209 210 /** 211 * Generate profile cert. 212 * 213 * @param options options 214 * @return Generate or not 215 */ 216 @Override generateProfileCert(Options options)217 public boolean generateProfileCert(Options options) { 218 LocalizationAdapter adapter = new LocalizationAdapter(options); 219 KeyPair keyPair = adapter.getAliasKey(false); 220 if (options.containsKey(Options.ISSUER_KEY_STORE_FILE)) { 221 adapter.setKeyStoreHelper(null); 222 adapter.setIssuerKeyStoreFile(true); 223 } 224 KeyPair issueKeyPair = adapter.getIssuerAliasKey(); 225 adapter.releasePwd(); 226 227 byte[] csr = CertTools.generateCsr(keyPair, adapter.getSignAlg(), adapter.getSubject()); 228 X509Certificate cert = CertTools.generateEndCert(issueKeyPair, csr, adapter, PROFILE_SIGNING_CAPABILITY); 229 return getOutputCert(adapter, cert); 230 } 231 getOutputCert(LocalizationAdapter adapter, X509Certificate cert)232 private boolean getOutputCert(LocalizationAdapter adapter, X509Certificate cert) { 233 if (adapter.isOutFormChain()) { 234 List<X509Certificate> certificates = new ArrayList<>(); 235 certificates.add(cert); 236 certificates.add(adapter.getSubCaCertFile()); 237 certificates.add(adapter.getCaCertFile()); 238 return outputCertChain(certificates, adapter.getOutFile()); 239 } else { 240 return outputCert(cert, adapter.getOutFile()); 241 } 242 } 243 244 /** 245 * Sign for profile. 246 * 247 * @param options options 248 * @return Sign or not 249 */ 250 @Override signProfile(Options options)251 public boolean signProfile(Options options) { 252 boolean isSuccess; 253 try { 254 LocalizationAdapter adapter = new LocalizationAdapter(options); 255 byte[] provisionContent = getProvisionContent(new File(adapter.getInFile())); 256 byte[] p7b = ProfileSignTool.generateP7b(adapter, provisionContent); 257 FileUtils.write(p7b, new File(adapter.getOutFile())); 258 isSuccess = true; 259 } catch (IOException exception) { 260 logger.debug(exception.getMessage(), exception); 261 logger.error(exception.getMessage()); 262 isSuccess = false; 263 } 264 return isSuccess; 265 } 266 267 /** 268 * Verify profile. 269 * 270 * @param options options 271 * @return Verify or not 272 */ 273 @Override verifyProfile(Options options)274 public boolean verifyProfile(Options options) { 275 boolean isSign; 276 try { 277 LocalizationAdapter adapter = new LocalizationAdapter(options); 278 VerifyHelper verifyHelper = new VerifyHelper(); 279 byte[] p7b = FileUtils.readFile(new File(adapter.getInFile())); 280 VerificationResult verificationResult = verifyHelper.verify(p7b); 281 isSign = verificationResult.isVerifiedPassed(); 282 if (!isSign) { 283 logger.error(verificationResult.getMessage()); 284 } 285 outputString(FileUtils.GSON_PRETTY_PRINT.toJson(verificationResult), adapter.getOutFile()); 286 } catch (IOException exception) { 287 logger.debug(exception.getMessage(), exception); 288 logger.error(exception.getMessage()); 289 isSign = false; 290 } catch (VerifyException e) { 291 CustomException.throwException(ERROR.VERIFY_ERROR, "Verify Profile Failed! " + 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, exception.getMessage()); 354 } 355 } 356 } 357 358 /** 359 * Output certificate. 360 * 361 * @param certificate certificate 362 * @param file file 363 * @return Whether to output certificate 364 */ outputCert(X509Certificate certificate, String file)365 public boolean outputCert(X509Certificate certificate, String file) { 366 try { 367 if (StringUtils.isEmpty(file)) { 368 logger.info(CertUtils.generateCertificateInCer(certificate)); 369 } else { 370 FileUtils.write(CertUtils.generateCertificateInCer(certificate).getBytes(StandardCharsets.UTF_8), 371 new File(file)); 372 } 373 return true; 374 } catch (CertificateException | IOException exception) { 375 logger.debug(exception.getMessage(), exception); 376 CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); 377 return false; 378 } 379 } 380 381 /** 382 * Output certificate in certificates list. 383 * 384 * @param certificates certificates 385 * @param file file 386 * @return Whether to output certificate in certificates list 387 */ outputCertChain(List<X509Certificate> certificates, String file)388 public boolean outputCertChain(List<X509Certificate> certificates, String file) { 389 try { 390 StringBuilder stringBuilder = new StringBuilder(); 391 for (X509Certificate cert : certificates) { 392 stringBuilder.append(CertUtils.generateCertificateInCer(cert)); 393 } 394 if (StringUtils.isEmpty(file)) { 395 logger.info(stringBuilder.toString()); 396 } else { 397 FileUtils.write(stringBuilder.toString().getBytes(StandardCharsets.UTF_8), new File(file)); 398 } 399 return true; 400 } catch (CertificateException | IOException exception) { 401 logger.debug(exception.getMessage(), exception); 402 CustomException.throwException(ERROR.WRITE_FILE_ERROR, exception.getMessage()); 403 return false; 404 } 405 } 406 407 /** 408 * Get provision content. 409 * 410 * @param input input provision profile 411 * @return file data 412 * @throws IOException IOException 413 */ getProvisionContent(File input)414 public static byte[] getProvisionContent(File input) throws IOException { 415 byte[] bytes = FileUtils.readFile(input); 416 String json = JsonParser.parseString(new String(bytes, StandardCharsets.UTF_8)).toString(); 417 Provision provision = FileUtils.GSON.fromJson(new String(bytes, StandardCharsets.UTF_8), Provision.class); 418 Provision.enforceValid(provision); 419 return json.getBytes(StandardCharsets.UTF_8); 420 } 421 } 422