Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions api/utils/keys/piv/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,31 @@ func (s *YubiKeyService) Sign(ctx context.Context, ref *hardwarekey.PrivateKeyRe
return nil, trace.Wrap(err)
}

s.signMu.Lock()
defer s.signMu.Unlock()
pivSlot, err := parsePIVSlot(ref.SlotKey)
if err != nil {
return nil, trace.Wrap(err)
}

// Check that the public key in the slot matches our record.
publicKey, err := y.getPublicKey(pivSlot)
if err != nil {
return nil, trace.Wrap(err)
}

if !publicKey.Equal(ref.PublicKey) {
return nil, trace.CompareFailed("public key mismatch on PIV slot 0x%x", pivSlot.Key)
}

// If the sign request is for an unknown agent key, ensure that the requested PIV slot was
// configured with a self-signed Teleport metadata certificate.
if keyInfo.AgentKeyInfo.UnknownAgentKey {
switch err := y.checkCertificate(pivSlot); {
case trace.IsNotFound(err), errors.As(err, &nonTeleportCertError{}):
return nil, trace.Wrap(err, agentRequiresTeleportCertMessage)
case err != nil:
return nil, trace.Wrap(err)
}
}

return y.sign(ctx, ref, keyInfo, s.getPrompt(), rand, digest, opts)
}
Expand Down
43 changes: 43 additions & 0 deletions api/utils/keys/piv/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ import (
"crypto/x509/pkix"
"fmt"
"os"
"sync"
"testing"
"time"

pivgo "github.com/go-piv/piv-go/piv"
"github.com/gravitational/trace"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/gravitational/teleport/api/utils/keys"
Expand Down Expand Up @@ -291,6 +293,47 @@ func TestPINCaching(t *testing.T) {
require.Error(t, err)
}

func TestConcurrentSignature(t *testing.T) {
// This test will overwrite any PIV data on the yubiKey.
if os.Getenv("TELEPORT_TEST_YUBIKEY_PIV") == "" {
t.Skipf("Skipping TestGenerateYubiKeyPrivateKey because TELEPORT_TEST_YUBIKEY_PIV is not set")
}

ctx := context.Background()
promptReader := prompt.NewFakeReader()
prompt := hardwarekey.NewCLIPrompt(os.Stderr, promptReader)
s := piv.NewYubiKeyService(prompt)

y, err := piv.FindYubiKey(0)
require.NoError(t, err)

resetYubikey(t, y)
t.Cleanup(func() { resetYubikey(t, y) })

// Set pin.
const testPIN = "123123"
require.NoError(t, y.SetPIN(pivgo.DefaultPIN, testPIN))

promptReader.AddString(testPIN)
priv, err := keys.NewHardwarePrivateKey(ctx, s, hardwarekey.PrivateKeyConfig{
// Use PIN policy to slow down the signatures a bit so that they are concurrent.
Policy: hardwarekey.PromptPolicyPIN,
})
require.NoError(t, err)

var wg sync.WaitGroup
for range 5 {
wg.Add(1)
go func() {
defer wg.Done()
err = priv.WarmupHardwareKey(ctx)
assert.NoError(t, err)
}()
}

wg.Wait()
}

// resetYubikey connects to the first yubiKey and resets it to defaults.
func resetYubikey(t *testing.T, y *piv.YubiKey) {
t.Helper()
Expand Down
Loading
Loading