-
Notifications
You must be signed in to change notification settings - Fork 43
/
pool.go
100 lines (90 loc) · 2.97 KB
/
pool.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
package pkcs11key
import (
"crypto"
"fmt"
"io"
"sync"
)
// Pool is a pool of Keys suitable for high performance parallel work. Key
// on its own is suitable for multi-threaded use because it has built-in
// locking, but one Key can have at most one operation inflight at a time.
// If you are using an HSM that supports multiple sessions, you may want to
// use a Pool instead, which contains multiple signers. Pool satisfies the
// Signer interface just as Key does, and farms out work to multiple sessions
// under the hood. This assumes you are calling Sign from multiple goroutines
// (as would be common in an RPC or HTTP environment). If you only call Sign
// from a single goroutine, you will only ever get single-session performance.
type Pool struct {
// This slice acts more or less like a concurrent stack. Keys are popped off
// the top for use, and then pushed back on when they are no longer in use.
signers []*Key
// The initial length of signers, before any are popped off for use.
totalCount int
// This variable signals the condition that there are Keys available to be
// used.
cond *sync.Cond
}
func (p *Pool) get() *Key {
p.cond.L.Lock()
for len(p.signers) == 0 {
p.cond.Wait()
}
instance := p.signers[len(p.signers)-1]
p.signers = p.signers[:len(p.signers)-1]
p.cond.L.Unlock()
return instance
}
func (p *Pool) put(instance *Key) {
p.cond.L.Lock()
p.signers = append(p.signers, instance)
p.cond.Signal()
p.cond.L.Unlock()
}
// Sign performs a signature using an available PKCS #11 key. If there is no key
// available, it blocks until there is.
func (p *Pool) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
instance := p.get()
defer p.put(instance)
return instance.Sign(rand, msg, opts)
}
// Public returns the public key of any one of the signers in the pool. Since
// they were all created with the same arguments, the public key should be the
// same for each one.
func (p *Pool) Public() crypto.PublicKey {
instance := p.get()
defer p.put(instance)
return instance.Public()
}
// NewPool creates a pool of Keys of size n.
func NewPool(n int, modulePath, tokenLabel, pin string, publicKey crypto.PublicKey) (*Pool, error) {
var err error
signers := make([]*Key, n)
for i := 0; i < n; i++ {
signers[i], err = New(modulePath, tokenLabel, pin, publicKey)
// If any of the signers fail, exit early. This could be, e.g., a bad PIN,
// and we want to make sure not to lock the token.
if err != nil {
for j := 0; j < i; j++ {
signers[j].Destroy()
}
return nil, fmt.Errorf("pkcs11key: problem making Key: %s", err)
}
}
var mutex sync.Mutex
return &Pool{
signers: signers,
totalCount: len(signers),
cond: sync.NewCond(&mutex),
}, nil
}
// Destroy calls destroy for each of the member keys, shutting down their
// sessions.
func (p *Pool) Destroy() error {
for i := 0; i < p.totalCount; i++ {
err := p.get().Destroy()
if err != nil {
return fmt.Errorf("pkcs11key: destroy: %s", err)
}
}
return nil
}