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.hap.sign; 17 18 import com.ohos.hapsigntool.api.model.Options; 19 import com.ohos.hapsigntool.hap.config.SignerConfig; 20 import com.ohos.hapsigntool.hap.entity.Pair; 21 import com.ohos.hapsigntool.hap.entity.SigningBlock; 22 import com.ohos.hapsigntool.hap.exception.SignatureException; 23 import com.ohos.hapsigntool.utils.HapUtils; 24 import com.ohos.hapsigntool.zip.ZipDataInput; 25 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.nio.ByteBuffer; 29 import java.nio.ByteOrder; 30 import java.security.DigestException; 31 import java.util.ArrayList; 32 import java.util.Collections; 33 import java.util.Enumeration; 34 import java.util.HashMap; 35 import java.util.HashSet; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.jar.JarEntry; 40 import java.util.jar.JarFile; 41 import java.util.jar.JarOutputStream; 42 43 /** 44 * Hap Signature Scheme signer 45 * 46 * @since 2021/12/21 47 */ 48 public abstract class SignHap { 49 private static final int STORED_ENTRY_SO_ALIGNMENT = 4096; 50 private static final int BUFFER_LENGTH = 4096; 51 private static final int BLOCK_COUNT = 4; 52 private static final int BLOCK_SIZE = 8; 53 private static final int BLOCK_MAGIC = 16; 54 private static final int BLOCK_VERSION = 4; 55 private static final long INIT_OFFSET_LEN = 4L; 56 private static final int OPTIONAL_TYPE_SIZE = 4; 57 private static final int OPTIONAL_LENGTH_SIZE = 4; 58 private static final int OPTIONAL_OFFSET_SIZE = 4; 59 SignHap()60 private SignHap() {} 61 getBlockSize()62 public static int getBlockSize() { 63 return BLOCK_SIZE; 64 } 65 66 /** 67 * Get all entries' name from hap which is opened as a jar-file. 68 * 69 * @param hap input hap-file which is opened as a jar-file. 70 * @return list of entries' names. 71 */ getEntryNamesFromHap(JarFile hap)72 public static List<String> getEntryNamesFromHap(JarFile hap) { 73 List<String> result = new ArrayList<String>(); 74 for (Enumeration<JarEntry> e = hap.entries(); e.hasMoreElements();) { 75 JarEntry entry = e.nextElement(); 76 if (!entry.isDirectory()) { 77 result.add(entry.getName()); 78 } 79 } 80 return result; 81 } 82 83 /** 84 * Copy the jar file and align the storage entries. 85 * 86 * @param entryNames list of entries' name 87 * @param in input hap-file which is opened as a jar-file. 88 * @param out output stream of jar. 89 * @param timestamp ZIP file timestamps 90 * @param defaultAlignment default value of alignment. 91 * @throws IOException io error. 92 */ copyFiles(List<String> entryNames, JarFile in, JarOutputStream out, long timestamp, int defaultAlignment)93 public static void copyFiles(List<String> entryNames, JarFile in, 94 JarOutputStream out, long timestamp, int defaultAlignment) throws IOException { 95 Collections.sort(entryNames); 96 long offset = INIT_OFFSET_LEN; 97 for (String name : entryNames) { 98 JarEntry inEntry = in.getJarEntry(name); 99 if (inEntry.getMethod() != JarEntry.STORED) { 100 continue; 101 } 102 103 offset += JarFile.LOCHDR; 104 105 JarEntry outEntry = new JarEntry(inEntry); 106 outEntry.setTime(timestamp); 107 108 outEntry.setComment(null); 109 outEntry.setExtra(null); 110 111 offset += outEntry.getName().length(); 112 113 int alignment = getStoredEntryDataAlignment(name, defaultAlignment); 114 if (alignment > 0 && (offset % alignment != 0)) { 115 int needed = alignment - (int) (offset % alignment); 116 outEntry.setExtra(new byte[needed]); 117 offset += needed; 118 } 119 120 out.putNextEntry(outEntry); 121 byte[] buffer = new byte[BUFFER_LENGTH]; 122 try (InputStream data = in.getInputStream(inEntry)) { 123 int num; 124 while ((num = data.read(buffer)) > 0) { 125 out.write(buffer, 0, num); 126 offset += num; 127 } 128 out.flush(); 129 } 130 } 131 132 copyFilesExceptStoredFile(entryNames, in, out, timestamp); 133 } 134 copyFilesExceptStoredFile(List<String> entryNames, JarFile in, JarOutputStream out, long timestamp)135 private static void copyFilesExceptStoredFile(List<String> entryNames, JarFile in, 136 JarOutputStream out, long timestamp) throws IOException { 137 byte[] buffer = new byte[BUFFER_LENGTH]; 138 139 for (String name : entryNames) { 140 JarEntry inEntry = in.getJarEntry(name); 141 if (inEntry.getMethod() == JarEntry.STORED) { 142 continue; 143 } 144 145 JarEntry outEntry = new JarEntry(name); 146 outEntry.setTime(timestamp); 147 out.putNextEntry(outEntry); 148 149 try (InputStream data = in.getInputStream(inEntry);) { 150 int num; 151 while ((num = data.read(buffer)) > 0) { 152 out.write(buffer, 0, num); 153 } 154 out.flush(); 155 } 156 } 157 } 158 159 /** 160 * If store entry is end with '.so', use 4096-alignment, otherwise, use default-alignment. 161 * 162 * @param entryName name of entry 163 * @param defaultAlignment default value of alignment. 164 * @return value of alignment. 165 */ getStoredEntryDataAlignment(String entryName, int defaultAlignment)166 private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) { 167 if (defaultAlignment <= 0) { 168 return 0; 169 } 170 if (entryName.endsWith(".so")) { 171 return STORED_ENTRY_SO_ALIGNMENT; 172 } 173 return defaultAlignment; 174 } 175 getHapSigningBlock( Set<ContentDigestAlgorithm> contentDigestAlgorithms, List<SigningBlock> optionalBlocks, SignerConfig signerConfig, ZipDataInput[] hapData)176 private static byte[] getHapSigningBlock( 177 Set<ContentDigestAlgorithm> contentDigestAlgorithms, 178 List<SigningBlock> optionalBlocks, 179 SignerConfig signerConfig, 180 ZipDataInput[] hapData) 181 throws SignatureException { 182 /** 183 * Compute digests of Hap contents 184 * Sign the digests and wrap the signature and signer info into the Hap Signing Block 185 */ 186 byte[] hapSignatureBytes = null; 187 try { 188 Map<ContentDigestAlgorithm, byte[]> contentDigests = 189 HapUtils.computeDigests(contentDigestAlgorithms, hapData, optionalBlocks); 190 hapSignatureBytes = generateHapSigningBlock(signerConfig, contentDigests, optionalBlocks); 191 } catch (DigestException | IOException e) { 192 throw new SignatureException("Failed to compute digests of HAP", e); 193 } 194 return hapSignatureBytes; 195 } 196 generateHapSigningBlock( SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests, List<SigningBlock> optionalBlocks)197 private static byte[] generateHapSigningBlock( 198 SignerConfig signerConfig, 199 Map<ContentDigestAlgorithm, byte[]> contentDigests, 200 List<SigningBlock> optionalBlocks) 201 throws SignatureException { 202 byte[] hapSignatureSchemeBlock = generateHapSignatureSchemeBlock(signerConfig, contentDigests); 203 return generateHapSigningBlock(hapSignatureSchemeBlock, optionalBlocks, signerConfig.getCompatibleVersion()); 204 } 205 generateHapSigningBlock(byte[] hapSignatureSchemeBlock, List<SigningBlock> optionalBlocks, int compatibleVersion)206 private static byte[] generateHapSigningBlock(byte[] hapSignatureSchemeBlock, 207 List<SigningBlock> optionalBlocks, int compatibleVersion) { 208 // FORMAT: 209 // Proof-of-Rotation pairs(optional): 210 // uint32:type 211 // uint32:length 212 // uint32:offset 213 214 // Property pairs(optional): 215 // uint32:type 216 // uint32:length 217 // uint32:offset 218 219 // Profile capability pairs(optional): 220 // uint32:type 221 // uint32:length 222 // uint32:offset 223 224 // length bytes : app signing pairs 225 // uint32:type 226 // uint32:length 227 // uint32:offset 228 229 // repeated ID-value pairs(reserved extensions): 230 // length bytes : Proof-of-Rotation values 231 // length bytes : property values 232 // length bytes : profile capability values 233 // length bytes : signature schema values 234 235 // uint32: block count 236 // uint64: size 237 // uint128: magic 238 // uint32: version 239 long optionalBlockSize = 0L; 240 for (SigningBlock optionalBlock : optionalBlocks) { 241 optionalBlockSize += optionalBlock.getLength(); 242 } 243 244 long resultSize = 245 ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE + OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1)) 246 + optionalBlockSize // optional pair 247 + hapSignatureSchemeBlock.length // App signing pairs 248 + BLOCK_COUNT // block count 249 + BLOCK_SIZE // size 250 + BLOCK_MAGIC // magic 251 + BLOCK_VERSION; // version 252 if (resultSize > Integer.MAX_VALUE) { 253 throw new IllegalArgumentException("HapSigningBlock out of range : " + resultSize); 254 } 255 ByteBuffer result = ByteBuffer.allocate((int) resultSize); 256 result.order(ByteOrder.LITTLE_ENDIAN); 257 258 Map<Integer, Integer> typeAndOffsetMap = new HashMap<Integer, Integer>(); 259 int currentOffset = ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE + 260 OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1)); 261 int currentOffsetInBlockValue = 0; 262 int blockValueSizes = (int) (optionalBlockSize + hapSignatureSchemeBlock.length); 263 byte[] blockValues = new byte[blockValueSizes]; 264 265 for (SigningBlock optionalBlock : optionalBlocks) { 266 System.arraycopy( 267 optionalBlock.getValue(), 0, blockValues, currentOffsetInBlockValue, optionalBlock.getLength()); 268 typeAndOffsetMap.put(optionalBlock.getType(), currentOffset); 269 currentOffset += optionalBlock.getLength(); 270 currentOffsetInBlockValue += optionalBlock.getLength(); 271 } 272 273 System.arraycopy( 274 hapSignatureSchemeBlock, 0, blockValues, currentOffsetInBlockValue, hapSignatureSchemeBlock.length); 275 typeAndOffsetMap.put(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID, currentOffset); 276 277 int offset = 0; 278 for (SigningBlock optionalBlock : optionalBlocks) { 279 result.putInt(optionalBlock.getType()); // type 280 result.putInt(optionalBlock.getLength()); // length 281 offset = typeAndOffsetMap.get(optionalBlock.getType()); 282 result.putInt(offset); // offset 283 } 284 result.putInt(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID); // type 285 result.putInt(hapSignatureSchemeBlock.length); // length 286 offset = typeAndOffsetMap.get(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID); 287 result.putInt(offset); // offset 288 289 result.put(blockValues); 290 291 result.putInt(optionalBlocks.size() + 1); // Signing block count 292 result.putLong(resultSize); // length of hap signing block 293 result.put(HapUtils.getHapSigningBlockMagic(compatibleVersion)); // magic 294 result.putInt(HapUtils.getHapSigningBlockVersion(compatibleVersion)); // version 295 return result.array(); 296 } 297 generateHapSignatureSchemeBlock( SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests)298 private static byte[] generateHapSignatureSchemeBlock( 299 SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests) throws SignatureException { 300 byte[] signerBlock = null; 301 try { 302 signerBlock = generateSignerBlock(signerConfig, contentDigests); 303 } catch (SignatureException e) { 304 throw new SignatureException("generate SignerBlock failed", e); 305 } 306 return signerBlock; 307 } 308 generateSignerBlock( SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests)309 private static byte[] generateSignerBlock( 310 SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests) throws SignatureException { 311 String mode = signerConfig.getOptions().getString(Options.MODE); 312 if (!("remoteSign".equalsIgnoreCase(mode)) && signerConfig.getCertificates().isEmpty()) { 313 throw new SignatureException("No certificates configured for signer"); 314 } 315 316 List<Pair<Integer, byte[]>> digests = 317 new ArrayList<Pair<Integer, byte[]>>(signerConfig.getSignatureAlgorithms().size()); 318 for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) { 319 ContentDigestAlgorithm contentDigestAlgorithm = signatureAlgorithm.getContentDigestAlgorithm(); 320 byte[] contentDigest = contentDigests.get(contentDigestAlgorithm); 321 if (contentDigest == null) { 322 throw new SignatureException( 323 contentDigestAlgorithm.getDigestAlgorithm() 324 + " content digest for " 325 + signatureAlgorithm.getSignatureAlgAndParams().getFirst() 326 + " not computed"); 327 } 328 digests.add(Pair.create(signatureAlgorithm.getId(), contentDigest)); 329 } 330 byte[] unsignedHapDigest = HapUtils.encodeListOfPairsToByteArray(digests); 331 return Pkcs7Generator.BC.generateSignedData(unsignedHapDigest, signerConfig); 332 } 333 334 /** 335 * Signs the provided Hap using Hap Signature Scheme and returns the 336 * signed block as an array of ByteBuffer 337 * 338 * @param contents Hap content before ZIP CD 339 * @param signerConfig signer config 340 * @param optionalBlocks optional blocks 341 * @return signed block 342 * @throws SignatureException if an error occurs when sign hap file. 343 */ sign(ZipDataInput[] contents, SignerConfig signerConfig, List<SigningBlock> optionalBlocks)344 public static byte[] sign(ZipDataInput[] contents, SignerConfig signerConfig, List<SigningBlock> optionalBlocks) 345 throws SignatureException { 346 Set<ContentDigestAlgorithm> contentDigestAlgorithms = new HashSet<ContentDigestAlgorithm>(); 347 for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) { 348 contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm()); 349 } 350 return getHapSigningBlock(contentDigestAlgorithms, optionalBlocks, signerConfig, contents); 351 } 352 } 353