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 */ 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 } 153 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 163 public byte[] sign(byte[] image, PrivateKey key) throws Exception { 164 byte[] signable = generateSignableImage(image); 165 return Utils.sign(key, signable); 166 } 167 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 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 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 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 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 */ 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