/* * Copyright (c) 2013, Psiphon Inc. * All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package ca.psiphon.ploggy; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.Security; import java.security.Signature; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.AlgorithmParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.RSAKeyGenParameterSpec; import java.util.Calendar; import java.util.Date; import javax.security.auth.x500.X500Principal; import org.spongycastle.x509.X509V3CertificateGenerator; /** * Helpers generating X509 key pairs and digital signing/verification. * * See "Crypto algorithm specifications" below for current algorithms and key strengths. */ public class X509 { private static final String LOG_TAG = "X509"; public static class KeyMaterial { public final String mCertificate; public final String mPrivateKey; public KeyMaterial(String certificate, String privateKey) { mCertificate = certificate; mPrivateKey = privateKey; } } static { Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); } public static KeyMaterial generateKeyMaterial(String commonName) throws Utils.ApplicationError { try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_TYPE, "SC"); keyPairGenerator.initialize(KEY_SPEC, new SecureRandom()); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // TODO: use http://www.bouncycastle.org/wiki/display/JA1/BC+Version+2+APIs X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator(); // TODO: use common -- e.g., generic Apache web server -- values? See what Tor does. Calendar calendar = Calendar.getInstance(); int currentYear = calendar.get(Calendar.YEAR); calendar.set(currentYear, Calendar.JANUARY, 1); Date validityBeginDate = calendar.getTime(); calendar.add(Calendar.YEAR, 30); Date validityEndDate = calendar.getTime(); X500Principal subjectDN = new X500Principal("CN="+commonName); certificateGenerator.setSerialNumber(BigInteger.valueOf(1)); certificateGenerator.setSubjectDN(subjectDN); certificateGenerator.setIssuerDN(subjectDN); certificateGenerator.setNotBefore(validityBeginDate); certificateGenerator.setNotAfter(validityEndDate); certificateGenerator.setPublicKey(keyPair.getPublic()); certificateGenerator.setSignatureAlgorithm(SIGNATURE_TYPE); X509Certificate x509certificate = certificateGenerator.generate(keyPair.getPrivate(), "SC"); return new KeyMaterial( Utils.encodeBase64(x509certificate.getEncoded()), Utils.encodeBase64(keyPair.getPrivate().getEncoded())); } catch (IllegalArgumentException e) { throw new Utils.ApplicationError(LOG_TAG, e); } catch (GeneralSecurityException e) { throw new Utils.ApplicationError(LOG_TAG, e); } } public static String sign(KeyMaterial keyMaterial, byte[] data) throws Utils.ApplicationError { return sign(keyMaterial.mPrivateKey, data); } public static String sign(String privateKey, byte[] data) throws Utils.ApplicationError { try { Signature signer = java.security.Signature.getInstance(SIGNATURE_TYPE); signer.initSign(decodePrivateKey(privateKey)); signer.update(data); return Utils.encodeBase64(signer.sign()); } catch (GeneralSecurityException e) { throw new Utils.ApplicationError(LOG_TAG, e); } } public static boolean verify(String certificate, byte[] data, String signature) throws Utils.ApplicationError { try { Signature verifier = java.security.Signature.getInstance(SIGNATURE_TYPE); verifier.initVerify(decodeCertificate(certificate)); verifier.update(data); return verifier.verify(Utils.decodeBase64(signature)); } catch (GeneralSecurityException e) { throw new Utils.ApplicationError(LOG_TAG, e); } } public static byte[] getFingerprint(String ...params) throws Utils.ApplicationError { try { MessageDigest hash = MessageDigest.getInstance(FINGERPRINT_ALGORITHM); for (int i = 0; i < params.length; i++) { hash.update(params[i].getBytes("UTF-8")); } return hash.digest(); } catch (UnsupportedEncodingException e) { throw new Utils.ApplicationError(LOG_TAG, e); } catch (GeneralSecurityException e) { throw new Utils.ApplicationError(LOG_TAG, e); } } public static KeyStore makeKeyStore() throws Utils.ApplicationError { try { KeyStore keyStore; keyStore = KeyStore.getInstance("BKS"); keyStore.load(null); return keyStore; } catch (GeneralSecurityException e) { throw new Utils.ApplicationError(LOG_TAG, e); } catch (IOException e) { throw new Utils.ApplicationError(LOG_TAG, e); } } public static void loadKeyMaterial( KeyStore keyStore, KeyMaterial keyMaterial) throws Utils.ApplicationError { loadKeyMaterial(keyStore, keyMaterial.mCertificate, keyMaterial.mPrivateKey); } public static void loadKeyMaterial( KeyStore keyStore, String certificate, String privateKey) throws Utils.ApplicationError { try { X509Certificate x509certificate = decodeCertificate(certificate); String alias = x509certificate.getSubjectDN().getName(); keyStore.setCertificateEntry(alias, x509certificate); if (privateKey != null) { PrivateKey decodedPrivateKey = decodePrivateKey(privateKey); keyStore.setKeyEntry(alias, decodedPrivateKey, null, new X509Certificate[] {x509certificate}); } } catch (IllegalArgumentException e) { // TODO: ... malformed public key (generatePrivate) or invalid Base64 throw new Utils.ApplicationError(LOG_TAG, e); } catch (NullPointerException e) { // TODO: ...getSubjectDN returns null and/or throws NPE on invalid input throw new Utils.ApplicationError(LOG_TAG, e); } catch (GeneralSecurityException e) { throw new Utils.ApplicationError(LOG_TAG, e); } } private static X509Certificate decodeCertificate(String certificate) throws Utils.ApplicationError, CertificateException { CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE); return (X509Certificate)certificateFactory.generateCertificate(new ByteArrayInputStream(Utils.decodeBase64(certificate))); } private static PrivateKey decodePrivateKey(String privateKey) throws Utils.ApplicationError, NoSuchAlgorithmException, InvalidKeySpecException { KeyFactory privateKeyFactory = KeyFactory.getInstance(KEY_TYPE); return privateKeyFactory.generatePrivate(new PKCS8EncodedKeySpec(Utils.decodeBase64(privateKey))); } // Crypto algorithm specifications private static final String FINGERPRINT_ALGORITHM = "SHA-256"; private static final String CERTIFICATE_TYPE = "X.509"; // TODO: currently using RSA instead of ECC due to compatibility issues with Android TLS. //private static final String KEY_TYPE = "EC"; //private static final AlgorithmParameterSpec KEY_SPEC = new ECGenParameterSpec("secp256r1"); //private static final String SIGNATURE_TYPE = "SHA256withECDSA"; private static final String KEY_TYPE = "RSA"; private static final AlgorithmParameterSpec KEY_SPEC = new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4); private static final String SIGNATURE_TYPE = "SHA256withRSA"; }