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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/logrusorgru/aurora/v4"
|
|
||||||
"src.lwithers.me.uk/go/rsa/pkg/inspect"
|
"src.lwithers.me.uk/go/rsa/pkg/inspect"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,7 +13,7 @@ var (
|
||||||
displayedFingerprints = map[string]int{}
|
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
|
// compute max key length, for nicely aligning columns
|
||||||
var maxKey int
|
var maxKey int
|
||||||
for _, item := range info {
|
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
|
// display loop
|
||||||
for _, item := range info {
|
for _, item := range info {
|
||||||
fmt.Printf("════════ %s:%s ════════\n",
|
fmt.Printf("════════ %s:%s ════════\n",
|
||||||
a.BrightBlue(src), a.Blue(item.Location()))
|
aura.BrightBlue(src), aura.Blue(item.Location()))
|
||||||
for _, section := range item.Info() {
|
for _, section := range item.Info() {
|
||||||
fmt.Println(aurora.Underline(section.Title))
|
fmt.Println(aura.Underline(section.Title))
|
||||||
for _, field := range section.Fields {
|
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) {
|
switch v := field.Value.(type) {
|
||||||
case int:
|
case int:
|
||||||
fmt.Print(a.Blue(v))
|
fmt.Print(aura.Blue(v))
|
||||||
|
|
||||||
case bool:
|
case bool:
|
||||||
fmt.Print(a.Blue(v))
|
fmt.Print(aura.Blue(v))
|
||||||
|
|
||||||
case time.Time:
|
case time.Time:
|
||||||
var note string
|
var note string
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(field.Key, "from"):
|
case strings.Contains(field.Key, "from"):
|
||||||
if v.After(time.Now()) {
|
if v.After(time.Now()) {
|
||||||
note = aurora.Red("not valid yet").String()
|
note = aura.Red("not valid yet").String()
|
||||||
} else {
|
} else {
|
||||||
note = aurora.Green("ok").String()
|
note = aura.Green("ok").String()
|
||||||
}
|
}
|
||||||
case strings.Contains(field.Key, "until"):
|
case strings.Contains(field.Key, "until"):
|
||||||
if v.After(time.Now()) {
|
if v.After(time.Now()) {
|
||||||
note = aurora.Green("ok").String()
|
note = aura.Green("ok").String()
|
||||||
} else {
|
} 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
|
var note string
|
||||||
if firstSeen {
|
if firstSeen {
|
||||||
note = fmt.Sprintf("#%d %s", a.Blue(fidx),
|
note = fmt.Sprintf("#%d %s", aura.Blue(fidx),
|
||||||
a.Magenta("first occurrence"))
|
aura.Magenta("first occurrence"))
|
||||||
} else {
|
} else {
|
||||||
note = fmt.Sprintf("#%d %s", a.Blue(fidx),
|
note = fmt.Sprintf("#%d %s", aura.Blue(fidx),
|
||||||
a.Green("already seen"))
|
aura.Green("already seen"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%v [%s]", f, note)
|
fmt.Printf("%v [%s]", f, note)
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -13,14 +14,16 @@ import (
|
||||||
"github.com/mattn/go-isatty"
|
"github.com/mattn/go-isatty"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"src.lwithers.me.uk/go/rsa/pkg/inspect"
|
"src.lwithers.me.uk/go/rsa/pkg/inspect"
|
||||||
|
"src.lwithers.me.uk/go/stdinprompt"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
outputFormat string = "text"
|
outputFormat string = "text"
|
||||||
display func(src string, info []inspect.Info)
|
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) {
|
func Register(root *cobra.Command) {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "inspect",
|
Use: "inspect",
|
||||||
|
@ -39,64 +42,88 @@ func Register(root *cobra.Command) {
|
||||||
root.AddCommand(cmd)
|
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 {
|
func Inspect(cmd *cobra.Command, args []string) error {
|
||||||
|
setupAura(false)
|
||||||
switch outputFormat {
|
switch outputFormat {
|
||||||
case "coloured":
|
case "coloured":
|
||||||
display = displayColoured
|
setupAura(true)
|
||||||
|
display = displayText
|
||||||
case "text":
|
case "text":
|
||||||
display = displayText
|
display = displayText
|
||||||
case "json":
|
case "json":
|
||||||
display = displayJSON
|
display = displayStructured
|
||||||
|
defer displayStructuredJSON()
|
||||||
case "yaml":
|
case "yaml":
|
||||||
display = displayYAML
|
display = displayStructured
|
||||||
|
defer displayStructuredYAML()
|
||||||
default:
|
default:
|
||||||
return errors.New("invalid --output format (try: coloured, text, json or yaml)")
|
return errors.New("invalid --output format (try: coloured, text, json or yaml)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
switch {
|
switch {
|
||||||
case arg == "-":
|
case arg == "-":
|
||||||
inspectStdin()
|
ok = inspectStdin() && ok
|
||||||
|
|
||||||
case strings.HasPrefix(arg, "https://"):
|
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)
|
_, _, err := net.SplitHostPort(arg)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inspectHost(arg)
|
ok = inspectHost(arg) && ok
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
default:
|
default:
|
||||||
inspectFile(arg)
|
ok = inspectFile(arg) && ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !ok {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func inspectStdin() {
|
func inspectStdin() (ok bool) {
|
||||||
// TODO
|
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)
|
u, err := url.Parse(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO
|
displayErr(err, addr)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
inspectHost(u.Host)
|
addr = u.Host
|
||||||
// TODO: need to add default port
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// TODO: context dial
|
|
||||||
|
|
||||||
conn, err := tls.Dial("tcp", addr, &tls.Config{
|
conn, err := tls.Dial("tcp", addr, &tls.Config{
|
||||||
// we want to skip verification so we can inspect invalid certs too
|
// we want to skip verification so we can inspect invalid certs too
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
|
@ -128,34 +155,34 @@ func inspectHost(addr string) {
|
||||||
MinVersion: tls.VersionTLS10,
|
MinVersion: tls.VersionTLS10,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO
|
displayErr(err, addr)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conn.HandshakeContext(ctx); err != nil {
|
if err := conn.HandshakeContext(ctx); err != nil {
|
||||||
// TODO
|
displayErr(err, addr)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
display(addr, inspect.ConnectionState(conn.ConnectionState()))
|
display(addr, inspect.ConnectionState(conn.ConnectionState()))
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func inspectFile(filename string) {
|
func inspectFile(filename string) (ok bool) {
|
||||||
raw, err := os.ReadFile(filename)
|
raw, err := os.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO
|
displayErr(err, filename)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
info := inspect.LoadPEM(raw)
|
info := inspect.LoadPEM(raw)
|
||||||
if len(info) == 0 {
|
if len(info) == 0 {
|
||||||
// TODO
|
displayErr(noPEMData, filename)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
display(filename, info)
|
display(filename, info)
|
||||||
}
|
return true
|
||||||
|
|
||||||
func displayText(src string, info []inspect.Info) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func displayJSON(src string, info []inspect.Info) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayYAML(src string, info []inspect.Info) {
|
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/logrusorgru/aurora/v4 v4.0.0
|
||||||
github.com/mattn/go-isatty v0.0.17
|
github.com/mattn/go-isatty v0.0.17
|
||||||
github.com/spf13/cobra v1.6.1
|
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 (
|
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.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 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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/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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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.1 h1:Ht84EsH8yZqTXxOOXMZg1mLzUmn0ocvS1wQv9Fo4wlA=
|
||||||
src.lwithers.me.uk/go/rsa/pkg v1.0.0/go.mod h1:nZra8VAzQIbrQg2L6ev2BRQxvnpu7FBxfMtBrZ1ksek=
|
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 h1:bwBGtvyZfCxFIM14e1aYgJWlZuowKkwJx53OJlUPd0s=
|
||||||
src.lwithers.me.uk/go/writefile v1.0.1/go.mod h1:NahlmRCtB7kg4ai+zHZgxXdUs+MR8VqWG8mql35TsxA=
|
src.lwithers.me.uk/go/writefile v1.0.1/go.mod h1:NahlmRCtB7kg4ai+zHZgxXdUs+MR8VqWG8mql35TsxA=
|
||||||
|
|
Loading…
Reference in New Issue