-
Notifications
You must be signed in to change notification settings - Fork 2
/
ssh.go
237 lines (192 loc) · 4.96 KB
/
ssh.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
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package ssh
import (
"crypto/ed25519"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"log"
mrand "math/rand"
"os"
"golang.org/x/crypto/ssh"
)
const (
DefaultBitSize = 4096
DefaultPrivateKeyPath = "id_rsa"
DefaultPublicKeyPath = "id_rsa.pub"
)
// SSHKey is a struct that holds the options when
// creating a new ssh key
type Options struct {
BitSize int `json:"bit_size"`
PriKeyPath string `json:"pri_key_path"`
PubKeyPath string `json:"pub_key_path"`
}
// SetDefaults sets the default values for the options
func (o *Options) SetDefaults() {
if o.BitSize == 0 {
o.BitSize = DefaultBitSize
}
if o.PriKeyPath == "" {
o.PriKeyPath = DefaultPrivateKeyPath
}
if o.PubKeyPath == "" {
o.PubKeyPath = DefaultPublicKeyPath
}
}
// DefaultOptions returns the default options
func DefaultOptions() Options {
return Options{
BitSize: DefaultBitSize,
PriKeyPath: DefaultPrivateKeyPath,
PubKeyPath: DefaultPublicKeyPath,
}
}
type SshKey struct {
options Options
PrivKey *rsa.PrivateKey
Priv []byte
Pub []byte
}
func NewSSHKey(opt Options) *SshKey {
return &SshKey{
options: opt,
}
}
func (s *SshKey) Generate() error {
privateKey, err := s.generatePrivateKey()
if err != nil {
return err
}
s.PrivKey = privateKey
s.generatePublicKey()
if err != nil {
log.Fatal(err.Error())
}
s.Priv = s.encodePrivateKeyToPEM()
return nil
}
// generatePrivateKey creates a RSA Private Key of specified byte size
func (s *SshKey) generatePrivateKey() (*rsa.PrivateKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, s.options.BitSize)
if err != nil {
return nil, err
}
err = privateKey.Validate()
if err != nil {
return nil, err
}
return privateKey, nil
}
// encodePrivateKeyToPEM encodes Private Key from RSA to PEM format
func (s *SshKey) encodePrivateKeyToPEM() []byte {
privBlock := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: x509.MarshalPKCS1PrivateKey(s.PrivKey),
}
return pem.EncodeToMemory(&privBlock)
}
// generatePublicKey take a rsa.PublicKey and return
// bytes suitable for writing to .pub file
// returns in the format "ssh-rsa ..."
func (s *SshKey) generatePublicKey() ([]byte, error) {
pub, err := ssh.NewPublicKey(s.PrivKey.Public())
if err != nil {
return []byte{}, err
}
s.Pub = ssh.MarshalAuthorizedKey(pub)
return s.Pub, nil
}
func (s *SshKey) SaveToFile() error {
err := write(s.Priv, s.options.PriKeyPath)
if err != nil {
return err
}
err = write(s.Pub, s.options.PubKeyPath)
if err != nil {
return err
}
return nil
}
func write(keyBytes []byte, saveFileTo string) error {
err := os.WriteFile(saveFileTo, keyBytes, 0600)
if err != nil {
return err
}
return nil
}
func GenerateED25519Key() ([]byte, []byte) {
pubKey, privKey, _ := ed25519.GenerateKey(rand.Reader)
publicKey, _ := ssh.NewPublicKey(pubKey)
pemKey := &pem.Block{
Type: "OPENSSH PRIVATE KEY",
Bytes: MarshalED25519PrivateKey(privKey),
}
privateKey := pem.EncodeToMemory(pemKey)
authorizedKey := ssh.MarshalAuthorizedKey(publicKey)
return privateKey, authorizedKey
}
func MarshalED25519PrivateKey(key ed25519.PrivateKey) []byte {
// Add our key header (followed by a null byte)
magic := append([]byte("openssh-key-v1"), 0)
var w struct {
CipherName string
KdfName string
KdfOpts string
NumKeys uint32
PubKey []byte
PrivKeyBlock []byte
}
// Fill out the private key fields
pk1 := struct {
Check1 uint32
Check2 uint32
Keytype string
Pub []byte
Priv []byte
Comment string
Pad []byte `ssh:"rest"`
}{}
// Set our check ints
ci := mrand.Uint32()
pk1.Check1 = ci
pk1.Check2 = ci
// Set our key type
pk1.Keytype = ssh.KeyAlgoED25519
// Add the pubkey to the optionally-encrypted block
pk, ok := key.Public().(ed25519.PublicKey)
if !ok {
//fmt.Fprintln(os.Stderr, "ed25519.PublicKey type assertion failed on an ed25519 public key. This should never ever happen.")
return nil
}
pubKey := []byte(pk)
pk1.Pub = pubKey
// Add our private key
pk1.Priv = []byte(key)
// Might be useful to put something in here at some point
pk1.Comment = ""
// Add some padding to match the encryption block size within PrivKeyBlock (without Pad field)
// 8 doesn't match the documentation, but that's what ssh-keygen uses for unencrypted keys. *shrug*
bs := 8
blockLen := len(ssh.Marshal(pk1))
padLen := (bs - (blockLen % bs)) % bs
pk1.Pad = make([]byte, padLen)
// Padding is a sequence of bytes like: 1, 2, 3...
for i := 0; i < padLen; i++ {
pk1.Pad[i] = byte(i + 1)
}
// Generate the pubkey prefix "\0\0\0\nssh-ed25519\0\0\0 "
prefix := []byte{0x0, 0x0, 0x0, 0x0b}
prefix = append(prefix, []byte(ssh.KeyAlgoED25519)...)
prefix = append(prefix, []byte{0x0, 0x0, 0x0, 0x20}...)
// Only going to support unencrypted keys for now
w.CipherName = "none"
w.KdfName = "none"
w.KdfOpts = ""
w.NumKeys = 1
w.PubKey = append(prefix, pubKey...)
w.PrivKeyBlock = ssh.Marshal(pk1)
magic = append(magic, ssh.Marshal(w)...)
return magic
}