1 /* 2 * Copyright (C) 2014 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.verity; 18 19 import java.lang.reflect.Constructor; 20 import java.io.File; 21 import java.io.ByteArrayInputStream; 22 import java.io.Console; 23 import java.io.FileInputStream; 24 import java.io.FileOutputStream; 25 import java.io.InputStreamReader; 26 import java.io.IOException; 27 import java.security.GeneralSecurityException; 28 import java.security.Key; 29 import java.security.PrivateKey; 30 import java.security.PublicKey; 31 import java.security.KeyFactory; 32 import java.security.Provider; 33 import java.security.Security; 34 import java.security.Signature; 35 import java.security.cert.Certificate; 36 import java.security.cert.CertificateFactory; 37 import java.security.cert.X509Certificate; 38 import java.security.spec.ECPublicKeySpec; 39 import java.security.spec.ECPrivateKeySpec; 40 import java.security.spec.X509EncodedKeySpec; 41 import java.security.spec.PKCS8EncodedKeySpec; 42 import java.security.spec.InvalidKeySpecException; 43 import java.util.Arrays; 44 import java.util.HashMap; 45 import java.util.Map; 46 47 import javax.crypto.Cipher; 48 import javax.crypto.EncryptedPrivateKeyInfo; 49 import javax.crypto.SecretKeyFactory; 50 import javax.crypto.spec.PBEKeySpec; 51 52 import org.bouncycastle.asn1.ASN1InputStream; 53 import org.bouncycastle.asn1.ASN1ObjectIdentifier; 54 import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; 55 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; 56 import org.bouncycastle.asn1.x509.AlgorithmIdentifier; 57 import org.bouncycastle.asn1.x9.X9ObjectIdentifiers; 58 import org.bouncycastle.util.encoders.Base64; 59 60 public class Utils { 61 62 private static final Map<String, String> ID_TO_ALG; 63 private static final Map<String, String> ALG_TO_ID; 64 65 static { 66 ID_TO_ALG = new HashMap<String, String>(); 67 ALG_TO_ID = new HashMap<String, String>(); 68 69 ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA256.getId(), "SHA256withECDSA"); 70 ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA384.getId(), "SHA384withECDSA"); 71 ID_TO_ALG.put(X9ObjectIdentifiers.ecdsa_with_SHA512.getId(), "SHA512withECDSA"); 72 ID_TO_ALG.put(PKCSObjectIdentifiers.sha1WithRSAEncryption.getId(), "SHA1withRSA"); 73 ID_TO_ALG.put(PKCSObjectIdentifiers.sha256WithRSAEncryption.getId(), "SHA256withRSA"); 74 ID_TO_ALG.put(PKCSObjectIdentifiers.sha512WithRSAEncryption.getId(), "SHA512withRSA"); 75 76 ALG_TO_ID.put("SHA256withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA256.getId()); 77 ALG_TO_ID.put("SHA384withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA384.getId()); 78 ALG_TO_ID.put("SHA512withECDSA", X9ObjectIdentifiers.ecdsa_with_SHA512.getId()); 79 ALG_TO_ID.put("SHA1withRSA", PKCSObjectIdentifiers.sha1WithRSAEncryption.getId()); 80 ALG_TO_ID.put("SHA256withRSA", PKCSObjectIdentifiers.sha256WithRSAEncryption.getId()); 81 ALG_TO_ID.put("SHA512withRSA", PKCSObjectIdentifiers.sha512WithRSAEncryption.getId()); 82 } 83 84 private static void loadProviderIfNecessary(String providerClassName) { 85 if (providerClassName == null) { 86 return; 87 } 88 89 final Class<?> klass; 90 try { 91 final ClassLoader sysLoader = ClassLoader.getSystemClassLoader(); 92 if (sysLoader != null) { 93 klass = sysLoader.loadClass(providerClassName); 94 } else { 95 klass = Class.forName(providerClassName); 96 } 97 } catch (ClassNotFoundException e) { 98 e.printStackTrace(); 99 System.exit(1); 100 return; 101 } 102 103 Constructor<?> constructor = null; 104 for (Constructor<?> c : klass.getConstructors()) { 105 if (c.getParameterTypes().length == 0) { 106 constructor = c; 107 break; 108 } 109 } 110 if (constructor == null) { 111 System.err.println("No zero-arg constructor found for " + providerClassName); 112 System.exit(1); 113 return; 114 } 115 116 final Object o; 117 try { 118 o = constructor.newInstance(); 119 } catch (Exception e) { 120 e.printStackTrace(); 121 System.exit(1); 122 return; 123 } 124 if (!(o instanceof Provider)) { 125 System.err.println("Not a Provider class: " + providerClassName); 126 System.exit(1); 127 } 128 129 Security.insertProviderAt((Provider) o, 1); 130 } 131 132 static byte[] pemToDer(String pem) throws Exception { 133 pem = pem.replaceAll("^-.*", ""); 134 String base64_der = pem.replaceAll("-.*$", ""); 135 return Base64.decode(base64_der); 136 } 137 138 private static PKCS8EncodedKeySpec decryptPrivateKey(byte[] encryptedPrivateKey) 139 throws GeneralSecurityException { 140 EncryptedPrivateKeyInfo epkInfo; 141 try { 142 epkInfo = new EncryptedPrivateKeyInfo(encryptedPrivateKey); 143 } catch (IOException ex) { 144 // Probably not an encrypted key. 145 return null; 146 } 147 148 char[] password = System.console().readPassword("Password for the private key file: "); 149 150 SecretKeyFactory skFactory = SecretKeyFactory.getInstance(epkInfo.getAlgName()); 151 Key key = skFactory.generateSecret(new PBEKeySpec(password)); 152 Arrays.fill(password, '\0'); 153 154 Cipher cipher = Cipher.getInstance(epkInfo.getAlgName()); 155 cipher.init(Cipher.DECRYPT_MODE, key, epkInfo.getAlgParameters()); 156 157 try { 158 return epkInfo.getKeySpec(cipher); 159 } catch (InvalidKeySpecException ex) { 160 System.err.println("Password may be bad."); 161 throw ex; 162 } 163 } 164 165 static PrivateKey loadDERPrivateKey(byte[] der) throws Exception { 166 PKCS8EncodedKeySpec spec = decryptPrivateKey(der); 167 168 if (spec == null) { 169 spec = new PKCS8EncodedKeySpec(der); 170 } 171 172 ASN1InputStream bIn = new ASN1InputStream(new ByteArrayInputStream(spec.getEncoded())); 173 PrivateKeyInfo pki = PrivateKeyInfo.getInstance(bIn.readObject()); 174 String algOid = pki.getPrivateKeyAlgorithm().getAlgorithm().getId(); 175 176 return KeyFactory.getInstance(algOid).generatePrivate(spec); 177 } 178 179 static PrivateKey loadPEMPrivateKey(byte[] pem) throws Exception { 180 byte[] der = pemToDer(new String(pem)); 181 return loadDERPrivateKey(der); 182 } 183 184 static PrivateKey loadPEMPrivateKeyFromFile(String keyFname) throws Exception { 185 return loadPEMPrivateKey(read(keyFname)); 186 } 187 188 static PrivateKey loadDERPrivateKeyFromFile(String keyFname) throws Exception { 189 return loadDERPrivateKey(read(keyFname)); 190 } 191 192 static PublicKey loadDERPublicKey(byte[] der) throws Exception { 193 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(der); 194 KeyFactory factory = KeyFactory.getInstance("RSA"); 195 return factory.generatePublic(publicKeySpec); 196 } 197 198 static PublicKey loadPEMPublicKey(byte[] pem) throws Exception { 199 byte[] der = pemToDer(new String(pem)); 200 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(der); 201 KeyFactory factory = KeyFactory.getInstance("RSA"); 202 return factory.generatePublic(publicKeySpec); 203 } 204 205 static PublicKey loadPEMPublicKeyFromFile(String keyFname) throws Exception { 206 return loadPEMPublicKey(read(keyFname)); 207 } 208 209 static PublicKey loadDERPublicKeyFromFile(String keyFname) throws Exception { 210 return loadDERPublicKey(read(keyFname)); 211 } 212 213 static X509Certificate loadPEMCertificate(String fname) throws Exception { 214 try (FileInputStream fis = new FileInputStream(fname)) { 215 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 216 return (X509Certificate) cf.generateCertificate(fis); 217 } 218 } 219 220 private static String getSignatureAlgorithm(Key key) throws Exception { 221 if ("EC".equals(key.getAlgorithm())) { 222 int curveSize; 223 KeyFactory factory = KeyFactory.getInstance("EC"); 224 225 if (key instanceof PublicKey) { 226 ECPublicKeySpec spec = factory.getKeySpec(key, ECPublicKeySpec.class); 227 curveSize = spec.getParams().getCurve().getField().getFieldSize(); 228 } else if (key instanceof PrivateKey) { 229 ECPrivateKeySpec spec = factory.getKeySpec(key, ECPrivateKeySpec.class); 230 curveSize = spec.getParams().getCurve().getField().getFieldSize(); 231 } else { 232 throw new InvalidKeySpecException(); 233 } 234 235 if (curveSize <= 256) { 236 return "SHA256withECDSA"; 237 } else if (curveSize <= 384) { 238 return "SHA384withECDSA"; 239 } else { 240 return "SHA512withECDSA"; 241 } 242 } else if ("RSA".equals(key.getAlgorithm())) { 243 return "SHA256withRSA"; 244 } else { 245 throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm()); 246 } 247 } 248 249 static AlgorithmIdentifier getSignatureAlgorithmIdentifier(Key key) throws Exception { 250 String id = ALG_TO_ID.get(getSignatureAlgorithm(key)); 251 252 if (id == null) { 253 throw new IllegalArgumentException("Unsupported key type " + key.getAlgorithm()); 254 } 255 256 return new AlgorithmIdentifier(new ASN1ObjectIdentifier(id)); 257 } 258 259 static boolean verify(PublicKey key, byte[] input, byte[] signature, 260 AlgorithmIdentifier algId) throws Exception { 261 String algName = ID_TO_ALG.get(algId.getAlgorithm().getId()); 262 263 if (algName == null) { 264 throw new IllegalArgumentException("Unsupported algorithm " + algId.getAlgorithm()); 265 } 266 267 Signature verifier = Signature.getInstance(algName); 268 verifier.initVerify(key); 269 verifier.update(input); 270 271 return verifier.verify(signature); 272 } 273 274 static byte[] sign(PrivateKey privateKey, byte[] input) throws Exception { 275 Signature signer = Signature.getInstance(getSignatureAlgorithm(privateKey)); 276 signer.initSign(privateKey); 277 signer.update(input); 278 return signer.sign(); 279 } 280 281 static byte[] read(String fname) throws Exception { 282 long offset = 0; 283 File f = new File(fname); 284 long length = f.length(); 285 byte[] image = new byte[(int)length]; 286 FileInputStream fis = new FileInputStream(f); 287 while (offset < length) { 288 offset += fis.read(image, (int)offset, (int)(length - offset)); 289 } 290 fis.close(); 291 return image; 292 } 293 294 static void write(byte[] data, String fname) throws Exception{ 295 FileOutputStream out = new FileOutputStream(fname); 296 out.write(data); 297 out.close(); 298 } 299 } 300