Skip to content

Commit 3ba230e

Browse files
committed
v 1.0.0 changes
1 parent c4b3781 commit 3ba230e

File tree

7 files changed

+489
-133
lines changed

7 files changed

+489
-133
lines changed

README.md

+137-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,138 @@
1-
:rocket:Install Certs
2-
----------------------
1+
# 🏺 Install Certs [ ![version](https://img.shields.io/badge/installcerts-1.0.0-green.svg) ](https://github.com/sureshg/InstallCerts/releases/latest)
2+
3+
`InstallCerts` is a simple cli tool to create [PKCS12](https://en.wikipedia.org/wiki/PKCS_12) trustStore by retrieving server's TLS certificates.
4+
You can achieve the same using [OpenSSL](https://en.wikipedia.org/wiki/OpenSSL) and java [Keytool](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html) commands, but `InstallCerts` makes it fully automated using a single command.
5+
6+
### Download
7+
8+
* Binary
9+
10+
[Download (v1.0.0)](https://github.com/sureshg/InstallCerts/releases/latest)
11+
12+
> After download, make sure to set the execute permission (`chmod +x installcerts`)
13+
> Windows users can run the executable jar.
14+
15+
* Source
16+
17+
```ruby
18+
$ git clone https://github.com/sureshg/InstallCerts
19+
$ cd InstallCerts
20+
$ ./gradlew
21+
```
22+
> The binary would be located at `build/libs/installcerts`
23+
24+
### Usage
25+
26+
```ruby
27+
$ installcerts -h
28+
NAME
29+
installcerts - Creates PKCS12 TrustStore by retrieving server certificates
30+
31+
SYNOPSIS
32+
installcerts [(-a | --all)] [(-h | --help)]
33+
[(-p <storePasswd> | --passwd <storePasswd>)] [(-v | --verbose)]
34+
[(-V | --version)] [--] <host>[:port]
35+
36+
OPTIONS
37+
-a, --all
38+
Show all certs and exits.
39+
40+
-h, --help
41+
Display help information
42+
43+
-p <storePasswd>, --passwd <storePasswd>
44+
Trust store password. Default is 'changeit'
45+
46+
-v, --verbose
47+
Verbose mode
48+
49+
-V, --version
50+
Show version
51+
52+
--
53+
This option can be used to separate command-line options from the
54+
list of argument, (useful when arguments might be mistaken for
55+
command-line options
56+
57+
<host>[:port]
58+
Server URL. Default port is 443
59+
```
60+
61+
### Examples
62+
63+
* To list all TLS certificates
64+
65+
```ruby
66+
$ installcerts walmart.com -a
67+
Loading default ca truststore...
68+
Opening connection to walmart.com:443...
69+
70+
Starting SSL handshake...
71+
72+
1) Subject - CN=www.walmart.com, O="Wal-Mart Stores, Inc.", L=Bentonville, ST=Arkansas, C=US
73+
Issuer : CN=GlobalSign Organization Validation CA - SHA256 - G2, O=GlobalSign nv-sa, C=BE
74+
SHA1 : DF 3C BB 19 68 95 F7 9A BE 99 44 D1 0D 3A CA A5 C7 21 1A 90
75+
MD5 : CE 58 55 38 BE A5 A8 E4 FA 45 4C 5D 88 7B 98 04
76+
SAN : [2, www.walmart.com, 2, walmart.com]
77+
Expiry : Fri Sep 07 23:10:43 PDT 2018
78+
79+
2) Subject - CN=GlobalSign Organization Validation CA - SHA256 - G2, O=GlobalSign nv-sa, C=BE
80+
Issuer : CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE
81+
SHA1 : 90 2E F2 DE EB 3C 5B 13 EA 4C 3D 51 93 62 93 09 E2 31 AE 55
82+
MD5 : D3 E8 70 6D 82 92 AC E4 DD EB F7 A8 BB BD 56 6B
83+
SAN :
84+
Expiry : Tue Feb 20 02:00:00 PST 2024
85+
86+
3) Subject - CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE
87+
Issuer : CN=GlobalSign Root CA, OU=Root CA, O=GlobalSign nv-sa, C=BE
88+
SHA1 : B1 BC 96 8B D4 F4 9D 62 2A A8 9A 81 F2 15 01 52 A4 1D 82 9C
89+
MD5 : 3E 45 52 15 09 51 92 E1 B7 5D 37 9F B1 87 29 8A
90+
SAN :
91+
Expiry : Fri Jan 28 04:00:00 PST 2028
92+
```
93+
94+
* To create PKCS12 file
95+
96+
```
97+
$ installcerts https://self-signed.badssl.com/
98+
Loading default ca truststore...
99+
Opening connection to self-signed.badssl.com:443...
100+
101+
Starting SSL handshake...
102+
Server sent 1 certificate(s)...
103+
104+
1) Adding certificate to keystore using alias self-signed.badssl.com-1...
105+
Subject - CN=*.badssl.com, O=BadSSL, L=San Francisco, ST=California, C=US
106+
Issuer : CN=*.badssl.com, O=BadSSL, L=San Francisco, ST=California, C=US
107+
SHA1 : 64 14 50 D9 4A 65 FA EB 3B 63 10 28 D8 E8 6C 95 43 1D B8 11
108+
MD5 : 46 10 F4 1F 93 A3 EE 58 E0 CC 69 BE 1C 71 E0 C0
109+
SAN : [2, *.badssl.com, 2, badssl.com]
110+
Expiry : Wed Aug 08 14:17:05 PDT 2018
111+
112+
Starting SSL handshake...
113+
Certificate is trusted. Saving the trustore...
114+
115+
🍺 PKCS12 truststore saved to installcerts/self-signed_badssl_com.p12
116+
```
117+
* Some useful Keytool commands
118+
119+
```ruby
120+
# List all certificates from the pkcs12 truststore.
121+
$ keytool -list -keystore self-signed_badssl_com.p12 --storetype pkcs12
122+
Enter keystore password: changeit
123+
124+
# Extract certificate from pkcs12 truststore.
125+
$ keytool -exportcert -alias [host]-1 -keystore self-signed_badssl_com.p12 -storepass changeit -file [host].cer
126+
127+
# Import certificate into system keystore
128+
$ keytool -importcert -alias [host] -keystore [path to system keystore] -storepass changeit -file [host].cer
129+
```
130+
131+
132+
## Credits
133+
134+
- Got the original idea from this [oracle blog](https://blogs.oracle.com/gc/entry/unable_to_find_valid_certification) post.
135+
136+
----------
137+
<sup>**</sup>Require [Java 8 or later](http://www.oracle.com/technetwork/java/javase/downloads/index.html)
3138

src/main/kotlin/io/sureshg/cmd/Install.kt

+5-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import io.airlift.airline.Command
55
import io.airlift.airline.HelpOption
66
import io.airlift.airline.Option
77
import io.sureshg.crypto.InstallCerts
8-
import io.sureshg.extn.*
8+
import io.sureshg.extn.bold
9+
import io.sureshg.extn.cyan
10+
import io.sureshg.extn.jarManifest
911
import java.net.URL
1012
import java.util.jar.Attributes.Name.*
1113
import javax.inject.Inject
@@ -24,10 +26,10 @@ class Install {
2426
@Arguments(description = "Server URL. Default port is 443", usage = "<host>[:port]")
2527
var uri = ""
2628

27-
@Option(name = arrayOf("-p"), description = "Trust store password. Default is 'changeit'")
29+
@Option(name = arrayOf("-p", "--passwd"), description = "Trust store password. Default is 'changeit'")
2830
var storePasswd = "changeit"
2931

30-
@Option(name = arrayOf("-a", "--all"), description = "Install all certs")
32+
@Option(name = arrayOf("-a", "--all"), description = "Show all certs and exits.")
3133
var all = false
3234

3335
@Option(name = arrayOf("-v", "--verbose"), description = "Verbose mode")

src/main/kotlin/io/sureshg/crypto/InstallCerts.kt

+66-24
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package io.sureshg.crypto
33
import io.sureshg.cmd.Install
44
import io.sureshg.extn.*
55
import java.io.File
6+
import java.lang.System.exit
7+
import java.security.KeyStore
8+
import java.security.cert.X509Certificate
69
import javax.net.ssl.SSLException
710
import javax.net.ssl.SSLSocket
811

912
/**
10-
* Creates a PKCS12 TrustStore by retrieving server's certificates
11-
* with JDK trusted certificates.
13+
* Creates a PKCS12 TrustStore by retrieving server's
14+
* certificates with JDK trusted certificates.
1215
*
1316
* @author Suresh
1417
*/
@@ -22,50 +25,89 @@ object InstallCerts {
2225
val storePasswd = args.storePasswd
2326

2427
val keystoreFile = File(host.replace(".", "_").plus(".p12"))
25-
2628
if (keystoreFile.isFile) {
27-
val uin: String? = System.console()?.readLine(" $keystoreFile PKCS12 file exists. Do you want to overwrite it (y/n)? ".warn)
28-
if (uin?.toLowerCase() != "y") {
29+
val uin = System.console()?.readLine(" $keystoreFile file exists. Do you want to overwrite it (y/n)? ".warn) ?: "n"
30+
if (uin.toLowerCase() != "y") {
2931
println("Existing...".red)
30-
System.exit(-1)
32+
exit(-1)
3133
}
3234
}
3335

3436
println("Loading default ca truststore...".cyan)
3537
val keyStore = CACertsKeyStore
36-
val tm = keyStore.defaultTrustManager.saving()
37-
val sslFactory = getSSLSockFactory("TLS", trustManagers = arrayOf(tm))
38-
3938
println("Opening connection to $host:$port...".cyan)
39+
val result = validateCerts(host, port, keyStore, args)
40+
41+
when {
42+
result.chain.isEmpty() -> {
43+
println("Could not obtain server certificate chain!".err)
44+
exit(-1)
45+
}
46+
47+
args.all -> {
48+
result.chain.forEachIndexed { idx, cert ->
49+
println("\n${idx + 1}) ${cert.info().fg256()}")
50+
}
51+
exit(0)
52+
}
53+
54+
result.valid -> {
55+
println("No errors, certificate is already trusted!".done)
56+
exit(0)
57+
}
58+
}
59+
60+
println("Server sent ${result.chain.size} certificate(s)...".yellow)
61+
result.chain.forEachIndexed { idx, cert ->
62+
val alias = "$host-${idx + 1}"
63+
println("\n${idx + 1}) Adding certificate to keystore using alias ${alias.bold}...")
64+
println(cert.info().fg256())
65+
keyStore.setCertificateEntry(alias, cert)
66+
if (validateCerts(host, port, keyStore, args).valid) {
67+
println("Certificate is trusted. Saving the trustore...\n".cyan)
68+
keyStore.toPKCS12().store(keystoreFile.outputStream(), storePasswd.toCharArray())
69+
println("PKCS12 truststore saved to ${keystoreFile.absolutePath.bold}".done)
70+
exit(0)
71+
}
72+
}
73+
}
74+
75+
/**
76+
* Validate the TLS server using given keystore.
77+
*
78+
* @param host server host
79+
* @param port server port
80+
* @param keystore trustore to make TLS connection
81+
* @param args install cli config.
82+
*/
83+
private fun validateCerts(host: String, port: Int, keystore: KeyStore, args: Install): Result {
84+
val tm = keystore.defaultTrustManager.saving()
85+
val sslFactory = getSSLSockFactory("TLS", trustManagers = arrayOf(tm))
4086
val socket = sslFactory.createSocket(host, port) as SSLSocket
4187

4288
try {
89+
println("\nStarting SSL handshake...".cyan)
4390
with(socket) {
4491
soTimeout = 5_000
4592
startHandshake()
4693
close()
4794
}
48-
println(" No error, certificate is already trusted using jre ca certs!".sux)
49-
System.exit(0)
95+
return Result(true, tm.chain)
5096
} catch(e: SSLException) {
51-
e.printStackTrace()
52-
}
53-
54-
if (tm.chain.isEmpty()) {
55-
println("Could not obtain server certificate chain".err)
56-
System.exit(-1)
57-
} else {
58-
59-
tm.chain.forEach {
60-
println(it.info().fg256((RAND.nextDouble() * 255).toInt()))
97+
if (args.verbose) {
98+
e.printStackTrace()
6199
}
62-
100+
return Result(false, tm.chain)
63101
}
64-
65102
}
103+
}
104+
105+
/**
106+
* Holds the cert validation result. Mainly validation status and cert chain.
107+
*/
108+
data class Result(val valid: Boolean, val chain: List<X509Certificate>)
66109

67110

68-
}
69111

70112

71113

0 commit comments

Comments
 (0)