• 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 HAP_SIGN_SCHEME_VERSION = 3;
50     private static final int STORED_ENTRY_SO_ALIGNMENT = 4096;
51     private static final int BUFFER_LENGTH = 4096;
52     private static final int BLOCK_COUNT = 4;
53     private static final int BLOCK_SIZE = 8;
54     private static final int BLOCK_MAGIC = 16;
55     private static final int BLOCK_VERSION = 4;
56     private static final long INIT_OFFSET_LEN = 4L;
57     private static final int OPTIONAL_TYPE_SIZE = 4;
58     private static final int OPTIONAL_LENGTH_SIZE = 4;
59     private static final int OPTIONAL_OFFSET_SIZE = 4;
60 
SignHap()61     private SignHap() {}
62 
getBlockSize()63     public static int getBlockSize() {
64         return BLOCK_SIZE;
65     }
66 
67     /**
68      * Get all entries' name from hap which is opened as a jar-file.
69      *
70      * @param hap input hap-file which is opened as a jar-file.
71      * @return list of entries' names.
72      */
getEntryNamesFromHap(JarFile hap)73     public static List<String> getEntryNamesFromHap(JarFile hap) {
74         List<String> result = new ArrayList<String>();
75         for (Enumeration<JarEntry> e = hap.entries(); e.hasMoreElements();) {
76             JarEntry entry = e.nextElement();
77             if (!entry.isDirectory()) {
78                 result.add(entry.getName());
79             }
80         }
81         return result;
82     }
83 
84     /**
85      * Copy the jar file and align the storage entries.
86      *
87      * @param entryNames list of entries' name
88      * @param in input hap-file which is opened as a jar-file.
89      * @param out output stream of jar.
90      * @param timestamp ZIP file timestamps
91      * @param defaultAlignment default value of alignment.
92      * @throws IOException io error.
93      */
copyFiles(List<String> entryNames, JarFile in, JarOutputStream out, long timestamp, int defaultAlignment)94     public static void copyFiles(List<String> entryNames, JarFile in,
95         JarOutputStream out, long timestamp, int defaultAlignment) throws IOException {
96         Collections.sort(entryNames);
97         long offset = INIT_OFFSET_LEN;
98         for (String name : entryNames) {
99             JarEntry inEntry = in.getJarEntry(name);
100             if (inEntry.getMethod() != JarEntry.STORED) {
101                 continue;
102             }
103 
104             offset += JarFile.LOCHDR;
105 
106             JarEntry outEntry = new JarEntry(inEntry);
107             outEntry.setTime(timestamp);
108 
109             outEntry.setComment(null);
110             outEntry.setExtra(null);
111 
112             offset += outEntry.getName().length();
113 
114             int alignment = getStoredEntryDataAlignment(name, defaultAlignment);
115             if (alignment > 0 && (offset % alignment != 0)) {
116                 int needed = alignment - (int) (offset % alignment);
117                 outEntry.setExtra(new byte[needed]);
118                 offset += needed;
119             }
120 
121             out.putNextEntry(outEntry);
122             byte[] buffer = new byte[BUFFER_LENGTH];
123             try (InputStream data = in.getInputStream(inEntry)) {
124                 int num;
125                 while ((num = data.read(buffer)) > 0) {
126                     out.write(buffer, 0, num);
127                     offset += num;
128                 }
129                 out.flush();
130             }
131         }
132 
133         copyFilesExceptStoredFile(entryNames, in, out, timestamp);
134     }
135 
copyFilesExceptStoredFile(List<String> entryNames, JarFile in, JarOutputStream out, long timestamp)136     private static void copyFilesExceptStoredFile(List<String> entryNames, JarFile in,
137         JarOutputStream out, long timestamp) throws IOException {
138         byte[] buffer = new byte[BUFFER_LENGTH];
139 
140         for (String name : entryNames) {
141             JarEntry inEntry = in.getJarEntry(name);
142             if (inEntry.getMethod() == JarEntry.STORED) {
143                 continue;
144             }
145 
146             JarEntry outEntry = new JarEntry(name);
147             outEntry.setTime(timestamp);
148             out.putNextEntry(outEntry);
149 
150             try (InputStream data = in.getInputStream(inEntry);) {
151                 int num;
152                 while ((num = data.read(buffer)) > 0) {
153                     out.write(buffer, 0, num);
154                 }
155                 out.flush();
156             }
157         }
158     }
159 
160     /**
161      * If store entry is end with '.so', use 4096-alignment, otherwise, use default-alignment.
162      *
163      * @param entryName name of entry
164      * @param defaultAlignment default value of alignment.
165      * @return value of alignment.
166      */
getStoredEntryDataAlignment(String entryName, int defaultAlignment)167     private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) {
168         if (defaultAlignment <= 0) {
169             return 0;
170         }
171         if (entryName.endsWith(".so")) {
172             return STORED_ENTRY_SO_ALIGNMENT;
173         }
174         return defaultAlignment;
175     }
176 
getHapSigningBlock( Set<ContentDigestAlgorithm> contentDigestAlgorithms, List<SigningBlock> optionalBlocks, SignerConfig signerConfig, ZipDataInput[] hapData)177     private static byte[] getHapSigningBlock(
178             Set<ContentDigestAlgorithm> contentDigestAlgorithms,
179             List<SigningBlock> optionalBlocks,
180             SignerConfig signerConfig,
181             ZipDataInput[] hapData)
182         throws SignatureException {
183         /**
184          * Compute digests of Hap contents
185          * Sign the digests and wrap the signature and signer info into the Hap Signing Block
186          */
187         byte[] hapSignatureBytes = null;
188         try {
189             Map<ContentDigestAlgorithm, byte[]> contentDigests =
190                 HapUtils.computeDigests(contentDigestAlgorithms, hapData, optionalBlocks);
191             hapSignatureBytes = generateHapSigningBlock(signerConfig, contentDigests, optionalBlocks);
192         } catch (DigestException | IOException e) {
193             throw new SignatureException("Failed to compute digests of HAP", e);
194         }
195         return hapSignatureBytes;
196     }
197 
generateHapSigningBlock( SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests, List<SigningBlock> optionalBlocks)198     private static byte[] generateHapSigningBlock(
199             SignerConfig signerConfig,
200             Map<ContentDigestAlgorithm, byte[]> contentDigests,
201             List<SigningBlock> optionalBlocks)
202             throws SignatureException {
203         byte[] hapSignatureSchemeBlock = generateHapSignatureSchemeBlock(signerConfig, contentDigests);
204         return generateHapSigningBlock(hapSignatureSchemeBlock, optionalBlocks);
205     }
206 
generateHapSigningBlock(byte[] hapSignatureSchemeBlock, List<SigningBlock> optionalBlocks)207     private static byte[] generateHapSigningBlock(byte[] hapSignatureSchemeBlock, List<SigningBlock> optionalBlocks) {
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()); // magic
294         result.putInt(HAP_SIGN_SCHEME_VERSION); // 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