Skip to content

Commit 8f614fa

Browse files
committed
Initial commit
0 parents  commit 8f614fa

File tree

9 files changed

+251
-0
lines changed

9 files changed

+251
-0
lines changed

.editorconfig

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# https://editorconfig.org
2+
3+
root = true
4+
5+
# Global parameters for all files
6+
[*]
7+
charset = utf-8
8+
indent_style = space
9+
indent_size = 4
10+
end_of_line = lf
11+
insert_final_newline = true
12+
trim_trailing_whitespace = true
13+
14+
# Go files
15+
[{Makefile,go.mod,go.sum,*.go}]
16+
indent_style = tab
17+
indent_size = 8
18+
19+
# YAML file parameters
20+
[*.{yaml,yml}]
21+
indent_size = 2

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# IDE files
2+
/.idea/
3+
4+
# Binaries for programs and plugins
5+
*.exe
6+
*.exe~
7+
*.dll
8+
*.so
9+
*.dylib
10+
/go-check-ssl
11+
12+
# Test binary, built with `go test -c`
13+
*.test
14+
15+
# Output of the go coverage tool, specifically when used with LiteIDE
16+
*.out

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2020 Colin O'Dell
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# go-check-ssl
2+
3+
Simple command line utility to check the status of an SSL certificate.
4+
5+
Basically, it attempts to establish a TLS connection to a server and reports back useful info about the cert's status.
6+
7+
## Usage
8+
9+
Either build it yourself or grab a pre-compiled download from the [Releases](https://github.com/colinodell/go-check-ssl/releases) page.
10+
11+
To check a certificate, simply run:
12+
13+
```bash
14+
./go-check-ssl [domain]
15+
```
16+
17+
![](screenshot-1.png)
18+
19+
Example of allowed arguments include:
20+
21+
- `example.com`
22+
- `example.com:443`
23+
- `https://www.example.com:443/foo/bar`
24+
25+
By default, it'll resolve the IP of the given domain and test against that server. But you can also use this tool to check other servers by providing two arguments: the server to test and the SNI to use. For example:
26+
27+
```bash
28+
./go-check-ssl [server] [SNI domain]
29+
```
30+
31+
![](screenshot-2.png)

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module go-check-ssl
2+
3+
go 1.13
4+
5+
require github.com/dustin/go-humanize v1.0.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
2+
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=

main.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package main
2+
3+
import (
4+
"crypto/tls"
5+
"crypto/x509"
6+
"fmt"
7+
"net"
8+
"net/url"
9+
"os"
10+
"reflect"
11+
"strings"
12+
13+
"github.com/dustin/go-humanize"
14+
)
15+
16+
func main() {
17+
if len(os.Args) < 2 || len(os.Args) > 3 {
18+
fmt.Print("go-check-ssl: Simple command line utility to check the status of an SSL certificate.\n\n")
19+
fmt.Printf("Usage: %s server [domain]\n", os.Args[0])
20+
fmt.Println()
21+
fmt.Print("Arguments:\n")
22+
fmt.Print(" - 'server' should be any valid hostname, IP, or URL to test\n")
23+
fmt.Print(" - '[domain]' allows you to provide an arbitrary domain name to use for SNI\n")
24+
fmt.Println()
25+
fmt.Print("Example usage:\n")
26+
fmt.Printf(" - %s example.com\n", os.Args[0])
27+
fmt.Printf(" - %s https://www.example.com:443/foo/bar\n", os.Args[0])
28+
fmt.Printf(" - %s 93.184.216.34 www.example.com\n", os.Args[0])
29+
fmt.Printf(" - %s 93.184.216.34:443 www.example.com\n", os.Args[0])
30+
os.Exit(1)
31+
}
32+
33+
input := os.Args[1]
34+
if !strings.Contains(input, "://") {
35+
input = "https://" + input
36+
}
37+
38+
parsedUrl, err := url.Parse(input)
39+
if err != nil {
40+
fmt.Printf("Invalid URL: %s\n", err)
41+
os.Exit(1)
42+
}
43+
44+
// Hostname is used for SNI
45+
hostname := parsedUrl.Hostname()
46+
// Server is used for the underlying connection
47+
server := hostname
48+
port := parsedUrl.Port()
49+
if port == "" {
50+
port = "443"
51+
}
52+
53+
// Did the user provide a different hostname to use for SNI?
54+
if len(os.Args) > 2 {
55+
hostname = os.Args[2]
56+
}
57+
58+
// Resolve the IP of the server
59+
if addr, err := net.LookupIP(server); err == nil {
60+
server = addr[0].String()
61+
}
62+
63+
server += ":" + port
64+
65+
fmt.Printf("Connecting to %s as %s...\n\n", server, hostname)
66+
67+
var valid bool
68+
if err := checkIfCertValid(server, hostname); err != nil {
69+
valid = false
70+
fmt.Printf("ERROR: %s\n\n", err)
71+
} else {
72+
valid = true
73+
fmt.Printf("Cert seems to be valid\n\n")
74+
}
75+
76+
cert, err := getCert(server, hostname)
77+
if err != nil {
78+
fmt.Printf("ERROR: %s\n", err)
79+
os.Exit(1)
80+
}
81+
82+
fmt.Printf("Issued by: %s\n", split(cert.Issuer, 12))
83+
84+
fmt.Printf("Subject: %s\n", cert.Subject)
85+
fmt.Printf("DNS Names: %s\n", split(cert.DNSNames, 12))
86+
87+
fmt.Printf("Expires: %s (%s)\n", humanize.Time(cert.NotAfter), cert.NotAfter.String())
88+
89+
if !valid {
90+
os.Exit(1)
91+
}
92+
}
93+
94+
func checkIfCertValid(server, hostname string) error {
95+
conf := &tls.Config{
96+
ServerName: hostname,
97+
}
98+
99+
conn, err := tls.Dial("tcp", server, conf)
100+
if err != nil {
101+
return err
102+
}
103+
104+
conn.Close()
105+
106+
return nil
107+
}
108+
109+
func getCert(server, hostname string) (*x509.Certificate, error) {
110+
conf := &tls.Config{
111+
InsecureSkipVerify: true,
112+
ServerName: hostname,
113+
}
114+
115+
conn, err := tls.Dial("tcp", server, conf)
116+
if err != nil {
117+
return nil, err
118+
}
119+
defer conn.Close()
120+
121+
return conn.ConnectionState().PeerCertificates[0], nil
122+
}
123+
124+
func split(value interface{}, padding int) string {
125+
var lines []string
126+
127+
if reflect.TypeOf(value).Kind() == reflect.Slice {
128+
s := reflect.ValueOf(value)
129+
for i := 0; i < s.Len(); i++ {
130+
lines = append(lines, fmt.Sprintf("%v", s.Index(i)))
131+
}
132+
} else {
133+
v := fmt.Sprintf("%v", value)
134+
for _, line := range strings.Split(v, "\n") {
135+
lines = append(lines, line)
136+
}
137+
}
138+
139+
if len(lines) == 0 {
140+
return ""
141+
}
142+
143+
var sb strings.Builder
144+
145+
// No padding for the first line
146+
sb.WriteString(fmt.Sprintf("%v", lines[0]))
147+
148+
for _, s := range lines[1:] {
149+
sb.WriteString("\n")
150+
sb.WriteString(strings.Repeat(" ", padding))
151+
sb.WriteString(fmt.Sprintf("%v", s))
152+
}
153+
154+
return sb.String()
155+
}

screenshot-1.png

28.8 KB
Loading

screenshot-2.png

30.4 KB
Loading

0 commit comments

Comments
 (0)