blob: 28864ceeac5e6775a702e946c31323ea398c2a97 [file] [log] [blame]
Geremy Condracee5bfd2014-06-11 13:38:45 -07001/*
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
Sami Tolvanen3380f2f2014-10-31 17:18:23 -070019import java.io.ByteArrayInputStream;
Geremy Condracee5bfd2014-06-11 13:38:45 -070020import java.io.IOException;
Paul Lawrence29131b92014-11-13 22:15:30 +000021import java.nio.ByteBuffer;
22import java.nio.ByteOrder;
Geremy Condracee5bfd2014-06-11 13:38:45 -070023import java.security.PrivateKey;
Sami Tolvanen3380f2f2014-10-31 17:18:23 -070024import java.security.PublicKey;
Paul Lawrence29131b92014-11-13 22:15:30 +000025import java.security.Security;
26import java.security.cert.X509Certificate;
Sami Tolvanen3380f2f2014-10-31 17:18:23 -070027import java.security.cert.Certificate;
28import java.security.cert.CertificateFactory;
Paul Lawrence29131b92014-11-13 22:15:30 +000029import java.security.cert.CertificateEncodingException;
Geremy Condracee5bfd2014-06-11 13:38:45 -070030import java.util.Arrays;
31import org.bouncycastle.asn1.ASN1Encodable;
32import org.bouncycastle.asn1.ASN1EncodableVector;
33import org.bouncycastle.asn1.ASN1Integer;
34import org.bouncycastle.asn1.ASN1Object;
Sami Tolvanen3380f2f2014-10-31 17:18:23 -070035import org.bouncycastle.asn1.ASN1ObjectIdentifier;
36import org.bouncycastle.asn1.ASN1OctetString;
Geremy Condracee5bfd2014-06-11 13:38:45 -070037import org.bouncycastle.asn1.ASN1Primitive;
Sami Tolvanen3380f2f2014-10-31 17:18:23 -070038import org.bouncycastle.asn1.ASN1Sequence;
Paul Lawrence29131b92014-11-13 22:15:30 +000039import org.bouncycastle.asn1.ASN1InputStream;
Geremy Condracee5bfd2014-06-11 13:38:45 -070040import org.bouncycastle.asn1.DEROctetString;
41import org.bouncycastle.asn1.DERPrintableString;
42import org.bouncycastle.asn1.DERSequence;
Geremy Condracee5bfd2014-06-11 13:38:45 -070043import org.bouncycastle.asn1.util.ASN1Dump;
44import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
Paul Lawrence29131b92014-11-13 22:15:30 +000045import org.bouncycastle.jce.provider.BouncyCastleProvider;
Geremy Condracee5bfd2014-06-11 13:38:45 -070046
47/**
48 * AndroidVerifiedBootSignature DEFINITIONS ::=
49 * BEGIN
Paul Lawrence29131b92014-11-13 22:15:30 +000050 * formatVersion ::= INTEGER
51 * certificate ::= Certificate
52 * algorithmIdentifier ::= SEQUENCE {
Geremy Condracee5bfd2014-06-11 13:38:45 -070053 * algorithm OBJECT IDENTIFIER,
54 * parameters ANY DEFINED BY algorithm OPTIONAL
55 * }
Paul Lawrence29131b92014-11-13 22:15:30 +000056 * authenticatedAttributes ::= SEQUENCE {
Geremy Condracee5bfd2014-06-11 13:38:45 -070057 * target CHARACTER STRING,
58 * length INTEGER
59 * }
Paul Lawrence29131b92014-11-13 22:15:30 +000060 * signature ::= OCTET STRING
Geremy Condracee5bfd2014-06-11 13:38:45 -070061 * END
62 */
63
64public class BootSignature extends ASN1Object
65{
66 private ASN1Integer formatVersion;
Paul Lawrence29131b92014-11-13 22:15:30 +000067 private ASN1Encodable certificate;
Geremy Condracee5bfd2014-06-11 13:38:45 -070068 private AlgorithmIdentifier algorithmIdentifier;
69 private DERPrintableString target;
70 private ASN1Integer length;
71 private DEROctetString signature;
Sami Tolvanen3380f2f2014-10-31 17:18:23 -070072 private PublicKey publicKey;
Geremy Condracee5bfd2014-06-11 13:38:45 -070073
Paul Lawrence29131b92014-11-13 22:15:30 +000074 private static final int FORMAT_VERSION = 1;
Hridya Valsaraju9bb9f8f2018-03-23 16:01:03 -070075 /**
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;
Hridya Valsaraju590e5842019-01-24 18:38:07 -080080 /**
81 * Offset of DTB length in a boot image header of version greater than
82 * or equal to 2.
83 */
84 private static final int BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET = 1648;
85
Paul Lawrence29131b92014-11-13 22:15:30 +000086
Sami Tolvanen3380f2f2014-10-31 17:18:23 -070087 /**
88 * Initializes the object for signing an image file
89 * @param target Target name, included in the signed data
90 * @param length Length of the image, included in the signed data
91 */
Geremy Condracee5bfd2014-06-11 13:38:45 -070092 public BootSignature(String target, int length) {
Paul Lawrence29131b92014-11-13 22:15:30 +000093 this.formatVersion = new ASN1Integer(FORMAT_VERSION);
Geremy Condracee5bfd2014-06-11 13:38:45 -070094 this.target = new DERPrintableString(target);
95 this.length = new ASN1Integer(length);
Geremy Condracee5bfd2014-06-11 13:38:45 -070096 }
97
Sami Tolvanen3380f2f2014-10-31 17:18:23 -070098 /**
99 * Initializes the object for verifying a signed image file
100 * @param signature Signature footer
101 */
102 public BootSignature(byte[] signature)
103 throws Exception {
104 ASN1InputStream stream = new ASN1InputStream(signature);
105 ASN1Sequence sequence = (ASN1Sequence) stream.readObject();
106
107 formatVersion = (ASN1Integer) sequence.getObjectAt(0);
108 if (formatVersion.getValue().intValue() != FORMAT_VERSION) {
109 throw new IllegalArgumentException("Unsupported format version");
110 }
111
112 certificate = sequence.getObjectAt(1);
113 byte[] encoded = ((ASN1Object) certificate).getEncoded();
114 ByteArrayInputStream bis = new ByteArrayInputStream(encoded);
115
116 CertificateFactory cf = CertificateFactory.getInstance("X.509");
117 X509Certificate c = (X509Certificate) cf.generateCertificate(bis);
118 publicKey = c.getPublicKey();
119
120 ASN1Sequence algId = (ASN1Sequence) sequence.getObjectAt(2);
121 algorithmIdentifier = new AlgorithmIdentifier(
122 (ASN1ObjectIdentifier) algId.getObjectAt(0));
123
124 ASN1Sequence attrs = (ASN1Sequence) sequence.getObjectAt(3);
125 target = (DERPrintableString) attrs.getObjectAt(0);
126 length = (ASN1Integer) attrs.getObjectAt(1);
127
128 this.signature = (DEROctetString) sequence.getObjectAt(4);
129 }
130
Geremy Condracee5bfd2014-06-11 13:38:45 -0700131 public ASN1Object getAuthenticatedAttributes() {
132 ASN1EncodableVector attrs = new ASN1EncodableVector();
133 attrs.add(target);
134 attrs.add(length);
135 return new DERSequence(attrs);
136 }
137
138 public byte[] getEncodedAuthenticatedAttributes() throws IOException {
139 return getAuthenticatedAttributes().getEncoded();
140 }
141
Sami Tolvanen241f9642014-11-14 14:51:25 +0000142 public AlgorithmIdentifier getAlgorithmIdentifier() {
143 return algorithmIdentifier;
144 }
145
146 public PublicKey getPublicKey() {
147 return publicKey;
148 }
149
150 public byte[] getSignature() {
151 return signature.getOctets();
152 }
153
Paul Lawrence29131b92014-11-13 22:15:30 +0000154 public void setSignature(byte[] sig, AlgorithmIdentifier algId) {
155 algorithmIdentifier = algId;
Geremy Condracee5bfd2014-06-11 13:38:45 -0700156 signature = new DEROctetString(sig);
157 }
158
Paul Lawrence29131b92014-11-13 22:15:30 +0000159 public void setCertificate(X509Certificate cert)
160 throws Exception, IOException, CertificateEncodingException {
161 ASN1InputStream s = new ASN1InputStream(cert.getEncoded());
162 certificate = s.readObject();
Sami Tolvanen53e79042015-09-15 10:41:16 +0100163 publicKey = cert.getPublicKey();
Paul Lawrence29131b92014-11-13 22:15:30 +0000164 }
165
Geremy Condracee5bfd2014-06-11 13:38:45 -0700166 public byte[] generateSignableImage(byte[] image) throws IOException {
167 byte[] attrs = getEncodedAuthenticatedAttributes();
168 byte[] signable = Arrays.copyOf(image, image.length + attrs.length);
169 for (int i=0; i < attrs.length; i++) {
170 signable[i+image.length] = attrs[i];
171 }
172 return signable;
173 }
174
175 public byte[] sign(byte[] image, PrivateKey key) throws Exception {
176 byte[] signable = generateSignableImage(image);
Geremy Condrad66cefd2014-08-14 16:44:31 -0700177 return Utils.sign(key, signable);
Geremy Condracee5bfd2014-06-11 13:38:45 -0700178 }
179
Sami Tolvanen3380f2f2014-10-31 17:18:23 -0700180 public boolean verify(byte[] image) throws Exception {
181 if (length.getValue().intValue() != image.length) {
182 throw new IllegalArgumentException("Invalid image length");
183 }
184
185 byte[] signable = generateSignableImage(image);
186 return Utils.verify(publicKey, signable, signature.getOctets(),
187 algorithmIdentifier);
188 }
189
Geremy Condracee5bfd2014-06-11 13:38:45 -0700190 public ASN1Primitive toASN1Primitive() {
191 ASN1EncodableVector v = new ASN1EncodableVector();
192 v.add(formatVersion);
Paul Lawrence29131b92014-11-13 22:15:30 +0000193 v.add(certificate);
Geremy Condracee5bfd2014-06-11 13:38:45 -0700194 v.add(algorithmIdentifier);
195 v.add(getAuthenticatedAttributes());
196 v.add(signature);
197 return new DERSequence(v);
198 }
199
Paul Lawrence29131b92014-11-13 22:15:30 +0000200 public static int getSignableImageSize(byte[] data) throws Exception {
201 if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8),
202 "ANDROID!".getBytes("US-ASCII"))) {
203 throw new IllegalArgumentException("Invalid image header: missing magic");
204 }
205
206 ByteBuffer image = ByteBuffer.wrap(data);
207 image.order(ByteOrder.LITTLE_ENDIAN);
208
209 image.getLong(); // magic
210 int kernelSize = image.getInt();
211 image.getInt(); // kernel_addr
212 int ramdskSize = image.getInt();
213 image.getInt(); // ramdisk_addr
214 int secondSize = image.getInt();
215 image.getLong(); // second_addr + tags_addr
216 int pageSize = image.getInt();
217
218 int length = pageSize // include the page aligned image header
219 + ((kernelSize + pageSize - 1) / pageSize) * pageSize
220 + ((ramdskSize + pageSize - 1) / pageSize) * pageSize
221 + ((secondSize + pageSize - 1) / pageSize) * pageSize;
222
Hridya Valsaraju9bb9f8f2018-03-23 16:01:03 -0700223 int headerVersion = image.getInt(); // boot image header version
224 if (headerVersion > 0) {
225 image.position(BOOT_IMAGE_HEADER_V1_RECOVERY_DTBO_SIZE_OFFSET);
226 int recoveryDtboLength = image.getInt();
227 length += ((recoveryDtboLength + pageSize - 1) / pageSize) * pageSize;
228
229 image.getLong(); // recovery_dtbo address
Hridya Valsaraju590e5842019-01-24 18:38:07 -0800230 int headerSize = image.getInt();
231 if (headerVersion == 2) {
232 image.position(BOOT_IMAGE_HEADER_V2_DTB_SIZE_OFFSET);
233 int dtbLength = image.getInt();
234 length += ((dtbLength + pageSize - 1) / pageSize) * pageSize;
235 image.getLong(); // dtb address
236 }
237 if (image.position() != headerSize) {
238 throw new IllegalArgumentException(
239 "Invalid image header: invalid header length");
Hridya Valsaraju9bb9f8f2018-03-23 16:01:03 -0700240 }
241 }
242
Paul Lawrence29131b92014-11-13 22:15:30 +0000243 length = ((length + pageSize - 1) / pageSize) * pageSize;
244
245 if (length <= 0) {
246 throw new IllegalArgumentException("Invalid image header: invalid length");
247 }
248
249 return length;
250 }
251
Geremy Condracee5bfd2014-06-11 13:38:45 -0700252 public static void doSignature( String target,
253 String imagePath,
254 String keyPath,
Paul Lawrence29131b92014-11-13 22:15:30 +0000255 String certPath,
Geremy Condracee5bfd2014-06-11 13:38:45 -0700256 String outPath) throws Exception {
Paul Lawrence29131b92014-11-13 22:15:30 +0000257
Geremy Condracee5bfd2014-06-11 13:38:45 -0700258 byte[] image = Utils.read(imagePath);
Paul Lawrence29131b92014-11-13 22:15:30 +0000259 int signableSize = getSignableImageSize(image);
260
261 if (signableSize < image.length) {
262 System.err.println("NOTE: truncating file " + imagePath +
263 " from " + image.length + " to " + signableSize + " bytes");
264 image = Arrays.copyOf(image, signableSize);
265 } else if (signableSize > image.length) {
266 throw new IllegalArgumentException("Invalid image: too short, expected " +
267 signableSize + " bytes");
268 }
269
Geremy Condracee5bfd2014-06-11 13:38:45 -0700270 BootSignature bootsig = new BootSignature(target, image.length);
Paul Lawrence29131b92014-11-13 22:15:30 +0000271
272 X509Certificate cert = Utils.loadPEMCertificate(certPath);
273 bootsig.setCertificate(cert);
274
275 PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath);
276 bootsig.setSignature(bootsig.sign(image, key),
277 Utils.getSignatureAlgorithmIdentifier(key));
278
Geremy Condrad66cefd2014-08-14 16:44:31 -0700279 byte[] encoded_bootsig = bootsig.getEncoded();
280 byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length);
Paul Lawrence29131b92014-11-13 22:15:30 +0000281
282 System.arraycopy(encoded_bootsig, 0, image_with_metadata,
283 image.length, encoded_bootsig.length);
284
Geremy Condrad66cefd2014-08-14 16:44:31 -0700285 Utils.write(image_with_metadata, outPath);
Geremy Condracee5bfd2014-06-11 13:38:45 -0700286 }
287
Sami Tolvanen53e79042015-09-15 10:41:16 +0100288 public static void verifySignature(String imagePath, String certPath) throws Exception {
Sami Tolvanen3380f2f2014-10-31 17:18:23 -0700289 byte[] image = Utils.read(imagePath);
290 int signableSize = getSignableImageSize(image);
291
292 if (signableSize >= image.length) {
293 throw new IllegalArgumentException("Invalid image: not signed");
294 }
295
296 byte[] signature = Arrays.copyOfRange(image, signableSize, image.length);
297 BootSignature bootsig = new BootSignature(signature);
298
Sami Tolvanen53e79042015-09-15 10:41:16 +0100299 if (!certPath.isEmpty()) {
300 System.err.println("NOTE: verifying using public key from " + certPath);
301 bootsig.setCertificate(Utils.loadPEMCertificate(certPath));
302 }
303
Sami Tolvanen3380f2f2014-10-31 17:18:23 -0700304 try {
305 if (bootsig.verify(Arrays.copyOf(image, signableSize))) {
306 System.err.println("Signature is VALID");
307 System.exit(0);
308 } else {
309 System.err.println("Signature is INVALID");
310 }
311 } catch (Exception e) {
312 e.printStackTrace(System.err);
313 }
314 System.exit(1);
315 }
316
Sami Tolvanen40193d92014-11-14 11:00:18 +0000317 /* Example usage for signing a boot image using dev keys:
318 java -cp \
319 ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \
320 classes/com.android.verity.BootSignature \
321 /boot \
322 ../../../out/target/product/$PRODUCT/boot.img \
323 ../../../build/target/product/security/verity.pk8 \
324 ../../../build/target/product/security/verity.x509.pem \
325 /tmp/boot.img.signed
Paul Lawrence29131b92014-11-13 22:15:30 +0000326 */
Geremy Condracee5bfd2014-06-11 13:38:45 -0700327 public static void main(String[] args) throws Exception {
Paul Lawrence29131b92014-11-13 22:15:30 +0000328 Security.addProvider(new BouncyCastleProvider());
Sami Tolvanen3380f2f2014-10-31 17:18:23 -0700329
330 if ("-verify".equals(args[0])) {
Sami Tolvanen53e79042015-09-15 10:41:16 +0100331 String certPath = "";
332
333 if (args.length >= 4 && "-certificate".equals(args[2])) {
334 /* args[3] is the path to a public key certificate */
335 certPath = args[3];
336 }
337
Sami Tolvanen40193d92014-11-14 11:00:18 +0000338 /* args[1] is the path to a signed boot image */
Sami Tolvanen53e79042015-09-15 10:41:16 +0100339 verifySignature(args[1], certPath);
Sami Tolvanen3380f2f2014-10-31 17:18:23 -0700340 } else {
Sami Tolvanen40193d92014-11-14 11:00:18 +0000341 /* args[0] is the target name, typically /boot
342 args[1] is the path to a boot image to sign
343 args[2] is the path to a private key
344 args[3] is the path to the matching public key certificate
345 args[4] is the path where to output the signed boot image
346 */
Sami Tolvanen3380f2f2014-10-31 17:18:23 -0700347 doSignature(args[0], args[1], args[2], args[3], args[4]);
348 }
Geremy Condracee5bfd2014-06-11 13:38:45 -0700349 }
Paul Lawrence29131b92014-11-13 22:15:30 +0000350}