-
Notifications
You must be signed in to change notification settings - Fork 0
/
keypair.go
143 lines (115 loc) · 3.44 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
142
143
package keystore
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"go.uber.org/multierr"
)
// ErrInvalidDecryptionKey is returned if the given password for the decryption is invalid and can't be used.
var ErrInvalidDecryptionKey = errors.New("given password can't decrypt the message")
// KeyPair represents a 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", ErrInvalidDecryptionKey)
}
// split nonce and cipher
nonce, sealed := cipherText[:nonceSize], cipherText[nonceSize:]
// open sealed cipher
pemData, err := gcm.Open(nil, nonce, sealed, nil)
if err != nil {
return nil, multierr.Append(fmt.Errorf("open sealed cipher text: %w", err), ErrInvalidDecryptionKey)
}
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
}