...

Source file src/github.com/ThalesIgnite/crypto11/crypto11.go

Documentation: github.com/ThalesIgnite/crypto11

     1  // Copyright 2016 Thales e-Security, Inc
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining
     4  // a copy of this software and associated documentation files (the
     5  // "Software"), to deal in the Software without restriction, including
     6  // without limitation the rights to use, copy, modify, merge, publish,
     7  // distribute, sublicense, and/or sell copies of the Software, and to
     8  // permit persons to whom the Software is furnished to do so, subject to
     9  // the following conditions:
    10  //
    11  // The above copyright notice and this permission notice shall be
    12  // included in all copies or substantial portions of the Software.
    13  //
    14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    15  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    16  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    17  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    18  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    19  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    20  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    21  
    22  // Package crypto11 enables access to cryptographic keys from PKCS#11 using Go crypto API.
    23  //
    24  // Configuration
    25  //
    26  // PKCS#11 tokens are accessed via Context objects. Each Context connects to one token.
    27  //
    28  // Context objects are created by calling Configure or ConfigureFromFile.
    29  // In the latter case, the file should contain a JSON representation of
    30  // a Config.
    31  //
    32  // Key Generation and Usage
    33  //
    34  // There is support for generating DSA, RSA and ECDSA keys. These keys
    35  // can be found later using FindKeyPair. All three key types implement
    36  // the crypto.Signer interface and the RSA keys also implement crypto.Decrypter.
    37  //
    38  // RSA keys obtained through FindKeyPair will need a type assertion to be
    39  // used for decryption. Assert either crypto.Decrypter or SignerDecrypter, as you
    40  // prefer.
    41  //
    42  // Symmetric keys can also be generated. These are found later using FindKey.
    43  // See the documentation for SecretKey for further information.
    44  //
    45  // Sessions and concurrency
    46  //
    47  // Note that PKCS#11 session handles must not be used concurrently
    48  // from multiple threads. Consumers of the Signer interface know
    49  // nothing of this and expect to be able to sign from multiple threads
    50  // without constraint. We address this as follows.
    51  //
    52  // 1. When a Context is created, a session is created and the user is
    53  // logged in. This session remains open until the Context is closed,
    54  // to ensure all object handles remain valid and to avoid repeatedly
    55  // calling C_Login.
    56  //
    57  // 2. The Context also maintains a pool of read-write sessions. The pool expands
    58  // dynamically as needed, but never beyond the maximum number of r/w sessions
    59  // supported by the token (as reported by C_GetInfo). If other applications
    60  // are using the token, a lower limit should be set in the Config.
    61  //
    62  // 3. Each operation transiently takes a session from the pool. They
    63  // have exclusive use of the session, meeting PKCS#11's concurrency
    64  // requirements. Sessions are returned to the pool afterwards and may
    65  // be re-used.
    66  //
    67  // Behaviour of the pool can be tweaked via Config fields:
    68  //
    69  // - PoolWaitTimeout controls how long an operation can block waiting on a
    70  // session from the pool. A zero value means there is no limit. Timeouts
    71  // occur if the pool is fully used and additional operations are requested.
    72  //
    73  // - MaxSessions sets an upper bound on the number of sessions. If this value is zero,
    74  // a default maximum is used (see DefaultMaxSessions). In every case the maximum
    75  // supported sessions as reported by the token is obeyed.
    76  //
    77  // Limitations
    78  //
    79  // The PKCS1v15DecryptOptions SessionKeyLen field is not implemented
    80  // and an error is returned if it is nonzero.
    81  // The reason for this is that it is not possible for crypto11 to guarantee the constant-time behavior in the specification.
    82  // See https://github.com/thalesignite/crypto11/issues/5 for further discussion.
    83  //
    84  // Symmetric crypto support via cipher.Block is very slow.
    85  // You can use the BlockModeCloser API
    86  // but you must call the Close() interface (not found in cipher.BlockMode).
    87  // See https://github.com/ThalesIgnite/crypto11/issues/6 for further discussion.
    88  package crypto11
    89  
    90  import (
    91  	"crypto"
    92  	"encoding/json"
    93  	"fmt"
    94  	"io"
    95  	"os"
    96  	"strings"
    97  	"sync"
    98  	"time"
    99  
   100  	"github.com/miekg/pkcs11"
   101  	"github.com/pkg/errors"
   102  	"github.com/thales-e-security/pool"
   103  )
   104  
   105  const (
   106  	// DefaultMaxSessions controls the maximum number of concurrent sessions to
   107  	// open, unless otherwise specified in the Config object.
   108  	DefaultMaxSessions = 1024
   109  
   110  	// DefaultGCMIVLength controls the expected length of IVs generated by the token
   111  	DefaultGCMIVLength = 16
   112  
   113  	// Thales vendor constant for CKU_CRYPTO_USER
   114  	CryptoUser      = 0x80000001
   115  	DefaultUserType = 1 // 1 -> CKU_USER
   116  )
   117  
   118  // errTokenNotFound represents the failure to find the requested PKCS#11 token
   119  var errTokenNotFound = errors.New("could not find PKCS#11 token")
   120  
   121  // errClosed is returned if a Context is used after a call to Close.
   122  var errClosed = errors.New("cannot used closed Context")
   123  
   124  // pkcs11Object contains a reference to a loaded PKCS#11 object.
   125  type pkcs11Object struct {
   126  	// The PKCS#11 object handle.
   127  	handle pkcs11.ObjectHandle
   128  
   129  	// The PKCS#11 context. This is used  to find a session handle that can
   130  	// access this object.
   131  	context *Context
   132  }
   133  
   134  func (o *pkcs11Object) Delete() error {
   135  	return o.context.withSession(func(session *pkcs11Session) error {
   136  		err := session.ctx.DestroyObject(session.handle, o.handle)
   137  		return errors.WithMessage(err, "failed to destroy key")
   138  	})
   139  }
   140  
   141  // pkcs11PrivateKey contains a reference to a loaded PKCS#11 private key object.
   142  type pkcs11PrivateKey struct {
   143  	pkcs11Object
   144  
   145  	// pubKeyHandle is a handle to the public key.
   146  	pubKeyHandle pkcs11.ObjectHandle
   147  
   148  	// pubKey is an exported copy of the public key. We pre-export the key material because crypto.Signer.Public
   149  	// doesn't allow us to return errors.
   150  	pubKey crypto.PublicKey
   151  }
   152  
   153  // Delete implements Signer.Delete.
   154  func (k *pkcs11PrivateKey) Delete() error {
   155  	err := k.pkcs11Object.Delete()
   156  	if err != nil {
   157  		return err
   158  	}
   159  
   160  	return k.context.withSession(func(session *pkcs11Session) error {
   161  		err := session.ctx.DestroyObject(session.handle, k.pubKeyHandle)
   162  		return errors.WithMessage(err, "failed to destroy public key")
   163  	})
   164  }
   165  
   166  // A Context stores the connection state to a PKCS#11 token. Use Configure or ConfigureFromFile to create a new
   167  // Context. Call Close when finished with the token, to free up resources.
   168  //
   169  // All functions, except Close, are safe to call from multiple goroutines.
   170  type Context struct {
   171  	// Atomic fields must be at top (according to the package owners)
   172  	closed pool.AtomicBool
   173  
   174  	ctx *pkcs11.Ctx
   175  	cfg *Config
   176  
   177  	token *pkcs11.TokenInfo
   178  	slot  uint
   179  	pool  *pool.ResourcePool
   180  
   181  	// persistentSession is a session held open so we can be confident handles and login status
   182  	// persist for the duration of this context
   183  	persistentSession pkcs11.SessionHandle
   184  }
   185  
   186  // Signer is a PKCS#11 key that implements crypto.Signer.
   187  type Signer interface {
   188  	crypto.Signer
   189  
   190  	// Delete deletes the key pair from the token.
   191  	Delete() error
   192  }
   193  
   194  // SignerDecrypter is a PKCS#11 key implements crypto.Signer and crypto.Decrypter.
   195  type SignerDecrypter interface {
   196  	Signer
   197  
   198  	// Decrypt implements crypto.Decrypter.
   199  	Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) (plaintext []byte, err error)
   200  }
   201  
   202  // findToken finds a token given exactly one of serial, label or slotNumber
   203  func (c *Context) findToken(slots []uint, serial, label string, slotNumber *int) (uint, *pkcs11.TokenInfo, error) {
   204  	for _, slot := range slots {
   205  
   206  		tokenInfo, err := c.ctx.GetTokenInfo(slot)
   207  		if err != nil {
   208  			return 0, nil, err
   209  		}
   210  
   211  		if (slotNumber != nil && uint(*slotNumber) == slot) ||
   212  			(tokenInfo.SerialNumber != "" && tokenInfo.SerialNumber == serial) ||
   213  			(tokenInfo.Label != "" && tokenInfo.Label == label) {
   214  
   215  			return slot, &tokenInfo, nil
   216  		}
   217  
   218  	}
   219  	return 0, nil, errTokenNotFound
   220  }
   221  
   222  // Config holds PKCS#11 configuration information.
   223  //
   224  // A token may be selected by label, serial number or slot number. It is an error to specify
   225  // more than one way to select the token.
   226  //
   227  // Supply this to Configure(), or alternatively use ConfigureFromFile().
   228  type Config struct {
   229  	// Full path to PKCS#11 library.
   230  	Path string
   231  
   232  	// Token serial number.
   233  	TokenSerial string
   234  
   235  	// Token label.
   236  	TokenLabel string
   237  
   238  	// SlotNumber identifies a token to use by the slot containing it.
   239  	SlotNumber *int
   240  
   241  	// User PIN (password).
   242  	Pin string
   243  
   244  	// Maximum number of concurrent sessions to open. If zero, DefaultMaxSessions is used.
   245  	// Otherwise, the value specified must be at least 2.
   246  	MaxSessions int
   247  
   248  	// User type identifies the user type logging in. If zero, DefaultUserType is used.
   249  	UserType int
   250  
   251  	// Maximum time to wait for a session from the sessions pool. Zero means wait indefinitely.
   252  	PoolWaitTimeout time.Duration
   253  
   254  	// LoginNotSupported should be set to true for tokens that do not support logging in.
   255  	LoginNotSupported bool
   256  
   257  	// UseGCMIVFromHSM should be set to true for tokens such as CloudHSM, which ignore the supplied IV for
   258  	// GCM mode and generate their own. In this case, the token will write the IV used into the CK_GCM_PARAMS.
   259  	// If UseGCMIVFromHSM is true, we will copy this IV and overwrite the 'nonce' slice passed to Seal and Open. It
   260  	// is therefore necessary that the nonce is the correct length (12 bytes for CloudHSM).
   261  	UseGCMIVFromHSM bool
   262  
   263  	// GCMIVLength is the length of IVs to use in GCM mode. Refer to NIST SP800-38 for guidance on the length of
   264  	// RBG-based IVs in GCM mode. When the UseGCMIVFromHSM parameter is true
   265  	GCMIVLength int
   266  
   267  	GCMIVFromHSMControl GCMIVFromHSMConfig
   268  }
   269  
   270  type GCMIVFromHSMConfig struct {
   271  
   272  	// SupplyIvForHSMGCM_encrypt controls the supply of a non-nil IV for GCM use during C_EncryptInit
   273  	SupplyIvForHSMGCMEncrypt bool
   274  
   275  	// SupplyIvForHSMGCM_decrypt controls the supply of a non-nil IV for GCM use during C_DecryptInit
   276  	SupplyIvForHSMGCMDecrypt bool
   277  }
   278  
   279  // refCount counts the number of contexts using a particular P11 library. It must not be read or modified
   280  // without holding refCountMutex.
   281  var refCount = map[string]int{}
   282  var refCountMutex = sync.Mutex{}
   283  
   284  // Configure creates a new Context based on the supplied PKCS#11 configuration.
   285  func Configure(config *Config) (*Context, error) {
   286  	// Have we been given exactly one way to select a token?
   287  	var fields []string
   288  	if config.SlotNumber != nil {
   289  		fields = append(fields, "slot number")
   290  	}
   291  	if config.TokenLabel != "" {
   292  		fields = append(fields, "token label")
   293  	}
   294  	if config.TokenSerial != "" {
   295  		fields = append(fields, "token serial number")
   296  	}
   297  	if len(fields) == 0 {
   298  		return nil, fmt.Errorf("config must specify exactly one way to select a token: none given")
   299  	} else if len(fields) > 1 {
   300  		return nil, fmt.Errorf("config must specify exactly one way to select a token: %v given", strings.Join(fields, ", "))
   301  	}
   302  
   303  	if config.MaxSessions == 0 {
   304  		config.MaxSessions = DefaultMaxSessions
   305  	}
   306  	if config.MaxSessions == 1 {
   307  		return nil, errors.New("MaxSessions must be larger than 1")
   308  	}
   309  
   310  	if config.UserType == 0 {
   311  		config.UserType = DefaultUserType
   312  	}
   313  
   314  	if config.GCMIVLength == 0 {
   315  		config.GCMIVLength = DefaultGCMIVLength
   316  	}
   317  
   318  	instance := &Context{
   319  		cfg: config,
   320  		ctx: pkcs11.New(config.Path),
   321  	}
   322  
   323  	if instance.ctx == nil {
   324  		return nil, errors.New("could not open PKCS#11")
   325  	}
   326  
   327  	// Check how many contexts are currently using this library
   328  	refCountMutex.Lock()
   329  	defer refCountMutex.Unlock()
   330  	numExistingContexts := refCount[config.Path]
   331  
   332  	// Only Initialize if we are the first Context using the library
   333  	if numExistingContexts == 0 {
   334  		if err := instance.ctx.Initialize(); err != nil {
   335  			instance.ctx.Destroy()
   336  			return nil, errors.WithMessage(err, "failed to initialize PKCS#11 library")
   337  		}
   338  	}
   339  	slots, err := instance.ctx.GetSlotList(true)
   340  	if err != nil {
   341  		_ = instance.ctx.Finalize()
   342  		instance.ctx.Destroy()
   343  		return nil, errors.WithMessage(err, "failed to list PKCS#11 slots")
   344  	}
   345  
   346  	instance.slot, instance.token, err = instance.findToken(slots, config.TokenSerial, config.TokenLabel, config.SlotNumber)
   347  	if err != nil {
   348  		_ = instance.ctx.Finalize()
   349  		instance.ctx.Destroy()
   350  		return nil, err
   351  	}
   352  
   353  	// Create the session pool.
   354  	maxSessions := instance.cfg.MaxSessions
   355  	tokenMaxSessions := instance.token.MaxRwSessionCount
   356  	if tokenMaxSessions != pkcs11.CK_EFFECTIVELY_INFINITE && tokenMaxSessions != pkcs11.CK_UNAVAILABLE_INFORMATION {
   357  		maxSessions = min(maxSessions, castDown(tokenMaxSessions))
   358  	}
   359  
   360  	// We will use one session to keep state alive, so the pool gets maxSessions - 1
   361  	instance.pool = pool.NewResourcePool(instance.resourcePoolFactoryFunc, maxSessions-1, maxSessions-1, 0, 0)
   362  
   363  	// Create a long-term session and log it in (if supported). This session won't be used by callers, instead it is
   364  	// used to keep a connection alive to the token to ensure object handles and the log in status remain accessible.
   365  	instance.persistentSession, err = instance.ctx.OpenSession(instance.slot, pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
   366  	if err != nil {
   367  		_ = instance.ctx.Finalize()
   368  		instance.ctx.Destroy()
   369  		return nil, errors.WithMessagef(err, "failed to create long term session")
   370  	}
   371  
   372  	if !config.LoginNotSupported {
   373  		// Try to log in our persistent session. This may fail with CKR_USER_ALREADY_LOGGED_IN if another instance
   374  		// already exists.
   375  		if instance.cfg.UserType == 1 {
   376  			err = instance.ctx.Login(instance.persistentSession, pkcs11.CKU_USER, instance.cfg.Pin)
   377  		} else {
   378  			err = instance.ctx.Login(instance.persistentSession, CryptoUser, instance.cfg.Pin)
   379  		}
   380  		if err != nil {
   381  
   382  			pErr, isP11Error := err.(pkcs11.Error)
   383  
   384  			if !isP11Error || pErr != pkcs11.CKR_USER_ALREADY_LOGGED_IN {
   385  				_ = instance.ctx.Finalize()
   386  				instance.ctx.Destroy()
   387  				return nil, errors.WithMessagef(err, "failed to log into long term session")
   388  			}
   389  		}
   390  	}
   391  
   392  	// Increment the reference count
   393  	refCount[config.Path] = numExistingContexts + 1
   394  
   395  	return instance, nil
   396  }
   397  
   398  func min(a, b int) int {
   399  	if b < a {
   400  		return b
   401  	}
   402  	return a
   403  }
   404  
   405  // castDown returns orig as a signed integer. If an overflow would have occurred,
   406  // the maximum possible value is returned.
   407  func castDown(orig uint) int {
   408  	// From https://stackoverflow.com/a/6878625/474189
   409  	const maxUint = ^uint(0)
   410  	const maxInt = int(maxUint >> 1)
   411  
   412  	if orig > uint(maxInt) {
   413  		return maxInt
   414  	}
   415  
   416  	return int(orig)
   417  }
   418  
   419  // ConfigureFromFile is a convenience method, which parses the configuration file
   420  // and calls Configure. The configuration file should be a JSON representation
   421  // of a Config object.
   422  func ConfigureFromFile(configLocation string) (*Context, error) {
   423  	config, err := loadConfigFromFile(configLocation)
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  
   428  	return Configure(config)
   429  }
   430  
   431  // loadConfigFromFile reads a Config struct from a file.
   432  func loadConfigFromFile(configLocation string) (*Config, error) {
   433  	file, err := os.Open(configLocation)
   434  	if err != nil {
   435  		return nil, errors.WithMessagef(err, "could not open config file: %s", configLocation)
   436  	}
   437  	defer func() {
   438  		closeErr := file.Close()
   439  		if err == nil {
   440  			err = closeErr
   441  		}
   442  	}()
   443  
   444  	configDecoder := json.NewDecoder(file)
   445  	config := &Config{}
   446  	err = configDecoder.Decode(config)
   447  	return config, errors.WithMessage(err, "could decode config file:")
   448  }
   449  
   450  // Close releases resources used by the Context and unloads the PKCS #11 library if there are no other
   451  // Contexts using it. Close blocks until existing operations have finished. A closed Context cannot be reused.
   452  func (c *Context) Close() error {
   453  
   454  	// Take lock on the reference count
   455  	refCountMutex.Lock()
   456  	defer refCountMutex.Unlock()
   457  
   458  	c.closed.Set(true)
   459  
   460  	// Block until all resources returned to pool
   461  	c.pool.Close()
   462  
   463  	// Close our long-term session. We ignore any returned error,
   464  	// since we plan to kill our collection to the library anyway.
   465  	_ = c.ctx.CloseSession(c.persistentSession)
   466  
   467  	count, found := refCount[c.cfg.Path]
   468  	if !found || count == 0 {
   469  		// We have somehow lost track of reference counts, this is very bad
   470  		panic("invalid reference count for PKCS#11 library")
   471  	}
   472  
   473  	refCount[c.cfg.Path] = count - 1
   474  
   475  	// If we were the last Context, finalize the library
   476  	if count == 1 {
   477  		err := c.ctx.Finalize()
   478  		if err != nil {
   479  			return err
   480  		}
   481  	}
   482  
   483  	c.ctx.Destroy()
   484  	return nil
   485  }
   486  

View as plain text