cmd/inspect: finish command implementation

This commit is contained in:
Laurence Withers 2023-04-29 10:59:15 +01:00
parent 279e9af791
commit 5e6b089971
6 changed files with 191 additions and 60 deletions

40
cmd/inspect/aurora.go Normal file
View File

@ -0,0 +1,40 @@
package inspect
import (
"fmt"
"os"
"strings"
"github.com/logrusorgru/aurora/v4"
)
var (
aura *aurora.Aurora
)
func setupAura(coloured bool) {
var hyperlinks bool
if coloured {
switch os.Getenv("TERM") {
case "xterm-kitty":
hyperlinks = true
}
}
aura = aurora.New(aurora.WithColors(coloured), aurora.WithHyperlinks(hyperlinks))
}
func displayErr(err error, filename string) {
fmt.Fprintf(os.Stderr, "%s: ", displayFilename(filename))
fmt.Fprintf(os.Stderr, "%v\n", aura.Red(err))
}
func displayFilename(filename string) aurora.Value {
switch {
case strings.HasPrefix(filename, "https://"):
return aura.Hyperlink(filename, filename)
case filename == "-":
return aura.Gray(12, "(stdin)")
default:
return aura.Blue(filename)
}
}

View File

@ -0,0 +1,69 @@
package inspect
import (
"encoding/json"
"fmt"
"os"
"gopkg.in/yaml.v3"
"src.lwithers.me.uk/go/rsa/pkg/inspect"
)
var (
displayStructures []displayStructure
)
func displayStructured(src string, info []inspect.Info) {
for _, item := range info {
displayStructures = append(displayStructures, displayStructure{
Source: src,
Type: displayInfoType(item.Type()),
Location: item.Location(),
Info: item.Info(),
})
}
}
func displayStructuredJSON() {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(displayStructures); err != nil {
fmt.Fprintln(os.Stderr, "failed to encode as JSON: %v\n", aura.Red(err))
os.Exit(1)
}
}
func displayStructuredYAML() {
enc := yaml.NewEncoder(os.Stdout)
enc.SetIndent(2)
if err := enc.Encode(displayStructures); err != nil {
fmt.Fprintln(os.Stderr, "failed to encode as YAML: %v\n", aura.Red(err))
os.Exit(2)
}
}
type displayStructure struct {
Source string `json:"source" yaml:"source"`
Type string `json:"type" yaml:"type"`
Location string `json:"location" yaml:"location"`
Info []inspect.Section `json:"info" yaml:"info"`
}
func displayInfoType(t inspect.Type) string {
switch t {
case inspect.TypePrivateKey:
return "private_key"
case inspect.TypePublicKey:
return "public_key"
case inspect.TypeX509Certificate:
return "certificate"
case inspect.TypeX509CRL:
return "crl"
case inspect.TypeX509CSR:
return "csr"
case inspect.TypeTLSConnectionState:
return "tls_state"
default:
return "???"
}
}

View File

