1 /*
2  * Copyright (C) 2014 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 
17 package com.android.verity;
18 
19 import java.io.ByteArrayInputStream;
20 import java.io.IOException;
21 import java.nio.ByteBuffer;
22 import java.nio.ByteOrder;
23 import java.security.PrivateKey;
24 import java.security.PublicKey;
25 import java.security.Security;
26 import java.security.cert.X509Certificate;
27 import java.security.cert.Certificate;
28 import java.security.cert.CertificateFactory;
29 import java.security.cert.CertificateEncodingException;
30 import java.util.Arrays;
31 import org.bouncycastle.asn1.ASN1Encodable;
32 import org.bouncycastle.asn1.ASN1EncodableVector;
33 import org.bouncycastle.asn1.ASN1Integer;
34 import org.bouncycastle.asn1.ASN1Object;
35 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
36 import org.bouncycastle.asn1.ASN1OctetString;
37 import org.bouncycastle.asn1.ASN1Primitive;
38 import org.bouncycastle.asn1.ASN1Sequence;
39 import org.bouncycastle.asn1.ASN1InputStream;
40 import org.bouncycastle.asn1.DEROctetString;
41 import org.bouncycastle.asn1.DERPrintableString;
42 import org.bouncycastle.asn1.DERSequence;
43 import org.bouncycastle.asn1.util.ASN1Dump;
44 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
45 import org.bouncycastle.jce.provider.BouncyCastleProvider;
46 
47 /**
48  *    AndroidVerifiedBootSignature DEFINITIONS ::=
49  *    BEGIN
50  *        formatVersion ::= INTEGER
51  *        certificate ::= Certificate
52  *        algorithmIdentifier ::= SEQUENCE {
53  *            algorithm OBJECT IDENTIFIER,
54  *            parameters ANY DEFINED BY algorithm OPTIONAL
55  *        }
56  *        authenticatedAttributes ::= SEQUENCE {
57  *            target CHARACTER STRING,
58  *            length INTEGER
59  *        }
60  *        signature ::= OCTET STRING
61  *     END
62  */
63 
64 public class BootSignature extends ASN1Object
65 {
66     private ASN1Integer             formatVersion;
67     private ASN1Encodable           certificate;
68     private AlgorithmIdentifier     algorithmIdentifier;
69     private DERPrintableString      target;
70     private ASN1Integer             length;
71     private DEROctetString          signature;
72     private PublicKey               publicKey;
73 
74     private static final int FORMAT_VERSION = 1;
75     /**
76      * Offset of recovery DTBO length in a boot image header of version greater than
77      * or equal to 1.
78      */
79     private static final int BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET = 1632;
80     /**
81      * Offset of DTB length in a boot image header of version greater than
82      * or equal to 2.
83      */
84     private static final int BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET = 1648;
85 
86 
87     /**
88      * Initializes the object for signing an image file
89      * @param target Target name, included in the signed data
90      * @param length Length of the image, included in the signed data
91      */
BootSignature(String target, int length)92     public BootSignature(String target, int length) {
93         this.formatVersion = new ASN1Integer(FORMAT_VERSION);
94         this.target = new DERPrintableString(target);
95         this.length = new ASN1Integer(length);
96     }
97 
98     /**
99      * Initializes the object for verifying a signed image file
100      * @param signature Signature footer
101      */
BootSignature(byte[] signature)102     public BootSignature(byte[] signature)
103             throws Exception {
104         ASN1InputStream stream = new ASN1InputStream(signature);
105         ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
106 
107         formatVersion = (ASN1Integer) sequence.getObjectAt(0);
108         if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
109             throw new IllegalArgumentException("Unsupported format version");
110         }
111 
112         certificate = sequence.getObjectAt(1);
113         byte[] encoded = ((ASN1Object) certificate).getEncoded();
114         ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
115 
116         CertificateFactory cf = CertificateFactory.getInstance("X.509");
117         X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
118         publicKey = c.getPublicKey();
119 
120         ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
121         algorithmIdentifier = new AlgorithmIdentifier(
122             (ASN1ObjectIdentifier) algId.getObjectAt(0));
123 
124         ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
125         target = (DERPrintableString) attrs.getObjectAt(0);
126         length = (ASN1Integer) attrs.getObjectAt(1);
127 
128         this.signature = (DEROctetString) sequence.getObjectAt(4);
129     }
130 
getAuthenticatedAttributes()131     public ASN1Object getAuthenticatedAttributes() {
132         ASN1EncodableVector attrs = new ASN1EncodableVector();
133         attrs.add(target);
134         attrs.add(length);
135         return new DERSequence(attrs);
136     }
137 
getEncodedAuthenticatedAttributes()138     public byte[] getEncodedAuthenticatedAttributes() throws IOException {
139         return getAuthenticatedAttributes().getEncoded();
140     }
141 
getAlgorithmIdentifier()142     public AlgorithmIdentifier getAlgorithmIdentifier() {
143         return algorithmIdentifier;
144     }
145 
getPublicKey()146     public PublicKey getPublicKey() {
147         return publicKey;
148     }
149 
getSignature()150     public byte[] getSignature() {
151         return signature.getOctets();
152     }
153 
setSignature(byte[] sig, AlgorithmIdentifier algId)154     public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
155         algorithmIdentifier = algId;
156         signature = new DEROctetString(sig);
157     }
158 
setCertificate(X509Certificate cert)159     public void setCertificate(X509Certificate cert)
160             throws Exception, IOException, CertificateEncodingException {
161         ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
162         certificate = s.readObject();
163         publicKey = cert.getPublicKey();
164     }
165 
generateSignableImage(byte[] image)166     public byte[] generateSignableImage(byte[] image) throws IOException {
167         byte[] attrs = getEncodedAuthenticatedAttributes();
168         byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
169         for (int i=0; i < attrs.length; i++) {
170             signable[i+image.length] = attrs[i];
171         }
172         return signable;
173     }
174 
sign(byte[] image, PrivateKey key)175     public byte[] sign(byte[] image, PrivateKey key) throws Exception {
176         byte[] signable = generateSignableImage(image);
177         return Utils.sign(key, signable);
178     }
179 
verify(byte[] image)180     public boolean verify(byte[] image) throws Exception {
181         if (length.getValue().intValue() != image.length) {
182             throw new IllegalArgumentException("Invalid image length");
183         }
184 
185         byte[] signable = generateSignableImage(image);
186         return Utils.verify(publicKey, signable, signature.getOctets(),
187                     algorithmIdentifier);
188     }
189 
toASN1Primitive()190     public ASN1Primitive toASN1Primitive() {
191         ASN1EncodableVector v = new ASN1EncodableVector();
192         v.add(formatVersion);
193         v.add(certificate);
194         v.add(algorithmIdentifier);
195         v.add(getAuthenticatedAttributes());
196         v.add(signature);
197         return new DERSequence(v);
198     }
199 
getSignableImageSize(byte[] data)200     public static int getSignableImageSize(byte[] data) throws Exception {
201         if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
202                 "ANDROID!".getBytes("US-ASCII"))) {
203             throw new IllegalArgumentException("Invalid image header: missing magic");
204         }
205 
206         ByteBuffer image = ByteBuffer.wrap(data);
207         image.order(ByteOrder.LITTLE_ENDIAN);
208 
209         image.getLong(); // magic
210         int kernelSize = image.getInt();
211         image.getInt(); // kernel_addr
212         int ramdskSize = image.getInt();
213         image.getInt(); // ramdisk_addr
214         int secondSize = image.getInt();
215         image.getLong(); // second_addr + tags_addr
216         int pageSize = image.getInt();
217 
218         int length = pageSize // include the page aligned image header
219                 + ((kernelSize + pageSize - 1) / pageSize) * pageSize
220                 + ((ramdskSize + pageSize - 1) / pageSize) * pageSize
221                 + ((secondSize + pageSize - 1) / pageSize) * pageSize;
222 
223         int headerVersion = image.getInt(); // boot image header version
224         if (headerVersion > 0) {
225             image.position(BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET);
226             int recoveryDtboLength = image.getInt();
227             length += ((recoveryDtboLength + pageSize - 1) / pageSize) * pageSize;
228 
229             image.getLong(); // recovery_dtbo address
230             int headerSize = image.getInt();
231             if (headerVersion == 2) {
232                 image.position(BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET);
233                 int dtbLength = image.getInt();
234                 length += ((dtbLength + pageSize - 1) / pageSize) * pageSize;
235                 image.getLong(); // dtb address
236             }
237             if (image.position() != headerSize) {
238                 throw new IllegalArgumentException(
239                         "Invalid image header: invalid header length");
240             }
241         }
242 
243         length = ((length + pageSize - 1) / pageSize) * pageSize;
244 
245         if (length <= 0) {
246             throw new IllegalArgumentException("Invalid image header: invalid length");
247         }
248 
249         return length;
250     }
251 
doSignature( String target, String imagePath, String keyPath, String certPath, String outPath)252     public static void doSignature( String target,
253                                     String imagePath,
254                                     String keyPath,
255                                     String certPath,
256                                     String outPath) throws Exception {
257 
258         byte[] image = Utils.read(imagePath);
259         int signableSize = getSignableImageSize(image);
260 
261         if (signableSize < image.length) {
262             System.err.println("NOTE: truncating file " + imagePath +
263                     " from " + image.length + " to " + signableSize + " bytes");
264             image = Arrays.copyOf(image, signableSize);
265         } else if (signableSize > image.length) {
266             throw new IllegalArgumentException("Invalid image: too short, expected " +
267                     signableSize + " bytes");
268         }
269 
270         BootSignature bootsig = new BootSignature(target, image.length);
271 
272         X509Certificate cert = Utils.loadPEMCertificate(certPath);
273         bootsig.setCertificate(cert);
274 
275         PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
276         bootsig.setSignature(bootsig.sign(image, key),
277             Utils.getSignatureAlgorithmIdentifier(key));
278 
279         byte[] encoded_bootsig = bootsig.getEncoded();
280         byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
281 
282         System.arraycopy(encoded_bootsig, 0, image_with_metadata,
283                 image.length, encoded_bootsig.length);
284 
285         Utils.write(image_with_metadata, outPath);
286     }
287 
verifySignature(String imagePath, String certPath)288     public static void verifySignature(String imagePath, String certPath) throws Exception {
289         byte[] image = Utils.read(imagePath);
290         int signableSize = getSignableImageSize(image);
291 
292         if (signableSize >= image.length) {
293             throw new IllegalArgumentException("Invalid image: not signed");
294         }
295 
296         byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
297         BootSignature bootsig = new BootSignature(signature);
298 
299         if (!certPath.isEmpty()) {
300             System.err.println("NOTE: verifying using public key from " + certPath);
301             bootsig.setCertificate(Utils.loadPEMCertificate(certPath));
302         }
303 
304         try {
305             if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
306                 System.err.println("Signature is VALID");
307                 System.exit(0);
308             } else {
309                 System.err.println("Signature is INVALID");
310             }
311         } catch (Exception e) {
312             e.printStackTrace(System.err);
313         }
314         System.exit(1);
315     }
316 
317     /* Example usage for signing a boot image using dev keys:
318         java -cp \
319             ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \
320                 classes/com.android.verity.BootSignature \
321             /boot \
322             ../../../out/target/product/$PRODUCT/boot.img \
323             ../../../build/target/product/security/verity.pk8 \
324             ../../../build/target/product/security/verity.x509.pem \
325             /tmp/boot.img.signed
326     */
main(String[] args)327     public static void main(String[] args) throws Exception {
328         Security.addProvider(new BouncyCastleProvider());
329 
330         if ("-verify".equals(args[0])) {
331             String certPath = "";
332 
333             if (args.length >= 4 && "-certificate".equals(args[2])) {
334                 /* args[3] is the path to a public key certificate */
335                 certPath = args[3];
336             }
337 
338             /* args[1] is the path to a signed boot image */
339             verifySignature(args[1], certPath);
340         } else {
341             /* args[0] is the target name, typically /boot
342                args[1] is the path to a boot image to sign
343                args[2] is the path to a private key
344                args[3] is the path to the matching public key certificate
345                args[4] is the path where to output the signed boot image
346             */
347             doSignature(args[0], args[1], args[2], args[3], args[4]);
348         }
349     }
350 }
351