• 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     /**
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     }
153 
generateSignableImage(byte[] image)154     public byte[] generateSignableImage(byte[] image) throws IOException {
155         byte[] attrs = getEncodedAuthenticatedAttributes();
156         byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
157         for (int i=0; i < attrs.length; i++) {
158             signable[i+image.length] = attrs[i];
159         }
160         return signable;
161     }
162 
sign(byte[] image, PrivateKey key)163     public byte[] sign(byte[] image, PrivateKey key) throws Exception {
164         byte[] signable = generateSignableImage(image);
165         return Utils.sign(key, signable);
166     }
167 
verify(byte[] image)168     public boolean verify(byte[] image) throws Exception {
169         if (length.getValue().intValue() != image.length) {
170             throw new IllegalArgumentException("Invalid image length");
171         }
172 
173         byte[] signable = generateSignableImage(image);
174         return Utils.verify(publicKey, signable, signature.getOctets(),
175                     algorithmIdentifier);
176     }
177 
toASN1Primitive()178     public ASN1Primitive toASN1Primitive() {
179         ASN1EncodableVector v = new ASN1EncodableVector();
180         v.add(formatVersion);
181         v.add(certificate);
182         v.add(algorithmIdentifier);
183         v.add(getAuthenticatedAttributes());
184         v.add(signature);
185         return new DERSequence(v);
186     }
187 
getSignableImageSize(byte[] data)188     public static int getSignableImageSize(byte[] data) throws Exception {
189         if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
190                 "ANDROID!".getBytes("US-ASCII"))) {
191             throw new IllegalArgumentException("Invalid image header: missing magic");
192         }
193 
194         ByteBuffer image = ByteBuffer.wrap(data);
195         image.order(ByteOrder.LITTLE_ENDIAN);
196 
197         image.getLong(); // magic
198         int kernelSize = image.getInt();
199         image.getInt(); // kernel_addr
200         int ramdskSize = image.getInt();
201         image.getInt(); // ramdisk_addr
202         int secondSize = image.getInt();
203         image.getLong(); // second_addr + tags_addr
204         int pageSize = image.getInt();
205 
206         int length = pageSize // include the page aligned image header
207                 + ((kernelSize + pageSize - 1) / pageSize) * pageSize
208                 + ((ramdskSize + pageSize - 1) / pageSize) * pageSize
209                 + ((secondSize + pageSize - 1) / pageSize) * pageSize;
210 
211         length = ((length + pageSize - 1) / pageSize) * pageSize;
212 
213         if (length <= 0) {
214             throw new IllegalArgumentException("Invalid image header: invalid length");
215         }
216 
217         return length;
218     }
219 
doSignature( String target, String imagePath, String keyPath, String certPath, String outPath)220     public static void doSignature( String target,
221                                     String imagePath,
222                                     String keyPath,
223                                     String certPath,
224                                     String outPath) throws Exception {
225 
226         byte[] image = Utils.read(imagePath);
227         int signableSize = getSignableImageSize(image);
228 
229         if (signableSize < image.length) {
230             System.err.println("NOTE: truncating file " + imagePath +
231                     " from " + image.length + " to " + signableSize + " bytes");
232             image = Arrays.copyOf(image, signableSize);
233         } else if (signableSize > image.length) {
234             throw new IllegalArgumentException("Invalid image: too short, expected " +
235                     signableSize + " bytes");
236         }
237 
238         BootSignature bootsig = new BootSignature(target, image.length);
239 
240         X509Certificate cert = Utils.loadPEMCertificate(certPath);
241         bootsig.setCertificate(cert);
242 
243         PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
244         bootsig.setSignature(bootsig.sign(image, key),
245             Utils.getSignatureAlgorithmIdentifier(key));
246 
247         byte[] encoded_bootsig = bootsig.getEncoded();
248         byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
249 
250         System.arraycopy(encoded_bootsig, 0, image_with_metadata,
251                 image.length, encoded_bootsig.length);
252 
253         Utils.write(image_with_metadata, outPath);
254     }
255 
verifySignature(String imagePath)256     public static void verifySignature(String imagePath) throws Exception {
257         byte[] image = Utils.read(imagePath);
258         int signableSize = getSignableImageSize(image);
259 
260         if (signableSize >= image.length) {
261             throw new IllegalArgumentException("Invalid image: not signed");
262         }
263 
264         byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
265         BootSignature bootsig = new BootSignature(signature);
266 
267         try {
268             if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
269                 System.err.println("Signature is VALID");
270                 System.exit(0);
271             } else {
272                 System.err.println("Signature is INVALID");
273             }
274         } catch (Exception e) {
275             e.printStackTrace(System.err);
276         }
277         System.exit(1);
278     }
279 
280     /* Example usage for signing a boot image using dev keys:
281         java -cp \
282             ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \
283                 classes/com.android.verity.BootSignature \
284             /boot \
285             ../../../out/target/product/$PRODUCT/boot.img \
286             ../../../build/target/product/security/verity.pk8 \
287             ../../../build/target/product/security/verity.x509.pem \
288             /tmp/boot.img.signed
289     */
main(String[] args)290     public static void main(String[] args) throws Exception {
291         Security.addProvider(new BouncyCastleProvider());
292 
293         if ("-verify".equals(args[0])) {
294             /* args[1] is the path to a signed boot image */
295             verifySignature(args[1]);
296         } else {
297             /* args[0] is the target name, typically /boot
298                args[1] is the path to a boot image to sign
299                args[2] is the path to a private key
300                args[3] is the path to the matching public key certificate
301                args[4] is the path where to output the signed boot image
302             */
303             doSignature(args[0], args[1], args[2], args[3], args[4]);
304         }
305     }
306 }
307