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