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