operations.go 6.63 KB
Newer Older
1
package keysharecore
2
3
4

import (
	"crypto/rand"
5
	"crypto/subtle"
6
7
8
9
10
11
	"encoding/base64"
	"encoding/binary"
	"time"

	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
12
	"github.com/privacybydesign/gabi/gabikeys"
13
	irma "github.com/privacybydesign/irmago"
14
15
16

	"github.com/dgrijalva/jwt-go"
	"github.com/go-errors/errors"
17
18
19
20
21
22
23
24
25
26
27
)

var (
	ErrInvalidPin       = errors.New("invalid pin")
	ErrPinTooLong       = errors.New("pin too long")
	ErrInvalidChallenge = errors.New("challenge out of bounds")
	ErrInvalidJWT       = errors.New("invalid jwt token")
	ErrKeyNotFound      = errors.New("public key not found")
	ErrUnknownCommit    = errors.New("unknown commit id")
)

28
29
// NewUserSecrets generates a new keyshare secret, secured with the given pin.
func (c *Core) NewUserSecrets(pinRaw string) (UserSecrets, error) {
30
	secret, err := gabi.NewKeyshareSecret()
31
	if err != nil {
32
		return UserSecrets{}, err
33
34
	}

David Venhoek's avatar
David Venhoek committed
35
36
	pin, err := padPin(pinRaw)
	if err != nil {
37
		return UserSecrets{}, err
David Venhoek's avatar
David Venhoek committed
38
39
40
41
42
	}

	var id [32]byte
	_, err = rand.Read(id[:])
	if err != nil {
43
		return UserSecrets{}, err
David Venhoek's avatar
David Venhoek committed
44
45
	}

46
	// Build unencrypted secrets
47
48
49
	var s unencryptedUserSecrets
	s.setPin(pin)
	err = s.setKeyshareSecret(secret)
David Venhoek's avatar
David Venhoek committed
50
	if err != nil {
51
		return UserSecrets{}, err
David Venhoek's avatar
David Venhoek committed
52
	}
53
	s.setID(id)
David Venhoek's avatar
David Venhoek committed
54

55
	// And encrypt
56
	return c.encryptUserSecrets(s)
David Venhoek's avatar
David Venhoek committed
57
58
}

59
// ValidatePin checks pin for validity and generates JWT for future access.
60
61
func (c *Core) ValidatePin(secrets UserSecrets, pin string) (string, error) {
	s, err := c.decryptUserSecretsIfPinOK(secrets, pin)
62
63
64
65
66
	if err != nil {
		return "", err
	}

	// Generate jwt token
67
	id := s.id()
68
	t := time.Now()
69
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
70
		"iss":      c.jwtIssuer,
71
		"sub":      "auth_tok",
72
73
		"iat":      t.Unix(),
		"exp":      t.Add(time.Duration(c.jwtPinExpiry) * time.Second).Unix(),
74
		"token_id": base64.StdEncoding.EncodeToString(id[:]),
75
	})
76
77
	token.Header["kid"] = c.jwtPrivateKeyID
	return token.SignedString(c.jwtPrivateKey)
78
79
}

80
// ValidateJWT checks whether the given JWT is currently valid as an access token for operations
81
// on the provided encrypted keyshare user secrets.
82
83
func (c *Core) ValidateJWT(secrets UserSecrets, jwt string) error {
	_, err := c.verifyAccess(secrets, jwt)
84
85
86
	return err
}

87
// ChangePin changes the pin in an encrypted keyshare user secret to a new value, after validating that
88
// the old value is known by the caller.
89
90
func (c *Core) ChangePin(secrets UserSecrets, oldpinRaw, newpinRaw string) (UserSecrets, error) {
	s, err := c.decryptUserSecretsIfPinOK(secrets, oldpinRaw)
91
	if err != nil {
92
		return UserSecrets{}, err
93
94
	}

95
	newpin, err := padPin(newpinRaw)
96
	if err != nil {
97
		return UserSecrets{}, err
98
99
100
	}

	// change and reencrypt
101
102
103
	var id [32]byte
	_, err = rand.Read(id[:])
	if err != nil {
104
		return UserSecrets{}, err
105
	}
106
107
108
	s.setPin(newpin)
	s.setID(id)
	return c.encryptUserSecrets(s)
109
110
}

