cmd/inspect: finish command implementation
This commit is contained in:
parent
279e9af791
commit
5e6b089971
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 "???"
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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 inspectStdin() (ok bool) {
|
||||
in := stdinprompt.New()
|
||||
raw, err := io.ReadAll(in)
|
||||
if err != nil {
|
||||
displayErr(err, "-")
|
||||
return false
|
||||
}
|
||||
|
||||
func inspectHTTPS(addr string) {
|
||||
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 {
|
||||
// TODO
|
||||
displayErr(err, addr)
|
||||
return false
|
||||
}
|
||||
|
||||
inspectHost(u.Host)
|
||||
// TODO: need to add default port
|
||||
addr = u.Host
|
||||
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||
addr += ":443"
|
||||
}
|
||||
return inspectHost(addr)
|
||||
}
|
||||
|
||||
func inspectHost(addr string) {
|
||||
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
4
go.mod
|
@ -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
7
go.sum
|
@ -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=
|
||||
|
|
Loading…
Reference in New Issue