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      */
checkRSA(RSAPublicKey key, boolean useSHA256)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      */
checkEC(ECPublicKey key)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      */
check(PublicKey key, boolean useSHA256)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      */
printRSA(RSAPublicKey key, boolean useSHA256)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      */
printEC(ECPublicKey key)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 
print(PublicKey key, boolean useSHA256)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 
main(String[] args)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