-
Notifications
You must be signed in to change notification settings - Fork 0
/
keypair.go
141 lines (114 loc) · 3.38 KB
/
keypair.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
package keystore
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
)
// ErrInvalidCipherKey is returned if the given password for decryption is invalid and can't be used.
var ErrInvalidCipherKey = errors.New("invalid cipher key")
// KeyPair represents a key pair combination of a certificate and a private key.
type KeyPair struct {
Cert *x509.Certificate
PK *rsa.PrivateKey
}
func (kp *KeyPair) encrypt(password, salt []byte) (cert []byte, pk []byte, err error) {
gcm, err := gcmCipher(salt, password)
if err != nil {
return nil, nil, fmt.Errorf("generate GCM: %w", err)
}
// encrypt certificate
derCert := kp.Cert.Raw
cert, err = encryptPEMWithGCM(gcm, "CERTIFICATE", derCert)
if err != nil {
return nil, nil, fmt.Errorf("encrypt certificate with GCM: %w", err)
}
// encrypt private key
derPK := x509.MarshalPKCS1PrivateKey(kp.PK)
pk, err = encryptPEMWithGCM(gcm, "RSA PRIVATE KEY", derPK)
if err != nil {
return nil, nil, fmt.Errorf("encrypt private key with GCM: %w", err)
}
return cert, pk, nil
}
func encryptPEMWithGCM(gcm cipher.AEAD, pemType string, data []byte) ([]byte, error) {
pemData := pem.EncodeToMemory(&pem.Block{
Type: pemType,
Bytes: data,
})
// random nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, fmt.Errorf("generate a random nonce: %w", err)
}
return gcm.Seal(nonce, nonce, pemData, nil), nil
}
func (kp *KeyPair) decrypt(password, salt, cert, pk []byte) error {
gcm, err := gcmCipher(salt, password)
if err != nil {
return fmt.Errorf("generate GCM cipher: %w", err)
}
// certificate
certPem, err := decryptPemWithGCM(gcm, cert)
if err != nil {
return fmt.Errorf("decrypt certificate: %w", err)
}
kp.Cert, err = x509.ParseCertificate(certPem)
if err != nil {
return fmt.Errorf("parse certificate: %w", err)
}
// private key
pkPem, err := decryptPemWithGCM(gcm, pk)
if err != nil {
return fmt.Errorf("decrypt private key: %w", err)
}
kp.PK, err = x509.ParsePKCS1PrivateKey(pkPem)
if err != nil {
return fmt.Errorf("parse private key: %w", err)
}
return nil
}
func decryptPemWithGCM(gcm cipher.AEAD, cipherText []byte) ([]byte, error) {
nonceSize := gcm.NonceSize()
// nonce is part of the cipher text and therefore must be smaller
if len(cipherText) < nonceSize {
return nil, fmt.Errorf("nonce is longer than cipher text: %w", ErrInvalidCipherKey)
}
// split nonce and the cipher
nonce, sealed := cipherText[:nonceSize], cipherText[nonceSize:]
// open sealed cipher
pemData, err := gcm.Open(nil, nonce, sealed, nil)
if err != nil {
return nil, fmt.Errorf("open sealed cipher text: %v: %w", err, ErrInvalidCipherKey)
}
block, _ := pem.Decode(pemData)
return block.Bytes, nil
}
func gcmCipher(salt, password []byte) (cipher.AEAD, error) {
salted := addSalt(salt, password)
hash := sha256.Sum256(salted)
c, err := aes.NewCipher(hash[:])
if err != nil {
return nil, fmt.Errorf("create a new cipher block: %w", err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, fmt.Errorf("create a GCM cipher mode: %w", err)
}
return gcm, nil
}
func addSalt(salt []byte, password []byte) []byte {
lp := len(password)
ls := len(salt)
// concatenate password and salt
out := make([]byte, lp+ls)
copy(out[:lp], password)
copy(out[lp:], salt)
return out
}