2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.verity;
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;
48 * AndroidVerifiedBootSignature DEFINITIONS ::=
50 * formatVersion ::= INTEGER
51 * certificate ::= Certificate
52 * algorithmIdentifier ::= SEQUENCE {
53 * algorithm OBJECT IDENTIFIER,
54 * parameters ANY DEFINED BY algorithm OPTIONAL
56 * authenticatedAttributes ::= SEQUENCE {
57 * target CHARACTER STRING,
60 * signature ::= OCTET STRING
64 public class BootSignature extends ASN1Object
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;
74 private static final int FORMAT_VERSION = 1;
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
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);
88 * Initializes the object for verifying a signed image file
89 * @param signature Signature footer
91 public BootSignature(byte[] signature)
93 ASN1InputStream stream = new ASN1InputStream(signature);
94 ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
96 formatVersion = (ASN1Integer) sequence.getObjectAt(0);
97 if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
98 throw new IllegalArgumentException("Unsupported format version");
101 certificate = sequence.getObjectAt(1);
102 byte[] encoded = ((ASN1Object) certificate).getEncoded();
103 ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
105 CertificateFactory cf = CertificateFactory.getInstance("X.509");
106 X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
107 publicKey = c.getPublicKey();
109 ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
110 algorithmIdentifier = new AlgorithmIdentifier(
111 (ASN1ObjectIdentifier) algId.getObjectAt(0));
113 ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
114 target = (DERPrintableString) attrs.getObjectAt(0);
115 length = (ASN1Integer) attrs.getObjectAt(1);
117 this.signature = (DEROctetString) sequence.getObjectAt(4);
120 public ASN1Object getAuthenticatedAttributes() {
121 ASN1EncodableVector attrs = new ASN1EncodableVector();
124 return new DERSequence(attrs);
127 public byte[] getEncodedAuthenticatedAttributes() throws IOException {
128 return getAuthenticatedAttributes().getEncoded();
131 public AlgorithmIdentifier getAlgorithmIdentifier() {
132 return algorithmIdentifier;
135 public PublicKey getPublicKey() {
139 public byte[] getSignature() {
140 return signature.getOctets();
143 public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
144 algorithmIdentifier = algId;
145 signature = new DEROctetString(sig);
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();
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];
164 public byte[] sign(byte[] image, PrivateKey key) throws Exception {
165 byte[] signable = generateSignableImage(image);
166 return Utils.sign(key, signable);
169 public boolean verify(byte[] image) throws Exception {
170 if (length.getValue().intValue() != image.length) {
171 throw new IllegalArgumentException("Invalid image length");
174 byte[] signable = generateSignableImage(image);
175 return Utils.verify(publicKey, signable, signature.getOctets(),
176 algorithmIdentifier);
179 public ASN1Primitive toASN1Primitive() {
180 ASN1EncodableVector v = new ASN1EncodableVector();
181 v.add(formatVersion);
183 v.add(algorithmIdentifier);
184 v.add(getAuthenticatedAttributes());
186 return new DERSequence(v);
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");
195 ByteBuffer image = ByteBuffer.wrap(data);
196 image.order(ByteOrder.LITTLE_ENDIAN);
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();
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;
212 length = ((length + pageSize - 1) / pageSize) * pageSize;
215 throw new IllegalArgumentException("Invalid image header: invalid length");
221 public static void doSignature( String target,
225 String outPath) throws Exception {
227 byte[] image = Utils.read(imagePath);
228 int signableSize = getSignableImageSize(image);
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");
239 BootSignature bootsig = new BootSignature(target, image.length);
241 X509Certificate cert = Utils.loadPEMCertificate(certPath);
242 bootsig.setCertificate(cert);
244 PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
245 bootsig.setSignature(bootsig.sign(image, key),
246 Utils.getSignatureAlgorithmIdentifier(key));
248 byte[] encoded_bootsig = bootsig.getEncoded();
249 byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
251 System.arraycopy(encoded_bootsig, 0, image_with_metadata,
252 image.length, encoded_bootsig.length);
254 Utils.write(image_with_metadata, outPath);
257 public static void verifySignature(String imagePath, String certPath) throws Exception {
258 byte[] image = Utils.read(imagePath);
259 int signableSize = getSignableImageSize(image);
261 if (signableSize >= image.length) {
262 throw new IllegalArgumentException("Invalid image: not signed");
265 byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
266 BootSignature bootsig = new BootSignature(signature);
268 if (!certPath.isEmpty()) {
269 System.err.println("NOTE: verifying using public key from " + certPath);
270 bootsig.setCertificate(Utils.loadPEMCertificate(certPath));
274 if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
275 System.err.println("Signature is VALID");
278 System.err.println("Signature is INVALID");
280 } catch (Exception e) {
281 e.printStackTrace(System.err);
286 /* Example usage for signing a boot image using dev keys:
288 ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \
289 classes/com.android.verity.BootSignature \
291 ../../../out/target/product/$PRODUCT/boot.img \
292 ../../../build/target/product/security/verity.pk8 \
293 ../../../build/target/product/security/verity.x509.pem \
296 public static void main(String[] args) throws Exception {
297 Security.addProvider(new BouncyCastleProvider());
299 if ("-verify".equals(args[0])) {
300 String certPath = "";
302 if (args.length >= 4 && "-certificate".equals(args[2])) {
303 /* args[3] is the path to a public key certificate */
307 /* args[1] is the path to a signed boot image */
308 verifySignature(args[1], certPath);
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
316 doSignature(args[0], args[1], args[2], args[3], args[4]);