/* * Copyright (C) 2015 - Holy Lobster * * Nuntius 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 2 of the License, or * (at your option) any later version. * * Nuntius 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 Nuntius. If not, see <http://www.gnu.org/licenses/>. */ package org.holylobster.nuntius.utils; import android.util.Log; import org.holylobster.nuntius.activity.SettingsActivity; import org.spongycastle.asn1.x500.X500Name; import org.spongycastle.cert.X509CertificateHolder; import org.spongycastle.cert.X509v3CertificateBuilder; import org.spongycastle.cert.jcajce.JcaX509CertificateConverter; import org.spongycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.operator.ContentSigner; import org.spongycastle.operator.jcajce.JcaContentSignerBuilder; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.net.Socket; import java.security.Key; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.security.PrivateKey; import java.security.Provider; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; /** * Created by andreac on 25/03/15. */ public class SslUtils { private static final Provider PROVIDER = new BouncyCastleProvider(); /** Current time minus 1 year, just in case software clock goes back due to time synchronization */ static final Date NOT_BEFORE = new Date(System.currentTimeMillis() - 86400000L * 365); /** The maximum possible value in X.509 specification: 9999-12-31 23:59:59 */ static final Date NOT_AFTER = new Date(253402300799000L); private static final char[] pwd = null; private static final String TAG = SslUtils.class.getSimpleName(); public static SSLContext getSSLContext(File myTrustStore) throws Exception { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init( new KeyManager[] { new MyX509KeyManager() }, new TrustManager[] { new MyX509TrustManager(myTrustStore) }, null ); return sslContext; } private static class MyX509TrustManager implements X509TrustManager { private final File trustStorePath; private X509TrustManager trustManager; public MyX509TrustManager(File trustStorePath) throws Exception { this.trustStorePath = trustStorePath; reloadTrustManager(); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { Log.i(TAG, "Checking client certificate chain " + chain); try { trustManager.checkClientTrusted(chain, authType); } catch (CertificateException e) { if (pairAndTrust(chain[0])) { trustManager.checkClientTrusted(chain, authType); } else { throw e; } } } private boolean pairAndTrust(X509Certificate cert) { PairingData pairingData = SettingsActivity.getCurrentPairingData(); if (pairingData == null) { return false; } try { byte[] fingerprint = fingerprint(cert); byte[] bytes = pairingData.getFingerprint(); if (Arrays.equals(fingerprint, bytes)) { Log.i(TAG, "The fingerprint matches!"); trustCertificate(cert, pairingData.getDeviceLabel()); reloadTrustManager(); return true; } else { Log.e(TAG, "The fingerprint does NOT match!"); } } catch (Exception e) { Log.e(TAG, "Error trying to pair a new certificate", e); } return false; } private void trustCertificate(Certificate cert, String deviceLabel) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException { KeyStore ts = getKeyStore(); Log.i(TAG, "Adding certificate ID " + deviceLabel + " to Trust store (" + trustStorePath + "): " + cert); ts.setCertificateEntry(deviceLabel, cert); ts.store(new FileOutputStream(trustStorePath), null); } private byte[] fingerprint(X509Certificate cert) throws CertificateEncodingException, NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA1"); md.reset(); return md.digest(cert.getEncoded()); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkServerTrusted(chain, authType); } @Override public X509Certificate[] getAcceptedIssuers() { return trustManager.getAcceptedIssuers(); } private void reloadTrustManager() throws Exception { KeyStore ts = getKeyStore(); Enumeration<String> aliases = ts.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); Log.i(TAG, "Trusted certificate " + alias + ": " + ts.getCertificate(alias)); } TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ts); TrustManager tms[] = tmf.getTrustManagers(); for (int i = 0; i < tms.length; i++) { if (tms[i] instanceof X509TrustManager) { trustManager = (X509TrustManager) tms[i]; return; } } throw new NoSuchAlgorithmException("No X509TrustManager in TrustManagerFactory"); } private KeyStore getKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType()); if (trustStorePath.exists()) { Log.i(TAG, "Loading certificates from Trust store (" + trustStorePath + ")"); InputStream in = null; try { in = new FileInputStream(trustStorePath); ts.load(in, null); } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } } } else { Log.i(TAG, "Creating custom Trust Manager " + trustStorePath.getAbsolutePath()); ts.load(null, null); ts.store(new FileOutputStream(trustStorePath), null); } return ts; } } /** * Always returns the only key associated to "nuntius" alias */ private static class MyX509KeyManager implements X509KeyManager { @Override public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) { return null; } @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { return "nuntius"; } @Override public X509Certificate[] getCertificateChain(String alias) { Log.i(TAG, "getCertificateChain for " + alias); try { KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); return new X509Certificate[] { (X509Certificate) ks.getCertificate(alias) }; } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { Log.e(TAG, "Error during getCertificateChain(" + alias + ")", e); } return null; } @Override public String[] getClientAliases(String keyType, Principal[] issuers) { return new String[0]; } @Override public String[] getServerAliases(String keyType, Principal[] issuers) { return new String[] { "nuntius" }; } @Override public PrivateKey getPrivateKey(String alias) { Log.i(TAG, "getPrivateKey for " + alias); try { KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); return (PrivateKey) ks.getKey(alias, null); } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException | UnrecoverableKeyException e) { Log.e(TAG, "Error during getPrivateKey(" + alias + ")", e); } return null; } } public static void generateSelfSignedCertificate() throws Exception { String alias = "nuntius"; KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); Enumeration<String> aliases = ks.aliases(); boolean found = false; while (aliases.hasMoreElements()) { String currentAlias = aliases.nextElement(); if (alias.equals(currentAlias)) { found = true; Log.i(TAG, "Self Signed Certificate found in keystore"); Key key = ks.getKey(alias, pwd); Log.i(TAG, "Key: " + key); Certificate certificate = ks.getCertificate(alias); Log.i(TAG, "Certificate: " + certificate); } } if (found) { return; } Log.i(TAG, "Self Signed Certificate not found in keystore. Generating a new one..."); KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); keyGen.initialize(1024); KeyPair keyPair = keyGen.generateKeyPair(); X500Name subject = new X500Name("CN=nuntius"); X500Name issuer = subject ; X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( issuer, new BigInteger(64, new SecureRandom()), NOT_BEFORE, NOT_AFTER, subject, keyPair.getPublic()); ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(keyPair.getPrivate()); X509CertificateHolder certHolder = builder.build(signer); X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certHolder); cert.verify(keyPair.getPublic()); Log.i(TAG, "Certificate generated: " + cert); ks.setKeyEntry(alias, keyPair.getPrivate(), pwd, new Certificate[] { cert }); } }