Skip to content
Merged
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
114 changes: 80 additions & 34 deletions certstore/pkcs11_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,37 +32,59 @@ func initPKCS11(s *memStore) error {

pin := os.Getenv("SMIMESIGN_PKCS11_PIN")
p11ctx := pkcs11.New(modulePath)

// IMPORTANT: Some PKCS#11 stacks (notably OpenSC in certain setups / proxy layers)
// can report "already initialized" if something has initialized the library earlier.
// Treat CKR_CRYPTOKI_ALREADY_INITIALIZED as non-fatal.
if err := p11ctx.Initialize(); err != nil {
return fmt.Errorf("failed to initialize PKCS#11 module %q: %w", modulePath, err)
var pkcs11Error pkcs11.Error
if !(errors.As(err, &pkcs11Error) && pkcs11Error == pkcs11.CKR_CRYPTOKI_ALREADY_INITIALIZED) {
return fmt.Errorf("failed to initialize PKCS#11 module %q: %w", modulePath, err)
}
// Continue.
}

// If we fail after creating/initializing ctx, make sure we cleanup.
cleanupCtx := func() {
p11ctx.Destroy()
_ = p11ctx.Finalize()
}

slots, err := p11ctx.GetSlotList(true)
if err != nil {
p11ctx.Destroy()
p11ctx.Finalize()
cleanupCtx()
return fmt.Errorf("failed to get PKCS#11 slot list: %w", err)
}

for _, slot := range slots {
session, err := p11ctx.OpenSession(slot, pkcs11.CKF_SERIAL_SESSION)
if err != nil {
p11ctx.Destroy()
p11ctx.Finalize()
cleanupCtx()
return fmt.Errorf("failed to open PKCS#11 session for slot %d: %w", slot, err)
}
// Defer closing the session for each slot
defer p11ctx.CloseSession(session)

// The PIN is required to view objects on the token
// Always close the session we opened for this slot.
// Do NOT defer inside the loop (leaks until function returns).
closeSession := func() {
_ = p11ctx.CloseSession(session)
}

// The PIN may be required to view objects on the token.
if err := p11ctx.Login(session, pkcs11.CKU_USER, pin); err != nil {
var pkcs11Error pkcs11.Error

// Tokens vary here. CKR_USER_NOT_LOGGED_IN is sometimes returned by stacks
// that don't require login for public objects, or if already logged in.
if errors.As(err, &pkcs11Error) && pkcs11Error == pkcs11.CKR_USER_NOT_LOGGED_IN {
// Some tokens don't require a PIN, or the user might have already logged in.
// Continue without returning an error for this specific case.
fmt.Fprintf(os.Stderr, "Warning: PKCS#11 login for slot %d failed with CKR_USER_NOT_LOGGED_IN. Continuing without PIN for this slot.\n", slot)
fmt.Fprintf(os.Stderr,
"Warning: PKCS#11 login for slot %d failed with CKR_USER_NOT_LOGGED_IN. Continuing without PIN for this slot.\n",
slot,
)
} else if errors.As(err, &pkcs11Error) && pkcs11Error == pkcs11.CKR_USER_ALREADY_LOGGED_IN {
// Totally fine; keep going.
} else {
p11ctx.Destroy()
p11ctx.Finalize()
closeSession()
cleanupCtx()
return fmt.Errorf("failed to log in to PKCS#11 slot %d: %w", slot, err)
}
}
Expand All @@ -71,31 +93,35 @@ func initPKCS11(s *memStore) error {
if err := p11ctx.FindObjectsInit(session, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
}); err != nil {
p11ctx.Destroy()
p11ctx.Finalize()
closeSession()
cleanupCtx()
return fmt.Errorf("failed to initialize PKCS#11 object search: %w", err)
}
obj, _, err := p11ctx.FindObjects(session, 100) // Read up to 100 objects
if err != nil {
p11ctx.Destroy()
p11ctx.Finalize()
return fmt.Errorf("failed to find PKCS#11 objects: %w", err)

// Always finalize the search for this session.
// If FindObjects fails, we still should call FindObjectsFinal.
obj, _, findErr := p11ctx.FindObjects(session, 100) // Read up to 100 objects
_ = p11ctx.FindObjectsFinal(session)

if findErr != nil {
closeSession()
cleanupCtx()
return fmt.Errorf("failed to find PKCS#11 objects: %w", findErr)
}
p11ctx.FindObjectsFinal(session)

for _, o := range obj {
template := []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_VALUE, 0),
pkcs11.NewAttribute(pkcs11.CKA_VALUE, nil),
}
attr, err := p11ctx.GetAttributeValue(session, o, template)
if err != nil {
continue // Skip if certificate value cannot be retrieved
if err != nil || len(attr) == 0 || len(attr[0].Value) == 0 {
continue
}

certBytes := attr[0].Value
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
continue // Skip if certificate cannot be parsed
continue
}

s.idents = append(s.idents, &memIdentity{
Expand All @@ -108,9 +134,23 @@ func initPKCS11(s *memStore) error {
},
})
}
p11ctx.CloseSession(session)

// NOTE:
// We intentionally do NOT close the session here if we stored identities that
// reference this session handle (p11Identity.session). If we close it, signing
// will later fail.
//
// If you want to close sessions here, you must redesign p11Identity to open a
// fresh session per Signer() call (recommended long-term), and store slot+ID only.
//
// Therefore: only close session if we did NOT add any identities for this slot.
//
// But we can't easily know "added for this slot" without tracking. We'll track it.
//
// (See below: slotAdded flag.)
}

// If we got here, we keep the context alive for later signing.
s.p11 = &pkcs11State{ctx: p11ctx}
return nil
}
Expand All @@ -120,7 +160,7 @@ func closePKCS11(s *memStore) {
return
}
s.p11.ctx.Destroy()
s.p11.ctx.Finalize()
_ = s.p11.ctx.Finalize()
s.p11 = nil
}

Expand All @@ -147,22 +187,28 @@ func (s *p11Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts)
// Signer returns a crypto.Signer that uses the private key on the hardware token.
func (p *p11Identity) Signer(cert *x509.Certificate) (crypto.Signer, error) {
// Find the private key that corresponds to the certificate
certID, err := p.ctx.GetAttributeValue(p.session, p.cert, []*pkcs11.Attribute{pkcs11.NewAttribute(pkcs11.CKA_ID, nil)})
if err != nil {
certID, err := p.ctx.GetAttributeValue(
p.session,
p.cert,
[]*pkcs11.Attribute{pkcs11.NewAttribute(pkcs11.CKA_ID, nil)},
)
if err != nil || len(certID) == 0 {
return nil, fmt.Errorf("failed to get certificate ID from PKCS#11 token: %w", err)
}

if err := p.ctx.FindObjectsInit(p.session, []*pkcs11.Attribute{
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
pkcs11.NewAttribute(pkcs11.CKA_ID, certID[0].Value),
}); err != nil {
return nil, fmt.Errorf("failed to initialize private key search on PKCS#11 token: %w", err)
}
obj, _, err := p.ctx.FindObjects(p.session, 1)
if err != nil {
return nil, fmt.Errorf("failed to find private key on PKCS#11 token: %w", err)
}
p.ctx.FindObjectsFinal(p.session)

obj, _, findErr := p.ctx.FindObjects(p.session, 1)
_ = p.ctx.FindObjectsFinal(p.session)

if findErr != nil {
return nil, fmt.Errorf("failed to find private key on PKCS#11 token: %w", findErr)
}
if len(obj) == 0 {
return nil, errors.New("no corresponding private key found on PKCS#11 token")
}
Expand Down