// // 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 scdtoken import ( "crypto" "crypto/ecdsa" "crypto/rsa" "crypto/x509" "errors" "fmt" "io" "os" "strings" "sync" "github.com/sassoftware/relic/config" "github.com/sassoftware/relic/lib/assuan" "github.com/sassoftware/relic/lib/passprompt" "github.com/sassoftware/relic/lib/x509tools" "github.com/sassoftware/relic/signers/sigerrors" "github.com/sassoftware/relic/token" ) func init() { token.Openers["scdtoken"] = Open token.Listers["scdtoken"] = List } var defaultScdSockets = []string{ "/run/user/$UID/gnupg/S.scdaemon", "/var/run/user/$UID/gnupg/S.scdaemon", "$HOME/.gnupg/S.scdaemon", } type scdToken struct { config *config.Config tokenConf *config.TokenConfig sock *assuan.ScdConn serial, pin string keyInfos []*assuan.ScdKey mu sync.Mutex } type scdKey struct { token *scdToken keyConf *config.KeyConfig key *assuan.ScdKey publicKey crypto.PublicKey } func findSock() string { uid := fmt.Sprintf("%d", os.Getuid()) for _, fp := range defaultScdSockets { fp = strings.Replace(fp, "$UID", uid, -1) fp = os.ExpandEnv(fp) _, err := os.Stat(fp) if err == nil { return fp } } return "" } func List(sockPath string, output io.Writer) error { if sockPath == "" { sockPath = findSock() } if sockPath == "" { return errors.New("scdaemon not found; provide an explicit path to the scdaemon socket") } sock, err := assuan.DialScd(sockPath) if err != nil { return err } defer sock.Close() keyInfos, err := sock.Learn() if err != nil { return err } if len(keyInfos) == 0 { fmt.Fprintln(output, "token is empty or missing") } else { fmt.Fprintf(output, "serial: %s\n", keyInfos[0].Serial) } return nil } func Open(conf *config.Config, tokenName string, prompt passprompt.PasswordGetter) (token.Token, error) { tconf, err := conf.GetToken(tokenName) if err != nil { return nil, err } sockPath := tconf.Provider if sockPath == "" { sockPath = findSock() } if sockPath == "" { return nil, fmt.Errorf("scdaemon not found; set tokens.%s.provider to the path to the scdaemon socket", tokenName) } sock, err := assuan.DialScd(sockPath) if err != nil { return nil, err } tok := &scdToken{ config: conf, tokenConf: tconf, sock: sock, } if err := tok.login(prompt); err != nil { sock.Close() return nil, err } return tok, nil } func (tok *scdToken) login(prompt passprompt.PasswordGetter) error { tconf := tok.tokenConf keyInfos, err := tok.sock.Learn() if err != nil { return err } tok.keyInfos = keyInfos tok.serial = keyInfos[0].Serial if tconf.Serial != "" && tconf.Serial != tok.serial { return fmt.Errorf("scdaemon token %s has serial %s but configuration specifies %s", tconf.Name(), tok.serial, tconf.Serial) } loginFunc := func(pin string) (bool, error) { if err := tok.sock.CheckPin(pin); err == nil { tok.pin = pin return true, nil } else if _, ok := err.(sigerrors.PinIncorrectError); ok { return false, nil } else { return false, err } } initialPrompt := fmt.Sprintf("PIN for token %s (serial %s): ", tconf.Name(), tok.serial) keyringUser := tok.serial return token.Login(tconf, prompt, loginFunc, keyringUser, initialPrompt) } func (tok *scdToken) Ping() error { tok.mu.Lock() defer tok.mu.Unlock() // TODO return nil } func (tok *scdToken) Close() error { tok.mu.Lock() defer tok.mu.Unlock() if tok.sock != nil { tok.sock.Close() tok.sock = nil } return nil } func (tok *scdToken) Config() *config.TokenConfig { return tok.tokenConf } func (tok *scdToken) ListKeys(opts token.ListOptions) error { tok.mu.Lock() defer tok.mu.Unlock() fmt.Fprintf(opts.Output, "serial: %#v\n", tok.serial) for i, key := range tok.keyInfos { if opts.ID != "" && opts.ID != key.KeyId { continue } fmt.Fprintf(opts.Output, "key %d:\n id: %s\n fingerprint: %s\n keygrip: %s\n", i+1, key.KeyId, key.Fingerprint, key.KeyGrip) if opts.Values { pub, err := key.Public() if err != nil { fmt.Fprintln(opts.Output, " error reading key:", err) continue } switch k := pub.(type) { case *rsa.PublicKey: fmt.Fprintf(opts.Output, " n: 0x%x\n e: %d\n", k.N, k.E) case *ecdsa.PublicKey: curve, err := x509tools.CurveByCurve(k.Curve) if err == nil { fmt.Fprintf(opts.Output, " bits: %d\n", curve.Bits) } fmt.Fprintf(opts.Output, " x: %x\n y: %x\n", k.X, k.Y) } } } return nil } func (tok *scdToken) GetKey(keyName string) (token.Key, error) { tok.mu.Lock() defer tok.mu.Unlock() keyConf, err := tok.config.GetKey(keyName) if err != nil { return nil, err } var key *assuan.ScdKey for _, kc := range tok.keyInfos { if keyConf.ID != "" && keyConf.ID != kc.KeyId { continue } key = kc break } if key.KeyId == "" { return nil, fmt.Errorf("key %s not found in token %s", keyName, tok.tokenConf.Name()) } pubkey, err := key.Public() if err != nil { return nil, err } return &scdKey{ token: tok, keyConf: keyConf, key: key, publicKey: pubkey, }, nil } func (key *scdKey) Public() crypto.PublicKey { return key.publicKey } func (key *scdKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { key.token.mu.Lock() defer key.token.mu.Unlock() return key.key.Sign(digest, opts, key.token.pin) } func (key *scdKey) Config() *config.KeyConfig { return key.keyConf } func (key *scdKey) GetID() []byte { return []byte(key.token.serial) } func (tok *scdToken) Import(keyName string, privKey crypto.PrivateKey) (token.Key, error) { return nil, errors.New("function not implemented for tokens of type \"scdaemon\"") } func (tok *scdToken) ImportCertificate(cert *x509.Certificate, labelBase string) error { return errors.New("function not implemented for tokens of type \"scdaemon\"") } func (tok *scdToken) Generate(keyName string, keyType token.KeyType, bits uint) (token.Key, error) { // TODO - probably useful return nil, errors.New("function not implemented for tokens of type \"scdaemon\"") } func (key *scdKey) ImportCertificate(cert *x509.Certificate) error { return errors.New("function not implemented for tokens of type \"scdaemon\"") }