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 java.io.FileInputStream;
     20 import java.math.BigInteger;
     21 import java.security.cert.CertificateFactory;
     22 import java.security.cert.X509Certificate;
     23 import java.security.KeyStore;
     24 import java.security.Key;
     25 import java.security.PublicKey;
     26 import java.security.interfaces.RSAPublicKey;
     27 
     28 /**
     29  * Command line tool to extract RSA public keys from X.509 certificates
     30  * and output source code with data initializers for the keys.
     31  * @hide
     32  */
     33 class DumpPublicKey {
     34     /**
     35      * @param key to perform sanity checks on
     36      * @return version number of key.  Supported versions are:
     37      *     1: 2048-bit RSA key with e=3 and SHA-1 hash
     38      *     2: 2048-bit RSA key with e=65537 and SHA-1 hash
     39      *     3: 2048-bit RSA key with e=3 and SHA-256 hash
     40      *     4: 2048-bit RSA key with e=65537 and SHA-256 hash
     41      * @throws Exception if the key has the wrong size or public exponent
     42 
     43      */
     44     static int check(RSAPublicKey key, boolean useSHA256) throws Exception {
     45         BigInteger pubexp = key.getPublicExponent();
     46         BigInteger modulus = key.getModulus();
     47         int version;
     48 
     49         if (pubexp.equals(BigInteger.valueOf(3))) {
     50             version = useSHA256 ? 3 : 1;
     51         } else if (pubexp.equals(BigInteger.valueOf(65537))) {
     52             version = useSHA256 ? 4 : 2;
     53         } else {
     54             throw new Exception("Public exponent should be 3 or 65537 but is " +
     55                                 pubexp.toString(10) + ".");
     56         }
     57 
     58         if (modulus.bitLength() != 2048) {
     59              throw new Exception("Modulus should be 2048 bits long but is " +
     60                         modulus.bitLength() + " bits.");
     61         }
     62 
     63         return version;
     64     }
     65 
     66     /**
     67      * @param key to output
     68      * @return a String representing this public key.  If the key is a
     69      *    version 1 key, the string will be a C initializer; this is
     70      *    not true for newer key versions.
     71      */
     72     static String print(RSAPublicKey key, boolean useSHA256) throws Exception {
     73         int version = check(key, useSHA256);
     74 
     75         BigInteger N = key.getModulus();
     76 
     77         StringBuilder result = new StringBuilder();
     78 
     79         int nwords = N.bitLength() / 32;    // # of 32 bit integers in modulus
     80 
     81         if (version > 1) {
     82             result.append("v");
     83             result.append(Integer.toString(version));
     84             result.append(" ");
     85         }
     86 
     87         result.append("{");
     88         result.append(nwords);
     89 
     90         BigInteger B = BigInteger.valueOf(0x100000000L);  // 2^32
     91         BigInteger N0inv = B.subtract(N.modInverse(B));   // -1 / N[0] mod 2^32
     92 
     93         result.append(",0x");
     94         result.append(N0inv.toString(16));
     95 
     96         BigInteger R = BigInteger.valueOf(2).pow(N.bitLength());
     97         BigInteger RR = R.multiply(R).mod(N);    // 2^4096 mod N
     98 
     99         // Write out modulus as little endian array of integers.
    100         result.append(",{");
    101         for (int i = 0; i < nwords; ++i) {
    102             long n = N.mod(B).longValue();
    103             result.append(n);
    104 
    105             if (i != nwords - 1) {
    106                 result.append(",");
    107             }
    108 
    109             N = N.divide(B);
    110         }
    111         result.append("}");
    112 
    113         // Write R^2 as little endian array of integers.
    114         result.append(",{");
    115         for (int i = 0; i < nwords; ++i) {
    116             long rr = RR.mod(B).longValue();
    117             result.append(rr);
    118 
    119             if (i != nwords - 1) {
    120                 result.append(",");
    121             }
    122 
    123             RR = RR.divide(B);
    124         }
    125         result.append("}");
    126 
    127         result.append("}");
    128         return result.toString();
    129     }
    130 
    131     public static void main(String[] args) {
    132         if (args.length < 1) {
    133             System.err.println("Usage: DumpPublicKey certfile ... > source.c");
    134             System.exit(1);
    135         }
    136         try {
    137             for (int i = 0; i < args.length; i++) {
    138                 FileInputStream input = new FileInputStream(args[i]);
    139                 CertificateFactory cf = CertificateFactory.getInstance("X.509");
    140                 X509Certificate cert = (X509Certificate) cf.generateCertificate(input);
    141 
    142                 boolean useSHA256 = false;
    143                 String sigAlg = cert.getSigAlgName();
    144                 if ("SHA1withRSA".equals(sigAlg) || "MD5withRSA".equals(sigAlg)) {
    145                     // SignApk has historically accepted "MD5withRSA"
    146                     // certificates, but treated them as "SHA1withRSA"
    147                     // anyway.  Continue to do so for backwards
    148                     // compatibility.
    149                   useSHA256 = false;
    150                 } else if ("SHA256withRSA".equals(sigAlg)) {
    151                   useSHA256 = true;
    152                 } else {
    153                   System.err.println(args[i] + ": unsupported signature algorithm \"" +
    154                                      sigAlg + "\"");
    155                   System.exit(1);
    156                 }
    157 
    158                 RSAPublicKey key = (RSAPublicKey) (cert.getPublicKey());
    159                 check(key, useSHA256);
    160                 System.out.print(print(key, useSHA256));
    161                 System.out.println(i < args.length - 1 ? "," : "");
    162             }
    163         } catch (Exception e) {
    164             e.printStackTrace();
    165             System.exit(1);
    166         }
    167         System.exit(0);
    168     }
    169 }
    170