-
Notifications
You must be signed in to change notification settings - Fork 641
/
gitsshkey.go
145 lines (125 loc) · 4.03 KB
/
gitsshkey.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
package gitsshkey
import (
"bufio"
"crypto"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"flag"
"io"
"strings"
"time"
insecurerand "math/rand"
"golang.org/x/crypto/ssh"
"golang.org/x/xerrors"
)
type Algorithm string
const (
// AlgorithmEd25519 is the Edwards-curve Digital Signature Algorithm using Curve25519
AlgorithmEd25519 Algorithm = "ed25519"
// AlgorithmECDSA is the Digital Signature Algorithm (DSA) using NIST Elliptic Curve
AlgorithmECDSA Algorithm = "ecdsa"
// AlgorithmRSA4096 is the venerable Rivest-Shamir-Adleman algorithm
// and creates a key with a fixed size of 4096-bit.
AlgorithmRSA4096 Algorithm = "rsa4096"
)
func entropy() io.Reader {
if flag.Lookup("test.v") != nil {
// This helps speed along our tests, esp. in CI where entropy is
// sparse.
//nolint:gosec
return insecurerand.New(insecurerand.NewSource(time.Now().UnixNano()))
}
// Buffering to reduce the number of system calls
// doubles performance without any loss of security.
return bufio.NewReader(rand.Reader)
}
// ParseAlgorithm returns a valid Algorithm or error if input is not a valid.
func ParseAlgorithm(t string) (Algorithm, error) {
ok := []string{
string(AlgorithmEd25519),
string(AlgorithmECDSA),
string(AlgorithmRSA4096),
}
for _, a := range ok {
if strings.EqualFold(a, t) {
return Algorithm(a), nil
}
}
return "", xerrors.Errorf(`invalid key type: %s, must be one of: %s`, t, strings.Join(ok, ","))
}
// Generate creates a private key in the OpenSSH PEM format and public key in
// the authorized key format.
func Generate(algo Algorithm) (privateKey string, publicKey string, err error) {
switch algo {
case AlgorithmEd25519:
return ed25519KeyGen()
case AlgorithmECDSA:
return ecdsaKeyGen()
case AlgorithmRSA4096:
return rsa4096KeyGen()
default:
return "", "", xerrors.Errorf("invalid algorithm: %s", algo)
}
}
// ed25519KeyGen returns an ED25519-based SSH private key.
func ed25519KeyGen() (privateKey string, publicKey string, err error) {
_, privateKeyRaw, err := ed25519.GenerateKey(entropy())
if err != nil {
return "", "", xerrors.Errorf("generate ed25519 private key: %w", err)
}
// NOTE: as of the time of writing, x/crypto/ssh is unable to marshal an ED25519 private key
// into the format expected by OpenSSH. See: https://github.com/golang/go/issues/37132
// Until this support is added, using a third-party implementation.
byt, err := MarshalED25519PrivateKey(privateKeyRaw)
if err != nil {
return "", "", xerrors.Errorf("marshal ed25519 private key: %w", err)
}
return generateKeys(pem.Block{
Type: "OPENSSH PRIVATE KEY",
Bytes: byt,
}, privateKeyRaw)
}
// ecdsaKeyGen returns an ECDSA-based SSH private key.
func ecdsaKeyGen() (privateKey string, publicKey string, err error) {
privateKeyRaw, err := ecdsa.GenerateKey(elliptic.P256(), entropy())
if err != nil {
return "", "", xerrors.Errorf("generate ecdsa private key: %w", err)
}
byt, err := x509.MarshalECPrivateKey(privateKeyRaw)
if err != nil {
return "", "", xerrors.Errorf("marshal private key: %w", err)
}
return generateKeys(pem.Block{
Type: "EC PRIVATE KEY",
Bytes: byt,
}, privateKeyRaw)
}
// rsaKeyGen returns an RSA-based SSH private key of size 4096.
//
// Administrators may configure this for SSH key compatibility with Azure DevOps.
func rsa4096KeyGen() (privateKey string, publicKey string, err error) {
privateKeyRaw, err := rsa.GenerateKey(entropy(), 4096)
if err != nil {
return "", "", xerrors.Errorf("generate RSA4096 private key: %w", err)
}
return generateKeys(pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKeyRaw),
}, privateKeyRaw)
}
func generateKeys(block pem.Block, cp crypto.Signer) (privateKey string, publicKey string, err error) {
pkBytes := pem.EncodeToMemory(&block)
privateKey = string(pkBytes)
publicKeyRaw := cp.Public()
p, err := ssh.NewPublicKey(publicKeyRaw)
if err != nil {
return "", "", err
}
publicKey = string(ssh.MarshalAuthorizedKey(p))
return privateKey, publicKey, nil
}