Skip to content

Commit 0880d21

Browse files
committed
Implement in go
1 parent 1a8ce1c commit 0880d21

File tree

9 files changed

+276
-335
lines changed

9 files changed

+276
-335
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.jks
2+
*.pem
3+
.vscode
4+
cert-fetcher

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# cert-fetcher
2+
3+
Fetch ssl certificates from https urls and store them in different formats.
4+
5+
## Supported output formats
6+
7+
- pem
8+
- jks (java keystore)
9+
10+
## usage
11+
12+
see
13+
14+
```bash
15+
cert-fetcher --help
16+
```

cmd/jks.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package cmd
2+
3+
import (
4+
"crypto/x509"
5+
"fmt"
6+
"log"
7+
"net/url"
8+
"os"
9+
"strings"
10+
"time"
11+
12+
keystore "github.com/pavel-v-chernykh/keystore-go"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
var (
17+
jskPassword = "changeit"
18+
)
19+
20+
// jksCmd represents the jks command
21+
var jksCmd = &cobra.Command{
22+
Use: "jks",
23+
Short: "store the certificates into an java keystore",
24+
Long: "store the certificates into an java keystore",
25+
Run: func(cmd *cobra.Command, args []string) {
26+
certs := fetchCertificates()
27+
ks := keystore.KeyStore{}
28+
29+
for _, cert := range certs {
30+
ce := &keystore.TrustedCertificateEntry{
31+
Entry: keystore.Entry{
32+
CreationDate: time.Now(),
33+
},
34+
Certificate: keystore.Certificate{
35+
Content: cert.Raw,
36+
Type: "X.509",
37+
},
38+
}
39+
ce.CreationDate = time.Now()
40+
ks[alias(cert)] = ce
41+
}
42+
43+
var fileName string
44+
if outputFile != "" {
45+
fileName = outputFile
46+
} else {
47+
u, _ := url.Parse(targetURL)
48+
fileName = u.Host + ".jks"
49+
}
50+
51+
k, _ := os.Create(fileName)
52+
defer k.Close()
53+
keystore.Encode(k, ks, []byte(jskPassword))
54+
log.Printf("java keystore file %s created.", fileName)
55+
56+
},
57+
}
58+
59+
func init() {
60+
rootCmd.AddCommand(jksCmd)
61+
jksCmd.PersistentFlags().StringVarP(&jskPassword, "password", "p", "changeit", "the password to be used for the java keystore")
62+
}
63+
64+
func alias(cert *x509.Certificate) string {
65+
return fmt.Sprintf("%s (%s)", strings.ToLower(cert.Subject.CommonName), strings.ToLower(cert.Issuer.CommonName))
66+
}

cmd/pem.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package cmd
2+
3+
import (
4+
"bytes"
5+
"crypto/x509"
6+
"encoding/pem"
7+
"log"
8+
"net/url"
9+
"os"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
// pemCmd represents the pem command
15+
var pemCmd = &cobra.Command{
16+
Use: "pem",
17+
Short: "store the certificates ad pem file",
18+
Long: "store the certificates ad pem file",
19+
20+
Run: func(cmd *cobra.Command, args []string) {
21+
22+
certs := fetchCertificates()
23+
pem, err := certChainToPEM(certs)
24+
25+
if err != nil {
26+
log.Fatal(err)
27+
os.Exit(1)
28+
}
29+
30+
var fileName string
31+
if outputFile != "" {
32+
fileName = outputFile
33+
} else {
34+
u, _ := url.Parse(targetURL)
35+
fileName = u.Host + ".pem"
36+
}
37+
f, _ := os.Create(fileName)
38+
39+
defer f.Close()
40+
f.Write(pem)
41+
log.Printf("pem file %s created.", fileName)
42+
},
43+
}
44+
45+
func init() {
46+
rootCmd.AddCommand(pemCmd)
47+
}
48+
49+
// CertChainToPEM is a utility function returns a PEM encoded chain of x509 Certificates, in the order they are passed
50+
func certChainToPEM(certChain []*x509.Certificate) ([]byte, error) {
51+
var pemBytes bytes.Buffer
52+
for _, cert := range certChain {
53+
if err := pem.Encode(&pemBytes, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}); err != nil {
54+
return nil, err
55+
}
56+
}
57+
return pemBytes.Bytes(), nil
58+
}

cmd/root.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package cmd
2+
3+
import (
4+
"crypto/tls"
5+
"crypto/x509"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"os"
10+
"time"
11+
12+
"github.com/spf13/cobra"
13+
)
14+
15+
const (
16+
certTemplate string = `Certificate %d:
17+
Subject: %s
18+
Issuer: %s
19+
NotBefore: %s
20+
NotAfter: %s
21+
22+
`
23+
)
24+
25+
var (
26+
targetURL string
27+
outputFile string
28+
)
29+
30+
// rootCmd represents the base command when called without any subcommands
31+
var rootCmd = &cobra.Command{
32+
Use: "cert-fetcher",
33+
Short: "Fetch client certificates from https urls",
34+
Long: `A go application that fetches public certificates from https sites and stores them into different output formates.`,
35+
Run: func(cmd *cobra.Command, args []string) {
36+
37+
certs := fetchCertificates()
38+
loc := time.Local
39+
for i, cert := range certs {
40+
fmt.Printf(certTemplate, i, cert.Subject.CommonName, cert.Issuer.CommonName, cert.NotBefore.In(loc).String(), cert.NotAfter.In(loc).String())
41+
}
42+
},
43+
}
44+
45+
// Execute adds all child commands to the root command and sets flags appropriately.
46+
// This is called by main.main(). It only needs to happen once to the rootCmd.
47+
func Execute() {
48+
if err := rootCmd.Execute(); err != nil {
49+
fmt.Println(err)
50+
os.Exit(1)
51+
}
52+
}
53+
54+
func init() {
55+
rootCmd.PersistentFlags().StringVarP(&targetURL, "url", "u", "", "the URL to fetch the certificate from")
56+
rootCmd.MarkPersistentFlagRequired("url")
57+
rootCmd.PersistentFlags().StringVarP(&outputFile, "out-file", "o", "", "the output file")
58+
}
59+
60+
func fetchCertificates() []*x509.Certificate {
61+
62+
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
63+
64+
resp, err := http.Get(targetURL)
65+
66+
if err != nil {
67+
log.Fatal(err)
68+
os.Exit(1)
69+
}
70+
71+
if resp.TLS != nil {
72+
return resp.TLS.PeerCertificates
73+
}
74+
log.Fatal(fmt.Errorf("Could not find any certificates"))
75+
os.Exit(1)
76+
return nil
77+
}

go.mod

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module github.com/bakito/cert-fetcher
2+
3+
require (
4+
github.com/google/pprof v0.0.0-20190109223431-e84dfd68c163 // indirect
5+
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6 // indirect
6+
github.com/mitchellh/go-homedir v1.0.0
7+
github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible
8+
github.com/spf13/cobra v0.0.3
9+
github.com/spf13/viper v1.3.1
10+
golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045 // indirect
11+
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc // indirect
12+
golang.org/x/sys v0.0.0-20190116161447-11f53e031339 // indirect
13+
)

go.sum

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
2+
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
3+
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
4+
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
5+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
7+
github.com/google/pprof v0.0.0-20190109223431-e84dfd68c163/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
8+
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
9+
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
10+
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
11+
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
12+
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
13+
github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible h1:Jd6xfriVlJ6hWPvYOE0Ni0QWcNTLRehfGPFxr3eSL80=
14+
github.com/pavel-v-chernykh/keystore-go v2.1.0+incompatible/go.mod h1:xlUlxe/2ItGlQyMTstqeDv9r3U4obH7xYd26TbDQutY=
15+
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
16+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17+
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
18+
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
19+
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
20+
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
21+
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
22+
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
23+
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
24+
github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
25+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
26+
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
27+
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
28+
golang.org/x/arch v0.0.0-20181203225421-5a4828bb7045/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
29+
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
30+
golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
31+
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
32+
golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
33+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
34+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
35+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

0 commit comments

Comments
 (0)