@ -2,12 +2,10 @@ package inspect
import (
"fmt"
"os"
"strings"
"time"
"unicode/utf8"
"github.com/logrusorgru/aurora/v4"
"src.lwithers.me.uk/go/rsa/pkg/inspect"
)
@ -15,7 +13,7 @@ var (
displayedFingerprints = map[string]int{}
)
func displayColoured(src string, info []inspect.Info) {
func displayText(src string, info []inspect.Info) {
// compute max key length, for nicely aligning columns
var maxKey int
for _, item := range info {
@ -29,43 +27,35 @@ func displayColoured(src string, info []inspect.Info) {
}
}
// output options
var auroraHyper bool
switch os.Getenv("TERM") {
case "xterm-kitty":
auroraHyper = true
}
a := aurora.New(aurora.WithColors(true), aurora.WithHyperlinks(auroraHyper))
// display loop
for _, item := range info {
fmt.Printf("════════ %s:%s ════════\n",
a.BrightBlue(src), a.Blue(item.Location()))
aura.BrightBlue(src), aura.Blue(item.Location()))
for _, section := range item.Info() {
fmt.Println(aurora.Underline(section.Title))
fmt.Println(aura.Underline(section.Title))
for _, field := range section.Fields {
fmt.Printf(" %*s: ", maxKey, a.Yellow(field.Key))
fmt.Printf(" %*s: ", maxKey, aura.Yellow(field.Key))
switch v := field.Value.(type) {
case int:
fmt.Print(a.Blue(v))
fmt.Print(aura.Blue(v))
case bool:
fmt.Print(a.Blue(v))
fmt.Print(aura.Blue(v))
case time.Time:
var note string
switch {
case strings.Contains(field.Key, "from"):
if v.After(time.Now()) {
note = aurora.Red("not valid yet").String()
note = aura.Red("not valid yet").String()
} else {
note = aurora.Green("ok").String()
note = aura.Green("ok").String()
}
case strings.Contains(field.Key, "until"):
if v.After(time.Now()) {
note = aurora.Green("ok").String()
note = aura.Green("ok").String()
} else {
note = aurora.Red("expired").String()
note = aura.Red("expired").String()
}
}
@ -91,11 +81,11 @@ func displayColoured(src string, info []inspect.Info) {
var note string
if firstSeen {
note = fmt.Sprintf("#%d %s", a.Blue(fidx),
a.Magenta("first occurrence"))
note = fmt.Sprintf("#%d %s", aura.Blue(fidx),
aura.Magenta("first occurrence"))
} else {
note = fmt.Sprintf("#%d %s", a.Blue(fidx),
a.Green("already seen"))
note = fmt.Sprintf("#%d %s", aura.Blue(fidx),
aura.Green("already seen"))
}
fmt.Printf("%v [%s]", f, note)

View File

@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/url"
"os"
@ -13,14 +14,16 @@ import (
"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 "keygen" subcommand.
// Register the "inspect" subcommand.
func Register(root *cobra.Command) {
cmd := &cobra.Command{
Use: "inspect",
@ -39,64 +42,88 @@ func Register(root *cobra.Command) {
root.AddCommand(cmd)
}
// Keygen will generate a new RSA private key and save it to a file.
// Inspect PEM data from files or TLS handshakes.
func Inspect(cmd *cobra.Command, args []string) error {
setupAura(false)
switch outputFormat {
case "coloured":
display = displayColoured
setupAura(true)
display = displayText
case "text":
display = displayText
case "json":
display = displayJSON
display = displayStructured
defer displayStructuredJSON()
case "yaml":
display = displayYAML
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 == "-":
inspectStdin()
ok = inspectStdin() && ok
case strings.HasPrefix(arg, "https://"):
inspectHTTPS(arg)
ok = inspectHTTPS(arg) && ok
case strings.IndexByte(arg, ':') != 0:
case strings.IndexByte(arg, '/') == -1 && strings.IndexByte(arg, ':') != -1:
_, _, err := net.SplitHostPort(arg)
if err == nil {
inspectHost(arg)
ok = inspectHost(arg) && ok
break
}
fallthrough
default:
inspectFile(arg)
ok = inspectFile(arg) && ok
}
}
if !ok {
os.Exit(1)
}
return nil
}
func inspectStdin() {
// TODO
}
func inspectHTTPS(addr string) {
u, err := url.Parse(addr)
func inspectStdin() (ok bool) {
in := stdinprompt.New()
raw, err := io.ReadAll(in)
if err != nil {
// TODO
displayErr(err, "-")
return false
}
inspectHost(u.Host)
// TODO: need to add default port
info := inspect.LoadPEM(raw)
if len(info) == 0 {
displayErr(noPEMData, "-")
return false
}
display("(stdin)", info)
return true
}
func inspectHost(addr string) {
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()
// TODO: context dial
conn, err := tls.Dial("tcp", addr, &tls.Config{
// we want to skip verification so we can inspect invalid certs too
InsecureSkipVerify: true,
@ -128,34 +155,34 @@ func inspectHost(addr string) {
MinVersion: tls.VersionTLS10,
})
if err != nil {
// TODO
displayErr(err, addr)
return false
}
if err := conn.HandshakeContext(ctx); err != nil {
// TODO
displayErr(err, addr)
return false
}
display(addr, inspect.ConnectionState(conn.ConnectionState()))
return true
}
func inspectFile(filename string) {
func inspectFile(filename string) (ok bool) {
raw, err := os.ReadFile(filename)
if err != nil {
// TODO
displayErr(err, filename)
return false
}
info := inspect.LoadPEM(raw)
if len(info) == 0 {
// TODO
displayErr(noPEMData, filename)
return false
}
display(filename, info)
}
func displayText(src string, info []inspect.Info) {
}
func displayJSON(src string, info []inspect.Info) {
return true
}
func displayYAML(src string, info []inspect.Info) {

4
go.mod
View File

@ -6,7 +6,9 @@ require (
github.com/logrusorgru/aurora/v4 v4.0.0
github.com/mattn/go-isatty v0.0.17
github.com/spf13/cobra v1.6.1
src.lwithers.me.uk/go/rsa/pkg v1.0.0
gopkg.in/yaml.v3 v3.0.1
src.lwithers.me.uk/go/rsa/pkg v1.0.1
src.lwithers.me.uk/go/stdinprompt v1.0.0
)
require (

7
go.sum
View File

@ -17,10 +17,13 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
src.lwithers.me.uk/go/rsa/pkg v1.0.0 h1:JDJDlml1GfJE/jyiqhXhqkInQRVs/t3MvsRXWNEWPVs=
src.lwithers.me.uk/go/rsa/pkg v1.0.0/go.mod h1:nZra8VAzQIbrQg2L6ev2BRQxvnpu7FBxfMtBrZ1ksek=
src.lwithers.me.uk/go/rsa/pkg v1.0.1 h1:Ht84EsH8yZqTXxOOXMZg1mLzUmn0ocvS1wQv9Fo4wlA=
src.lwithers.me.uk/go/rsa/pkg v1.0.1/go.mod h1:nZra8VAzQIbrQg2L6ev2BRQxvnpu7FBxfMtBrZ1ksek=
src.lwithers.me.uk/go/stdinprompt v1.0.0 h1:AuVloVOjwJbfl/cgJXOGCiAAXlElYFe1n+sN7vPnccY=
src.lwithers.me.uk/go/stdinprompt v1.0.0/go.mod h1:jHpqKtXU/wfnpM7SmpAXyMUbQNG3VYzFRIAed7IZ/0Y=
src.lwithers.me.uk/go/writefile v1.0.1 h1:bwBGtvyZfCxFIM14e1aYgJWlZuowKkwJx53OJlUPd0s=
src.lwithers.me.uk/go/writefile v1.0.1/go.mod h1:NahlmRCtB7kg4ai+zHZgxXdUs+MR8VqWG8mql35TsxA=