From 7391da8567952f69990194ead2842d21df217c89 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Wed, 10 Jun 2015 16:29:52 -0700 Subject: [PATCH] Initial import. --- LICENSE | 13 ++++++++ README.md | 22 +++++++++++++ cmd/certchain/README | 19 ++++++++++++ cmd/certchain/certchain.go | 39 +++++++++++++++++++++++ cmd/csrpubdump/README | 18 +++++++++++ cmd/csrpubdump/pubdump.go | 55 +++++++++++++++++++++++++++++++++ cmd/readchain/README | 20 ++++++++++++ cmd/readchain/chain.go | 40 ++++++++++++++++++++++++ cmd/stealchain/README | 42 +++++++++++++++++++++++++ cmd/stealchain/thief.go | 63 ++++++++++++++++++++++++++++++++++++++ die/README.md | 12 ++++++++ die/die.go | 22 +++++++++++++ 12 files changed, 365 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cmd/certchain/README create mode 100644 cmd/certchain/certchain.go create mode 100644 cmd/csrpubdump/README create mode 100644 cmd/csrpubdump/pubdump.go create mode 100644 cmd/readchain/README create mode 100644 cmd/readchain/chain.go create mode 100644 cmd/stealchain/README create mode 100644 cmd/stealchain/thief.go create mode 100644 die/README.md create mode 100644 die/die.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd546f9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2015 Kyle Isom + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..959e022 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +GOUTILS + +This is a collection of small utility code I've written in Go; the `cmd/` +directory has a number of command-line utilities. Rather than keep all +of these in superfluous repositories of their own, I'm putting them here. + +Contents: + + die/ Death of a program. + cmd/ + certchain/ Display the certificate chain from a + TLS connection. + csrpubdump/ Dump the public key from an X.509 + certificate request. + readchain/ Print the common name for the certificates + in a bundle. + stealchain/ Dump the verified chain from a TLS + connection. + +Each program should have a small README in the directory with more information. + +All code here is licensed under the MIT license. diff --git a/cmd/certchain/README b/cmd/certchain/README new file mode 100644 index 0000000..0973676 --- /dev/null +++ b/cmd/certchain/README @@ -0,0 +1,19 @@ +certchain + +This is a utility for printing the X.509 certificate chain from a TLS +connection. + +Note: while this will accept more than one server, it will print all +of the chains without any indication where one chain ends and the next +begins. This was the intended behaviour for the use case, but it may +not be applicable in other cases. + +There are no knobs. + +Examples: + $ certchain www.kyleisom.net + -----BEGIN CERTIFICATE----- + MIIFUTCCBDmgAwIBAgIQaaTVw0yZFGAYvFDKAIo4BzANBgkqhkiG9w0BAQsFADCB + kDELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G + A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxNjA0BgNV + ... diff --git a/cmd/certchain/certchain.go b/cmd/certchain/certchain.go new file mode 100644 index 0000000..ff5ea7c --- /dev/null +++ b/cmd/certchain/certchain.go @@ -0,0 +1,39 @@ +package main + +import ( + "crypto/tls" + "encoding/pem" + "flag" + "fmt" + "regexp" + + "github.com/kisom/goutils/die" +) + +var hasPort = regexp.MustCompile(`:\d+$`) + +func main() { + flag.Parse() + + for _, server := range flag.Args() { + if !hasPort.MatchString(server) { + server += ":443" + } + + var chain string + + conn, err := tls.Dial("tcp", server, nil) + die.If(err) + + details := conn.ConnectionState() + for _, cert := range details.PeerCertificates { + p := pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + } + chain += string(pem.EncodeToMemory(&p)) + } + + fmt.Println(chain) + } +} diff --git a/cmd/csrpubdump/README b/cmd/csrpubdump/README new file mode 100644 index 0000000..bc8a6fa --- /dev/null +++ b/cmd/csrpubdump/README @@ -0,0 +1,18 @@ +csrpubdump + +This is a small utility to dump the public key from an X.509 certificate +signature request (CSR) to a standard RFC 5280 PKIX public key format. + +There are no command line flags; it accepts a number of PEM- or +DER-enoded CSRs and outputs a public key of the same name as the CSR +with the suffix ".pub" appended. + +Example: + + $ csrpubdump cert.csr + [+] wrote cert.csr.pub. + $ openssl req -text -in cert.csr -modulus | grep ^Modulus + Modulus=C7021819792D3ADE906156868B6475B67E475325A1AAD6C50BFD8CFE007A4B7C50E89CCA91EF14EEB13AD4B01E8FABDF6884AA74B9CAFFF4D4FD8C26ECAA9DD9DC4D232C3A54FFEE7EBB4CCD34BB4D4AFFD73FB2880A10A8E5CA99533FBC85746FDC3AEFFDA8A6FF25A95DDB010B15813AC6AD910C5CB1CA264E07783B67B1716B977A69D7C647067336D50BE3FF2CF8BCA2A9DC3D2357E441DEB4E29A1914DD30AD0B3895F8564D47E0D2B1EE879C2018A3F75736696EDF056F5BCD8DF6C7B688711B63C253A272F837356D27D4CD67109DE9E9F39F16E05F33EE9179C9B767151DF5DC78E8B2A5E71B6F33213ACE69D3131DA27ACCE86011A7D43965CECE33687C50456B622E0804FE213458D6D0BF82AA711B01FFCAE54DD7D046F14A67D3E1C089EDA62821DF48100A4FF5DEE2E98F79AC526C8A96B16F1C93E7776F8A2BF5166FE5C651713DE88A426DF92406EBDA56E0E01B6FE001B2CFCD22EAE2EB3D1EEC311E20BE739B2489A9DB581DD35837BFBEBFDC4136C2F822C53A204CB7F9 + $ openssl rsa -pubin -text -in cert.csr.pub -modulus | grep ^Modulus + Modulus=C7021819792D3ADE906156868B6475B67E475325A1AAD6C50BFD8CFE007A4B7C50E89CCA91EF14EEB13AD4B01E8FABDF6884AA74B9CAFFF4D4FD8C26ECAA9DD9DC4D232C3A54FFEE7EBB4CCD34BB4D4AFFD73FB2880A10A8E5CA99533FBC85746FDC3AEFFDA8A6FF25A95DDB010B15813AC6AD910C5CB1CA264E07783B67B1716B977A69D7C647067336D50BE3FF2CF8BCA2A9DC3D2357E441DEB4E29A1914DD30AD0B3895F8564D47E0D2B1EE879C2018A3F75736696EDF056F5BCD8DF6C7B688711B63C253A272F837356D27D4CD67109DE9E9F39F16E05F33EE9179C9B767151DF5DC78E8B2A5E71B6F33213ACE69D3131DA27ACCE86011A7D43965CECE33687C50456B622E0804FE213458D6D0BF82AA711B01FFCAE54DD7D046F14A67D3E1C089EDA62821DF48100A4FF5DEE2E98F79AC526C8A96B16F1C93E7776F8A2BF5166FE5C651713DE88A426DF92406EBDA56E0E01B6FE001B2CFCD22EAE2EB3D1EEC311E20BE739B2489A9DB581DD35837BFBEBFDC4136C2F822C53A204CB7F9 + diff --git a/cmd/csrpubdump/pubdump.go b/cmd/csrpubdump/pubdump.go new file mode 100644 index 0000000..24c4475 --- /dev/null +++ b/cmd/csrpubdump/pubdump.go @@ -0,0 +1,55 @@ +package main + +import ( + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "flag" + "fmt" + "io/ioutil" + "log" + + "github.com/kisom/die" +) + +func main() { + flag.Parse() + + for _, fileName := range flag.Args() { + in, err := ioutil.ReadFile(fileName) + die.If(err) + + if p, _ := pem.Decode(in); p != nil { + if p.Type != "CERTIFICATE REQUEST" { + log.Fatal("INVALID FILE TYPE") + } + in = p.Bytes + } + + csr, err := x509.ParseCertificateRequest(in) + die.If(err) + + out, err := x509.MarshalPKIXPublicKey(csr.PublicKey) + die.If(err) + + var t string + switch pub := csr.PublicKey.(type) { + case *rsa.PublicKey: + t = "RSA PUBLIC KEY" + case *ecdsa.PublicKey: + t = "EC PUBLIC KEY" + default: + die.With("unrecognised public key type %T", pub) + } + + p := &pem.Block{ + Type: t, + Bytes: out, + } + + err = ioutil.WriteFile(fileName+".pub", pem.EncodeToMemory(p), 0644) + die.If(err) + fmt.Printf("[+] wrote %s.\n", fileName+".pub") + } +} diff --git a/cmd/readchain/README b/cmd/readchain/README new file mode 100644 index 0000000..b3c190c --- /dev/null +++ b/cmd/readchain/README @@ -0,0 +1,20 @@ +readchain + +This is a small utility to read a chain of PEM-encoded X.509 certificates +and print their common names. It was written to quickly see what certificates +were in a bundle. + +It is called with the files containing chains to read passed in as an +argument. The program has no knobs or widgets to adjust. + +Examples: + + $ readchain google.com.pem microsoft.com.pem + [+] google.com.pem: + *.google.com + Google Internet Authority G2 + GeoTrust Global CA + [+] microsoft.com.pem: + microsoft.com + MSIT Machine Auth CA 2 + Microsoft Internet Authority diff --git a/cmd/readchain/chain.go b/cmd/readchain/chain.go new file mode 100644 index 0000000..db87e92 --- /dev/null +++ b/cmd/readchain/chain.go @@ -0,0 +1,40 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "flag" + "fmt" + "io/ioutil" + "os" +) + +func main() { + flag.Parse() + + for _, fileName := range flag.Args() { + data, err := ioutil.ReadFile(fileName) + if err != nil { + fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err) + continue + } + + fmt.Printf("[+] %s:\n", fileName) + rest := data[:] + for { + var p *pem.Block + p, rest = pem.Decode(rest) + if p == nil { + break + } + + cert, err := x509.ParseCertificate(p.Bytes) + if err != nil { + fmt.Fprintf(os.Stderr, "[!] %s: %v\n", fileName, err) + break + } + + fmt.Printf("\t%+v\n", cert.Subject.CommonName) + } + } +} diff --git a/cmd/stealchain/README b/cmd/stealchain/README new file mode 100644 index 0000000..f3ebfa0 --- /dev/null +++ b/cmd/stealchain/README @@ -0,0 +1,42 @@ +stealchain + +This is a utility to extract the verified X.509 chain from a TLS +connection. It takes a list of sites on the command line; for each +site that it can connect to, it will dump the certificates that the +peer actually sent (and not the verified chain that is built from +this). + +It was written to assist in debugging issues with certificate chains. + +There are a few knobs: + +-ca allows the trusted CA roots to be specified via a PEM bundle of +root certificates. + +-sni specifies the server name for SNI. This applies to all hosts in +the run; if this is run as + + $ stealchain -sni foo.com foo.com bar.com + +it will attempt to use "foo.com" as the server name for both hosts. + +-noverify skips certificate verification. This might be useful for seeing +what certificates a server is actually sending. + + +Examples: + + $ stealchain kyleisom.net + [+] wrote kyleisom.net.pem. + $ readchain kyleisom.net.pem + [+] kyleisom.net.pem: + *.kyleisom.net + COMODO RSA Domain Validation Secure Server CA + + $ stealchain google.com microsoft.com apple.com amazon.com + [+] wrote google.com.pem. + [+] wrote microsoft.com.pem. + [+] wrote apple.com.pem. + [+] wrote amazon.com.pem. + + diff --git a/cmd/stealchain/thief.go b/cmd/stealchain/thief.go new file mode 100644 index 0000000..ebb9aa0 --- /dev/null +++ b/cmd/stealchain/thief.go @@ -0,0 +1,63 @@ +package main + +import ( + "crypto/tls" + "crypto/x509" + "encoding/pem" + "flag" + "fmt" + "io/ioutil" + "os" + + "github.com/kisom/goutils/die" +) + +func main() { + var cfg = &tls.Config{} + + var sysRoot, serverName string + flag.StringVar(&sysRoot, "ca", "", "provide an alternate CA bundle") + flag.StringVar(&cfg.ServerName, "sni", cfg.ServerName, "provide an SNI name") + flag.BoolVar(&cfg.InsecureSkipVerify, "noverify", false, "don't verify certificates") + flag.Parse() + + if sysRoot != "" { + pemList, err := ioutil.ReadFile(sysRoot) + die.If(err) + + roots := x509.NewCertPool() + if !roots.AppendCertsFromPEM(pemList) { + fmt.Printf("[!] no valid roots found") + roots = nil + } + + cfg.RootCAs = roots + } + + if serverName != "" { + cfg.ServerName = serverName + } + + for _, site := range flag.Args() { + conn, err := tls.Dial("tcp", site+":443", cfg) + if err != nil { + fmt.Println(err.Error()) + os.Exit(1) + } + + cs := conn.ConnectionState() + var chain []byte + + for _, cert := range cs.PeerCertificates { + p := &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + } + chain = append(chain, pem.EncodeToMemory(p)...) + } + + err = ioutil.WriteFile(site+".pem", chain, 0644) + die.If(err) + fmt.Printf("[+] wrote %s.pem.\n", site) + } +} diff --git a/die/README.md b/die/README.md new file mode 100644 index 0000000..d509d84 --- /dev/null +++ b/die/README.md @@ -0,0 +1,12 @@ +Simple fatal utilities for Go programs. + +``` + result, err := doSomething() + die.If(err) + + ok := processResult(result) + if !ok { + die.With("failed to process result %s", result.Name) + } +``` + diff --git a/die/die.go b/die/die.go new file mode 100644 index 0000000..472d685 --- /dev/null +++ b/die/die.go @@ -0,0 +1,22 @@ +// Package die contains utilities for fatal error handling. +package die + +import ( + "fmt" + "os" +) + +// If prints the error to stderr and exits if err != nil. +func If(err error) { + if err != nil { + fmt.Fprintf(os.Stderr, "[!] %v\n", err) + os.Exit(1) + } +} + +// With prints the message to stderr, appending a newline, and exits. +func With(fstr string, args ...interface{}) { + out := fmt.Sprintf("[!] %s\n", fstr) + fmt.Fprintf(os.Stderr, out, args...) + os.Exit(1) +}