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 * 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 86 87 /** 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 */ BootSignature(String target, int length)92 public BootSignature(String target, int length) { 93 this.formatVersion = new ASN1Integer(FORMAT_VERSION); 94 this.target = new DERPrintableString(target); 95 this.length = new ASN1Integer(length); 96 } 97 98 /** 99 * Initializes the object for verifying a signed image file 100 * @param signature Signature footer 101 */ BootSignature(byte[] signature)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 getAuthenticatedAttributes()131 public ASN1Object getAuthenticatedAttributes() { 132 ASN1EncodableVector attrs = new ASN1EncodableVector(); 133 attrs.add(target); 134 attrs.add(length); 135 return new DERSequence(attrs); 136 } 137 getEncodedAuthenticatedAttributes()138 public byte[] getEncodedAuthenticatedAttributes() throws IOException { 139 return getAuthenticatedAttributes().getEncoded(); 140 } 141 getAlgorithmIdentifier()142 public AlgorithmIdentifier getAlgorithmIdentifier() { 143 return algorithmIdentifier; 144 } 145 getPublicKey()146 public PublicKey getPublicKey() { 147 return publicKey; 148 } 149 getSignature()150 public byte[] getSignature() { 151 return signature.getOctets(); 152 } 153 setSignature(byte[] sig, AlgorithmIdentifier algId)154 public void setSignature(byte[] sig, AlgorithmIdentifier algId) { 155 algorithmIdentifier = algId; 156 signature = new DEROctetString(sig); 157 } 158 setCertificate(X509Certificate cert)159 public void setCertificate(X509Certificate cert) 160 throws Exception, IOException, CertificateEncodingException { 161 ASN1InputStream s = new ASN1InputStream(cert.getEncoded()); 162 certificate = s.readObject(); 163 publicKey = cert.getPublicKey(); 164 } 165 generateSignableImage(byte[] image)166 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 sign(byte[] image, PrivateKey key)175 public byte[] sign(byte[] image, PrivateKey key) throws Exception { 176 byte[] signable = generateSignableImage(image); 177 return Utils.sign(key, signable); 178 } 179 verify(byte[] image)180 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 toASN1Primitive()190 public ASN1Primitive toASN1Primitive() { 191 ASN1EncodableVector v = new ASN1EncodableVector(); 192 v.add(formatVersion); 193 v.add(certificate); 194 v.add(algorithmIdentifier); 195 v.add(getAuthenticatedAttributes()); 196 v.add(signature); 197 return new DERSequence(v); 198 } 199 getSignableImageSize(byte[] data)200 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 223 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 230 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"); 240 } 241 } 242 243 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 doSignature( String target, String imagePath, String keyPath, String certPath, String outPath)252 public static void doSignature( String target, 253 String imagePath, 254 String keyPath, 255 String certPath, 256 String outPath) throws Exception { 257 258 byte[] image = Utils.read(imagePath); 259 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 270 BootSignature bootsig = new BootSignature(target, image.length); 271 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 279 byte[] encoded_bootsig = bootsig.getEncoded(); 280 byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length); 281 282 System.arraycopy(encoded_bootsig, 0, image_with_metadata, 283 image.length, encoded_bootsig.length); 284 285 Utils.write(image_with_metadata, outPath); 286 } 287 verifySignature(String imagePath, String certPath)288 public static void verifySignature(String imagePath, String certPath) throws Exception { 289 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 299 if (!certPath.isEmpty()) { 300 System.err.println("NOTE: verifying using public key from " + certPath); 301 bootsig.setCertificate(Utils.loadPEMCertificate(certPath)); 302 } 303 304 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 317 /* 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 326 */ main(String[] args)327 public static void main(String[] args) throws Exception { 328 Security.addProvider(new BouncyCastleProvider()); 329 330 if ("-verify".equals(args[0])) { 331 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 338 /* args[1] is the path to a signed boot image */ 339 verifySignature(args[1], certPath); 340 } else { 341 /* 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 */ 347 doSignature(args[0], args[1], args[2], args[3], args[4]); 348 } 349 } 350 } 351