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     }
    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