blob: 71891162766223463d72cfcb8702052535db0d19 [file] [log] [blame]
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -08001/*
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;
Doug Zongker8e5b63d2013-04-10 09:22:02 -070022import java.security.cert.X509Certificate;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080023import java.security.KeyStore;
24import java.security.Key;
25import java.security.PublicKey;
26import java.security.interfaces.RSAPublicKey;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080027
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
Doug Zongker35d9ad52012-07-25 12:08:33 -070036 * @return version number of key. Supported versions are:
Doug Zongker8e5b63d2013-04-10 09:22:02 -070037 * 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
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080041 * @throws Exception if the key has the wrong size or public exponent
Doug Zongker35d9ad52012-07-25 12:08:33 -070042
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080043 */
Doug Zongker8e5b63d2013-04-10 09:22:02 -070044 static int check(RSAPublicKey key, boolean useSHA256) throws Exception {
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080045 BigInteger pubexp = key.getPublicExponent();
46 BigInteger modulus = key.getModulus();
Doug Zongker35d9ad52012-07-25 12:08:33 -070047 int version;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080048
Doug Zongker35d9ad52012-07-25 12:08:33 -070049 if (pubexp.equals(BigInteger.valueOf(3))) {
Doug Zongker8e5b63d2013-04-10 09:22:02 -070050 version = useSHA256 ? 3 : 1;
Doug Zongker35d9ad52012-07-25 12:08:33 -070051 } else if (pubexp.equals(BigInteger.valueOf(65537))) {
Doug Zongker8e5b63d2013-04-10 09:22:02 -070052 version = useSHA256 ? 4 : 2;
Doug Zongker35d9ad52012-07-25 12:08:33 -070053 } else {
54 throw new Exception("Public exponent should be 3 or 65537 but is " +
55 pubexp.toString(10) + ".");
56 }
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080057
Doug Zongker35d9ad52012-07-25 12:08:33 -070058 if (modulus.bitLength() != 2048) {
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080059 throw new Exception("Modulus should be 2048 bits long but is " +
60 modulus.bitLength() + " bits.");
Doug Zongker35d9ad52012-07-25 12:08:33 -070061 }
62
63 return version;
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080064 }
65
66 /**
67 * @param key to output
Doug Zongker35d9ad52012-07-25 12:08:33 -070068 * @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.
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080071 */
Doug Zongker8e5b63d2013-04-10 09:22:02 -070072 static String print(RSAPublicKey key, boolean useSHA256) throws Exception {
73 int version = check(key, useSHA256);
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080074
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
Doug Zongker35d9ad52012-07-25 12:08:33 -070081 if (version > 1) {
82 result.append("v");
83 result.append(Integer.toString(version));
84 result.append(" ");
85 }
86
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -080087 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) {
Doug Zongker5e12d732010-01-29 10:47:38 -0800102 long n = N.mod(B).longValue();
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800103 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) {
Doug Zongker5e12d732010-01-29 10:47:38 -0800116 long rr = RR.mod(B).longValue();
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800117 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");
Doug Zongker8e5b63d2013-04-10 09:22:02 -0700140 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
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800158 RSAPublicKey key = (RSAPublicKey) (cert.getPublicKey());
Doug Zongker8e5b63d2013-04-10 09:22:02 -0700159 check(key, useSHA256);
160 System.out.print(print(key, useSHA256));
The Android Open Source Projectdd7bc332009-03-03 19:32:55 -0800161 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}