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