111
// verifyAccess checks that a given access jwt is valid, and if so, return decrypted keyshare user secrets.
112
// Note: Although this is an internal function, it is tested directly
113
func (c *Core) verifyAccess(secrets UserSecrets, jwtToken string) (unencryptedUserSecrets, error) {
114
115
	// Verify token validity
	token, err := jwt.Parse(jwtToken, func(token *jwt.Token) (interface{}, error) {
116
		if token.Method != jwt.SigningMethodRS256 {
117
118
119
			return nil, ErrInvalidJWT
		}

120
		return &c.jwtPrivateKey.PublicKey, nil
121
122
	})
	if err != nil {
123
		return unencryptedUserSecrets{}, ErrInvalidJWT
124
125
126
127
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok || claims.Valid() != nil {
128
		return unencryptedUserSecrets{}, ErrInvalidJWT
129
130
	}
	if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
131
		return unencryptedUserSecrets{}, ErrInvalidJWT
132
	}
133
	if _, present := claims["token_id"]; !present {
134
		return unencryptedUserSecrets{}, ErrInvalidJWT
135
	}
136
	tokenIDB64, ok := claims["token_id"].(string)
137
	if !ok {
138
		return unencryptedUserSecrets{}, ErrInvalidJWT
139
	}
140
	tokenID, err := base64.StdEncoding.DecodeString(tokenIDB64)
141
	if err != nil {
142
		return unencryptedUserSecrets{}, ErrInvalidJWT
143
144
	}

145
	s, err := c.decryptUserSecrets(secrets)
146
	if err != nil {
147
		return unencryptedUserSecrets{}, err
148
	}
149
	refId := s.id()
150

151
	if subtle.ConstantTimeCompare(refId[:], tokenID) != 1 {
152
		return unencryptedUserSecrets{}, ErrInvalidJWT
153
154
	}

155
	return s, nil
156
157
}

158
// GenerateCommitments generates keyshare commitments using the specified Idemix public key(s).
159
func (c *Core) GenerateCommitments(secrets UserSecrets, accessToken string, keyIDs []irma.PublicKeyIdentifier) ([]*gabi.ProofPCommitment, uint64, error) {
160
	// Validate input request and build key list
161
	var keyList []*gabikeys.PublicKey
162
163
	for _, keyID := range keyIDs {
		key, ok := c.trustedKeys[keyID]
164
		if !ok {
165
			return nil, 0, ErrKeyNotFound
166
		}
167
		keyList = append(keyList, key)
168
169
170
	}

	// verify access and decrypt
171
	s, err := c.verifyAccess(secrets, accessToken)
172
	if err != nil {
173
		return nil, 0, err
174
175
176
	}

	// Generate commitment
177
	commitSecret, commitments, err := gabi.NewKeyshareCommitments(s.keyshareSecret(), keyList)
178
179
180
181
182
	if err != nil {
		return nil, 0, err
	}

	// Generate commitment id
183
184
	var commitID uint64
	err = binary.Read(rand.Reader, binary.LittleEndian, &commitID)
David Venhoek's avatar
David Venhoek committed
185
186
187
	if err != nil {
		return nil, 0, err
	}
188
189

	// Store commit in backing storage
190
	c.commitmentMutex.Lock()
191
	c.commitmentData[commitID] = commitSecret
192
	c.commitmentMutex.Unlock()
193

194
	return commitments, commitID, nil
195
196
}

197
// GenerateResponse generates the response of a zero-knowledge proof of the keyshare secret, for a given previous commit and challenge.
198
func (c *Core) GenerateResponse(secrets UserSecrets, accessToken string, commitID uint64, challenge *big.Int, keyID irma.PublicKeyIdentifier) (string, error) {
199
	// Validate request
200
	if uint(challenge.BitLen()) > gabikeys.DefaultSystemParameters[1024].Lh || challenge.Cmp(big.NewInt(0)) < 0 {
201
202
		return "", ErrInvalidChallenge
	}
203
	key, ok := c.trustedKeys[keyID]
204
205
	if !ok {
		return "", ErrKeyNotFound
206
207
208
	}

	// verify access and decrypt
209
	s, err := c.verifyAccess(secrets, accessToken)
210
	if err != nil {
211
		return "", err
212
213
214
	}

	// Fetch commit
215
	c.commitmentMutex.Lock()
216
217
	commit, ok := c.commitmentData[commitID]
	delete(c.commitmentData, commitID)
218
	c.commitmentMutex.Unlock()
219
	if !ok {
220
		return "", ErrUnknownCommit
221
222
223
	}

	// Generate response
224
	token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{
225
		"ProofP": gabi.KeyshareResponse(s.keyshareSecret(), commit, challenge, key),
226
227
		"iat":    time.Now().Unix(),
		"sub":    "ProofP",
228
		"iss":    c.jwtIssuer,
229
	})
230
231
	token.Header["kid"] = c.jwtPrivateKeyID
	return token.SignedString(c.jwtPrivateKey)
232
233
234
235
236
237
238
239
240
241
242
243
}

// Pad pin string into 64 bytes, extending it with 0s if neccessary
func padPin(pin string) ([64]byte, error) {
	data := []byte(pin)
	if len(data) > 64 {
		return [64]byte{}, ErrPinTooLong
	}
	res := [64]byte{}
	copy(res[:], data)
	return res, nil
}