/
ssh_agent.go
127 lines (98 loc) · 3.17 KB
/
ssh_agent.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package sshagent
import (
"crypto/rand"
"fmt"
"os"
"time"
"github.com/go-piv/piv-go/piv"
"github.com/vitalvas/oneauth/internal/tools"
"github.com/vitalvas/oneauth/internal/yubikey"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func (a *SSHAgent) List() ([]*agent.Key, error) {
a.lock.Lock()
defer a.lock.Unlock()
keys := make([]*agent.Key, 0, len(yubikey.AllSSHSlots))
activeSlots, err := a.yk.GetActiveSlots(yubikey.AllSSHSlots...)
if err != nil {
return nil, fmt.Errorf("failed to get active slots: %w", err)
}
for _, slot := range activeSlots {
certPublicKey, err := a.yk.GetCertPublicKey(slot.PIVSlot)
if err != nil {
return nil, fmt.Errorf("failed to get public key: %w", err)
}
pk, err := ssh.NewPublicKey(certPublicKey)
if err != nil {
return nil, fmt.Errorf("failed to create ssh public key: %w", err)
}
keys = append(keys, &agent.Key{
Format: pk.Type(),
Blob: pk.Marshal(),
Comment: fmt.Sprintf("YubiKey #%d PIV Slot %s", a.yk.Serial, slot.PIVSlot.String()),
})
}
return keys, nil
}
func (a *SSHAgent) Sign(reqKey ssh.PublicKey, data []byte) (*ssh.Signature, error) {
return a.SignWithFlags(reqKey, data, 0)
}
func (a *SSHAgent) SignWithFlags(reqKey ssh.PublicKey, data []byte, flags agent.SignatureFlags) (*ssh.Signature, error) {
a.lock.Lock()
defer a.lock.Unlock()
fp := tools.SSHFingerprint(reqKey)
keys, err := a.yk.ListKeys(yubikey.AllSlots...)
if err != nil {
return nil, fmt.Errorf("failed to list keys: %w", err)
}
dataHash := tools.FastHash(data)
a.log.Println("request to sign payload:", dataHash)
for _, key := range keys {
sshPublicKey, err := ssh.NewPublicKey(key.PublicKey)
if err != nil {
return nil, fmt.Errorf("failed to create ssh public key for sing: %w", err)
}
if fp != tools.SSHFingerprint(sshPublicKey) {
continue
}
hookEnv := map[string]string{
"YUBIKEY_SLOT": key.Slot.String(),
"YUBIKEY_SERIAL": fmt.Sprintf("%d", a.yk.Serial),
}
if a.actions.BeforeSignHook != "" {
if err := tools.RunCommand(a.actions.BeforeSignHook, hookEnv); err != nil {
return nil, fmt.Errorf("before sign hook failed: %w", err)
}
}
sig, err := a.sshSign(key, data, flags)
if err != nil {
return nil, fmt.Errorf("failed to sign: %w", err)
}
a.log.Println("signed with slot:", key.Slot.String(), "payload:", dataHash)
return sig, nil
}
return nil, fmt.Errorf("unknown key %s", fp)
}
func (a *SSHAgent) sshSign(key yubikey.Cert, data []byte, _ agent.SignatureFlags) (*ssh.Signature, error) {
_, skip := os.LookupEnv("I_AM_A_REALLY_STUPID_PERSON_WHO_IGNORES_SECURITY_ADVICE")
if !skip {
if !key.NotBefore.IsZero() && key.NotBefore.After(time.Now()) {
return nil, fmt.Errorf("key not yet valid")
}
if !key.NotAfter.IsZero() && key.NotAfter.Before(time.Now()) {
return nil, fmt.Errorf("key expired")
}
}
priv, err := a.yk.PrivateKey(key.Slot.PIVSlot, key.PublicKey, piv.KeyAuth{
PINPrompt: a.askPINPrompt,
})
if err != nil {
return nil, fmt.Errorf("failed to get private key: %w", err)
}
signer, err := ssh.NewSignerFromKey(priv)
if err != nil {
return nil, fmt.Errorf("failed to create signer: %w", err)
}
return signer.Sign(rand.Reader, data)
}