diff --git a/dist/hap-sign-tool.jar b/dist/hap-sign-tool.jar index 0257b52ff85021e4b584235c09ad234c9758255a..0ad7e6eda0ae8865de1f7c644d7b9bb1532b585c 100644 Binary files a/dist/hap-sign-tool.jar and b/dist/hap-sign-tool.jar differ diff --git a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java index 584816d2652508b702073565d6dc90d47021f978..7c6b83c5302f80955c5b82893c33c8cc6d3eb3a1 100644 --- a/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java +++ b/hapsigntool/hap_sign_tool/src/main/java/com/ohos/hapsigntool/HapSignTool.java @@ -272,6 +272,7 @@ public final class HapSignTool { if (LOCAL_SIGN.equalsIgnoreCase(mode)) { params.required(Options.KEY_STORE_FILE, Options.KEY_ALIAS, Options.APP_CERT_FILE); + FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); } checkProfile(params); String inForm = params.getString(Options.IN_FORM); @@ -280,7 +281,6 @@ public final class HapSignTool { } String signAlg = params.getString(Options.SIGN_ALG); CmdUtil.judgeEndSignAlgType(signAlg); - FileUtils.validFileType(params.getString(Options.KEY_STORE_FILE), "p12", "jks"); return api.signHap(params); } diff --git a/hapsigntool/hap_sign_tool/src/main/resources/help.txt b/hapsigntool/hap_sign_tool/src/main/resources/help.txt index c8497a6b577a87035919cf2db1b96ab2c4049867..4f293806a83eb915ac8de69a36c3b109391a92ec 100644 --- a/hapsigntool/hap_sign_tool/src/main/resources/help.txt +++ b/hapsigntool/hap_sign_tool/src/main/resources/help.txt @@ -165,20 +165,27 @@ USAGE: [options] sign-app [options]: -mode : signature mode, required fields, including localSign/remoteSign/remoteResign; -keyAlias : key alias, required fields; - -keyPwd : key password, optional fields; - -appCertFile : application signature certificate file, required fields; + -keyPwd : key password, optional fields on localSign mode; + -appCertFile : application signature certificate file, required fields on localSign mode, optional fields on remoteSign mode; -profileFile : signed Provision Profile file, p7b format, required fields; - -profileSigned : indicates whether the profile file has a signture.The options are as follows: 1:yes; 0:no; defalut value:1. optional fields; + -profileSigned : indicates whether the profile file has a signature.The options are as follows: 1:yes; 0:no; default value:1. optional fields; -inFile : input original application package file, hap or bin format, required fields; -signAlg : signature algorithm, required fields, including SHA256withRSA/SHA384withRSA/SHA256withECDSA/SHA384withECDSA; - -keystoreFile : keystore file, if signature mode is localSign, required fields, JKS or P12 format; - -keystorePwd : keystore password, optional fields; + -keystoreFile : keystore file, if signature mode is localSign, required fields on localSign mode, JKS or P12 format; + -keystorePwd : keystore password, optional fields on localSign mode; -outFile : output the signed Provision Profile file, required fields; -extCfgFile : Extend Profile, optional fields; - -inForm : enter the format of the origianl file, the format is .zip or .bin; + -inForm : enter the format of the original file, the format is .zip or .bin; + -compatibleVersion : min compatible api version for running app, required fields while input original application package file format is hap; + -signServer : remote signer plugin, required fields on remoteSign mode; + -signerPlugin : remote sign service url, required fields on remoteSign mode; + -onlineAuthMode : remote sign auth mode, required fields on remoteSign mode, including account; + -username : user account for online auth, required fields on remoteSign mode with account auth mode; + -userPwd : user password for online auth, required fields on remoteSign mode with account auth mode; + -ext : extend parameters for remote signer plugin, optional fields; EXAMPLE: - sign-app -mode localSign -keyAlias "oh-app1-key-v1" -appCertFile "D:\OH\app-release-cert.cer" -profileFile "D:\OH\signed-profile.p7b" -inFile "D:\OH\app1-unsigned.hap" -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\app1-signed.hap" + sign-app -mode localSign -keyAlias "oh-app1-key-v1" -appCertFile "D:\OH\app-release-cert.cer" -profileFile "D:\OH\signed-profile.p7b" -inFile "D:\OH\app1-unsigned.hap" -signAlg SHA256withECDSA -keystoreFile "D:\OH\app-keypair.jks" -keystorePwd ****** -outFile "D:\OH\app1-signed.hap -compatibleVersion 8" verify-app [options]: -inFile : signed application package file, hap or bin format, required fields; diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/config/SignerConfig.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/config/SignerConfig.java index 199eb79012dce89e8f452858516c11cfb7bf9e84..15e737c583dad37c3172ca604b5782f15797cce1 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/config/SignerConfig.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/config/SignerConfig.java @@ -58,6 +58,16 @@ public class SignerConfig { */ private Map signParamMap = new HashMap(); + /** + * Signer used for sign hap + */ + private ISigner signer; + + /** + * Minimum api version to run the application + */ + private int compatibleVersion; + /** * Get options. * @@ -69,6 +79,8 @@ public class SignerConfig { /** * set options. + * + * @param options parameters */ public void setOptions(Options options) { this.options = options; @@ -80,11 +92,16 @@ public class SignerConfig { * @return certificates */ public List getCertificates() { - return certificates; + if (isInputCertChainNotEmpty() || signer == null) { + return certificates; + } + return signer.getCertificates(); } /** * set certificate + * + * @param certificates certificate chain */ public void setCertificates(List certificates) { this.certificates = certificates; @@ -96,11 +113,16 @@ public class SignerConfig { * @return crl list */ public List getX509CRLs() { - return x509CRLs; + if (isInputCertChainNotEmpty() || isInputCrlNotEmpty() || signer == null) { + return x509CRLs; + } + return signer.getCrls(); } /** * set crl + * + * @param crls cert revocation list */ public void setX509CRLs(List crls) { this.x509CRLs = crls; @@ -117,6 +139,8 @@ public class SignerConfig { /** * set signature algorithm + * + * @param signatureAlgorithms sign algorithm */ public void setSignatureAlgorithms(List signatureAlgorithms) { this.signatureAlgorithms = signatureAlgorithms; @@ -133,6 +157,8 @@ public class SignerConfig { /** * set param map + * + * @param params params map */ public void fillParameters(Map params) { this.signParamMap = params; @@ -140,8 +166,39 @@ public class SignerConfig { /** * get signer + * + * @return content signer */ public ISigner getSigner() { - return new SignerFactory().getSigner(new LocalizationAdapter(options)); + if (signer == null) { + signer = new SignerFactory().getSigner(new LocalizationAdapter(options)); + } + return signer; + } + + /** + * get compatible version + * + * @return compatible version + */ + public int getCompatibleVersion() { + return compatibleVersion; + } + + /** + * set param compatible version + * + * @param compatibleVersion compatible version + */ + public void setCompatibleVersion(int compatibleVersion) { + this.compatibleVersion = compatibleVersion; + } + + private boolean isInputCertChainNotEmpty() { + return certificates != null && !certificates.isEmpty(); + } + + private boolean isInputCrlNotEmpty() { + return x509CRLs != null && !x509CRLs.isEmpty(); } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java index 7fd9a80eb628bee3f070121f7e00e03956bd1e30..731abb15719b00e6f84d2dc039efbc807455c522 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/LocalJKSSignProvider.java @@ -89,8 +89,7 @@ public class LocalJKSSignProvider extends SignProvider { String[] paramFileds = { ParamConstants.PARAM_LOCAL_JKS_KEYSTORE, ParamConstants.PARAM_LOCAL_JKS_KEYSTORE_CODE, - ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE, - ParamConstants.PARAM_LOCAL_PUBLIC_CERT + ParamConstants.PARAM_LOCAL_JKS_KEYALIAS_CODE }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java index dbc70d254f65a80bd8a89d61955e929347761c07..360a5ae57659ce091db18c474ef7cbadbd922b7f 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/provider/SignProvider.java @@ -33,13 +33,7 @@ import com.ohos.hapsigntool.hap.sign.SignBin; import com.ohos.hapsigntool.hap.sign.SignHap; import com.ohos.hapsigntool.hap.sign.SignatureAlgorithm; import com.ohos.hapsigntool.hap.verify.VerifyUtils; -import com.ohos.hapsigntool.utils.CertificateUtils; -import com.ohos.hapsigntool.utils.DigestUtils; -import com.ohos.hapsigntool.utils.EscapeCharacter; -import com.ohos.hapsigntool.utils.HapUtils; -import com.ohos.hapsigntool.utils.ParamConstants; -import com.ohos.hapsigntool.utils.ParamProcessUtil; -import com.ohos.hapsigntool.utils.StringUtils; +import com.ohos.hapsigntool.utils.*; import com.ohos.hapsigntool.zip.ByteBufferZipDataInput; import com.ohos.hapsigntool.zip.RandomAccessFileZipDataInput; import com.ohos.hapsigntool.zip.RandomAccessFileZipDataOutput; @@ -275,12 +269,11 @@ public abstract class SignProvider { boolean isPathOverlap = false; try { publicCerts = getX509Certificates(options); - + checkCompatibleVersion(); File input = new File(signParams.get(ParamConstants.PARAM_BASIC_INPUT_FILE)); output = new File(signParams.get(ParamConstants.PARAM_BASIC_OUTPUT_FILE)); if (input.getCanonicalPath().equals(output.getCanonicalPath())) { tmpOutput = File.createTempFile("signedHap", ".hap"); - tmpOutput.deleteOnExit(); isPathOverlap = true; } else { tmpOutput = output; @@ -303,6 +296,8 @@ public abstract class SignProvider { Optional crl = getCrl(); SignerConfig signerConfig = createSignerConfigs(publicCerts, crl, options); + signerConfig.setCompatibleVersion(Integer.parseInt( + signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION))); ZipDataInput[] contents = {beforeCentralDir, centralDirectory, eocd}; byte[] signingBlock = SignHap.sign(contents, signerConfig, optionalBlocks); long newCentralDirectoryOffset = centralDirectoryOffset + signingBlock.length; @@ -313,17 +308,16 @@ public abstract class SignProvider { isRet = true; } } catch (IOException | InvalidKeyException | HapFormatException | MissingParamsException - | InvalidParamsException | ProfileException | CustomException e) { + | InvalidParamsException | ProfileException | NumberFormatException | CustomException e) { printErrorLogWithoutStack(e); - isRet = false; } catch (SignatureException e) { printErrorLog(e); - isRet = false; } return doAfterSign(isRet, isPathOverlap, tmpOutput, output); } /** + * Load certificate chain from input parameters * * @param options parameters used to sign hap file * @return list of type x509certificate @@ -362,12 +356,11 @@ public abstract class SignProvider { isRet = false; } } - if ((!isRet) && (!pathOverlap) && (output != null)) { - output.deleteOnExit(); - } if (isRet) { LOGGER.info("Sign Hap success!"); + } else { + FileUtils.deleteFile(tmpOutput); } return isRet; } @@ -570,7 +563,9 @@ public abstract class SignProvider { ParamConstants.PARAM_BASIC_PROOF, ParamConstants.PARAM_BASIC_PROPERTY, ParamConstants.PARAM_REMOTE_SERVER, - ParamConstants.PARAM_BASIC_PROFILE_SIGNED + ParamConstants.PARAM_BASIC_PROFILE_SIGNED, + ParamConstants.PARAM_LOCAL_PUBLIC_CERT, + ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION }; Set paramSet = ParamProcessUtil.initParamField(paramFileds); @@ -586,6 +581,19 @@ public abstract class SignProvider { checkSignAlignment(); } + protected void checkCompatibleVersion() throws InvalidParamsException, MissingParamsException { + if (!signParams.containsKey(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION)) { + signParams.put(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION, "9"); + return; + } + String compatibleApiVersionVal = signParams.get(ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION); + try { + int compatibleApiVersion = Integer.parseInt(compatibleApiVersionVal); + } catch (NumberFormatException e) { + throw new InvalidParamsException("Invalid parameter: " + ParamConstants.PARAM_BASIC_COMPATIBLE_VERSION); + } + } + /** * Get parameters from inputted strings. This function unescape some escaped parameters and return it. * diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/BcPkcs7Generator.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/BcPkcs7Generator.java index 85bfa7a570a1a4b8d89d22cbb201329bbe2b3100..999b2c9965ab8217bbfd9d0c376d225fa932646a 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/BcPkcs7Generator.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/BcPkcs7Generator.java @@ -155,7 +155,7 @@ public class BcPkcs7Generator implements Pkcs7Generator { if (signatureBytes == null) { throw new SignatureException("Generate signature bytes error"); } - if (signerConfig.getCertificates().isEmpty()) { + if (!checkListNotNullOrEmty(signerConfig.getCertificates())) { throw new SignatureException("No certificates configured for signer"); } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java index 2d479c26ad5828a3cfc5bc7231b6073d9a29c098..eede0eaab64946ffd2011a6bd7bfcc4e20fd9e18 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/sign/SignHap.java @@ -46,7 +46,6 @@ import java.util.jar.JarOutputStream; * @since 2021/12/21 */ public abstract class SignHap { - private static final int HAP_SIGN_SCHEME_VERSION = 3; private static final int STORED_ENTRY_SO_ALIGNMENT = 4096; private static final int BUFFER_LENGTH = 4096; private static final int BLOCK_COUNT = 4; @@ -201,10 +200,11 @@ public abstract class SignHap { List optionalBlocks) throws SignatureException { byte[] hapSignatureSchemeBlock = generateHapSignatureSchemeBlock(signerConfig, contentDigests); - return generateHapSigningBlock(hapSignatureSchemeBlock, optionalBlocks); + return generateHapSigningBlock(hapSignatureSchemeBlock, optionalBlocks, signerConfig.getCompatibleVersion()); } - private static byte[] generateHapSigningBlock(byte[] hapSignatureSchemeBlock, List optionalBlocks) { + private static byte[] generateHapSigningBlock(byte[] hapSignatureSchemeBlock, + List optionalBlocks, int compatibleVersion) { // FORMAT: // Proof-of-Rotation pairs(optional): // uint32:type @@ -290,8 +290,8 @@ public abstract class SignHap { result.putInt(optionalBlocks.size() + 1); // Signing block count result.putLong(resultSize); // length of hap signing block - result.put(HapUtils.getHapSigningBlockMagic()); // magic - result.putInt(HAP_SIGN_SCHEME_VERSION); // version + result.put(HapUtils.getHapSigningBlockMagic(compatibleVersion)); // magic + result.putInt(HapUtils.getHapSigningBlockVersion(compatibleVersion)); // version return result.array(); } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java index dff6560c4b26240f516584ded9f4cb3dbe3982c2..c5c5834cd78020f1436df6da0773e29b6104fc07 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyHap.java @@ -241,14 +241,14 @@ public class VerifyHap { LOGGER.error(errorMsg); return new VerifyResult(false, VerifyResult.RET_UNSUPPORTED_FORMAT_ERROR, errorMsg); } - Pair hapSigningBlockAndOffsetInFile = HapUtils.findHapSigningBlock(hapFile, zipInfo); - ByteBuffer signingBlock = hapSigningBlockAndOffsetInFile.getSecond(); + HapUtils.HapSignBlockInfo hapSigningBlockAndOffsetInFile = HapUtils.findHapSigningBlock(hapFile, zipInfo); + ByteBuffer signingBlock = hapSigningBlockAndOffsetInFile.getContent(); signingBlock.order(ByteOrder.LITTLE_ENDIAN); Pair> blockPair = getHapSignatureSchemeBlockAndOptionalBlocks(signingBlock); ByteBuffer signatureSchemeBlock = blockPair.getFirst(); List optionalBlocks = blockPair.getSecond(); Collections.reverse(optionalBlocks); - long signingBlockOffset = hapSigningBlockAndOffsetInFile.getFirst(); + long signingBlockOffset = hapSigningBlockAndOffsetInFile.getOffset(); ZipDataInput beforeHapSigningBlock = hapFile.slice(0, signingBlockOffset); ZipDataInput centralDirectoryBlock = hapFile.slice(zipInfo.getCentralDirectoryOffset(), zipInfo.getCentralDirectorySize()); @@ -259,6 +259,7 @@ public class VerifyHap { centralDirectoryBlock, eocdBlock, optionalBlocks); verifyEngine.setPrintCert(printCert); result = verifyEngine.verify(); + result.setSignBlockVersion(hapSigningBlockAndOffsetInFile.getVersion()); } catch (IOException e) { LOGGER.error("Verify Hap has IO error!", e); result = new VerifyResult(false, VerifyResult.RET_IO_ERROR, e.getMessage()); diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java index 8ba54b0ba1847c5abc271d6b174bca3af7a1ccab..484a23c6eb9f463ba89886f394c4ee4bbb55697e 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/hap/verify/VerifyResult.java @@ -104,9 +104,21 @@ public class VerifyResult { private Store certificateHolderStore; + private int signBlockVersion; + + /** + * Empty constructor + */ public VerifyResult() { } + /** + * Verify result constructor + * + * @param result verify result + * @param code error code + * @param message error message + */ public VerifyResult(boolean result, int code, String message) { this.result = result; this.code = code; @@ -176,4 +188,12 @@ public class VerifyResult { public void setCertificateHolderStore(Store certificateHolderStore) { this.certificateHolderStore = certificateHolderStore; } + + public int getSignBlockVersion() { + return signBlockVersion; + } + + public void setSignBlockVersion(int signBlockVersion) { + this.signBlockVersion = signBlockVersion; + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/SignerFactory.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/SignerFactory.java index 50a404fabbf0c71a1856886df0ef963a5c3e1b3f..d13b03b8d7093d6a4814eed4f4d05acb3511bed3 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/SignerFactory.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/signer/SignerFactory.java @@ -16,8 +16,24 @@ package com.ohos.hapsigntool.signer; import com.ohos.hapsigntool.api.LocalizationAdapter; +import com.ohos.hapsigntool.error.CustomException; +import com.ohos.hapsigntool.error.ERROR; +import com.ohos.hapsigntool.utils.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLDecoder; import java.security.KeyPair; +import java.util.*; /** * Factory pattern to create signer. @@ -25,6 +41,10 @@ import java.security.KeyPair; * @since 2021/12/28 */ public class SignerFactory { + private static final Logger LOGGER = LogManager.getLogger(SignerFactory.class); + + private static final Map SIGNER_LOADERS = new HashMap<>(); + /** * Create a signer. * @@ -33,11 +53,98 @@ public class SignerFactory { */ public ISigner getSigner(LocalizationAdapter adapter) { if (adapter.isRemoteSigner()) { + Optional remoteSigner = loadRemoteSigner(adapter); + if (remoteSigner.isPresent()) { + return remoteSigner.get(); + } + LOGGER.warn("load remote signer failed, use default implementation"); return new RemoteSigner(adapter.getOptions()); - } else { - KeyPair keyPair = adapter.getAliasKey(false); - adapter.releasePwd(); - return new LocalSigner(keyPair.getPrivate(), adapter.getSignCertChain()); } + KeyPair keyPair = adapter.getAliasKey(false); + adapter.releasePwd(); + return new LocalSigner(keyPair.getPrivate(), adapter.getSignCertChain()); + } + + private Optional loadRemoteSigner(LocalizationAdapter adapter) { + String signerPlugin = adapter.getOptions().getString("signerPlugin"); + if (StringUtils.isEmpty(signerPlugin)) { + LOGGER.warn("lost parameter signerPlugin"); + return Optional.empty(); + } + File classLocation = getClassLocation(); + File plugin = new File(classLocation, signerPlugin); + Optional url = fileToUrl(plugin); + if (!url.isPresent()) { + return Optional.empty(); + } + try { + ClassLoader classLoader = generateSignerClassLoader(url.get()); + try (InputStream inputStream = classLoader.getResourceAsStream("signer.properties")) { + if (inputStream == null) { + LOGGER.warn("can not find entry signer.properties in {}", plugin); + return Optional.empty(); + } + Properties properties = new Properties(); + properties.load(inputStream); + String implClassName = properties.getProperty(ISigner.class.getName()); + if (StringUtils.isEmpty(implClassName)) { + LOGGER.warn("can not find {} in signer.properties", ISigner.class.getName()); + return Optional.empty(); + } + Class implClass = classLoader.loadClass(implClassName); + Constructor constructor = implClass.getConstructor(Map.class); + Object signer = constructor.newInstance(adapter.getOptions()); + if (signer instanceof ISigner) { + return Optional.of((ISigner) signer); + } + } + } catch (IOException | ClassNotFoundException | NoSuchMethodException + | InvocationTargetException | InstantiationException | IllegalAccessException e) { + LOGGER.warn("load remote signer from {} failed, msg: {}", signerPlugin, e.getMessage()); + } + return Optional.empty(); + } + + private Optional fileToUrl(File file) { + if (!file.exists()) { + LOGGER.warn("{} is not exists", file); + return Optional.empty(); + } + try { + return Optional.of(file.toURI().toURL()); + } catch (MalformedURLException e) { + LOGGER.warn("{} can not convert to valid url, msg: {}", file, e.getMessage()); + } + return Optional.empty(); + } + + private File getClassLocation() { + String jarPath = SignerFactory.class.getProtectionDomain().getCodeSource().getLocation().getFile(); + if (StringUtils.isEmpty(jarPath)) { + CustomException.throwException(ERROR.COMMAND_ERROR, "class path is empty"); + } + try { + jarPath = URLDecoder.decode(jarPath, "utf-8"); + } catch (UnsupportedEncodingException | IllegalArgumentException e) { + LOGGER.warn("decode class location failed, will ignored. msg :{}", e.getMessage()); + } + File jarFile = new File(jarPath); + if (!jarFile.exists()) { + CustomException.throwException(ERROR.COMMAND_ERROR, "class path" + jarFile + "is not exists"); + } + if (jarFile.isFile()) { + return jarFile.getParentFile(); + } + return jarFile; + } + + private static synchronized ClassLoader generateSignerClassLoader(URL signerClassUrl) { + ClassLoader classLoader = SIGNER_LOADERS.get(signerClassUrl); + if (classLoader == null) { + ClassLoader parent = SignerFactory.class.getClassLoader(); + classLoader = URLClassLoader.newInstance(new URL[]{signerClassUrl}, parent); + SIGNER_LOADERS.put(signerClassUrl, classLoader); + } + return classLoader; } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java index aa5a488a7e4fa54ef7db621f2998f77409a5a028..cc8d917f487594fcac05e88c72c3d915ebec73bd 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/FileUtils.java @@ -34,6 +34,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; +import java.nio.file.Files; /** * Common file operation. @@ -350,4 +351,19 @@ public final class FileUtils { writer.flush(); } } + + /** + * Delete a file quietly + * + * @param file the file to delete + */ + public static void deleteFile(File file) { + if (file != null && file.isFile()) { + try { + Files.delete(file.toPath()); + } catch (IOException e) { + LOGGER.warn("delete file '{}' error, error message: {}", file, e.getMessage()); + } + } + } } diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java index f7b8c93bd369291d8ca0f054b921c1aa0e1fe60a..e51a3f59505ded067c6a171757b0e1966c9843db 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/HapUtils.java @@ -104,6 +104,26 @@ public class HapUtils { */ public static final int BLOCK_NUMBER = 1; + /** + * hap sign schema v2 signature block version + */ + public static final int HAP_SIGN_SCHEME_V2_BLOCK_VERSION = 2; + + /** + * hap sign schema v3 signature block version + */ + public static final int HAP_SIGN_SCHEME_V3_BLOCK_VERSION = 3; + + /** + * The value of lower 8-bytes of old magic word + */ + public static final long HAP_SIG_BLOCK_MAGIC_LO_V2 = 0x2067695320504148L; + + /** + * The value of higher 8-bytes of old magic word + */ + public static final long HAP_SIG_BLOCK_MAGIC_HI_V2 = 0x3234206b636f6c42L; + private HapUtils() { } @@ -113,20 +133,31 @@ public class HapUtils { private static final Set HAP_SIGNATURE_OPTIONAL_BLOCK_IDS ; /** - * Magic word of hap signature block/ + * Minimum api version for hap sign schema v3. + */ + private static final int MIN_COMPATIBLE_VERSION_FOR_SCHEMA_V3 = 8; + + /** + * Magic word of hap signature block v2 */ - private static final byte[] HAP_SIGNING_BLOCK_MAGIC = + private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V2 = + new byte[] {0x48, 0x41, 0x50, 0x20, 0x53, 0x69, 0x67, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32}; + + /** + * Magic word of hap signature block + */ + private static final byte[] HAP_SIGNING_BLOCK_MAGIC_V3 = new byte[] {0x3c, 0x68, 0x61, 0x70, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x3e}; /** * The value of lower 8 bytes of magic word */ - public static final long HAP_SIG_BLOCK_MAGIC_LO = 0x676973207061683cL; + public static final long HAP_SIG_BLOCK_MAGIC_LO_V3 = 0x676973207061683cL; /** * The value of higher 8 bytes of magic word */ - public static final long HAP_SIG_BLOCK_MAGIC_HI = 0x3e6b636f6c62206eL; + public static final long HAP_SIG_BLOCK_MAGIC_HI_V3 = 0x3e6b636f6c62206eL; /** * Size of hap signature block header @@ -155,10 +186,27 @@ public class HapUtils { /** * Get HAP_SIGNING_BLOCK_MAGIC * + * @param compatibleVersion compatible api version * @return HAP_SIGNING_BLOCK_MAGIC */ - public static byte[] getHapSigningBlockMagic() { - return HAP_SIGNING_BLOCK_MAGIC; + public static byte[] getHapSigningBlockMagic(int compatibleVersion) { + if (compatibleVersion >= MIN_COMPATIBLE_VERSION_FOR_SCHEMA_V3) { + return HAP_SIGNING_BLOCK_MAGIC_V3.clone(); + } + return HAP_SIGNING_BLOCK_MAGIC_V2.clone(); + } + + /** + * Get version number of hap signature block + * + * @param compatibleVersion compatible api version + * @return magic to number + */ + public static int getHapSigningBlockVersion(int compatibleVersion) { + if (compatibleVersion >= MIN_COMPATIBLE_VERSION_FOR_SCHEMA_V3) { + return HAP_SIGN_SCHEME_V3_BLOCK_VERSION; + } + return HAP_SIGN_SCHEME_V2_BLOCK_VERSION; } /** @@ -437,7 +485,7 @@ public class HapUtils { * @throws SignatureNotFoundException No signing block is found * @throws IOException file operation error */ - public static Pair findHapSigningBlock(ZipDataInput hap, ZipFileInfo zipInfo) + public static HapSignBlockInfo findHapSigningBlock(ZipDataInput hap, ZipFileInfo zipInfo) throws SignatureNotFoundException, IOException { long centralDirectoryStartOffset = zipInfo.getCentralDirectoryOffset(); long centralDirectorySize = zipInfo.getCentralDirectorySize(); @@ -459,8 +507,7 @@ public class HapUtils { long hapSignBlockMagicLo = hapSigningBlockHeader.getLong(); long hapSignBlockMagicHi = hapSigningBlockHeader.getLong(); int version = hapSigningBlockHeader.getInt(); - if ((hapSignBlockMagicLo != HAP_SIG_BLOCK_MAGIC_LO) - || (hapSignBlockMagicHi != HAP_SIG_BLOCK_MAGIC_HI)) { + if (!isVersionAndMagicNumValid(version, hapSignBlockMagicLo, hapSignBlockMagicHi)) { throw new SignatureNotFoundException("No Hap Signing Block before ZIP Central Directory"); } if ((hapSigBlockSize < HAP_SIG_BLOCK_HEADER_SIZE) || @@ -475,6 +522,40 @@ public class HapUtils { ByteBuffer hapSigningBlockByteBuffer = hap.createByteBuffer(hapSigningBlockOffset, totalSize) .order(ByteOrder.LITTLE_ENDIAN); LOGGER.info("Find Hap Signing Block success, version: {}, block count: {}", version, blockCount); - return Pair.create(hapSigningBlockOffset, hapSigningBlockByteBuffer); + return new HapSignBlockInfo(hapSigningBlockOffset, version, hapSigningBlockByteBuffer); + } + + private static boolean isVersionAndMagicNumValid(int version, long hapSignBlockMagicLo, long hapSignBlockMagicHi) { + if (version < HAP_SIGN_SCHEME_V3_BLOCK_VERSION) { + return hapSignBlockMagicLo == HAP_SIG_BLOCK_MAGIC_LO_V2 && hapSignBlockMagicHi == HAP_SIG_BLOCK_MAGIC_HI_V2; + } + return hapSignBlockMagicLo == HAP_SIG_BLOCK_MAGIC_LO_V3 && hapSignBlockMagicHi == HAP_SIG_BLOCK_MAGIC_HI_V3; + } + + /** + * Hap sign block info + */ + public static class HapSignBlockInfo { + private final long offset; + private final int version; + private final ByteBuffer content; + + public HapSignBlockInfo(long offset, int version, ByteBuffer content) { + this.offset = offset; + this.version = version; + this.content = content; + } + + public int getVersion() { + return version; + } + + public ByteBuffer getContent() { + return content; + } + + public long getOffset() { + return offset; + } } } \ No newline at end of file diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java index 3f4bf319946cd19937d454556d86abe320a9ca42..3b1dbde6f783da007716b57a82f6bc557d008fa6 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/utils/ParamConstants.java @@ -171,6 +171,11 @@ public class ParamConstants { */ public static final String PARAM_BASIC_PROFILE_SIGNED = "profileSigned"; + /** + * The minimum SDK version required for running the application + */ + public static final String PARAM_BASIC_COMPATIBLE_VERSION = "compatibleVersion"; + /** * Url of signature server */ diff --git a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataInput.java b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataInput.java index 38b6e213a2a6008e1d60b862247ba221ba01ce81..f31e89188e1de24700f6abb3eb8579187b131fd3 100644 --- a/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataInput.java +++ b/hapsigntool/hap_sign_tool_lib/src/main/java/com/ohos/hapsigntool/zip/RandomAccessFileZipDataInput.java @@ -28,7 +28,6 @@ import java.nio.channels.FileChannel; */ public class RandomAccessFileZipDataInput implements ZipDataInput { private static final int MAX_READ_BLOCK_SIZE = 1024 * 1024; - private static final byte[] READ_BUFFER = new byte[MAX_READ_BLOCK_SIZE]; private final RandomAccessFile file; private final FileChannel fileChannel; private final long startIndex; @@ -76,12 +75,13 @@ public class RandomAccessFileZipDataInput implements ZipDataInput { long offsetInFile = startIndex + offset; long remaining = size; int blockSize; + byte[] buf = new byte[MAX_READ_BLOCK_SIZE]; while (remaining > 0) { - blockSize = (int) Math.min(remaining, READ_BUFFER.length); + blockSize = (int) Math.min(remaining, buf.length); synchronized (file) { file.seek(offsetInFile); - file.readFully(READ_BUFFER, 0, blockSize); - output.write(READ_BUFFER, 0, blockSize); + file.readFully(buf, 0, blockSize); + output.write(buf, 0, blockSize); } offsetInFile += blockSize; remaining -= blockSize;