-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkeys.go
153 lines (121 loc) · 4.18 KB
/
keys.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
/*
Copyright (c) 2020 GMO GlobalSign, Inc.
Licensed under the MIT License (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
https://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pemfile
import (
"crypto/x509"
"encoding/pem"
"fmt"
"os"
"golang.org/x/crypto/ssh/terminal"
)
const (
pkcs8PrivateKeyPEMType = "PRIVATE KEY"
pkcs1PrivateKeyPEMType = "RSA PRIVATE KEY"
ecPrivateKeyPEMType = "EC PRIVATE KEY"
pkixPublicKeyPEMType = "PUBLIC KEY"
pkcs1PublicKeyPEMType = "RSA PUBLIC KEY"
)
// ReadPrivateKey reads a single private key. PKCS1 RSA private keys, SEC1 EC
// private keys, and PKCS8 RSA and EC private keys are supported.
func ReadPrivateKey(filename string) (interface{}, error) {
block, err := ReadBlock(filename)
if err != nil {
return nil, err
}
return parsePrivateKeyBlock(block)
}
// ReadPrivateKeyWithPasswordFunc reads a single private key which may be in
// an encrypted PEM block. If it is, decryption is attempted with a password
// returned by the provided function. The two arguments to the function are
// a description of the type of credential (e.g. "password", "PIN") and a
// description of the target of the credential (e.g. "private key", "HSM").
// The function should return an error if the credential cannot be retrieved.
// If pwfunc is nil and the PEM block is encrypted, a password will be
// requested from the terminal.
func ReadPrivateKeyWithPasswordFunc(filename string, pwfunc func(string, string) ([]byte, error)) (interface{}, error) {
block, err := ReadBlock(filename)
if err != nil {
return nil, err
}
if x509.IsEncryptedPEMBlock(block) {
if pwfunc == nil {
pwfunc = passwordFromTerminal
}
pass, err := pwfunc("passphrase", "private key")
if err != nil {
return nil, err
}
block.Bytes, err = x509.DecryptPEMBlock(block, pass)
if err != nil {
return nil, fmt.Errorf("failed to decrypt PEM block: %w", err)
}
}
return parsePrivateKeyBlock(block)
}
// ReadPublicKey reads a single public key. PKCS1 RSA RSA public keys, and
// PKIX RSA and EC public keys are supported.
func ReadPublicKey(filename string) (interface{}, error) {
block, err := ReadBlock(filename)
if err != nil {
return nil, err
}
if err := IsType(block, pkixPublicKeyPEMType, pkcs1PublicKeyPEMType); err != nil {
return nil, err
}
var key interface{}
switch block.Type {
case pkixPublicKeyPEMType:
key, err = x509.ParsePKIXPublicKey(block.Bytes)
case pkcs1PublicKeyPEMType:
key, err = x509.ParsePKCS1PublicKey(block.Bytes)
}
if err != nil {
return nil, fmt.Errorf("failed to parse public key: %w", err)
}
return key, nil
}
// parsePrivateKeyBlock parses a private key in an (unencrypted) PEM block.
func parsePrivateKeyBlock(block *pem.Block) (interface{}, error) {
err := IsType(block, pkcs8PrivateKeyPEMType, pkcs1PrivateKeyPEMType, ecPrivateKeyPEMType)
if err != nil {
return nil, err
}
var key interface{}
switch block.Type {
case pkcs8PrivateKeyPEMType:
key, err = x509.ParsePKCS8PrivateKey(block.Bytes)
case pkcs1PrivateKeyPEMType:
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
case ecPrivateKeyPEMType:
key, err = x509.ParseECPrivateKey(block.Bytes)
}
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
return key, nil
}
// passwordFromTerminal prompts for a password at the terminal.
func passwordFromTerminal(cred, target string) ([]byte, error) {
// Open the (POSIX standard) /dev/tty to ensure we're reading from and
// writing to an actual terminal.
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, fmt.Errorf("failed to open terminal: %w", err)
}
tty.Write([]byte(fmt.Sprintf("Enter %s for %s: ", cred, target)))
pass, err := terminal.ReadPassword(int(tty.Fd()))
tty.Write([]byte("\n"))
if err != nil {
return nil, fmt.Errorf("failed to read password: %w", err)
}
return pass, nil
}