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