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 org.bouncycastle.jce.provider.BouncyCastleProvider;
20
21import java.io.FileInputStream;
22import java.math.BigInteger;
23import java.security.cert.CertificateFactory;
24import java.security.cert.X509Certificate;
25import java.security.KeyStore;
26import java.security.Key;
27import java.security.PublicKey;
28import java.security.Security;
29import java.security.interfaces.ECPublicKey;
30import java.security.interfaces.RSAPublicKey;
31import 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 */
38class 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