rsa/cmd/inspect/inspect.go

200 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package inspect
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/url"
"os"
"strings"
"time"
"github.com/mattn/go-isatty"
"github.com/spf13/cobra"
"src.lwithers.me.uk/go/rsa/pkg/inspect"
"src.lwithers.me.uk/go/stdinprompt"
)
var (
outputFormat string = "text"
display func(src string, info []inspect.Info)
noPEMData = errors.New("no PEM data found")
)
// Register the "inspect" subcommand.
func Register(root *cobra.Command) {
cmd := &cobra.Command{
Use: "inspect source [source2 …]",
Short: "Inspect files and TLS servers",
Long: `Allows inspection of RSA keys, X.509 certificates, CRLs and CSRs. Pass
it a list of sources. Valid sources are:
• filenames
- for stdin
• host:port for extracting the server's certificate chain from a TLS handshake
• https://addr.example/ as per host:port
The output will by default be human-readable formatted text for the terminal,
but JSON and YAML are also supported through the --output flag.`,
RunE: Inspect,
Args: cobra.MinimumNArgs(1),
}
if isatty.IsTerminal(1) {
outputFormat = "coloured"
}
cmd.Flags().StringVarP(&outputFormat, "output", "o", outputFormat,
"Output format (coloured, text, json, yaml)")
root.AddCommand(cmd)
}
// Inspect PEM data from files or TLS handshakes.
func Inspect(cmd *cobra.Command, args []string) error {
setupAura(false)
switch outputFormat {
case "coloured":
setupAura(true)
display = displayText
case "text":
display = displayText
case "json":
display = displayStructured
defer displayStructuredJSON()
case "yaml":
display = displayStructured
defer displayStructuredYAML()
default:
return errors.New("invalid --output format (try: coloured, text, json or yaml)")
}
ok := true
for _, arg := range args {
switch {
case arg == "-":
ok = inspectStdin() && ok
case strings.HasPrefix(arg, "https://"):
ok = inspectHTTPS(arg) && ok
case strings.IndexByte(arg, '/') == -1 && strings.IndexByte(arg, ':') != -1:
_, _, err := net.SplitHostPort(arg)
if err == nil {
ok = inspectHost(arg) && ok
break
}
fallthrough
default:
ok = inspectFile(arg) && ok
}
}
if !ok {
os.Exit(1)
}
return nil
}
func inspectStdin() (ok bool) {
in := stdinprompt.New()
raw, err := io.ReadAll(in)
if err != nil {
displayErr(err, "-")
return false
}
info := inspect.LoadPEM(raw)
if len(info) == 0 {
displayErr(noPEMData, "-")
return false
}
display("(stdin)", info)
return true
}
func inspectHTTPS(addr string) (ok bool) {
u, err := url.Parse(addr)
if err != nil {
displayErr(err, addr)
return false
}
addr = u.Host
if _, _, err := net.SplitHostPort(addr); err != nil {
addr += ":443"
}
return inspectHost(addr)
}
func inspectHost(addr string) (ok bool) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
conn, err := tls.Dial("tcp", addr, &tls.Config{
// we want to skip verification so we can inspect invalid certs too
InsecureSkipVerify: true,
// we only enable RSA ciphersuites, but we do allow old ones, again
// so that we can report about bad / invalid servers
CipherSuites: []uint16{
tls.TLS_RSA_WITH_RC4_128_SHA,
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
},
// we enable TLSv1.0 so that we can complain about it
MinVersion: tls.VersionTLS10,
})
if err != nil {
displayErr(err, addr)
return false
}
if err := conn.HandshakeContext(ctx); err != nil {
displayErr(err, addr)
return false
}
display(addr, inspect.ConnectionState(conn.ConnectionState()))
return true
}
func inspectFile(filename string) (ok bool) {
raw, err := os.ReadFile(filename)
if err != nil {
displayErr(err, filename)
return false
}
info := inspect.LoadPEM(raw)
if len(info) == 0 {
displayErr(noPEMData, filename)
return false
}
display(filename, info)
return true
}
func displayYAML(src string, info []inspect.Info) {
}