• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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