Home | History | Annotate | Download | only in verity
      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         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