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 17package com.android.verity; 18 19import java.io.ByteArrayInputStream; 20import java.io.IOException; 21import java.nio.ByteBuffer; 22import java.nio.ByteOrder; 23import java.security.PrivateKey; 24import java.security.PublicKey; 25import java.security.Security; 26import java.security.cert.X509Certificate; 27import java.security.cert.Certificate; 28import java.security.cert.CertificateFactory; 29import java.security.cert.CertificateEncodingException; 30import java.util.Arrays; 31import org.bouncycastle.asn1.ASN1Encodable; 32import org.bouncycastle.asn1.ASN1EncodableVector; 33import org.bouncycastle.asn1.ASN1Integer; 34import org.bouncycastle.asn1.ASN1Object; 35import org.bouncycastle.asn1.ASN1ObjectIdentifier; 36import org.bouncycastle.asn1.ASN1OctetString; 37import org.bouncycastle.asn1.ASN1Primitive; 38import org.bouncycastle.asn1.ASN1Sequence; 39import org.bouncycastle.asn1.ASN1InputStream; 40import org.bouncycastle.asn1.DEROctetString; 41import org.bouncycastle.asn1.DERPrintableString; 42import org.bouncycastle.asn1.DERSequence; 43import org.bouncycastle.asn1.util.ASN1Dump; 44import org.bouncycastle.asn1.x509.AlgorithmIdentifier; 45import 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 64public 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 */ 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 */ 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 120 public ASN1Object getAuthenticatedAttributes() { 121 ASN1EncodableVector attrs = new ASN1EncodableVector(); 122 attrs.add(target); 123 attrs.add(length); 124 return new DERSequence(attrs); 125 } 126 127 public byte[] getEncodedAuthenticatedAttributes() throws IOException { 128 return getAuthenticatedAttributes().getEncoded(); 129 } 130 131 public AlgorithmIdentifier getAlgorithmIdentifier() { 132 return algorithmIdentifier; 133 } 134 135 public PublicKey getPublicKey() { 136 return publicKey; 137 } 138 139 public byte[] getSignature() { 140 return signature.getOctets(); 141 } 142 143 public void setSignature(byte[] sig, AlgorithmIdentifier algId) { 144 algorithmIdentifier = algId; 145 signature = new DEROctetString(sig); 146 } 147 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 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 164 public byte[] sign(byte[] image, PrivateKey key) throws Exception { 165 byte[] signable = generateSignableImage(image); 166 return Utils.sign(key, signable); 167 } 168 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 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 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 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 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 */ 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