blob: 3eb139842f8427d39bf423959f909a723254c53b [file] [log] [blame]
Mattias Nissler03b72b02016-02-24 14:45:06 +01001/*
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}