-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhostkeys.go
More file actions
171 lines (145 loc) · 3.4 KB
/
hostkeys.go
File metadata and controls
171 lines (145 loc) · 3.4 KB
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package hostkeys
import (
"bytes"
"crypto/elliptic"
"fmt"
"io"
"os"
"path"
"github.com/fasmide/hostkeys/generator"
"golang.org/x/crypto/ssh"
)
// Manager configures hostkeys
type Manager struct {
// Directory where keys are stored
//
// default: current work directory
Directory string
// NamingScheme specifies naming scheme for keys.
// Must include a %s for inserting keytype.
//
// To use existing openssh keys: "ssh_host_%s_key"
//
// default: determines executable name at runtime
// and sets the value to "<executable>_host_%s_key"
NamingScheme string
// Keys defines which types of keys to manage.
//
// default: a set of keys similar to openssh,
// rsa 3072 bits, ecdsa P256, and an ed25519 key.
Keys []Generator
}
// Manage sets up a *ssh.ServerConfig with keys by generating or reuseing existing keys.
func (m *Manager) Manage(c *ssh.ServerConfig) error {
err := m.defaults()
if err != nil {
return fmt.Errorf("hostkeys: default settings failed: %w", err)
}
for _, k := range m.Keys {
signer, err := m.load(k)
if err == nil {
c.AddHostKey(signer)
continue
}
if !os.IsNotExist(err) {
return fmt.Errorf("hostkeys: invalid %s key: %w", k.Name(), err)
}
// this key should be generated, it did not exist
signer, err = m.storeAndLoad(k)
if err != nil {
return err
}
c.AddHostKey(signer)
}
return nil
}
func (m *Manager) storeAndLoad(g Generator) (ssh.Signer, error) {
err := g.Generate()
if err != nil {
return nil, err
}
// create private key
private, err := os.OpenFile(
path.Join(
m.Directory,
fmt.Sprintf(m.NamingScheme, g.Name()),
),
os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600,
)
if err != nil {
return nil, err
}
defer private.Close()
// write private key
err = g.Encode(private)
if err != nil {
return nil, err
}
// create public key
public, err := os.Create(
path.Join(
m.Directory,
fmt.Sprint(fmt.Sprintf(m.NamingScheme, g.Name()), ".pub"),
),
)
if err != nil {
return nil, err
}
defer public.Close()
// write public key
err = g.EncodePublic(public)
if err != nil {
return nil, err
}
// return back a signer based on this new key
var buffer bytes.Buffer
err = g.Encode(&buffer)
if err != nil {
return nil, err
}
return ssh.ParsePrivateKey(buffer.Bytes())
}
func (m *Manager) load(g Generator) (ssh.Signer, error) {
fd, err := os.Open(
path.Join(
m.Directory,
fmt.Sprintf(m.NamingScheme, g.Name()),
),
)
if err != nil {
return nil, err
}
// something is off we encounter a file larger then 64Kbyte
b, err := io.ReadAll(io.LimitReader(fd, 1024*64))
if err != nil {
return nil, err
}
return ssh.ParsePrivateKey(b)
}
func (m *Manager) defaults() error {
// is no NamingScheme is set, use a naming scheme similar to openssh
if m.NamingScheme == "" {
s, err := os.Readlink("/proc/self/exe")
if err != nil {
return fmt.Errorf("unable to read link of /proc/self/exe: %w", err)
}
m.NamingScheme = fmt.Sprintf("%s_host_%%s_key", path.Base(s))
}
// if no directory was provided, default to the current work directory
if m.Directory == "" {
var err error
m.Directory, err = os.Getwd()
if err != nil {
return fmt.Errorf("unable to determine current work directory: %w", err)
}
}
// default set of keys
if len(m.Keys) == 0 {
m.Keys = []Generator{
&generator.RSA{BitSize: 3072},
&generator.ED25519{},
&generator.ECDSA{Curve: elliptic.P256()},
}
}
return nil
}