1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.apksig;
17 import java.io.IOException;
18 import java.io.DataOutputStream;
19 import java.io.ByteArrayOutputStream;
20 import java.io.UnsupportedEncodingException;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 
26 public final class Hints {
27     /**
28      * Name of hint pattern asset file in APK.
29      */
30     public static final String PIN_HINT_ASSET_ZIP_ENTRY_NAME = "assets/com.android.hints.pins.txt";
31 
32     /**
33      * Name of hint byte range data file in APK.  Keep in sync with PinnerService.java.
34      */
35     public static final String PIN_BYTE_RANGE_ZIP_ENTRY_NAME = "pinlist.meta";
36 
clampToInt(long value)37     private static int clampToInt(long value) {
38         return (int) Math.max(0, Math.min(value, Integer.MAX_VALUE));
39     }
40 
41     public static final class ByteRange {
42         final long start;
43         final long end;
44 
ByteRange(long start, long end)45         public ByteRange(long start, long end) {
46             this.start = start;
47             this.end = end;
48         }
49     }
50 
51     public static final class PatternWithRange {
52         final Pattern pattern;
53         final long offset;
54         final long size;
55 
PatternWithRange(String pattern)56         public PatternWithRange(String pattern) {
57             this.pattern = Pattern.compile(pattern);
58             this.offset= 0;
59             this.size = Long.MAX_VALUE;
60         }
61 
PatternWithRange(String pattern, long offset, long size)62         public PatternWithRange(String pattern, long offset, long size) {
63             this.pattern = Pattern.compile(pattern);
64             this.offset = offset;
65             this.size = size;
66         }
67 
matcher(CharSequence input)68         public Matcher matcher(CharSequence input) {
69             return this.pattern.matcher(input);
70         }
71 
ClampToAbsoluteByteRange(ByteRange rangeIn)72         public ByteRange ClampToAbsoluteByteRange(ByteRange rangeIn) {
73             if (rangeIn.end - rangeIn.start < this.offset) {
74                 return null;
75             }
76             long rangeOutStart = rangeIn.start + this.offset;
77             long rangeOutSize = Math.min(rangeIn.end - rangeOutStart,
78                                            this.size);
79             return new ByteRange(rangeOutStart,
80                                  rangeOutStart + rangeOutSize);
81         }
82     }
83 
84     /**
85      * Create a blob of bytes that PinnerService understands as a
86      * sequence of byte ranges to pin.
87      */
encodeByteRangeList(List<ByteRange> pinByteRanges)88     public static byte[] encodeByteRangeList(List<ByteRange> pinByteRanges) {
89         ByteArrayOutputStream bos = new ByteArrayOutputStream(pinByteRanges.size() * 8);
90         DataOutputStream out = new DataOutputStream(bos);
91         try {
92             for (ByteRange pinByteRange : pinByteRanges) {
93                 out.writeInt(clampToInt(pinByteRange.start));
94                 out.writeInt(clampToInt(pinByteRange.end - pinByteRange.start));
95             }
96         } catch (IOException ex) {
97             throw new AssertionError("impossible", ex);
98         }
99         return bos.toByteArray();
100     }
101 
parsePinPatterns(byte[] patternBlob)102     public static ArrayList<PatternWithRange> parsePinPatterns(byte[] patternBlob) {
103         ArrayList<PatternWithRange> pinPatterns = new ArrayList<>();
104         try {
105             for (String rawLine : new String(patternBlob, "UTF-8").split("\n")) {
106                 String line = rawLine.replaceFirst("#.*", "");  // # starts a comment
107                 String[] fields = line.split(" ");
108                 if (fields.length == 1) {
109                     pinPatterns.add(new PatternWithRange(fields[0]));
110                 } else if (fields.length == 3) {
111                     long start = Long.parseLong(fields[1]);
112                     long end = Long.parseLong(fields[2]);
113                     pinPatterns.add(new PatternWithRange(fields[0], start, end - start));
114                 } else {
115                     throw new AssertionError("bad pin pattern line " + line);
116                 }
117             }
118         } catch (UnsupportedEncodingException ex) {
119             throw new RuntimeException("UTF-8 must be supported", ex);
120         }
121         return pinPatterns;
122     }
123 }
124