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
17package com.android.dumpkey;
18
19import java.io.FileInputStream;
20import java.math.BigInteger;
21import java.security.cert.CertificateFactory;
22import java.security.cert.X509Certificate;
23import java.security.KeyStore;
24import java.security.Key;
25import java.security.PublicKey;
26import 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 */
33class 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