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     /**
77      * Initializes the object for signing an image file
78      * @param target Target name, included in the signed data
79      * @param length Length of the image, included in the signed data
80      */
BootSignature(String target, int length)81     public BootSignature(String target, int length) {
82         this.formatVersion = new ASN1Integer(FORMAT_VERSION);
83         this.target = new DERPrintableString(target);
84         this.length = new ASN1Integer(length);
85     }
86 
87     /**
88      * Initializes the object for verifying a signed image file
89      * @param signature Signature footer
90      */
BootSignature(byte[] signature)91     public BootSignature(byte[] signature)
92             throws Exception {
93         ASN1InputStream stream = new ASN1InputStream(signature);
94         ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
95 
96         formatVersion = (ASN1Integer) sequence.getObjectAt(0);
97         if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
98             throw new IllegalArgumentException("Unsupported format version");
99         }
100 
101         certificate = sequence.getObjectAt(1);
102         byte[] encoded = ((ASN1Object) certificate).getEncoded();
103         ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
104 
105         CertificateFactory cf = CertificateFactory.getInstance("X.509");
106         X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
107         publicKey = c.getPublicKey();
108 
109         ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
110         algorithmIdentifier = new AlgorithmIdentifier(
111             (ASN1ObjectIdentifier) algId.getObjectAt(0));
112 
113         ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
114         target = (DERPrintableString) attrs.getObjectAt(0);
115         length = (ASN1Integer) attrs.getObjectAt(1);
116 
117         this.signature = (DEROctetString) sequence.getObjectAt(4);
118     }
119 
getAuthenticatedAttributes()120     public ASN1Object getAuthenticatedAttributes() {
121         ASN1EncodableVector attrs = new ASN1EncodableVector();
122         attrs.add(target);
123         attrs.add(length);
124         return new DERSequence(attrs);
125     }
126 
getEncodedAuthenticatedAttributes()127     public byte[] getEncodedAuthenticatedAttributes() throws IOException {
128         return getAuthenticatedAttributes().getEncoded();
129     }
130 
getAlgorithmIdentifier()131     public AlgorithmIdentifier getAlgorithmIdentifier() {
132         return algorithmIdentifier;
133     }
134 
getPublicKey()135     public PublicKey getPublicKey() {
136         return publicKey;
137     }
138 
getSignature()139     public byte[] getSignature() {
140         return signature.getOctets();
141     }
142 
setSignature(byte[] sig, AlgorithmIdentifier algId)143     public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
144         algorithmIdentifier = algId;
145         signature = new DEROctetString(sig);
146     }
147 
setCertificate(X509Certificate cert)148     public void setCertificate(X509Certificate cert)
149             throws Exception, IOException, CertificateEncodingException {
150         ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
151         certificate = s.readObject();
152         publicKey = cert.getPublicKey();
153     }
154 
generateSignableImage(byte[] image)155     public byte[] generateSignableImage(byte[] image) throws IOException {
156         byte[] attrs = getEncodedAuthenticatedAttributes();
157         byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
158         for (int i=0; i < attrs.length; i++) {
159             signable[i+image.length] = attrs[i];
160         }
161         return signable;
162     }
163 
sign(byte[] image, PrivateKey key)164     public byte[] sign(byte[] image, PrivateKey key) throws Exception {
165         byte[] signable = generateSignableImage(image);
166         return Utils.sign(key, signable);
167     }
168 
verify(byte[] image)169     public boolean verify(byte[] image) throws Exception {
170         if (length.getValue().intValue() != image.length) {
171             throw new IllegalArgumentException("Invalid image length");
172         }
173 
174         byte[] signable = generateSignableImage(image);
175         return Utils.verify(publicKey, signable, signature.getOctets(),
176                     algorithmIdentifier);
177     }
178 
toASN1Primitive()179     public ASN1Primitive toASN1Primitive() {
180         ASN1EncodableVector v = new ASN1EncodableVector();
181         v.add(formatVersion);
182         v.add(certificate);
183         v.add(algorithmIdentifier);
184         v.add(getAuthenticatedAttributes());
185         v.add(signature);
186         return new DERSequence(v);
187     }
188 
getSignableImageSize(byte[] data)189     public static int getSignableImageSize(byte[] data) throws Exception {
190         if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
191                 "ANDROID!".getBytes("US-ASCII"))) {
192             throw new IllegalArgumentException("Invalid image header: missing magic");
193         }
194 
195         ByteBuffer image = ByteBuffer.wrap(data);
196         image.order(ByteOrder.LITTLE_ENDIAN);
197 
198         image.getLong(); // magic
199         int kernelSize = image.getInt();
200         image.getInt(); // kernel_addr
201         int ramdskSize = image.getInt();
202         image.getInt(); // ramdisk_addr
203         int secondSize = image.getInt();
204         image.getLong(); // second_addr + tags_addr
205         int pageSize = image.getInt();
206 
207         int length = pageSize // include the page aligned image header
208                 + ((kernelSize + pageSize - 1) / pageSize) * pageSize
209                 + ((ramdskSize + pageSize - 1) / pageSize) * pageSize
210                 + ((secondSize + pageSize - 1) / pageSize) * pageSize;
211 
212         length = ((length + pageSize - 1) / pageSize) * pageSize;
213 
214         if (length <= 0) {
215             throw new IllegalArgumentException("Invalid image header: invalid length");
216         }
217 
218         return length;
219     }
220 
doSignature( String target, String imagePath, String keyPath, String certPath, String outPath)221     public static void doSignature( String target,
222                                     String imagePath,
223                                     String keyPath,
224                                     String certPath,
225                                     String outPath) throws Exception {
226 
227         byte[] image = Utils.read(imagePath);
228         int signableSize = getSignableImageSize(image);
229 
230         if (signableSize < image.length) {
231             System.err.println("NOTE: truncating file " + imagePath +
232                     " from " + image.length + " to " + signableSize + " bytes");
233             image = Arrays.copyOf(image, signableSize);
234         } else if (signableSize > image.length) {
235             throw new IllegalArgumentException("Invalid image: too short, expected " +
236                     signableSize + " bytes");
237         }
238 
239         BootSignature bootsig = new BootSignature(target, image.length);
240 
241         X509Certificate cert = Utils.loadPEMCertificate(certPath);
242         bootsig.setCertificate(cert);
243 
244         PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
245         bootsig.setSignature(bootsig.sign(image, key),
246             Utils.getSignatureAlgorithmIdentifier(key));
247 
248         byte[] encoded_bootsig = bootsig.getEncoded();
249         byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
250 
251         System.arraycopy(encoded_bootsig, 0, image_with_metadata,
252                 image.length, encoded_bootsig.length);
253 
254         Utils.write(image_with_metadata, outPath);
255     }
256 
verifySignature(String imagePath, String certPath)257     public static void verifySignature(String imagePath, String certPath) throws Exception {
258         byte[] image = Utils.read(imagePath);
259         int signableSize = getSignableImageSize(image);
260 
261         if (signableSize >= image.length) {
262             throw new IllegalArgumentException("Invalid image: not signed");
263         }
264 
265         byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
266         BootSignature bootsig = new BootSignature(signature);
267 
268         if (!certPath.isEmpty()) {
269             System.err.println("NOTE: verifying using public key from " + certPath);
270             bootsig.setCertificate(Utils.loadPEMCertificate(certPath));
271         }
272 
273         try {
274             if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
275                 System.err.println("Signature is VALID");
276                 System.exit(0);
277             } else {
278                 System.err.println("Signature is INVALID");
279             }
280         } catch (Exception e) {
281             e.printStackTrace(System.err);
282         }
283         System.exit(1);
284     }
285 
286     /* Example usage for signing a boot image using dev keys:
287         java -cp \
288             ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \
289                 classes/com.android.verity.BootSignature \
290             /boot \
291             ../../../out/target/product/$PRODUCT/boot.img \
292             ../../../build/target/product/security/verity.pk8 \
293             ../../../build/target/product/security/verity.x509.pem \
294             /tmp/boot.img.signed
295     */
main(String[] args)296     public static void main(String[] args) throws Exception {
297         Security.addProvider(new BouncyCastleProvider());
298 
299         if ("-verify".equals(args[0])) {
300             String certPath = "";
301 
302             if (args.length >= 4 && "-certificate".equals(args[2])) {
303                 /* args[3] is the path to a public key certificate */
304                 certPath = args[3];
305             }
306 
307             /* args[1] is the path to a signed boot image */
308             verifySignature(args[1], certPath);
309         } else {
310             /* args[0] is the target name, typically /boot
311                args[1] is the path to a boot image to sign
312                args[2] is the path to a private key
313                args[3] is the path to the matching public key certificate
314                args[4] is the path where to output the signed boot image
315             */
316             doSignature(args[0], args[1], args[2], args[3], args[4]);
317         }
318     }
319 }
320