forked from bytemare/opaque
-
Notifications
You must be signed in to change notification settings - Fork 0
/
opaque.go
319 lines (266 loc) · 9.02 KB
/
opaque.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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// SPDX-License-Identifier: MIT
//
// Copyright (C) 2020-2022 Daniel Bourdrez. All Rights Reserved.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree or at
// https://spdx.org/licenses/MIT.html
// Package opaque implements OPAQUE, an asymmetric password-authenticated key exchange protocol that is secure against
// pre-computation attacks. It enables a client to authenticate to a server without ever revealing its password to the
// server. Protocol details can be found on the IETF RFC page (https://datatracker.ietf.org/doc/draft-irtf-cfrg-opaque)
// and on the GitHub specification repository (https://github.com/cfrg/draft-irtf-cfrg-opaque).
package opaque
import (
"crypto"
"errors"
"fmt"
group "github.com/bytemare/crypto"
"github.com/bytemare/hash"
"github.com/bytemare/ksf"
"github.com/bytemare/opaque/internal"
"github.com/bytemare/opaque/internal/ake"
"github.com/bytemare/opaque/internal/encoding"
"github.com/bytemare/opaque/internal/oprf"
"github.com/bytemare/opaque/message"
)
// Group identifies the prime-order group with hash-to-curve capability to use in OPRF and AKE.
type Group byte
const (
// RistrettoSha512 identifies the Ristretto255 group and SHA-512.
RistrettoSha512 = Group(group.Ristretto255Sha512)
// decaf448Shake256 identifies the Decaf448 group and Shake-256.
// decaf448Shake256 = 2.
// P256Sha256 identifies the NIST P-256 group and SHA-256.
P256Sha256 = Group(group.P256Sha256)
// P384Sha512 identifies the NIST P-384 group and SHA-384.
P384Sha512 = Group(group.P384Sha384)
// P521Sha512 identifies the NIST P-512 group and SHA-512.
P521Sha512 = Group(group.P521Sha512)
)
// Available returns whether the Group byte is recognized in this implementation. This allows to fail early when
// working with multiple versions not using the same configuration and Group.
func (g Group) Available() bool {
return g == RistrettoSha512 ||
g == P256Sha256 ||
g == P384Sha512 ||
g == P521Sha512
}
// OPRF returns the OPRF Identifier used in the Ciphersuite.
func (g Group) OPRF() oprf.Identifier {
return oprf.IDFromGroup(g.Group())
}
// Group returns the EC Group used in the Ciphersuite.
func (g Group) Group() group.Group {
return group.Group(g)
}
const confIdsLength = 6
var (
errInvalidOPRFid = errors.New("invalid OPRF group id")
errInvalidKDFid = errors.New("invalid KDF id")
errInvalidMACid = errors.New("invalid MAC id")
errInvalidHASHid = errors.New("invalid Hash id")
errInvalidKSFid = errors.New("invalid KSF id")
errInvalidAKEid = errors.New("invalid AKE group id")
)
type KSFConfiguration struct {
Identifier ksf.Identifier `json:"identifier"`
Parameters []int `json:"parameters"`
Salt []byte `json:"salt"`
}
// Configuration represents an OPAQUE configuration. Note that OprfGroup and AKEGroup are recommended to be the same,
// as well as KDF, MAC, Hash should be the same.
type Configuration struct {
Context []byte
KDF crypto.Hash `json:"kdf"`
MAC crypto.Hash `json:"mac"`
Hash crypto.Hash `json:"hash"`
OPRF Group `json:"oprf"`
KSF KSFConfiguration `json:"ksf"`
AKE Group `json:"group"`
}
// DefaultConfiguration returns a default configuration with strong parameters.
func DefaultConfiguration() *Configuration {
return &Configuration{
OPRF: RistrettoSha512,
KDF: crypto.SHA512,
MAC: crypto.SHA512,
Hash: crypto.SHA512,
KSF: KSFConfiguration{
Identifier: ksf.Argon2id,
},
AKE: RistrettoSha512,
Context: nil,
}
}
// Client returns a newly instantiated Client from the Configuration.
func (c *Configuration) Client() (*Client, error) {
return NewClient(c)
}
// Server returns a newly instantiated Server from the Configuration.
func (c *Configuration) Server() (*Server, error) {
return NewServer(c)
}
// GenerateOPRFSeed returns a OPRF seed valid in the given configuration.
func (c *Configuration) GenerateOPRFSeed() []byte {
return RandomBytes(c.Hash.Size())
}
// KeyGen returns a key pair in the AKE group.
func (c *Configuration) KeyGen() (secretKey, publicKey []byte) {
return ake.KeyGen(group.Group(c.AKE))
}
// verify returns an error on the first non-compliant parameter, nil otherwise.
func (c *Configuration) verify() error {
if !c.OPRF.Available() || !c.OPRF.OPRF().Available() {
return errInvalidOPRFid
}
if !c.AKE.Available() || !c.AKE.Group().Available() {
return errInvalidAKEid
}
if !hash.Hash(c.KDF).Available() {
return errInvalidKDFid
}
if !hash.Hash(c.MAC).Available() {
return errInvalidMACid
}
if !hash.Hash(c.Hash).Available() {
return errInvalidHASHid
}
if c.KSF.Identifier != 0 && !c.KSF.Identifier.Available() {
return errInvalidKSFid
}
return nil
}
// toInternal builds the internal representation of the configuration parameters.
func (c *Configuration) toInternal() (*internal.Configuration, error) {
if err := c.verify(); err != nil {
return nil, err
}
g := c.AKE.Group()
o := c.OPRF.OPRF()
mac := internal.NewMac(c.MAC)
ip := &internal.Configuration{
OPRF: o,
KDF: internal.NewKDF(c.KDF),
MAC: mac,
Hash: internal.NewHash(c.Hash),
KSF: internal.NewKSF(c.KSF.Identifier),
KSFSalt: c.KSF.Salt,
NonceLen: internal.NonceLength,
EnvelopeSize: internal.NonceLength + mac.Size(),
Group: g,
Context: c.Context,
}
if c.KSF.Parameters != nil {
ip.KSF.Parameterize(c.KSF.Parameters...)
}
return ip, nil
}
// Deserializer returns a pointer to a Deserializer structure allowing deserialization of messages in the given
// configuration.
func (c *Configuration) Deserializer() (*Deserializer, error) {
conf, err := c.toInternal()
if err != nil {
return nil, err
}
return &Deserializer{conf: conf}, nil
}
// Serialize returns the byte encoding of the Configuration structure.
func (c *Configuration) Serialize() []byte {
ids := []byte{
byte(c.OPRF),
byte(c.KDF),
byte(c.MAC),
byte(c.Hash),
byte(c.KSF.Identifier),
byte(c.AKE),
}
var ksfEncodedParams []byte
for _, param := range c.KSF.Parameters {
ksfEncodedParams = append(ksfEncodedParams, encoding.I2OSP(param, 4)...)
}
ksfEncodedParams = encoding.EncodeVector(ksfEncodedParams)
ksfEncodedSalt := encoding.EncodeVector(c.KSF.Salt)
return encoding.Concatenate(ids, encoding.EncodeVector(c.Context), ksfEncodedParams, ksfEncodedSalt)
}
// GetFakeRecord creates a fake Client record to be used when no existing client record exists,
// to defend against client enumeration techniques.
func (c *Configuration) GetFakeRecord(credentialIdentifier []byte) (*ClientRecord, error) {
i, err := c.toInternal()
if err != nil {
return nil, err
}
scalar := i.Group.NewScalar().Random()
publicKey := i.Group.Base().Multiply(scalar)
regRecord := &message.RegistrationRecord{
PublicKey: publicKey,
MaskingKey: RandomBytes(i.KDF.Size()),
Envelope: make([]byte, internal.NonceLength+i.MAC.Size()),
}
return &ClientRecord{
CredentialIdentifier: credentialIdentifier,
ClientIdentity: nil,
RegistrationRecord: regRecord,
TestMaskNonce: nil,
}, nil
}
// DeserializeConfiguration decodes the input and returns a Parameter structure.
func DeserializeConfiguration(encoded []byte) (*Configuration, error) {
if len(encoded) < confIdsLength+2 { // corresponds to the configuration length + 2-byte encoding of empty context
return nil, internal.ErrConfigurationInvalidLength
}
ctx, _, err := encoding.DecodeVector(encoded[confIdsLength:])
if err != nil {
return nil, fmt.Errorf("decoding the configuration context: %w", err)
}
var ksfParams []int
ksfParamOffset := confIdsLength + 2 + len(ctx)
if len(encoded) >= ksfParamOffset+2 {
ksfEncodedParams, _, err := encoding.DecodeVector(encoded[ksfParamOffset:])
if err != nil {
return nil, fmt.Errorf("decoding the ksf configuration parameters: %w", err)
}
for i := 0; i < len(ksfEncodedParams); i += 4 {
ksfParams = append(ksfParams, encoding.OS2IP(ksfEncodedParams[i:i+4]))
}
}
var ksfSalt []byte
ksfSaltOffset := ksfParamOffset + 2 + (len(ksfParams) * 4)
if len(encoded) >= ksfSaltOffset+2 {
ksfSalt, _, err = encoding.DecodeVector(encoded[ksfSaltOffset:])
if err != nil {
return nil, fmt.Errorf("decoding the ksf salt: %w", err)
}
if len(ksfSalt) == 0 {
ksfSalt = nil
}
}
c := &Configuration{
OPRF: Group(encoded[0]),
KDF: crypto.Hash(encoded[1]),
MAC: crypto.Hash(encoded[2]),
Hash: crypto.Hash(encoded[3]),
KSF: KSFConfiguration{
Identifier: ksf.Identifier(encoded[4]),
Parameters: ksfParams,
Salt: ksfSalt,
},
AKE: Group(encoded[5]),
Context: ctx,
}
if err := c.verify(); err != nil {
return nil, err
}
return c, nil
}
// ClientRecord is a server-side structure enabling the storage of user relevant information.
type ClientRecord struct {
CredentialIdentifier []byte
ClientIdentity []byte
*message.RegistrationRecord
// testing
TestMaskNonce []byte
}
// RandomBytes returns random bytes of length len (wrapper for crypto/rand).
func RandomBytes(length int) []byte {
return internal.RandomBytes(length)
}