Eric Bower
·
18 May 24
keyview.go
1package pubkeys
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/picosh/pico/db"
8 "github.com/picosh/pico/tui/common"
9 "golang.org/x/crypto/ssh"
10)
11
12func algo(keyType string) string {
13 if idx := strings.Index(keyType, "@"); idx > 0 {
14 return algo(keyType[0:idx])
15 }
16 parts := strings.Split(keyType, "-")
17 if len(parts) == 2 {
18 return parts[1]
19 }
20 if parts[0] == "sk" {
21 return algo(strings.TrimPrefix(keyType, "sk-"))
22 }
23 return parts[0]
24}
25
26type Fingerprint struct {
27 Type string
28 Value string
29 Algorithm string
30 Styles common.Styles
31}
32
33// String outputs a string representation of the fingerprint.
34func (f Fingerprint) String() string {
35 return fmt.Sprintf(
36 "%s %s",
37 f.Styles.ListKey.Render(strings.ToUpper(f.Algorithm)),
38 f.Styles.ListKey.Render(f.Type+":"+f.Value),
39 )
40}
41
42// FingerprintSHA256 returns the algorithm and SHA256 fingerprint for the given
43// key.
44func FingerprintSHA256(styles common.Styles, k *db.PublicKey) (Fingerprint, error) {
45 key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(k.Key))
46 if err != nil {
47 return Fingerprint{}, fmt.Errorf("failed to parse public key: %w", err)
48 }
49
50 return Fingerprint{
51 Algorithm: algo(key.Type()),
52 Type: "SHA256",
53 Value: strings.TrimPrefix(ssh.FingerprintSHA256(key), "SHA256:"),
54 Styles: styles,
55 }, nil
56}
57
58// wrap fingerprint to support additional states.
59type fingerprint struct {
60 Fingerprint
61}
62
63func (f fingerprint) state(s keyState, styles common.Styles) string {
64 if s == keyDeleting {
65 return fmt.Sprintf(
66 "%s %s",
67 styles.Delete.Render(strings.ToUpper(f.Algorithm)),
68 styles.Delete.Render(f.Type+":"+f.Value),
69 )
70 }
71 return f.String()
72}
73
74type styledKey struct {
75 styles common.Styles
76 date string
77 fingerprint fingerprint
78 gutter string
79 keyLabel string
80 dateLabel string
81 commentLabel string
82 commentVal string
83 dateVal string
84 note string
85}
86
87func (m Model) newStyledKey(styles common.Styles, key *db.PublicKey, active bool) styledKey {
88 date := key.CreatedAt.Format(common.DateFormat)
89 fp, err := FingerprintSHA256(styles, key)
90 if err != nil {
91 fp = Fingerprint{Value: "[error generating fingerprint]"}
92 }
93
94 var note string
95 if active {
96 note = m.shared.Styles.Note.Render("• Current Key")
97 }
98
99 // Default state
100 return styledKey{
101 styles: styles,
102 date: date,
103 fingerprint: fingerprint{fp},
104 gutter: " ",
105 keyLabel: "Key:",
106 dateLabel: "Added:",
107 commentLabel: "Name:",
108 commentVal: key.Name,
109 dateVal: styles.Label.Render(date),
110 note: note,
111 }
112}
113
114// Selected state.
115func (k *styledKey) selected() {
116 k.gutter = common.VerticalLine(k.styles.Renderer, common.StateSelected)
117 k.keyLabel = k.styles.Label.Render("Key:")
118 k.dateLabel = k.styles.Label.Render("Added:")
119 k.commentLabel = k.styles.Label.Render("Name:")
120}
121
122// Deleting state.
123func (k *styledKey) deleting() {
124 k.gutter = common.VerticalLine(k.styles.Renderer, common.StateDeleting)
125 k.keyLabel = k.styles.Delete.Render("Key:")
126 k.dateLabel = k.styles.Delete.Render("Added:")
127 k.commentLabel = k.styles.Delete.Render("Name:")
128 k.dateVal = k.styles.Delete.Render(k.date)
129}
130
131func (k styledKey) render(state keyState) string {
132 switch state {
133 case keySelected:
134 k.selected()
135 case keyDeleting:
136 k.deleting()
137 }
138 return fmt.Sprintf(
139 "%s %s %s %s\n%s %s %s\n%s %s %s\n\n",
140 k.gutter, k.commentLabel, k.commentVal, k.note,
141 k.gutter, k.keyLabel, k.fingerprint.state(state, k.styles),
142 k.gutter, k.dateLabel, k.dateVal,
143 )
144}