mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-12 00:19:08 +02:00
Stop retrying a PKCS#11 PIN the token rejected
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
package certproof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errPINRejectedBefore is returned instead of logging in with a PIN the token already
|
||||||
|
// refused: every failed login counts towards the token's lockout, which for a TPM is
|
||||||
|
// shared with everything else on the machine, and proofs are collected on every sync.
|
||||||
|
var errPINRejectedBefore = errors.New("PKCS#11 token rejected this PIN before, not trying it again")
|
||||||
|
|
||||||
|
// rejectedPINs outlives a single store, since a store is built for each collection.
|
||||||
|
var rejectedPINs = &pinLatch{keys: map[[sha256.Size]byte]struct{}{}}
|
||||||
|
|
||||||
|
// pinLatch remembers PINs a token rejected. Keys are hashes, so the PIN itself is not
|
||||||
|
// kept in memory any longer than the store that read it.
|
||||||
|
type pinLatch struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
keys map[[sha256.Size]byte]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *pinLatch) has(key [sha256.Size]byte) bool {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
_, ok := l.keys[key]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *pinLatch) add(key [sha256.Size]byte) {
|
||||||
|
l.mu.Lock()
|
||||||
|
defer l.mu.Unlock()
|
||||||
|
l.keys[key] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rejectedPINKey identifies a PIN for one token of one module, so a PIN another token
|
||||||
|
// rejected is still tried on the token it belongs to.
|
||||||
|
func rejectedPINKey(module, token string, pin []byte) [sha256.Size]byte {
|
||||||
|
var buf []byte
|
||||||
|
for _, part := range [][]byte{[]byte(module), []byte(token), pin} {
|
||||||
|
buf = binary.BigEndian.AppendUint64(buf, uint64(len(part)))
|
||||||
|
buf = append(buf, part...)
|
||||||
|
}
|
||||||
|
return sha256.Sum256(buf)
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package certproof
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRejectedPINKey_ScopesToModuleTokenAndPIN(t *testing.T) {
|
||||||
|
base := rejectedPINKey("/lib/a.so", "netbird", []byte("1234"))
|
||||||
|
|
||||||
|
assert.Equal(t, base, rejectedPINKey("/lib/a.so", "netbird", []byte("1234")), "the same PIN on the same token is the same key")
|
||||||
|
assert.NotEqual(t, base, rejectedPINKey("/lib/a.so", "netbird", []byte("4321")), "a corrected PIN must be tried")
|
||||||
|
assert.NotEqual(t, base, rejectedPINKey("/lib/a.so", "piv", []byte("1234")), "a PIN rejected by one token is still tried on another")
|
||||||
|
assert.NotEqual(t, base, rejectedPINKey("/lib/b.so", "netbird", []byte("1234")), "a PIN rejected through one module is still tried through another")
|
||||||
|
assert.NotEqual(t, rejectedPINKey("ab", "", []byte("1")), rejectedPINKey("a", "b", []byte("1")),
|
||||||
|
"field boundaries are part of the key, so shifted fields do not collide")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPINLatch_RemembersRejectedKeys(t *testing.T) {
|
||||||
|
latch := &pinLatch{keys: map[[32]byte]struct{}{}}
|
||||||
|
rejected := rejectedPINKey("/lib/a.so", "netbird", []byte("0000"))
|
||||||
|
other := rejectedPINKey("/lib/a.so", "netbird", []byte("1234"))
|
||||||
|
|
||||||
|
assert.False(t, latch.has(rejected), "nothing is rejected before a login fails")
|
||||||
|
latch.add(rejected)
|
||||||
|
assert.True(t, latch.has(rejected), "a rejected PIN is not tried again")
|
||||||
|
assert.False(t, latch.has(other), "other PINs are unaffected")
|
||||||
|
}
|
||||||
@@ -193,7 +193,20 @@ func (s *PKCS11Store) open() (*pkcs11.Session, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return module.OpenSession(s.uri.Token, pin)
|
if pin == nil {
|
||||||
|
return module.OpenSession(s.uri.Token, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := rejectedPINKey(s.uri.Module(), s.uri.Token, pin)
|
||||||
|
if rejectedPINs.has(key) {
|
||||||
|
return nil, errPINRejectedBefore
|
||||||
|
}
|
||||||
|
session, err := module.OpenSession(s.uri.Token, pin)
|
||||||
|
if pkcs11.PINRejected(err) {
|
||||||
|
rejectedPINs.add(key)
|
||||||
|
return nil, fmt.Errorf("%s rejected the PIN, not retrying it until the daemon restarts: %w", s, err)
|
||||||
|
}
|
||||||
|
return session, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PKCS11Store) userPIN() ([]byte, error) {
|
func (s *PKCS11Store) userPIN() ([]byte, error) {
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package pkcs11
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPINRejected(t *testing.T) {
|
||||||
|
for _, code := range []uint{rvPINIncorrect, rvPINInvalid, rvPINLenRange, rvPINExpired, rvPINLocked} {
|
||||||
|
err := fmt.Errorf("open session: %w", Error{Op: "C_Login", Code: code})
|
||||||
|
assert.True(t, PINRejected(err), "CKR 0x%x refuses the PIN, also when wrapped", code)
|
||||||
|
}
|
||||||
|
assert.False(t, PINRejected(Error{Op: "C_Login", Code: 0x30}), "a device error says nothing about the PIN")
|
||||||
|
assert.False(t, PINRejected(errors.New("CKR_PIN_INCORRECT")), "only a PKCS#11 return value counts")
|
||||||
|
assert.False(t, PINRejected(nil))
|
||||||
|
}
|
||||||
@@ -44,6 +44,11 @@ const (
|
|||||||
MGF1SHA384 = 0x3
|
MGF1SHA384 = 0x3
|
||||||
|
|
||||||
rvOK = 0x0
|
rvOK = 0x0
|
||||||
|
rvPINIncorrect = 0xa0
|
||||||
|
rvPINInvalid = 0xa1
|
||||||
|
rvPINLenRange = 0xa2
|
||||||
|
rvPINExpired = 0xa3
|
||||||
|
rvPINLocked = 0xa4
|
||||||
rvUserAlreadyLoggedIn = 0x100
|
rvUserAlreadyLoggedIn = 0x100
|
||||||
rvAlreadyInitialized = 0x191
|
rvAlreadyInitialized = 0x191
|
||||||
)
|
)
|
||||||
@@ -63,6 +68,20 @@ func (e Error) Error() string {
|
|||||||
return fmt.Sprintf("%s: CKR 0x%x", e.Op, e.Code)
|
return fmt.Sprintf("%s: CKR 0x%x", e.Op, e.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PINRejected reports whether err is the token refusing the user PIN. Retrying the same
|
||||||
|
// PIN cannot succeed, and each attempt counts towards the token's lockout.
|
||||||
|
func PINRejected(err error) bool {
|
||||||
|
var e Error
|
||||||
|
if !errors.As(err, &e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch e.Code {
|
||||||
|
case rvPINIncorrect, rvPINInvalid, rvPINLenRange, rvPINExpired, rvPINLocked:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var returnValueNames = map[uint]string{
|
var returnValueNames = map[uint]string{
|
||||||
0x2: "CKR_HOST_MEMORY",
|
0x2: "CKR_HOST_MEMORY",
|
||||||
0x3: "CKR_SLOT_ID_INVALID",
|
0x3: "CKR_SLOT_ID_INVALID",
|
||||||
@@ -77,6 +96,9 @@ var returnValueNames = map[uint]string{
|
|||||||
0x71: "CKR_MECHANISM_PARAM_INVALID",
|
0x71: "CKR_MECHANISM_PARAM_INVALID",
|
||||||
0x82: "CKR_OBJECT_HANDLE_INVALID",
|
0x82: "CKR_OBJECT_HANDLE_INVALID",
|
||||||
0xa0: "CKR_PIN_INCORRECT",
|
0xa0: "CKR_PIN_INCORRECT",
|
||||||
|
0xa1: "CKR_PIN_INVALID",
|
||||||
|
0xa2: "CKR_PIN_LEN_RANGE",
|
||||||
|
0xa3: "CKR_PIN_EXPIRED",
|
||||||
0xa4: "CKR_PIN_LOCKED",
|
0xa4: "CKR_PIN_LOCKED",
|
||||||
0xb3: "CKR_SESSION_HANDLE_INVALID",
|
0xb3: "CKR_SESSION_HANDLE_INVALID",
|
||||||
0xd0: "CKR_TEMPLATE_INCOMPLETE",
|
0xd0: "CKR_TEMPLATE_INCOMPLETE",
|
||||||
|
|||||||
Reference in New Issue
Block a user