| 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 |  | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 19 | import org.bouncycastle.jce.provider.BouncyCastleProvider; | 
 | 20 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 21 | import java.io.FileInputStream; | 
 | 22 | import java.math.BigInteger; | 
 | 23 | import java.security.cert.CertificateFactory; | 
| Doug Zongker | 8e5b63d | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 24 | import java.security.cert.X509Certificate; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 25 | import java.security.KeyStore; | 
 | 26 | import java.security.Key; | 
 | 27 | import java.security.PublicKey; | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 28 | import java.security.Security; | 
 | 29 | import java.security.interfaces.ECPublicKey; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 30 | import java.security.interfaces.RSAPublicKey; | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 31 | import java.security.spec.ECPoint; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 41 |      * @return version number of key.  Supported versions are: | 
| Doug Zongker | 8e5b63d | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 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 | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 46 |      * @throws Exception if the key has the wrong size or public exponent | 
 | 47 |      */ | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 48 |     static int checkRSA(RSAPublicKey key, boolean useSHA256) throws Exception { | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 49 |         BigInteger pubexp = key.getPublicExponent(); | 
 | 50 |         BigInteger modulus = key.getModulus(); | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 51 |         int version; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 52 |  | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 53 |         if (pubexp.equals(BigInteger.valueOf(3))) { | 
| Doug Zongker | 8e5b63d | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 54 |             version = useSHA256 ? 3 : 1; | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 55 |         } else if (pubexp.equals(BigInteger.valueOf(65537))) { | 
| Doug Zongker | 8e5b63d | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 56 |             version = useSHA256 ? 4 : 2; | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 57 |         } else { | 
 | 58 |             throw new Exception("Public exponent should be 3 or 65537 but is " + | 
 | 59 |                                 pubexp.toString(10) + "."); | 
 | 60 |         } | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 61 |  | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 62 |         if (modulus.bitLength() != 2048) { | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 63 |              throw new Exception("Modulus should be 2048 bits long but is " + | 
 | 64 |                         modulus.bitLength() + " bits."); | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 65 |         } | 
 | 66 |  | 
 | 67 |         return version; | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 68 |     } | 
 | 69 |  | 
 | 70 |     /** | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 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 |     /** | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 101 |      * @param key to output | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 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. | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 105 |      */ | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 106 |     static String printRSA(RSAPublicKey key, boolean useSHA256) throws Exception { | 
| Doug Zongker | 8e5b63d | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 107 |         int version = check(key, useSHA256); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 |  | 
| Doug Zongker | 35d9ad5 | 2012-07-25 12:08:33 -0700 | [diff] [blame] | 115 |         if (version > 1) { | 
 | 116 |             result.append("v"); | 
 | 117 |             result.append(Integer.toString(version)); | 
 | 118 |             result.append(" "); | 
 | 119 |         } | 
 | 120 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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) { | 
| Doug Zongker | 5e12d73 | 2010-01-29 10:47:38 -0800 | [diff] [blame] | 136 |             long n = N.mod(B).longValue(); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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) { | 
| Doug Zongker | 5e12d73 | 2010-01-29 10:47:38 -0800 | [diff] [blame] | 150 |             long rr = RR.mod(B).longValue(); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 |  | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 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 |  | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 |         } | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 236 |         Security.addProvider(new BouncyCastleProvider()); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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"); | 
| Doug Zongker | 8e5b63d | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 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; | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 251 |                 } else if ("SHA256withRSA".equals(sigAlg) || "SHA256withECDSA".equals(sigAlg)) { | 
| Doug Zongker | 8e5b63d | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 252 |                   useSHA256 = true; | 
 | 253 |                 } else { | 
 | 254 |                   System.err.println(args[i] + ": unsupported signature algorithm \"" + | 
 | 255 |                                      sigAlg + "\""); | 
 | 256 |                   System.exit(1); | 
 | 257 |                 } | 
 | 258 |  | 
| Kenny Root | db0850c | 2013-10-08 12:52:07 -0700 | [diff] [blame] | 259 |                 PublicKey key = cert.getPublicKey(); | 
| Doug Zongker | 8e5b63d | 2013-04-10 09:22:02 -0700 | [diff] [blame] | 260 |                 check(key, useSHA256); | 
 | 261 |                 System.out.print(print(key, useSHA256)); | 
| The Android Open Source Project | dd7bc33 | 2009-03-03 19:32:55 -0800 | [diff] [blame] | 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 | } |