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
17package com.android.verity;
18
19import java.io.ByteArrayInputStream;
20import java.io.IOException;
21import java.nio.ByteBuffer;
22import java.nio.ByteOrder;
23import java.security.PrivateKey;
24import java.security.PublicKey;
25import java.security.Security;
26import java.security.cert.X509Certificate;
27import java.security.cert.Certificate;
28import java.security.cert.CertificateFactory;
29import java.security.cert.CertificateEncodingException;
30import java.util.Arrays;
31import org.bouncycastle.asn1.ASN1Encodable;
32import org.bouncycastle.asn1.ASN1EncodableVector;
33import org.bouncycastle.asn1.ASN1Integer;
34import org.bouncycastle.asn1.ASN1Object;
35import org.bouncycastle.asn1.ASN1ObjectIdentifier;
36import org.bouncycastle.asn1.ASN1OctetString;
37import org.bouncycastle.asn1.ASN1Primitive;
38import org.bouncycastle.asn1.ASN1Sequence;
39import org.bouncycastle.asn1.ASN1InputStream;
40import org.bouncycastle.asn1.DEROctetString;
41import org.bouncycastle.asn1.DERPrintableString;
42import org.bouncycastle.asn1.DERSequence;
43import org.bouncycastle.asn1.util.ASN1Dump;
44import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
45import 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
64public 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     */
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     */
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
120    public ASN1Object getAuthenticatedAttributes() {
121        ASN1EncodableVector attrs = new ASN1EncodableVector();
122        attrs.add(target);
123        attrs.add(length);
124        return new DERSequence(attrs);
125    }
126
127    public byte[] getEncodedAuthenticatedAttributes() throws IOException {
128        return getAuthenticatedAttributes().getEncoded();
129    }
130
131    public AlgorithmIdentifier getAlgorithmIdentifier() {
132        return algorithmIdentifier;
133    }
134
135    public PublicKey getPublicKey() {
136        return publicKey;
137    }
138
139    public byte[] getSignature() {
140        return signature.getOctets();
141    }
142
143    public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
144        algorithmIdentifier = algId;
145        signature = new DEROctetString(sig);
146    }
147
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
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
164    public byte[] sign(byte[] image, PrivateKey key) throws Exception {
165        byte[] signable = generateSignableImage(image);
166        return Utils.sign(key, signable);
167    }
168
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
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
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
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
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    */
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