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 /** 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 */ BootSignature(String target, int length)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 */ BootSignature(byte[] signature)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 getAuthenticatedAttributes()120 public ASN1Object getAuthenticatedAttributes() { 121 ASN1EncodableVector attrs = new ASN1EncodableVector(); 122 attrs.add(target); 123 attrs.add(length); 124 return new DERSequence(attrs); 125 } 126 getEncodedAuthenticatedAttributes()127 public byte[] getEncodedAuthenticatedAttributes() throws IOException { 128 return getAuthenticatedAttributes().getEncoded(); 129 } 130 getAlgorithmIdentifier()131 public AlgorithmIdentifier getAlgorithmIdentifier() { 132 return algorithmIdentifier; 133 } 134 getPublicKey()135 public PublicKey getPublicKey() { 136 return publicKey; 137 } 138 getSignature()139 public byte[] getSignature() { 140 return signature.getOctets(); 141 } 142 setSignature(byte[] sig, AlgorithmIdentifier algId)143 public void setSignature(byte[] sig, AlgorithmIdentifier algId) { 144 algorithmIdentifier = algId; 145 signature = new DEROctetString(sig); 146 } 147 setCertificate(X509Certificate cert)148 public void setCertificate(X509Certificate cert) 149 throws Exception, IOException, CertificateEncodingException { 150 ASN1InputStream s = new ASN1InputStream(cert.getEncoded()); 151 certificate = s.readObject(); 152 } 153 generateSignableImage(byte[] image)154 public byte[] generateSignableImage(byte[] image) throws IOException { 155 byte[] attrs = getEncodedAuthenticatedAttributes(); 156 byte[] signable = Arrays.copyOf(image, image.length + attrs.length); 157 for (int i=0; i < attrs.length; i++) { 158 signable[i+image.length] = attrs[i]; 159 } 160 return signable; 161 } 162 sign(byte[] image, PrivateKey key)163 public byte[] sign(byte[] image, PrivateKey key) throws Exception { 164 byte[] signable = generateSignableImage(image); 165 return Utils.sign(key, signable); 166 } 167 verify(byte[] image)168 public boolean verify(byte[] image) throws Exception { 169 if (length.getValue().intValue() != image.length) { 170 throw new IllegalArgumentException("Invalid image length"); 171 } 172 173 byte[] signable = generateSignableImage(image); 174 return Utils.verify(publicKey, signable, signature.getOctets(), 175 algorithmIdentifier); 176 } 177 toASN1Primitive()178 public ASN1Primitive toASN1Primitive() { 179 ASN1EncodableVector v = new ASN1EncodableVector(); 180 v.add(formatVersion); 181 v.add(certificate); 182 v.add(algorithmIdentifier); 183 v.add(getAuthenticatedAttributes()); 184 v.add(signature); 185 return new DERSequence(v); 186 } 187 getSignableImageSize(byte[] data)188 public static int getSignableImageSize(byte[] data) throws Exception { 189 if (!Arrays.equals(Arrays.copyOfRange(data, 0, 8), 190 "ANDROID!".getBytes("US-ASCII"))) { 191 throw new IllegalArgumentException("Invalid image header: missing magic"); 192 } 193 194 ByteBuffer image = ByteBuffer.wrap(data); 195 image.order(ByteOrder.LITTLE_ENDIAN); 196 197 image.getLong(); // magic 198 int kernelSize = image.getInt(); 199 image.getInt(); // kernel_addr 200 int ramdskSize = image.getInt(); 201 image.getInt(); // ramdisk_addr 202 int secondSize = image.getInt(); 203 image.getLong(); // second_addr + tags_addr 204 int pageSize = image.getInt(); 205 206 int length = pageSize // include the page aligned image header 207 + ((kernelSize + pageSize - 1) / pageSize) * pageSize 208 + ((ramdskSize + pageSize - 1) / pageSize) * pageSize 209 + ((secondSize + pageSize - 1) / pageSize) * pageSize; 210 211 length = ((length + pageSize - 1) / pageSize) * pageSize; 212 213 if (length <= 0) { 214 throw new IllegalArgumentException("Invalid image header: invalid length"); 215 } 216 217 return length; 218 } 219 doSignature( String target, String imagePath, String keyPath, String certPath, String outPath)220 public static void doSignature( String target, 221 String imagePath, 222 String keyPath, 223 String certPath, 224 String outPath) throws Exception { 225 226 byte[] image = Utils.read(imagePath); 227 int signableSize = getSignableImageSize(image); 228 229 if (signableSize < image.length) { 230 System.err.println("NOTE: truncating file " + imagePath + 231 " from " + image.length + " to " + signableSize + " bytes"); 232 image = Arrays.copyOf(image, signableSize); 233 } else if (signableSize > image.length) { 234 throw new IllegalArgumentException("Invalid image: too short, expected " + 235 signableSize + " bytes"); 236 } 237 238 BootSignature bootsig = new BootSignature(target, image.length); 239 240 X509Certificate cert = Utils.loadPEMCertificate(certPath); 241 bootsig.setCertificate(cert); 242 243 PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath); 244 bootsig.setSignature(bootsig.sign(image, key), 245 Utils.getSignatureAlgorithmIdentifier(key)); 246 247 byte[] encoded_bootsig = bootsig.getEncoded(); 248 byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length); 249 250 System.arraycopy(encoded_bootsig, 0, image_with_metadata, 251 image.length, encoded_bootsig.length); 252 253 Utils.write(image_with_metadata, outPath); 254 } 255 verifySignature(String imagePath)256 public static void verifySignature(String imagePath) throws Exception { 257 byte[] image = Utils.read(imagePath); 258 int signableSize = getSignableImageSize(image); 259 260 if (signableSize >= image.length) { 261 throw new IllegalArgumentException("Invalid image: not signed"); 262 } 263 264 byte[] signature = Arrays.copyOfRange(image, signableSize, image.length); 265 BootSignature bootsig = new BootSignature(signature); 266 267 try { 268 if (bootsig.verify(Arrays.copyOf(image, signableSize))) { 269 System.err.println("Signature is VALID"); 270 System.exit(0); 271 } else { 272 System.err.println("Signature is INVALID"); 273 } 274 } catch (Exception e) { 275 e.printStackTrace(System.err); 276 } 277 System.exit(1); 278 } 279 280 /* Example usage for signing a boot image using dev keys: 281 java -cp \ 282 ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \ 283 classes/com.android.verity.BootSignature \ 284 /boot \ 285 ../../../out/target/product/$PRODUCT/boot.img \ 286 ../../../build/target/product/security/verity.pk8 \ 287 ../../../build/target/product/security/verity.x509.pem \ 288 /tmp/boot.img.signed 289 */ main(String[] args)290 public static void main(String[] args) throws Exception { 291 Security.addProvider(new BouncyCastleProvider()); 292 293 if ("-verify".equals(args[0])) { 294 /* args[1] is the path to a signed boot image */ 295 verifySignature(args[1]); 296 } else { 297 /* args[0] is the target name, typically /boot 298 args[1] is the path to a boot image to sign 299 args[2] is the path to a private key 300 args[3] is the path to the matching public key certificate 301 args[4] is the path where to output the signed boot image 302 */ 303 doSignature(args[0], args[1], args[2], args[3], args[4]); 304 } 305 } 306 } 307