...

Source file src/github.com/sassoftware/relic/lib/assuan/scd.go

Documentation: github.com/sassoftware/relic/lib/assuan

     1  //
     2  // Copyright (c) SAS Institute Inc.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  //
    16  
    17  package assuan
    18  
    19  // Implement a libassuan client and wrap useful functions in scdaemon
    20  
    21  import (
    22  	"bytes"
    23  	"crypto"
    24  	"crypto/rsa"
    25  	"errors"
    26  	"fmt"
    27  	"math/big"
    28  	"strings"
    29  
    30  	"github.com/sassoftware/relic/lib/dlog"
    31  	"github.com/sassoftware/relic/lib/x509tools"
    32  	"github.com/sassoftware/relic/signers/sigerrors"
    33  )
    34  
    35  type ScdConn struct {
    36  	*Conn
    37  	Serial string
    38  }
    39  
    40  type ScdKey struct {
    41  	Serial      string
    42  	Fingerprint string
    43  	KeyGrip     string
    44  	KeyId       string
    45  
    46  	conn *ScdConn
    47  }
    48  
    49  func DialScd(path string) (*ScdConn, error) {
    50  	conn, err := Dial(path)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	return &ScdConn{Conn: conn}, nil
    55  }
    56  
    57  // Invoke LEARN and return info about the keys in the token
    58  func (s *ScdConn) Learn() ([]*ScdKey, error) {
    59  	res, err := s.Conn.Transact("LEARN", func(inquiry string, lines []string) (string, error) {
    60  		return "", nil
    61  	})
    62  	if err != nil {
    63  		return nil, fmt.Errorf("failed to enumerate token: %s", err)
    64  	}
    65  	var keyids, keygrips []string
    66  	fingerprints := make(map[string]string)
    67  	for _, line := range res.Lines {
    68  		parts := strings.Split(line, " ")
    69  		switch parts[0] {
    70  		case "SERIALNO":
    71  			if len(parts) < 2 {
    72  				continue
    73  			}
    74  			s.Serial = parts[1]
    75  		case "KEYPAIRINFO":
    76  			if len(parts) < 3 || parts[1] == "X" {
    77  				continue
    78  			}
    79  			keygrips = append(keygrips, parts[1])
    80  			keyids = append(keyids, parts[2])
    81  		case "KEY-FPR":
    82  			if len(parts) < 3 {
    83  				continue
    84  			}
    85  			keyid := "OPENPGP." + parts[1]
    86  			fingerprints[keyid] = parts[2]
    87  		}
    88  	}
    89  	if len(keyids) == 0 {
    90  		return nil, errors.New("failed to enumerate token: no valid key found")
    91  	}
    92  	infos := make([]*ScdKey, 0, len(keyids))
    93  	dlog.Printf(3, "scdaemon token with serial %s has keys:", s.Serial)
    94  	for i, keyid := range keyids {
    95  		info := &ScdKey{
    96  			conn:        s,
    97  			Serial:      s.Serial,
    98  			KeyId:       keyid,
    99  			KeyGrip:     keygrips[i],
   100  			Fingerprint: fingerprints[keyid],
   101  		}
   102  		dlog.Printf(3, " keyid=%s keygrip=%s fingerprint=%s", info.KeyId, info.KeyGrip, info.Fingerprint)
   103  		infos = append(infos, info)
   104  	}
   105  	return infos, nil
   106  }
   107  
   108  // Verify that the token can be unlocked with the given pin
   109  func (s *ScdConn) CheckPin(pin string) error {
   110  	if s.Serial == "" {
   111  		infos, err := s.Learn()
   112  		if err != nil {
   113  			return err
   114  		}
   115  		s.Serial = infos[0].Serial
   116  	}
   117  	_, err := s.Conn.Transact("CHECKPIN "+s.Serial, func(inquiry string, lines []string) (string, error) {
   118  		if strings.HasPrefix(inquiry, "NEEDPIN") {
   119  			return pin + "\x00", nil
   120  		} else {
   121  			return "", fmt.Errorf("unexpected INQUIRE: %s", inquiry)
   122  		}
   123  	})
   124  	if eres, ok := err.(Response); ok && strings.Contains(eres.StatusMessage, "Bad PIN") {
   125  		return sigerrors.PinIncorrectError{}
   126  	} else if err != nil {
   127  		return fmt.Errorf("failed to validate PIN: %s", err)
   128  	}
   129  	return nil
   130  }
   131  
   132  // Get the public key from the token
   133  func (k *ScdKey) Public() (crypto.PublicKey, error) {
   134  	res, err := k.conn.Transact("READKEY "+k.KeyId, nil)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	exp, err := parseCsExp(res.Blob)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	if len(exp.Items) != 1 {
   143  		return nil, errors.New("invalid public key in token")
   144  	}
   145  	exp = exp.Items[0]
   146  	if len(exp.Items) != 2 || !bytes.Equal(exp.Items[0].Value, []byte("public-key")) {
   147  		return nil, errors.New("invalid public key in token")
   148  	}
   149  	exp = exp.Items[1]
   150  	if len(exp.Items) == 0 {
   151  		return nil, errors.New("invalid public key in token")
   152  	}
   153  	keyType := string(exp.Items[0].Value)
   154  	values := make(map[string][]byte)
   155  	for _, item := range exp.Items[1:] {
   156  		if len(item.Items) != 2 {
   157  			return nil, errors.New("invalid public key in token")
   158  		}
   159  		name := string(item.Items[0].Value)
   160  		value := item.Items[1].Value
   161  		values[name] = value
   162  	}
   163  	switch keyType {
   164  	case "rsa":
   165  		n := values["n"]
   166  		e := values["e"]
   167  		if n == nil || e == nil {
   168  			return nil, errors.New("invalid RSA public key in token")
   169  		}
   170  		return &rsa.PublicKey{
   171  			N: new(big.Int).SetBytes(n),
   172  			E: int(new(big.Int).SetBytes(e).Int64()),
   173  		}, nil
   174  	default:
   175  		return nil, fmt.Errorf("unsupported public key of type %s in token", keyType)
   176  	}
   177  }
   178  
   179  // Create a signature over the given (unpadded) digest.
   180  func (k *ScdKey) Sign(hashValue []byte, opts crypto.SignerOpts, pin string) ([]byte, error) {
   181  	if opts == nil || opts.HashFunc() == 0 {
   182  		return nil, errors.New("Signer options are required")
   183  	} else if _, ok := opts.(*rsa.PSSOptions); ok {
   184  		return nil, errors.New("RSA-PSS not implemented")
   185  	}
   186  	hashName := x509tools.HashNames[opts.HashFunc()]
   187  	if hashName == "" {
   188  		return nil, errors.New("unsupported hash algorithm")
   189  	}
   190  	hashName = strings.ToLower(strings.Replace(hashName, "-", "", -1))
   191  	res, err := k.conn.Transact(fmt.Sprintf("SETDATA %X", hashValue), nil)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	res, err = k.conn.Transact(fmt.Sprintf("PKSIGN --hash=%s %s\n", hashName, k.KeyId),
   196  		func(inquiry string, lines []string) (string, error) {
   197  			if strings.HasPrefix(inquiry, "NEEDPIN") {
   198  				return pin + "\x00", nil
   199  			} else {
   200  				return "", fmt.Errorf("unexpected INQUIRE: %s", inquiry)
   201  			}
   202  		})
   203  	if eres, ok := err.(Response); ok && strings.Contains(eres.StatusMessage, "Bad PIN") {
   204  		return nil, sigerrors.PinIncorrectError{}
   205  	} else if err != nil {
   206  		return nil, fmt.Errorf("failed to sign: %s", err)
   207  	}
   208  	return res.Blob, nil
   209  }
   210  

View as plain text