Home | History | Annotate | Download | only in tools
      1 /*
      2  * Copyright (C) 2008 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.dumpkey;
     18 
     19 import org.bouncycastle.jce.provider.BouncyCastleProvider;
     20 
     21 import java.io.FileInputStream;
     22 import java.math.BigInteger;
     23 import java.security.cert.CertificateFactory;
     24 import java.security.cert.X509Certificate;
     25 import java.security.KeyStore;
     26 import java.security.Key;
     27 import java.security.PublicKey;
     28 import java.security.Security;
     29 import java.security.interfaces.ECPublicKey;
     30 import java.security.interfaces.RSAPublicKey;
     31 import java.security.spec.ECPoint;
     32 
     33 /**
     34  * Command line tool to extract RSA public keys from X.509 certificates
     35  * and output source code with data initializers for the keys.
     36  * @hide
     37  */
     38 class DumpPublicKey {
     39     /**
     40      * @param key to perform sanity checks on
     41      * @return version number of key.  Supported versions are:
     42      *     1: 2048-bit RSA key with e=3 and SHA-1 hash
     43      *     2: 2048-bit RSA key with e=65537 and SHA-1 hash
     44      *     3: 2048-bit RSA key with e=3 and SHA-256 hash
     45      *     4: 2048-bit RSA key with e=65537 and SHA-256 hash
     46      * @throws Exception if the key has the wrong size or public exponent
     47      */
     48     static int checkRSA(RSAPublicKey key, boolean useSHA256) throws Exception {
     49         BigInteger pubexp = key.getPublicExponent();
     50         BigInteger modulus = key.getModulus();
     51         int version;
     52 
     53         if (pubexp.equals(BigInteger.valueOf(3))) {
     54             version = useSHA256 ? 3 : 1;
     55         } else if (pubexp.equals(BigInteger.valueOf(65537))) {
     56             version = useSHA256 ? 4 : 2;
     57         } else {
     58             throw new Exception("Public exponent should be 3 or 65537 but is " +
     59                                 pubexp.toString(10) + ".");
     60         }
     61 
     62         if (modulus.bitLength() != 2048) {
     63              throw new Exception("Modulus should be 2048 bits long but is " +
     64                         modulus.bitLength() + " bits.");
     65         }
     66 
     67         return version;
     68     }
     69 
     70     /**
     71      * @param key to perform sanity checks on
     72      * @return version number of key.  Supported versions are:
     73      *     5: 256-bit EC key with curve NIST P-256
     74      * @throws Exception if the key has the wrong size or public exponent
     75      */
     76     static int checkEC(ECPublicKey key) throws Exception {
     77         if (key.getParams().getCurve().getField().getFieldSize() != 256) {
     78             throw new Exception("Curve must be NIST P-256");
     79         }
     80 
     81         return 5;
     82     }
     83 
     84     /**
     85      * Perform sanity check on public key.
     86      */
     87     static int check(PublicKey key, boolean useSHA256) throws Exception {
     88         if (key instanceof RSAPublicKey) {
     89             return checkRSA((RSAPublicKey) key, useSHA256);
     90         } else if (key instanceof ECPublicKey) {
     91             if (!useSHA256) {
     92                 throw new Exception("Must use SHA-256 with EC keys!");
     93             }
     94             return checkEC((ECPublicKey) key);
     95         } else {
     96             throw new Exception("Unsupported key class: " + key.getClass().getName());
     97         }
     98     }
     99 
    100     /**
    101      * @param key to output
    102      * @return a String representing this public key.  If the key is a
    103      *    version 1 key, the string will be a C initializer; this is
    104      *    not true for newer key versions.
    105      */
    106     static String printRSA(RSAPublicKey key, boolean useSHA256) throws Exception {
    107         int version = check(key, useSHA256);
    108 
    109         BigInteger N = key.getModulus();
    110 
    111         StringBuilder result = new StringBuilder();
    112 
    113         int nwords = N.bitLength() / 32;    // # of 32 bit integers in modulus
    114 
    115         if (version > 1) {
    116             result.append("v");
    117             result.append(Integer.toString(version));
    118             result.append(" ");
    119         }
    120 
    121         result.append("{");
    122         result.append(nwords);
    123 
    124         BigInteger B = BigInteger.valueOf(0x100000000L);  // 2^32
    125         BigInteger N0inv = B.subtract(N.modInverse(B));   // -1 / N[0] mod 2^32
    126 
    127         result.append(",0x");
    128         result.append(N0inv.toString(16));
    129 
    130         BigInteger R = BigInteger.valueOf(2).pow(N.bitLength());
    131         BigInteger RR = R.multiply(R).mod(N);    // 2^4096 mod N
    132 
    133         // Write out modulus as little endian array of integers.
    134         result.append(",{");
    135         for (int i = 0; i < nwords; ++i) {
    136             long n = N.mod(B).longValue();
    137             result.append(n);
    138 
    139             if (i != nwords - 1) {
    140                 result.append(",");
    141             }
    142 
    143             N = N.divide(B);
    144         }
    145         result.append("}");
    146 
    147         // Write R^2 as little endian array of integers.
    148         result.append(",{");
    149         for (int i = 0; i < nwords; ++i) {
    150             long rr = RR.mod(B).longValue();
    151             result.append(rr);
    152 
    153             if (i != nwords - 1) {
    154                 result.append(",");
    155             }
    156 
    157             RR = RR.divide(B);
    158         }
    159         result.append("}");
    160 
    161         result.append("}");
    162         return result.toString();
    163     }
    164 
    165     /**
    166      * @param key to output
    167      * @return a String representing this public key.  If the key is a
    168      *    version 1 key, the string will be a C initializer; this is
    169      *    not true for newer key versions.
    170      */
    171     static String printEC(ECPublicKey key) throws Exception {
    172         int version = checkEC(key);
    173 
    174         StringBuilder result = new StringBuilder();
    175 
    176         result.append("v");
    177         result.append(Integer.toString(version));
    178         result.append(" ");
    179 
    180         BigInteger X = key.getW().getAffineX();
    181         BigInteger Y = key.getW().getAffineY();
    182         int nbytes = key.getParams().getCurve().getField().getFieldSize() / 8;    // # of 32 bit integers in X coordinate
    183 
    184         result.append("{");
    185         result.append(nbytes);
    186 
    187         BigInteger B = BigInteger.valueOf(0x100L);  // 2^8
    188 
    189         // Write out Y coordinate as array of characters.
    190         result.append(",{");
    191         for (int i = 0; i < nbytes; ++i) {
    192             long n = X.mod(B).longValue();
    193             result.append(n);
    194 
    195             if (i != nbytes - 1) {
    196                 result.append(",");
    197             }
    198 
    199             X = X.divide(B);
    200         }
    201         result.append("}");
    202 
    203         // Write out Y coordinate as array of characters.
    204         result.append(",{");
    205         for (int i = 0; i < nbytes; ++i) {
    206             long n = Y.mod(B).longValue();
    207             result.append(n);
    208 
    209             if (i != nbytes - 1) {
    210                 result.append(",");
    211             }
    212 
    213             Y = Y.divide(B);
    214         }
    215         result.append("}");
    216 
    217         result.append("}");
    218         return result.toString();
    219     }
    220 
    221     static String print(PublicKey key, boolean useSHA256) throws Exception {
    222         if (key instanceof RSAPublicKey) {
    223             return printRSA((RSAPublicKey) key, useSHA256);
    224         } else if (key instanceof ECPublicKey) {
    225             return printEC((ECPublicKey) key);
    226         } else {
    227             throw new Exception("Unsupported key class: " + key.getClass().getName());
    228         }
    229     }
    230 
    231     public static void main(String[] args) {
    232         if (args.length < 1) {
    233             System.err.println("Usage: DumpPublicKey certfile ... > source.c");
    234             System.exit(1);
    235         }
    236         Security.addProvider(new BouncyCastleProvider());
    237         try {
    238             for (int i = 0; i < args.length; i++) {
    239                 FileInputStream input = new FileInputStream(args[i]);
    240                 CertificateFactory cf = CertificateFactory.getInstance("X.509");
    241                 X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
    242 
    243                 boolean useSHA256 = false;
    244                 String sigAlg = cert.getSigAlgName();
    245                 if ("SHA1withRSA".equals(sigAlg) || "MD5withRSA".equals(sigAlg)) {
    246                     // SignApk has historically accepted "MD5withRSA"
    247                     // certificates, but treated them as "SHA1withRSA"
    248                     // anyway.  Continue to do so for backwards
    249                     // compatibility.
    250                   useSHA256 = false;
    251                 } else if ("SHA256withRSA".equals(sigAlg) || "SHA256withECDSA".equals(sigAlg)) {
    252                   useSHA256 = true;
    253                 } else {
    254                   System.err.println(args[i] + ": unsupported signature algorithm \"" +
    255                                      sigAlg + "\"");
    256                   System.exit(1);
    257                 }
    258 
    259                 PublicKey key = cert.getPublicKey();
    260                 check(key, useSHA256);
    261                 System.out.print(print(key, useSHA256));
    262                 System.out.println(i < args.length - 1 ? "," : "");
    263             }
    264         } catch (Exception e) {
    265             e.printStackTrace();
    266             System.exit(1);
    267         }
    268         System.exit(0);
    269     }
    270 }
    271