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