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 publicKey = cert.getPublicKey(); 153 } 154 generateSignableImage(byte[] image)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]; 160 } 161 return signable; 162 } 163 sign(byte[] image, PrivateKey key)164 public byte[] sign(byte[] image, PrivateKey key) throws Exception { 165 byte[] signable = generateSignableImage(image); 166 return Utils.sign(key, signable); 167 } 168 verify(byte[] image)169 public boolean verify(byte[] image) throws Exception { 170 if (length.getValue().intValue() != image.length) { 171 throw new IllegalArgumentException("Invalid image length"); 172 } 173 174 byte[] signable = generateSignableImage(image); 175 return Utils.verify(publicKey, signable, signature.getOctets(), 176 algorithmIdentifier); 177 } 178 toASN1Primitive()179 public ASN1Primitive toASN1Primitive() { 180 ASN1EncodableVector v = new ASN1EncodableVector(); 181 v.add(formatVersion); 182 v.add(certificate); 183 v.add(algorithmIdentifier); 184 v.add(getAuthenticatedAttributes()); 185 v.add(signature); 186 return new DERSequence(v); 187 } 188 getSignableImageSize(byte[] data)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"); 193 } 194 195 ByteBuffer image = ByteBuffer.wrap(data); 196 image.order(ByteOrder.LITTLE_ENDIAN); 197 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(); 206 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; 211 212 length = ((length + pageSize - 1) / pageSize) * pageSize; 213 214 if (length <= 0) { 215 throw new IllegalArgumentException("Invalid image header: invalid length"); 216 } 217 218 return length; 219 } 220 doSignature( String target, String imagePath, String keyPath, String certPath, String outPath)221 public static void doSignature( String target, 222 String imagePath, 223 String keyPath, 224 String certPath, 225 String outPath) throws Exception { 226 227 byte[] image = Utils.read(imagePath); 228 int signableSize = getSignableImageSize(image); 229 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"); 237 } 238 239 BootSignature bootsig = new BootSignature(target, image.length); 240 241 X509Certificate cert = Utils.loadPEMCertificate(certPath); 242 bootsig.setCertificate(cert); 243 244 PrivateKey key = Utils.loadDERPrivateKeyFromFile(keyPath); 245 bootsig.setSignature(bootsig.sign(image, key), 246 Utils.getSignatureAlgorithmIdentifier(key)); 247 248 byte[] encoded_bootsig = bootsig.getEncoded(); 249 byte[] image_with_metadata = Arrays.copyOf(image, image.length + encoded_bootsig.length); 250 251 System.arraycopy(encoded_bootsig, 0, image_with_metadata, 252 image.length, encoded_bootsig.length); 253 254 Utils.write(image_with_metadata, outPath); 255 } 256 verifySignature(String imagePath, String certPath)257 public static void verifySignature(String imagePath, String certPath) throws Exception { 258 byte[] image = Utils.read(imagePath); 259 int signableSize = getSignableImageSize(image); 260 261 if (signableSize >= image.length) { 262 throw new IllegalArgumentException("Invalid image: not signed"); 263 } 264 265 byte[] signature = Arrays.copyOfRange(image, signableSize, image.length); 266 BootSignature bootsig = new BootSignature(signature); 267 268 if (!certPath.isEmpty()) { 269 System.err.println("NOTE: verifying using public key from " + certPath); 270 bootsig.setCertificate(Utils.loadPEMCertificate(certPath)); 271 } 272 273 try { 274 if (bootsig.verify(Arrays.copyOf(image, signableSize))) { 275 System.err.println("Signature is VALID"); 276 System.exit(0); 277 } else { 278 System.err.println("Signature is INVALID"); 279 } 280 } catch (Exception e) { 281 e.printStackTrace(System.err); 282 } 283 System.exit(1); 284 } 285 286 /* Example usage for signing a boot image using dev keys: 287 java -cp \ 288 ../../../out/host/common/obj/JAVA_LIBRARIES/BootSignature_intermediates/ \ 289 classes/com.android.verity.BootSignature \ 290 /boot \ 291 ../../../out/target/product/$PRODUCT/boot.img \ 292 ../../../build/target/product/security/verity.pk8 \ 293 ../../../build/target/product/security/verity.x509.pem \ 294 /tmp/boot.img.signed 295 */ main(String[] args)296 public static void main(String[] args) throws Exception { 297 Security.addProvider(new BouncyCastleProvider()); 298 299 if ("-verify".equals(args[0])) { 300 String certPath = ""; 301 302 if (args.length >= 4 && "-certificate".equals(args[2])) { 303 /* args[3] is the path to a public key certificate */ 304 certPath = args[3]; 305 } 306 307 /* args[1] is the path to a signed boot image */ 308 verifySignature(args[1], certPath); 309 } else { 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 315 */ 316 doSignature(args[0], args[1], args[2], args[3], args[4]); 317 } 318 } 319 } 320