|
| 1 | +import java.io.BufferedReader; |
| 2 | +import java.io.File; |
| 3 | +import java.io.FileInputStream; |
| 4 | +import java.io.FileOutputStream; |
| 5 | +import java.io.IOException; |
| 6 | +import java.io.InputStream; |
| 7 | +import java.io.InputStreamReader; |
| 8 | +import java.io.OutputStream; |
| 9 | +import java.net.Authenticator; |
| 10 | +import java.net.HttpURLConnection; |
| 11 | +import java.net.InetSocketAddress; |
| 12 | +import java.net.PasswordAuthentication; |
| 13 | +import java.net.Proxy; |
| 14 | +import java.net.SocketAddress; |
| 15 | +import java.net.URL; |
| 16 | +import java.security.KeyStore; |
| 17 | +import java.security.KeyStoreException; |
| 18 | +import java.security.MessageDigest; |
| 19 | +import java.security.NoSuchAlgorithmException; |
| 20 | +import java.security.cert.CertificateException; |
| 21 | +import java.security.cert.X509Certificate; |
| 22 | +import java.text.SimpleDateFormat; |
| 23 | +import java.util.Date; |
| 24 | +import java.util.HashMap; |
| 25 | +import java.util.Map; |
| 26 | +import java.util.Objects; |
| 27 | + |
| 28 | +import javax.naming.InvalidNameException; |
| 29 | +import javax.naming.ldap.LdapName; |
| 30 | +import javax.naming.ldap.Rdn; |
| 31 | +import javax.net.ssl.HttpsURLConnection; |
| 32 | +import javax.net.ssl.SSLContext; |
| 33 | +import javax.net.ssl.SSLException; |
| 34 | +import javax.net.ssl.TrustManager; |
| 35 | +import javax.net.ssl.TrustManagerFactory; |
| 36 | +import javax.net.ssl.X509TrustManager; |
| 37 | + |
| 38 | +/** |
| 39 | + * CSSLCertificateFetcher |
| 40 | + */ |
| 41 | +public class SSLCertificateFetcher { |
| 42 | + |
| 43 | + private static final String ANSI_RESET = "\u001B[0m"; |
| 44 | + private static final String ANSI_BLACK = "\u001B[30m"; |
| 45 | + private static final String ANSI_RED = "\u001B[31m"; |
| 46 | + private static final String ANSI_GREEN = "\u001B[32m"; |
| 47 | + private static final String ANSI_YELLOW = "\u001B[33m"; |
| 48 | + private static final String ANSI_BLUE = "\u001B[34m"; |
| 49 | + private static final String ANSI_PURPLE = "\u001B[35m"; |
| 50 | + private static final String ANSI_CYAN = "\u001B[36m"; |
| 51 | + private static final String ANSI_WHITE = "\u001B[37m"; |
| 52 | + |
| 53 | + private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray(); |
| 54 | + private static final String PROXY_HOST = "proxyHost"; |
| 55 | + private static final String PROXY_PORT = "proxyPort"; |
| 56 | + private static final String PASSPHRASE = "passphrase"; |
| 57 | + private static final String PROXY_USER = "proxyUser"; |
| 58 | + private static final String PROXY_PASSWORD = "proxyPassword"; |
| 59 | + private static final String URL = "url"; |
| 60 | + private static final String TRUSTSTORE = "truststore"; |
| 61 | + |
| 62 | + public static void main(final String[] args) throws Exception { |
| 63 | + SSLCertificateFetcher fetcher = new SSLCertificateFetcher(); |
| 64 | + try { |
| 65 | + boolean valid = fetcher.initialize(args); |
| 66 | + |
| 67 | + if (valid) { |
| 68 | + fetcher.fetch(); |
| 69 | + } else { |
| 70 | + readme(); |
| 71 | + } |
| 72 | + } catch (Exception e) { |
| 73 | + readme(); |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + private static void readme() { |
| 78 | + System.out.println("Usage: " + SSLCertificateFetcher.class.getName() + " " + URL + "=[url*] " + TRUSTSTORE + "=[truststore*] " + PASSPHRASE + "=[passphrase] " + PROXY_HOST |
| 79 | + + "=[proxyHost] " + PROXY_PORT + "=[proxyPort] " + PROXY_USER + "=[proxyUser] " + PROXY_PASSWORD + "=[proxyPassword]\n" |
| 80 | + + "passphrase, proxy details are not mandatory. Can be used default " + "in case when they are " + "not specified"); |
| 81 | + } |
| 82 | + |
| 83 | + private String proxyHost = null; |
| 84 | + private Integer proxyPort = null; |
| 85 | + private String remoteUrl; |
| 86 | + private char[] passphrase; |
| 87 | + private Proxy proxy = null; |
| 88 | + private String truststore; |
| 89 | + |
| 90 | + private boolean initialize(String... args) { |
| 91 | + Map<String, String> paramsMap = new HashMap<>(); |
| 92 | + // parsing all passed parameters and storing them in the map |
| 93 | + for (String arg : args) { |
| 94 | + paramsMap.put(arg.split("=")[0].replaceAll(" ", ""), arg.split("=")[1].replaceAll(" ", "")); |
| 95 | + } |
| 96 | + // Writing out all parameters that were passed to java |
| 97 | + paramsMap.forEach((k, v) -> System.out.println(k + " = " + (k.toLowerCase().contains("pass") ? "*******" : v))); |
| 98 | + |
| 99 | + // check if proxy parameters were passed in. If yes, then enable Proxy |
| 100 | + if (paramsMap.containsKey(PROXY_HOST)) { |
| 101 | + proxyHost = paramsMap.get(PROXY_HOST); |
| 102 | + } |
| 103 | + if (paramsMap.containsKey(PROXY_PORT)) { |
| 104 | + proxyPort = Integer.parseInt(paramsMap.get(PROXY_PORT)); |
| 105 | + } |
| 106 | + if (proxyHost != null && proxyPort != null) { |
| 107 | + SocketAddress addr = new InetSocketAddress(proxyHost, proxyPort); |
| 108 | + proxy = new Proxy(Proxy.Type.HTTP, addr); |
| 109 | + } else { |
| 110 | + System.out.println("Connecting to address without enabled proxy settings. "); |
| 111 | + } |
| 112 | + |
| 113 | + // now checking if host,port and passphrase parameters were passed succesfully |
| 114 | + if (paramsMap.containsKey(URL)) { |
| 115 | + remoteUrl = paramsMap.get(URL); |
| 116 | + } else { |
| 117 | + return false; |
| 118 | + } |
| 119 | + |
| 120 | + final String p = paramsMap.getOrDefault(PASSPHRASE, "changeit"); |
| 121 | + passphrase = p.toCharArray(); |
| 122 | + |
| 123 | + if (paramsMap.containsKey(PROXY_USER) && paramsMap.containsKey(PROXY_PASSWORD)) { |
| 124 | + Authenticator.setDefault(new Authenticator() { |
| 125 | + |
| 126 | + @Override |
| 127 | + public PasswordAuthentication getPasswordAuthentication() { |
| 128 | + return new PasswordAuthentication(paramsMap.get(PROXY_USER), paramsMap.get(PROXY_PASSWORD).toCharArray()); |
| 129 | + } |
| 130 | + }); |
| 131 | + } |
| 132 | + |
| 133 | + truststore = paramsMap.get(TRUSTSTORE); |
| 134 | + if (truststore == null) { |
| 135 | + System.out.println("'" + TRUSTSTORE + "' must be defined."); |
| 136 | + return false; |
| 137 | + } |
| 138 | + return true; |
| 139 | + } |
| 140 | + |
| 141 | + private void fetch() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, InvalidNameException { |
| 142 | + |
| 143 | + File file = new File(truststore); |
| 144 | + |
| 145 | + final KeyStore ks = getKeyStore(passphrase, file); |
| 146 | + final TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); |
| 147 | + tmf.init(ks); |
| 148 | + |
| 149 | + // Create a new trust manager that trust all certificates |
| 150 | + final X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0]; |
| 151 | + final SavingTrustManager tm = new SavingTrustManager(defaultTrustManager); |
| 152 | + TrustManager[] trustAllCerts = new TrustManager[]{tm}; |
| 153 | + |
| 154 | + // Activate the new trust manager |
| 155 | + try { |
| 156 | + SSLContext sc = SSLContext.getInstance("TLS"); |
| 157 | + sc.init(null, trustAllCerts, new java.security.SecureRandom()); |
| 158 | + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); |
| 159 | + } catch (Exception e) { |
| 160 | + e.printStackTrace(); |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + URL url = new URL(remoteUrl); |
| 165 | + final HttpURLConnection conn; |
| 166 | + if (proxy != null) { |
| 167 | + conn = (HttpURLConnection) url.openConnection(proxy); |
| 168 | + } else { |
| 169 | + conn = (HttpURLConnection) url.openConnection(); |
| 170 | + } |
| 171 | + conn.setReadTimeout(10_000); |
| 172 | + System.out.println("Starting SSL handshake..."); |
| 173 | + |
| 174 | + String defaultSelection; |
| 175 | + |
| 176 | + try { |
| 177 | + conn.connect(); |
| 178 | + System.out.println(); |
| 179 | + printSuccess("Connection could successfully be established, certificate is already trusted"); |
| 180 | + defaultSelection = "q"; |
| 181 | + } catch (SSLException e) { |
| 182 | + e.printStackTrace(System.out); |
| 183 | + System.out.println(); |
| 184 | + printError("Error, certificate is NOT trusted"); |
| 185 | + defaultSelection = "1"; |
| 186 | + } |
| 187 | + |
| 188 | + final X509Certificate[] chain = tm.chain; |
| 189 | + if (chain == null) { |
| 190 | + printError("Could not obtain server certificate chain"); |
| 191 | + return; |
| 192 | + } |
| 193 | + |
| 194 | + final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); |
| 195 | + |
| 196 | + System.out.println(); |
| 197 | + System.out.println("Server sent " + chain.length + " certificate(s):"); |
| 198 | + System.out.println(); |
| 199 | + final MessageDigest sha1 = MessageDigest.getInstance("SHA1"); |
| 200 | + final MessageDigest md5 = MessageDigest.getInstance("MD5"); |
| 201 | + for (int i = 0; i < chain.length; i++) { |
| 202 | + final X509Certificate cert = chain[i]; |
| 203 | + System.out.println(" " + (i + 1) + " Subject " + cert.getSubjectDN()); |
| 204 | + System.out.println(" Issuer " + cert.getIssuerDN()); |
| 205 | + sha1.update(cert.getEncoded()); |
| 206 | + System.out.println(" sha1 " + toHexString(sha1.digest())); |
| 207 | + md5.update(cert.getEncoded()); |
| 208 | + System.out.println(" md5 " + toHexString(md5.digest())); |
| 209 | + System.out.println(); |
| 210 | + } |
| 211 | + |
| 212 | + System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [" + defaultSelection + "]"); |
| 213 | + final String line = Objects.toString(reader.readLine(), "").trim(); |
| 214 | + int k; |
| 215 | + try { |
| 216 | + String option = (line.length() == 0) ? defaultSelection : line; |
| 217 | + k = Integer.parseInt(option) - 1; |
| 218 | + } catch (final NumberFormatException e) { |
| 219 | + System.out.println("KeyStore not changed"); |
| 220 | + return; |
| 221 | + } |
| 222 | + |
| 223 | + final X509Certificate cert = chain[k]; |
| 224 | + |
| 225 | + final String alias = getTruststoreAlias(url, k, cert); |
| 226 | + |
| 227 | + String backup = null; |
| 228 | + |
| 229 | + if (ks.containsAlias(alias)) { |
| 230 | + printSuccess("Certificate is already in the keystore."); |
| 231 | + } else { |
| 232 | + if (ks.size() > 0) { |
| 233 | + backup = file.getAbsolutePath() + "." + new SimpleDateFormat("yyyyMMdd").format(new Date()); |
| 234 | + try (OutputStream out = new FileOutputStream(backup)) { |
| 235 | + ks.store(out, passphrase); |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + ks.setCertificateEntry(alias, cert); |
| 240 | + try (OutputStream out = new FileOutputStream(file)) { |
| 241 | + ks.store(out, passphrase); |
| 242 | + } |
| 243 | + |
| 244 | + System.out.println(); |
| 245 | + System.out.println(cert); |
| 246 | + System.out.println(); |
| 247 | + if (backup != null) { |
| 248 | + System.out.println("Created backup of keystore " + backup); |
| 249 | + } |
| 250 | + printSuccess("Added certificate to keystore '" + file.getAbsolutePath() + "' using alias '" + alias + "'"); |
| 251 | + } |
| 252 | + |
| 253 | + } |
| 254 | + |
| 255 | + private KeyStore getKeyStore(char[] passphrase, File file) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { |
| 256 | + final KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); |
| 257 | + if (file.isFile() && file.exists()) { |
| 258 | + System.out.println("Loading KeyStore " + file + "..."); |
| 259 | + try (InputStream in = new FileInputStream(file)) { |
| 260 | + ks.load(in, passphrase); |
| 261 | + } |
| 262 | + } else { |
| 263 | + ks.load(null, passphrase); |
| 264 | + System.out.println("Creating empty truststore " + file + "..."); |
| 265 | + } |
| 266 | + return ks; |
| 267 | + } |
| 268 | + |
| 269 | + private String getTruststoreAlias(URL url, int k, X509Certificate cert) throws InvalidNameException { |
| 270 | + String cn = getCN(cert); |
| 271 | + String alias = url.getHost(); |
| 272 | + if (cn != null) { |
| 273 | + alias += " (" + cn + ")"; |
| 274 | + } |
| 275 | + alias += " (" + SSLCertificateFetcher.class.getSimpleName() + " chain index: " + (k + 1) + ")"; |
| 276 | + return alias; |
| 277 | + } |
| 278 | + |
| 279 | + private String getCN(X509Certificate cert) throws InvalidNameException { |
| 280 | + String dn = cert.getSubjectX500Principal().getName(); |
| 281 | + LdapName ldapDN = new LdapName(dn); |
| 282 | + for (Rdn rdn : ldapDN.getRdns()) { |
| 283 | + if ("CN".equals(rdn.getType())) { |
| 284 | + return Objects.toString(rdn.getValue(), null); |
| 285 | + } |
| 286 | + } |
| 287 | + return null; |
| 288 | + } |
| 289 | + |
| 290 | + private String toHexString(final byte[] bytes) { |
| 291 | + final StringBuilder sb = new StringBuilder(bytes.length * 3); |
| 292 | + |
| 293 | + for (byte aByte : bytes) { |
| 294 | + int b = aByte & 0xff; |
| 295 | + sb.append(HEXDIGITS[b >> 4]); |
| 296 | + sb.append(HEXDIGITS[b & 15]); |
| 297 | + sb.append(' '); |
| 298 | + } |
| 299 | + return sb.toString(); |
| 300 | + } |
| 301 | + |
| 302 | + private void printError(String message) { |
| 303 | + System.out.println(ANSI_RED + message + ANSI_RESET); |
| 304 | + } |
| 305 | + |
| 306 | + private void printSuccess(String message) { |
| 307 | + System.out.println(ANSI_GREEN + message + ANSI_RESET); |
| 308 | + } |
| 309 | + |
| 310 | + private static final class SavingTrustManager implements X509TrustManager { |
| 311 | + |
| 312 | + private final X509TrustManager tm; |
| 313 | + private X509Certificate[] chain; |
| 314 | + |
| 315 | + SavingTrustManager(final X509TrustManager tm) { |
| 316 | + this.tm = tm; |
| 317 | + } |
| 318 | + |
| 319 | + @Override |
| 320 | + public X509Certificate[] getAcceptedIssuers() { |
| 321 | + return new X509Certificate[0]; |
| 322 | + } |
| 323 | + |
| 324 | + @Override |
| 325 | + public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { |
| 326 | + throw new UnsupportedOperationException(); |
| 327 | + } |
| 328 | + |
| 329 | + @Override |
| 330 | + public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { |
| 331 | + this.chain = chain; |
| 332 | + this.tm.checkServerTrusted(chain, authType); |
| 333 | + } |
| 334 | + } |
| 335 | +} |
0 commit comments