/
crypto.go
235 lines (195 loc) · 5.51 KB
/
crypto.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
package crypto
import (
"bytes"
"context"
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"golang.org/x/crypto/ed25519"
"golang.org/x/crypto/scrypt"
base64url "github.com/manifoldco/go-base64"
"github.com/manifoldco/torus-cli/daemon/ctxutil"
"github.com/manifoldco/torus-cli/primitive"
)
// Crypto Algorithm name constants.
const (
Triplesec = "triplesec-v3"
EdDSA = "eddsa"
Curve25519 = "curve25519"
EasyBox = "easybox"
SecretBox = "secretbox"
Scrypt = "scrypt"
)
// scrypt parameter constants
const (
n = 32768 // 2^15
r = 8
p = 1
keyLen = 256
saltBytes = 16
masterKeyBytes = 256
)
// LoginKeypair represents an Ed25519 Keypair used for generating a login token
// signature for Passphrase-Dervied Public Key Authentication.
type LoginKeypair struct {
public ed25519.PublicKey
private ed25519.PrivateKey
salt *base64url.Value
}
// PublicKey returns the base64 value of the public key
func (k *LoginKeypair) PublicKey() *base64url.Value {
return base64url.New(k.public)
}
// Salt returns the base64 representation of the salt used to derive the
// LoginKeypair
func (k *LoginKeypair) Salt() *base64url.Value {
return k.salt
}
// Sign returns a signature of the given token as a base64 string
func (k *LoginKeypair) Sign(token []byte) *base64url.Value {
sig := ed25519.Sign(k.private, token)
return base64url.New(sig)
}
// DeriveLoginHMAC HMACs the provided token with a key derived from password
// and the provided base64 encoded salt.
func DeriveLoginHMAC(ctx context.Context, password []byte, salt, token string) (string, error) {
key, err := derivePassword(ctx, password, salt)
if err != nil {
return "", err
}
pwh := make([]byte, base64.RawURLEncoding.EncodedLen(32))
base64.RawURLEncoding.Encode(pwh, key)
mac := hmac.New(sha512.New, pwh)
mac.Write([]byte(token))
return base64.RawURLEncoding.EncodeToString(mac.Sum(nil)), nil
}
func deriveHash(ctx context.Context, password []byte, salt string) ([]byte, error) {
s := make([]byte, base64.RawURLEncoding.DecodedLen(len(salt)))
l, err := base64.RawURLEncoding.Decode(s, []byte(salt))
if err != nil {
return nil, err
}
err = ctxutil.ErrIfDone(ctx)
if err != nil {
return nil, err
}
key, err := scrypt.Key(password, s[:l], n, r, p, keyLen)
return key, err
}
func derivePassword(ctx context.Context, password []byte, salt string) ([]byte, error) {
key, err := deriveHash(ctx, password, salt)
if err != nil {
return nil, err
}
pwh := key[192:224]
err = ctxutil.ErrIfDone(ctx)
if err != nil {
return nil, err
}
return pwh, err
}
// GenerateSalt returns a 16-byte (128 bit) salt used in password and secret
// key derivation.
func GenerateSalt(ctx context.Context) (*base64url.Value, error) {
err := ctxutil.ErrIfDone(ctx)
if err != nil {
return nil, err
}
salt := make([]byte, saltBytes) // 16
_, err = rand.Read(salt)
if err != nil {
return nil, err
}
return base64url.New(salt), nil
}
// EncryptPasswordObject derives the master key (if necessary) and password hash
// from password and salt, returning the master and password objects
func EncryptPasswordObject(ctx context.Context, password string, currentMasterKey *[]byte) (*primitive.UserPassword, *primitive.MasterKey, error) {
pw := &primitive.UserPassword{
Alg: Scrypt,
}
// Generate 128 bit (16 byte) salt for password
salt := make([]byte, saltBytes) // 16
_, err := rand.Read(salt)
if err != nil {
return nil, nil, err
}
// Encode salt bytes to base64url
pw.Salt = base64.RawURLEncoding.EncodeToString(salt)
// Create password hash bytes
pwh, err := derivePassword(ctx, []byte(password), pw.Salt)
if err != nil {
return nil, nil, err
}
// Encode password value to base64url
pw.Value = base64url.New(pwh)
m, err := CreateMasterKeyObject(ctx, []byte(password), currentMasterKey)
if err != nil {
return nil, nil, err
}
return pw, m, nil
}
// CreateMasterKeyObject generates a 256 byte master key which is then
// encrypted using TripleSec-v3 using the given password.
func CreateMasterKeyObject(ctx context.Context, password []byte, masterKey *[]byte) (*primitive.MasterKey, error) {
m := &primitive.MasterKey{
Alg: Triplesec,
}
// We either need to generate a new key, or will use the existing
// key during a password change scenario
key := make([]byte, masterKeyBytes)
if masterKey == nil {
// Generate a master key of 256 bytes
_, err := rand.Read(key)
if err != nil {
return nil, err
}
} else {
// Use the provided key
key = *masterKey
}
err := ctxutil.ErrIfDone(ctx)
if err != nil {
return nil, err
}
ts, err := newTriplesec(ctx, password)
if err != nil {
return nil, err
}
ct, err := ts.Encrypt(key)
if err != nil {
return nil, err
}
// Encode the master key value to base64url
m.Value = base64url.New(ct)
return m, nil
}
// DeriveLoginKeypair dervies the ed25519 login keypair used for machine
// authentication from the given salt and secret values.
func DeriveLoginKeypair(ctx context.Context, secret []byte, salt *base64url.Value) (
*LoginKeypair, error) {
key, err := deriveHash(ctx, secret, salt.String())
if err != nil {
return nil, err
}
err = ctxutil.ErrIfDone(ctx)
if err != nil {
return nil, err
}
r := bytes.NewReader(key[224:]) // Use last 32 bytes of 256 to derive key
pubKey, privKey, err := ed25519.GenerateKey(r)
if err != nil {
return nil, err
}
err = ctxutil.ErrIfDone(ctx)
if err != nil {
return nil, err
}
keypair := &LoginKeypair{
public: pubKey,
private: privKey,
salt: salt,
}
return keypair, nil
}