• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.hap.sign;
17 
18 import com.ohos.hapsigntool.entity.ContentDigestAlgorithm;
19 import com.ohos.hapsigntool.entity.Options;
20 import com.ohos.hapsigntool.entity.SignatureAlgorithm;
21 import com.ohos.hapsigntool.hap.config.SignerConfig;
22 import com.ohos.hapsigntool.entity.Pair;
23 import com.ohos.hapsigntool.hap.entity.SigningBlock;
24 import com.ohos.hapsigntool.error.HapFormatException;
25 import com.ohos.hapsigntool.error.SignatureException;
26 import com.ohos.hapsigntool.utils.FileUtils;
27 import com.ohos.hapsigntool.hap.utils.HapUtils;
28 import com.ohos.hapsigntool.utils.StringUtils;
29 import com.ohos.hapsigntool.zip.ZipDataInput;
30 
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.nio.ByteBuffer;
34 import java.nio.ByteOrder;
35 import java.security.DigestException;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.jar.JarEntry;
43 import java.util.jar.JarFile;
44 import java.util.jar.JarOutputStream;
45 import java.util.stream.Collectors;
46 
47 /**
48  *
49  * Hap Signature Scheme signer
50  *
51  * @since 2021/12/21
52  */
53 public abstract class SignHap {
54     private static final int STORED_ENTRY_SO_ALIGNMENT = 4096;
55     private static final int BUFFER_LENGTH = 4096;
56     private static final int BLOCK_COUNT = 4;
57     private static final int BLOCK_MAGIC = 16;
58     private static final int BLOCK_VERSION = 4;
59     private static final long INIT_OFFSET_LEN = 4L;
60     private static final int OPTIONAL_TYPE_SIZE = 4;
61     private static final int OPTIONAL_LENGTH_SIZE = 4;
62     private static final int OPTIONAL_OFFSET_SIZE = 4;
63 
SignHap()64     private SignHap() {}
65 
66     /**
67      * Copy the jar file and align the storage entries.
68      *
69      * @param in input hap-file which is opened as a jar-file.
70      * @param out output stream of jar.
71      * @param timestamp ZIP file timestamps
72      * @param defaultAlignment default value of alignment.
73      * @throws IOException io error.
74      * @throws HapFormatException hap format error.
75      */
copyFiles(JarFile in, JarOutputStream out, long timestamp, int defaultAlignment)76     public static void copyFiles(JarFile in,
77         JarOutputStream out, long timestamp, int defaultAlignment) throws IOException, HapFormatException {
78         // split compressed and uncompressed
79         List<JarEntry> entryListStored = in.stream()
80                 .filter(jarFile -> jarFile.getMethod() == JarEntry.STORED).collect(Collectors.toList());
81 
82         // uncompressed special files and place in front
83         entryListStored = storedEntryListOfSort(entryListStored);
84         long offset = INIT_OFFSET_LEN;
85         String lastAlignmentEntryName = "";
86         for (JarEntry inEntry : entryListStored) {
87             String entryName = inEntry.getName();
88             if (!FileUtils.isRunnableFile(entryName)) {
89                 lastAlignmentEntryName = entryName;
90                 break;
91             }
92         }
93         for (JarEntry inEntry : entryListStored) {
94             if (inEntry == null) {
95                 continue;
96             }
97 
98             offset += JarFile.LOCHDR;
99 
100             JarEntry outEntry = getJarEntry(timestamp, inEntry);
101             offset += outEntry.getName().length();
102 
103             int alignment = getStoredEntryDataAlignment(inEntry.getName(), defaultAlignment, lastAlignmentEntryName);
104             if (alignment > 0 && (offset % alignment != 0)) {
105                 int needed = alignment - (int) (offset % alignment);
106                 outEntry.setExtra(new byte[needed]);
107                 offset += needed;
108             }
109 
110             out.putNextEntry(outEntry);
111             offset = writeOutputStreamAndGetOffset(in, out, inEntry, offset);
112         }
113         List<JarEntry> entryListNotStored = in.stream()
114                 .filter(jarFile -> jarFile.getMethod() != JarEntry.STORED).collect(Collectors.toList());
115         // process byte alignment of the first compressed file
116         boolean isAlignmentFlag = StringUtils.isEmpty(lastAlignmentEntryName);
117         if (isAlignmentFlag) {
118             if (entryListNotStored.isEmpty()) {
119                 throw new HapFormatException("Hap format is error, file missing");
120             }
121             JarEntry firstEntry = entryListNotStored.get(0);
122             offset += JarFile.LOCHDR;
123             JarEntry outEntry = getFirstJarEntry(firstEntry, offset, timestamp);
124             out.putNextEntry(outEntry);
125             byte[] buffer = new byte[BUFFER_LENGTH];
126             writeOutputStream(in, out, firstEntry, buffer);
127         }
128 
129         copyFilesExceptStoredFile(entryListNotStored, in, out, timestamp, isAlignmentFlag);
130     }
131 
132     /**
133      * uncompressed special files are placed in front
134      *
135      * @param entryListStored stored file entry list
136      * @return List<JarEntry> jarEntryList
137      */
storedEntryListOfSort(List<JarEntry> entryListStored)138     private static List<JarEntry> storedEntryListOfSort(List<JarEntry> entryListStored) {
139         return entryListStored.stream().sorted((entry1, entry2) -> {
140             String name1 = entry1.getName();
141             String name2 = entry2.getName();
142             // files ending with .abc or .so are placed before other files
143             boolean isSpecial1 = FileUtils.isRunnableFile(name1);
144             boolean isSpecial2 = FileUtils.isRunnableFile(name2);
145             if (isSpecial1 && !isSpecial2) {
146                 return -1;
147             } else if (!isSpecial1 && isSpecial2) {
148                 return 1;
149             } else {
150                 // if all files are special files or none of them are special files,the files are sorted lexically
151                 return name1.compareTo(name2);
152             }
153         }).collect(Collectors.toList());
154     }
155 
getFirstJarEntry(JarEntry firstEntry, long offset, long timestamp)156     private static JarEntry getFirstJarEntry(JarEntry firstEntry, long offset, long timestamp) {
157         long currentOffset = offset;
158         JarEntry outEntry = getJarEntry(timestamp, firstEntry);
159         currentOffset += outEntry.getName().length();
160         if (currentOffset % STORED_ENTRY_SO_ALIGNMENT != 0) {
161             int needed = STORED_ENTRY_SO_ALIGNMENT - (int) (currentOffset % STORED_ENTRY_SO_ALIGNMENT);
162             outEntry.setExtra(new byte[needed]);
163         }
164         return outEntry;
165     }
166 
167     /**
168      * write first not stored entry to outputStream
169      *
170      * @param in jar file
171      * @param out jarOutputStream
172      * @param firstEntry jarEntry
173      * @param buffer byte[]
174      * @throws IOException IOExpcetion
175      */
writeOutputStream(JarFile in, JarOutputStream out, JarEntry firstEntry, byte[] buffer)176     private static void writeOutputStream(JarFile in, JarOutputStream out, JarEntry firstEntry, byte[] buffer)
177             throws IOException {
178         try (InputStream data = in.getInputStream(firstEntry)) {
179             int num;
180             while ((num = data.read(buffer)) > 0) {
181                 out.write(buffer, 0, num);
182             }
183             out.flush();
184         }
185     }
186 
writeOutputStreamAndGetOffset(JarFile in, JarOutputStream out, JarEntry inEntry, long offset)187     private static long writeOutputStreamAndGetOffset(JarFile in, JarOutputStream out, JarEntry inEntry, long offset)
188             throws IOException {
189         byte[] buffer = new byte[BUFFER_LENGTH];
190         long currentOffset = offset;
191         try (InputStream data = in.getInputStream(inEntry)) {
192             int num;
193             while ((num = data.read(buffer)) > 0) {
194                 out.write(buffer, 0, num);
195                 currentOffset += num;
196             }
197             out.flush();
198         }
199         return currentOffset;
200     }
201 
getJarEntry(long timestamp, JarEntry inEntry)202     private static JarEntry getJarEntry(long timestamp, JarEntry inEntry) {
203         JarEntry outEntry = new JarEntry(inEntry);
204         outEntry.setTime(timestamp);
205 
206         outEntry.setComment(null);
207         outEntry.setExtra(null);
208         return outEntry;
209     }
210 
copyFilesExceptStoredFile(List<JarEntry> entryListNotStored, JarFile in, JarOutputStream out, long timestamp, boolean isAlignmentFlag)211     private static void copyFilesExceptStoredFile(List<JarEntry> entryListNotStored, JarFile in,
212         JarOutputStream out, long timestamp, boolean isAlignmentFlag) throws IOException {
213         byte[] buffer = new byte[BUFFER_LENGTH];
214         int index = 0;
215         if (isAlignmentFlag) {
216             index = 1;
217         }
218         for (; index < entryListNotStored.size(); index++) {
219             JarEntry inEntry = entryListNotStored.get(index);
220             if (inEntry == null || inEntry.getMethod() == JarEntry.STORED) {
221                 continue;
222             }
223 
224             JarEntry outEntry = new JarEntry(inEntry.getName());
225             outEntry.setTime(timestamp);
226             out.putNextEntry(outEntry);
227             writeOutputStream(in, out, inEntry, buffer);
228         }
229     }
230 
231     /**
232      * If store entry is end with '.so', use 4096-alignment, otherwise, use default-alignment.
233      *
234      * @param entryName name of entry
235      * @param defaultAlignment default value of alignment.
236      * @param lastAlignmentEntryName lastAlignmentEntryName
237      * @return value of alignment.
238      */
getStoredEntryDataAlignment(String entryName, int defaultAlignment, String lastAlignmentEntryName)239     private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment,
240                                                    String lastAlignmentEntryName) {
241         if (defaultAlignment <= 0) {
242             return 0;
243         }
244         if (!StringUtils.isEmpty(lastAlignmentEntryName) && entryName.equals(lastAlignmentEntryName)) {
245             return STORED_ENTRY_SO_ALIGNMENT;
246         }
247         if (FileUtils.isRunnableFile(entryName)) {
248             return STORED_ENTRY_SO_ALIGNMENT;
249         }
250         return defaultAlignment;
251     }
252 
getHapSigningBlock( Set<ContentDigestAlgorithm> contentDigestAlgorithms, List<SigningBlock> optionalBlocks, SignerConfig signerConfig, ZipDataInput[] hapData)253     private static byte[] getHapSigningBlock(
254             Set<ContentDigestAlgorithm> contentDigestAlgorithms,
255             List<SigningBlock> optionalBlocks,
256             SignerConfig signerConfig,
257             ZipDataInput[] hapData)
258             throws SignatureException {
259         /**
260          * Compute digests of Hap contents
261          * Sign the digests and wrap the signature and signer info into the Hap Signing Block
262          */
263         byte[] hapSignatureBytes = null;
264         try {
265             Map<ContentDigestAlgorithm, byte[]> contentDigests =
266                     HapUtils.computeDigests(contentDigestAlgorithms, hapData, optionalBlocks);
267             hapSignatureBytes = generateHapSigningBlock(signerConfig, contentDigests, optionalBlocks);
268         } catch (DigestException | IOException e) {
269             throw new SignatureException("Failed to compute digests of HAP", e);
270         }
271         return hapSignatureBytes;
272     }
273 
generateHapSigningBlock( SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests, List<SigningBlock> optionalBlocks)274     private static byte[] generateHapSigningBlock(
275             SignerConfig signerConfig,
276             Map<ContentDigestAlgorithm, byte[]> contentDigests,
277             List<SigningBlock> optionalBlocks)
278             throws SignatureException {
279         byte[] hapSignatureSchemeBlock = generateHapSignatureSchemeBlock(signerConfig, contentDigests);
280         return generateHapSigningBlock(hapSignatureSchemeBlock, optionalBlocks, signerConfig.getCompatibleVersion());
281     }
282 
generateHapSigningBlock(byte[] hapSignatureSchemeBlock, List<SigningBlock> optionalBlocks, int compatibleVersion)283     private static byte[] generateHapSigningBlock(byte[] hapSignatureSchemeBlock,
284                                                   List<SigningBlock> optionalBlocks, int compatibleVersion) {
285         // FORMAT:
286         // Proof-of-Rotation pairs(optional):
287         // uint32:type
288         // uint32:length
289         // uint32:offset
290         // Property pairs(optional):
291         // uint32:type
292         // uint32:length
293         // uint32:offset
294         // Profile capability pairs(optional):
295         // uint32:type
296         // uint32:length
297         // uint32:offset
298         // length bytes : app signing pairs
299         // uint32:type
300         // uint32:length
301         // uint32:offset
302         // repeated ID-value pairs(reserved extensions):
303         // length bytes : Proof-of-Rotation values
304         // length bytes : property values
305         // length bytes : profile capability values
306         // length bytes : signature schema values
307         // uint64: size
308         // uint128: magic
309         // uint32: version
310         long optionalBlockSize = 0L;
311         for (SigningBlock optionalBlock : optionalBlocks) {
312             optionalBlockSize += optionalBlock.getLength();
313         }
314         long resultSize =
315                 ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE + OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1))
316                         + optionalBlockSize // optional pair
317                         + hapSignatureSchemeBlock.length // App signing pairs
318                         + BLOCK_COUNT // block count
319                         + HapUtils.BLOCK_SIZE // size
320                         + BLOCK_MAGIC // magic
321                         + BLOCK_VERSION; // version
322         if (resultSize > Integer.MAX_VALUE) {
323             throw new IllegalArgumentException("HapSigningBlock out of range : " + resultSize);
324         }
325         ByteBuffer result = ByteBuffer.allocate((int) resultSize);
326         result.order(ByteOrder.LITTLE_ENDIAN);
327 
328         Map<Integer, Integer> typeAndOffsetMap = new HashMap<Integer, Integer>();
329         int currentOffset = ((OPTIONAL_TYPE_SIZE + OPTIONAL_LENGTH_SIZE
330                 + OPTIONAL_OFFSET_SIZE) * (optionalBlocks.size() + 1));
331         int currentOffsetInBlockValue = 0;
332         int blockValueSizes = (int) (optionalBlockSize + hapSignatureSchemeBlock.length);
333         byte[] blockValues = new byte[blockValueSizes];
334 
335         for (SigningBlock optionalBlock : optionalBlocks) {
336             System.arraycopy(
337                     optionalBlock.getValue(), 0, blockValues, currentOffsetInBlockValue, optionalBlock.getLength());
338             typeAndOffsetMap.put(optionalBlock.getType(), currentOffset);
339             currentOffset += optionalBlock.getLength();
340             currentOffsetInBlockValue += optionalBlock.getLength();
341         }
342 
343         System.arraycopy(
344                 hapSignatureSchemeBlock, 0, blockValues, currentOffsetInBlockValue, hapSignatureSchemeBlock.length);
345         typeAndOffsetMap.put(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID, currentOffset);
346 
347         extractedResult(optionalBlocks, result, typeAndOffsetMap);
348         result.putInt(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID); // type
349         result.putInt(hapSignatureSchemeBlock.length); // length
350         int offset = typeAndOffsetMap.get(HapUtils.HAP_SIGNATURE_SCHEME_V1_BLOCK_ID);
351         result.putInt(offset); // offset
352         result.put(blockValues);
353         result.putInt(optionalBlocks.size() + 1); // Signing block count
354         result.putLong(resultSize); // length of hap signing block
355         result.put(HapUtils.getHapSigningBlockMagic(compatibleVersion)); // magic
356         result.putInt(HapUtils.getHapSigningBlockVersion(compatibleVersion)); // version
357         return result.array();
358     }
359 
extractedResult(List<SigningBlock> optionalBlocks, ByteBuffer result, Map<Integer, Integer> typeAndOffsetMap)360     private static void extractedResult(List<SigningBlock> optionalBlocks, ByteBuffer result,
361                                         Map<Integer, Integer> typeAndOffsetMap) {
362         int offset;
363         for (SigningBlock optionalBlock : optionalBlocks) {
364             result.putInt(optionalBlock.getType()); // type
365             result.putInt(optionalBlock.getLength()); // length
366             offset = typeAndOffsetMap.get(optionalBlock.getType());
367             result.putInt(offset); // offset
368         }
369     }
370 
generateHapSignatureSchemeBlock( SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests)371     private static byte[] generateHapSignatureSchemeBlock(
372             SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests) throws SignatureException {
373         byte[] signerBlock = null;
374         try {
375             signerBlock = generateSignerBlock(signerConfig, contentDigests);
376         } catch (SignatureException e) {
377             throw new SignatureException("generate SignerBlock failed"
378                     + "\nSolutions:"
379                     + "\n> The keyAlias parameter is incorrect, please input a correct keyAlias parameter."
380                     + "\n> The certificate is incorrect, please check if your certificate matches the key");
381         }
382         return signerBlock;
383     }
384 
generateSignerBlock( SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests)385     private static byte[] generateSignerBlock(
386             SignerConfig signerConfig, Map<ContentDigestAlgorithm, byte[]> contentDigests) throws SignatureException {
387         String mode = signerConfig.getOptions().getString(Options.MODE);
388         if (!("remoteSign".equalsIgnoreCase(mode)) && signerConfig.getCertificates().isEmpty()) {
389             throw new SignatureException("No certificates configured for signer");
390         }
391 
392         List<Pair<Integer, byte[]>> digests =
393                 new ArrayList<Pair<Integer, byte[]>>(signerConfig.getSignatureAlgorithms().size());
394         for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) {
395             ContentDigestAlgorithm contentDigestAlgorithm = signatureAlgorithm.getContentDigestAlgorithm();
396             byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);
397             if (contentDigest == null) {
398                 throw new SignatureException(
399                         contentDigestAlgorithm.getDigestAlgorithm()
400                                 + " content digest for "
401                                 + signatureAlgorithm.getSignatureAlgAndParams().getFirst()
402                                 + " not computed");
403             }
404             digests.add(Pair.create(signatureAlgorithm.getId(), contentDigest));
405         }
406         byte[] unsignedHapDigest = HapUtils.encodeListOfPairsToByteArray(digests);
407         return Pkcs7Generator.BC.generateSignedData(unsignedHapDigest, signerConfig);
408     }
409 
410     /**
411      * Signs the provided Hap using Hap Signature Scheme and returns the
412      * signed block as an array of ByteBuffer
413      *
414      * @param contents Hap content before ZIP CD
415      * @param signerConfig signer config
416      * @param optionalBlocks optional blocks
417      * @return signed block
418      * @throws SignatureException if an error occurs when sign hap file.
419      */
sign(ZipDataInput[] contents, SignerConfig signerConfig, List<SigningBlock> optionalBlocks)420     public static byte[] sign(ZipDataInput[] contents, SignerConfig signerConfig, List<SigningBlock> optionalBlocks)
421             throws SignatureException {
422         Set<ContentDigestAlgorithm> contentDigestAlgorithms = new HashSet<ContentDigestAlgorithm>();
423         for (SignatureAlgorithm signatureAlgorithm : signerConfig.getSignatureAlgorithms()) {
424             contentDigestAlgorithms.add(signatureAlgorithm.getContentDigestAlgorithm());
425         }
426         return getHapSigningBlock(contentDigestAlgorithms, optionalBlocks, signerConfig, contents);
427     }
428 }
429