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 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} 271