-
Notifications
You must be signed in to change notification settings - Fork 86
/
crypto11.go
485 lines (406 loc) · 15.9 KB
/
crypto11.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
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
// Copyright 2024 Thales Group
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Package crypto11 enables access to cryptographic keys from PKCS#11 using Go crypto API.
//
// Configuration
//
// PKCS#11 tokens are accessed via Context objects. Each Context connects to one token.
//
// Context objects are created by calling Configure or ConfigureFromFile.
// In the latter case, the file should contain a JSON representation of
// a Config.
//
// Key Generation and Usage
//
// There is support for generating DSA, RSA and ECDSA keys. These keys
// can be found later using FindKeyPair. All three key types implement
// the crypto.Signer interface and the RSA keys also implement crypto.Decrypter.
//
// RSA keys obtained through FindKeyPair will need a type assertion to be
// used for decryption. Assert either crypto.Decrypter or SignerDecrypter, as you
// prefer.
//
// Symmetric keys can also be generated. These are found later using FindKey.
// See the documentation for SecretKey for further information.
//
// Sessions and concurrency
//
// Note that PKCS#11 session handles must not be used concurrently
// from multiple threads. Consumers of the Signer interface know
// nothing of this and expect to be able to sign from multiple threads
// without constraint. We address this as follows.
//
// 1. When a Context is created, a session is created and the user is
// logged in. This session remains open until the Context is closed,
// to ensure all object handles remain valid and to avoid repeatedly
// calling C_Login.
//
// 2. The Context also maintains a pool of read-write sessions. The pool expands
// dynamically as needed, but never beyond the maximum number of r/w sessions
// supported by the token (as reported by C_GetInfo). If other applications
// are using the token, a lower limit should be set in the Config.
//
// 3. Each operation transiently takes a session from the pool. They
// have exclusive use of the session, meeting PKCS#11's concurrency
// requirements. Sessions are returned to the pool afterwards and may
// be re-used.
//
// Behaviour of the pool can be tweaked via Config fields:
//
// - PoolWaitTimeout controls how long an operation can block waiting on a
// session from the pool. A zero value means there is no limit. Timeouts
// occur if the pool is fully used and additional operations are requested.
//
// - MaxSessions sets an upper bound on the number of sessions. If this value is zero,
// a default maximum is used (see DefaultMaxSessions). In every case the maximum
// supported sessions as reported by the token is obeyed.
//
// Limitations
//
// The PKCS1v15DecryptOptions SessionKeyLen field is not implemented
// and an error is returned if it is nonzero.
// The reason for this is that it is not possible for crypto11 to guarantee the constant-time behavior in the specification.
// See https://github.com/thalesignite/crypto11/issues/5 for further discussion.
//
// Symmetric crypto support via cipher.Block is very slow.
// You can use the BlockModeCloser API
// but you must call the Close() interface (not found in cipher.BlockMode).
// See https://github.com/ThalesIgnite/crypto11/issues/6 for further discussion.
package crypto11
import (
"crypto"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"sync"
"time"
"github.com/miekg/pkcs11"
"github.com/pkg/errors"
"github.com/thales-e-security/pool"
)
const (
// DefaultMaxSessions controls the maximum number of concurrent sessions to
// open, unless otherwise specified in the Config object.
DefaultMaxSessions = 1024
// DefaultGCMIVLength controls the expected length of IVs generated by the token
DefaultGCMIVLength = 16
// Thales vendor constant for CKU_CRYPTO_USER
CryptoUser = 0x80000001
DefaultUserType = 1 // 1 -> CKU_USER
)
// errTokenNotFound represents the failure to find the requested PKCS#11 token
var errTokenNotFound = errors.New("could not find PKCS#11 token")
// errClosed is returned if a Context is used after a call to Close.
var errClosed = errors.New("cannot used closed Context")
// pkcs11Object contains a reference to a loaded PKCS#11 object.
type pkcs11Object struct {
// The PKCS#11 object handle.
handle pkcs11.ObjectHandle
// The PKCS#11 context. This is used to find a session handle that can
// access this object.
context *Context
}
func (o *pkcs11Object) Delete() error {
return o.context.withSession(func(session *pkcs11Session) error {
err := session.ctx.DestroyObject(session.handle, o.handle)
return errors.WithMessage(err, "failed to destroy key")
})
}
// pkcs11PrivateKey contains a reference to a loaded PKCS#11 private key object.
type pkcs11PrivateKey struct {
pkcs11Object
// pubKeyHandle is a keyHandle to the public key.
pubKeyHandle pkcs11.ObjectHandle
// pubKey is an exported copy of the public key. We pre-export the key material because crypto.Signer.Public
// doesn't allow us to return errors.
pubKey crypto.PublicKey
}
// Delete implements Signer.Delete.
func (k *pkcs11PrivateKey) Delete() error {
err := k.pkcs11Object.Delete()
if err != nil {
return err
}
return k.context.withSession(func(session *pkcs11Session) error {
err := session.ctx.DestroyObject(session.handle, k.pubKeyHandle)
return errors.WithMessage(err, "failed to destroy public key")
})
}
// A Context stores the connection state to a PKCS#11 token. Use Configure or ConfigureFromFile to create a new
// Context. Call Close when finished with the token, to free up resources.
//
// All functions, except Close, are safe to call from multiple goroutines.
type Context struct {
// Atomic fields must be at top (according to the package owners)
closed pool.AtomicBool
ctx *pkcs11.Ctx
cfg *Config
token *pkcs11.TokenInfo
slot uint
pool *pool.ResourcePool
// persistentSession is a session held open so we can be confident handles and login status
// persist for the duration of this context
persistentSession pkcs11.SessionHandle
}
// Signer is a PKCS#11 key that implements crypto.Signer.
type Signer interface {
crypto.Signer
// Delete deletes the key pair from the token.
Delete() error
}
// SignerDecrypter is a PKCS#11 key implements crypto.Signer and crypto.Decrypter.
type SignerDecrypter interface {
Signer
// Decrypt implements crypto.Decrypter.
Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error)
}
// findToken finds a token given exactly one of serial, label or slotNumber
func (c *Context) findToken(slots []uint, serial, label string, slotNumber *int) (uint, *pkcs11.TokenInfo, error) {
for _, slot := range slots {
tokenInfo, err := c.ctx.GetTokenInfo(slot)
if err != nil {
return 0, nil, err
}
if (slotNumber != nil && uint(*slotNumber) == slot) ||
(tokenInfo.SerialNumber != "" && tokenInfo.SerialNumber == serial) ||
(tokenInfo.Label != "" && tokenInfo.Label == label) {
return slot, &tokenInfo, nil
}
}
return 0, nil, errTokenNotFound
}
// Config holds PKCS#11 configuration information.
//
// A token may be selected by label, serial number or slot number. It is an error to specify
// more than one way to select the token.
//
// Supply this to Configure(), or alternatively use ConfigureFromFile().
type Config struct {
// Full path to PKCS#11 library.
Path string
// Token serial number.
TokenSerial string
// Token label.
TokenLabel string
// SlotNumber identifies a token to use by the slot containing it.
SlotNumber *int
// User PIN (password).
Pin string
// Maximum number of concurrent sessions to open. If zero, DefaultMaxSessions is used.
// Otherwise, the value specified must be at least 2.
MaxSessions int
// User type identifies the user type logging in. If zero, DefaultUserType is used.
UserType int
// Maximum time to wait for a session from the sessions pool. Zero means wait indefinitely.
PoolWaitTimeout time.Duration
// LoginNotSupported should be set to true for tokens that do not support logging in.
LoginNotSupported bool
// UseGCMIVFromHSM should be set to true for tokens such as CloudHSM, which ignore the supplied IV for
// GCM mode and generate their own. In this case, the token will write the IV used into the CK_GCM_PARAMS.
// If UseGCMIVFromHSM is true, we will copy this IV and overwrite the 'nonce' slice passed to Seal and Open. It
// is therefore necessary that the nonce is the correct length (12 bytes for CloudHSM).
UseGCMIVFromHSM bool
// GCMIVLength is the length of IVs to use in GCM mode. Refer to NIST SP800-38 for guidance on the length of
// RBG-based IVs in GCM mode. When the UseGCMIVFromHSM parameter is true
GCMIVLength int
GCMIVFromHSMControl GCMIVFromHSMConfig
}
type GCMIVFromHSMConfig struct {
// SupplyIvForHSMGCM_encrypt controls the supply of a non-nil IV for GCM use during C_EncryptInit
SupplyIvForHSMGCMEncrypt bool
// SupplyIvForHSMGCM_decrypt controls the supply of a non-nil IV for GCM use during C_DecryptInit
SupplyIvForHSMGCMDecrypt bool
}
// refCount counts the number of contexts using a particular P11 library. It must not be read or modified
// without holding refCountMutex.
var refCount = map[string]int{}
var refCountMutex = sync.Mutex{}
// Configure creates a new Context based on the supplied PKCS#11 configuration.
func Configure(config *Config) (*Context, error) {
// Check for exactly one way to select a token
var fields []string
if config.SlotNumber != nil {
fields = append(fields, "slot number")
}
if config.TokenLabel != "" {
fields = append(fields, "token label")
}
if config.TokenSerial != "" {
fields = append(fields, "token serial number")
}
if len(fields) == 0 {
return nil, fmt.Errorf("config must specify exactly one way to select a token: none given")
} else if len(fields) > 1 {
return nil, fmt.Errorf("config must specify exactly one way to select a token: %v given", strings.Join(fields, ", "))
}
if config.MaxSessions == 0 {
config.MaxSessions = DefaultMaxSessions
}
if config.MaxSessions == 1 {
return nil, errors.New("MaxSessions must be larger than 1")
}
if config.UserType == 0 {
config.UserType = DefaultUserType
}
if config.GCMIVLength == 0 {
config.GCMIVLength = DefaultGCMIVLength
}
instance := &Context{
cfg: config,
ctx: pkcs11.New(config.Path),
}
if instance.ctx == nil {
return nil, errors.New("could not open PKCS#11")
}
// Check how many contexts are currently using this library
refCountMutex.Lock()
defer refCountMutex.Unlock()
numExistingContexts := refCount[config.Path]
// Only Initialize if we are the first Context using the library
if numExistingContexts == 0 {
if err := instance.ctx.Initialize(); err != nil {
instance.ctx.Destroy()
return nil, errors.WithMessage(err, "failed to initialize PKCS#11 library")
}
}
slots, err := instance.ctx.GetSlotList(true)
if err != nil {
_ = instance.ctx.Finalize()
instance.ctx.Destroy()
return nil, errors.WithMessage(err, "failed to list PKCS#11 slots")
}
instance.slot, instance.token, err = instance.findToken(slots, config.TokenSerial, config.TokenLabel, config.SlotNumber)
if err != nil {
_ = instance.ctx.Finalize()
instance.ctx.Destroy()
return nil, err
}
// Create the session pool.
maxSessions := instance.cfg.MaxSessions
tokenMaxSessions := instance.token.MaxRwSessionCount
if tokenMaxSessions != pkcs11.CK_EFFECTIVELY_INFINITE && tokenMaxSessions != pkcs11.CK_UNAVAILABLE_INFORMATION {
maxSessions = min(maxSessions, castDown(tokenMaxSessions))
}
// We will use one session to keep state alive, so the pool gets maxSessions - 1
instance.pool = pool.NewResourcePool(instance.resourcePoolFactoryFunc, maxSessions-1, maxSessions-1, 0, 0)
// Create a long-term session and log it in (if supported). This session won't be used by callers, instead it is
// used to keep a connection alive to the token to ensure object handles and the log in status remain accessible.
instance.persistentSession, err = instance.ctx.OpenSession(instance.slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
if err != nil {
_ = instance.ctx.Finalize()
instance.ctx.Destroy()
return nil, errors.WithMessagef(err, "failed to create long term session")
}
if !config.LoginNotSupported {
// Try to log in our persistent session. This may fail with CKR_USER_ALREADY_LOGGED_IN if another instance
// already exists.
if instance.cfg.UserType == 1 {
err = instance.ctx.Login(instance.persistentSession, pkcs11.CKU_USER, instance.cfg.Pin)
} else {
err = instance.ctx.Login(instance.persistentSession, CryptoUser, instance.cfg.Pin)
}
if err != nil {
pErr, isP11Error := err.(pkcs11.Error)
if !isP11Error || pErr != pkcs11.CKR_USER_ALREADY_LOGGED_IN {
_ = instance.ctx.Finalize()
instance.ctx.Destroy()
return nil, errors.WithMessagef(err, "failed to log into long term session")
}
}
}
// Increment the reference count
refCount[config.Path] = numExistingContexts + 1
return instance, nil
}
func min(a, b int) int {
if b < a {
return b
}
return a
}
// castDown returns orig as a signed integer. If an overflow would have occurred,
// the maximum possible value is returned.
func castDown(orig uint) int {
// From https://stackoverflow.com/a/6878625/474189
const maxUint = ^uint(0)
const maxInt = int(maxUint >> 1)
if orig > uint(maxInt) {
return maxInt
}
return int(orig)
}
// ConfigureFromFile is a convenience method, which parses the configuration file
// and calls Configure. The configuration file should be a JSON representation
// of a Config object.
func ConfigureFromFile(configLocation string) (*Context, error) {
config, err := loadConfigFromFile(configLocation)
if err != nil {
return nil, err
}
return Configure(config)
}
// loadConfigFromFile reads a Config struct from a file.
func loadConfigFromFile(configLocation string) (*Config, error) {
file, err := os.Open(configLocation)
if err != nil {
return nil, errors.WithMessagef(err, "could not open config file: %s", configLocation)
}
defer func() {
closeErr := file.Close()
if err == nil {
err = closeErr
}
}()
configDecoder := json.NewDecoder(file)
config := &Config{}
err = configDecoder.Decode(config)
return config, errors.WithMessage(err, "could not decode config file:")
}
// Close releases resources used by the Context and unloads the PKCS #11 library if there are no other
// Contexts using it. Close blocks until existing operations have finished. A closed Context cannot be reused.
func (c *Context) Close() error {
// Take lock on the reference count
refCountMutex.Lock()
defer refCountMutex.Unlock()
c.closed.Set(true)
// Block until all resources returned to pool
c.pool.Close()
// Close our long-term session. We ignore any returned error,
// since we plan to kill our collection to the library anyway.
_ = c.ctx.CloseSession(c.persistentSession)
count, found := refCount[c.cfg.Path]
if !found || count == 0 {
// We have somehow lost track of reference counts, this is very bad
panic("invalid reference count for PKCS#11 library")
}
refCount[c.cfg.Path] = count - 1
// If we were the last Context, finalize the library
if count == 1 {
err := c.ctx.Finalize()
if err != nil {
return err
}
}
c.ctx.Destroy()
return nil
}