| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 java.io.FileInputStream; | 
 | 20 | import java.math.BigInteger; | 
 | 21 | import java.security.cert.CertificateFactory; | 
| Doug Zongker | 515e163 | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 22 | import java.security.cert.X509Certificate; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 23 | import java.security.KeyStore; | 
 | 24 | import java.security.Key; | 
 | 25 | import java.security.PublicKey; | 
 | 26 | import java.security.interfaces.RSAPublicKey; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 |  */ | 
 | 33 | class DumpPublicKey { | 
 | 34 |     /** | 
 | 35 |      * @param key to perform sanity checks on | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 36 |      * @return version number of key.  Supported versions are: | 
| Doug Zongker | 515e163 | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 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 | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 41 |      * @throws Exception if the key has the wrong size or public exponent | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 42 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 43 |      */ | 
| Doug Zongker | 515e163 | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 44 |     static int check(RSAPublicKey key, boolean useSHA256) throws Exception { | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 45 |         BigInteger pubexp = key.getPublicExponent(); | 
 | 46 |         BigInteger modulus = key.getModulus(); | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 47 |         int version; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 48 |  | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 49 |         if (pubexp.equals(BigInteger.valueOf(3))) { | 
| Doug Zongker | 515e163 | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 50 |             version = useSHA256 ? 3 : 1; | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 51 |         } else if (pubexp.equals(BigInteger.valueOf(65537))) { | 
| Doug Zongker | 515e163 | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 52 |             version = useSHA256 ? 4 : 2; | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 53 |         } else { | 
 | 54 |             throw new Exception("Public exponent should be 3 or 65537 but is " + | 
 | 55 |                                 pubexp.toString(10) + "."); | 
 | 56 |         } | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 57 |  | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 58 |         if (modulus.bitLength() != 2048) { | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 59 |              throw new Exception("Modulus should be 2048 bits long but is " + | 
 | 60 |                         modulus.bitLength() + " bits."); | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 61 |         } | 
 | 62 |  | 
 | 63 |         return version; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 64 |     } | 
 | 65 |  | 
 | 66 |     /** | 
 | 67 |      * @param key to output | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 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. | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 71 |      */ | 
| Doug Zongker | 515e163 | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 72 |     static String print(RSAPublicKey key, boolean useSHA256) throws Exception { | 
 | 73 |         int version = check(key, useSHA256); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 |  | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 81 |         if (version > 1) { | 
 | 82 |             result.append("v"); | 
 | 83 |             result.append(Integer.toString(version)); | 
 | 84 |             result.append(" "); | 
 | 85 |         } | 
 | 86 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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) { | 
| Doug Zongker | 5e12d73 | 2010-01-29 10:47:38 -0800 | [diff] [blame] | 102 |             long n = N.mod(B).longValue(); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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) { | 
| Doug Zongker | 5e12d73 | 2010-01-29 10:47:38 -0800 | [diff] [blame] | 116 |             long rr = RR.mod(B).longValue(); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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"); | 
| Doug Zongker | 515e163 | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 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 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 158 |                 RSAPublicKey key = (RSAPublicKey) (cert.getPublicKey()); | 
| Doug Zongker | 515e163 | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 159 |                 check(key, useSHA256); | 
 | 160 |                 System.out.print(print(key, useSHA256)); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 | } |