// // Copyright (c) SAS Institute Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package p11token import ( "errors" "fmt" "io" "runtime" "sync" "github.com/miekg/pkcs11" "github.com/sassoftware/relic/config" "github.com/sassoftware/relic/lib/passprompt" "github.com/sassoftware/relic/signers/sigerrors" "github.com/sassoftware/relic/token" ) const ( CKS_RO_PUBLIC_SESSION = 0 CKS_RO_USER_FUNCTIONS = 1 CKS_RW_PUBLIC_SESSION = 2 CKS_RW_USER_FUNCTIONS = 3 CKS_RW_SO_FUNCTIONS = 4 CKA_ID = pkcs11.CKA_ID CKA_LABEL = pkcs11.CKA_LABEL CKA_SERIAL_NUMBER = pkcs11.CKA_SERIAL_NUMBER CKK_RSA = pkcs11.CKK_RSA CKK_ECDSA = pkcs11.CKK_ECDSA ) func init() { token.Openers["pkcs11"] = open token.Listers["pkcs11"] = List } var providerMap map[string]*pkcs11.Ctx var providerMutex sync.Mutex type Token struct { config *config.Config tokenConf *config.TokenConfig ctx *pkcs11.Ctx sh pkcs11.SessionHandle mutex sync.Mutex } func List(provider string, output io.Writer) error { ctx := pkcs11.New(provider) if ctx == nil { return errors.New("Failed to initialize pkcs11 provider") } defer ctx.Destroy() if err := ctx.Initialize(); err != nil { return err } slots, err := ctx.GetSlotList(false) if err != nil { return err } for _, slot := range slots { info, err := ctx.GetTokenInfo(slot) if rv, ok := err.(pkcs11.Error); ok && rv == pkcs11.CKR_TOKEN_NOT_PRESENT { continue } fmt.Fprintf(output, "slot %d:\n manuf: %s\n model: %s\n label: %s\n serial: %s\n", slot, info.ManufacturerID, info.Model, info.Label, info.SerialNumber) } return nil } // Load a PKCS#11 provider, open a session, and login func Open(config *config.Config, tokenName string, pinProvider passprompt.PasswordGetter) (*Token, error) { tokenConf, err := config.GetToken(tokenName) if err != nil { return nil, err } ctx, err := openLib(tokenConf, true) if err != nil { return nil, err } tok := &Token{ ctx: ctx, config: config, tokenConf: tokenConf, } runtime.SetFinalizer(tok, (*Token).Close) slot, err := tok.findSlot() if err != nil { tok.Close() return nil, err } mode := uint(pkcs11.CKF_SERIAL_SESSION | pkcs11.CKF_RW_SESSION) sh, err := tok.ctx.OpenSession(slot, mode) if err != nil { tok.Close() return nil, err } tok.sh = sh err = tok.autoLogIn(pinProvider) if err != nil { tok.Close() return nil, err } return tok, nil } // compat shim for token.Openers func open(cfg *config.Config, tokenName string, prompt passprompt.PasswordGetter) (token.Token, error) { return Open(cfg, tokenName, prompt) } func openLib(tokenConf *config.TokenConfig, write bool) (*pkcs11.Ctx, error) { if tokenConf.Provider == "" { return nil, errors.New("Missing attribute \"provider\" in token configuration") } providerMutex.Lock() defer providerMutex.Unlock() if providerMap == nil { providerMap = make(map[string]*pkcs11.Ctx) } ctx, ok := providerMap[tokenConf.Provider] if ok { return ctx, nil } ctx = pkcs11.New(tokenConf.Provider) if ctx == nil { return nil, errors.New("Failed to initialize pkcs11 provider") } err := ctx.Initialize() if err != nil { ctx.Destroy() return nil, err } providerMap[tokenConf.Provider] = ctx return ctx, nil } // Close the token session func (tok *Token) Close() error { tok.mutex.Lock() defer tok.mutex.Unlock() if tok.ctx != nil { tok.ctx.CloseSession(tok.sh) tok.ctx = nil runtime.SetFinalizer(tok, nil) } return nil } func (tok *Token) Config() *config.TokenConfig { return tok.tokenConf } func (tok *Token) findSlot() (uint, error) { tokenConf := tok.tokenConf slots, err := tok.ctx.GetSlotList(false) if err != nil { return 0, nil } candidates := make([]uint, 0, len(slots)) for _, slot := range slots { info, err := tok.ctx.GetTokenInfo(slot) if err != nil { if rv, ok := err.(pkcs11.Error); ok && rv == pkcs11.CKR_TOKEN_NOT_PRESENT { continue } return 0, err } if tokenConf.Label != "" && tokenConf.Label != info.Label { continue } else if tokenConf.Serial != "" && tokenConf.Serial != info.SerialNumber { continue } candidates = append(candidates, slot) } if len(candidates) == 0 { return 0, errors.New("No token found with the specified attributes") } else if len(candidates) != 1 { return 0, errors.New("Multiple tokens matched the specified attributes") } else { return candidates[0], nil } } // Test that the token is responding and the user is (still) logged in func (tok *Token) isLoggedIn() (bool, error) { tok.mutex.Lock() defer tok.mutex.Unlock() info, err := tok.ctx.GetSessionInfo(tok.sh) if err != nil { return false, err } return (info.State == CKS_RO_USER_FUNCTIONS || info.State == CKS_RW_USER_FUNCTIONS || info.State == CKS_RW_SO_FUNCTIONS), nil } func (tok *Token) Ping() error { loggedIn, err := tok.isLoggedIn() if err != nil { return err } else if !loggedIn { return errors.New("token not logged in") } return nil } func (tok *Token) login(user uint, pin string) error { tok.mutex.Lock() defer tok.mutex.Unlock() err := tok.ctx.Login(tok.sh, user, pin) if err != nil { if rv, ok := err.(pkcs11.Error); ok && rv == pkcs11.CKR_PIN_INCORRECT { return sigerrors.PinIncorrectError{} } } return err } func (tok *Token) autoLogIn(pinProvider passprompt.PasswordGetter) error { tokenConf := tok.tokenConf loggedIn, err := tok.isLoggedIn() if err != nil { return err } if loggedIn { return nil } user := pkcs11.CKU_USER if tokenConf.User != nil { user = *tokenConf.User } loginFunc := func(pin string) (bool, error) { if err := tok.login(user, pin); err == nil { return true, nil } else if _, ok := err.(sigerrors.PinIncorrectError); ok { return false, nil } else { return false, err } } initialPrompt := fmt.Sprintf("PIN for token %s user %08x: ", tokenConf.Name(), user) keyringUser := fmt.Sprintf("%s.%08x", tokenConf.Name(), user) return token.Login(tokenConf, pinProvider, loginFunc, keyringUser, initialPrompt) } func (tok *Token) getAttribute(handle pkcs11.ObjectHandle, attr uint) []byte { attrs, err := tok.ctx.GetAttributeValue(tok.sh, handle, []*pkcs11.Attribute{pkcs11.NewAttribute(attr, nil)}) if err != nil { return nil } return attrs[0].Value } func (tok *Token) findObject(attrs []*pkcs11.Attribute) ([]pkcs11.ObjectHandle, error) { if err := tok.ctx.FindObjectsInit(tok.sh, attrs); err != nil { return nil, err } objects, _, err := tok.ctx.FindObjects(tok.sh, 10) if err != nil { tok.ctx.FindObjectsFinal(tok.sh) return nil, err } if err := tok.ctx.FindObjectsFinal(tok.sh); err != nil { return nil, err } return objects, nil } func attrToInt(value []byte) uint { var n uint for i := len(value) - 1; i >= 0; i-- { n = n<<8 | uint(value[i]) } return n }