P11ex.Session (p11ex v0.1.1)

This module is a GenServer that manages a PKCS#11 session. A session is used to interact with a token, e.g. generate keys, encrypt data, decrypt data, etc. Sessions are created by the P11ex.Module module using the open_session/3 function. Depending on the type of token multiple for the same token can be opened in parallel (e.g. if the token is a network HSM). One session can only be used in a serialised way, i.e. only one operation can be performed at a time. Additionally, sessions have a state. This state can be non-persistent keys associated with the session or the state of an encryption or decryption operation.

Technically, most PKCS#11 functions require to login to the token first using a PIN. That is, the login state is not connected to a particular session opened on a token. Many tokens raise an :cka_already_logged_in error if a PIN is provided for a session that is already logged in. The P11ex.Session module tries to make handling of the login state more easy by tracking the login state of the session. That is, only the first call to login/3 will actually login to the token. Subsequent calls to login/3 will check if the session is already logged in and skip the login if so.

Usage

The following examples show how to log into a token and create a new session.

{:ok, module} = P11ex.Module.start_link("/usr/lib/softhsm/libsofthsm2.so")
{:ok, slot} = P11ex.Module.find_slot_by_tokenlabel("Token_0")

{:ok, session} = P11ex.Session.start_link(module: module, slot_id: slot.slot_id, flags: [:rw_session])
:ok = P11ex.Session.login(session, :user, "1234")

Summary

Functions

Returns a specification to start this module under a supervisor.

Decrypt data using the specified mechanism and key in a single call. See P11ex.Lib.encrypt_init/3 on how to select a decryption mechanism and set its parameters. Consider using P11ex.Session.decrypt_init/3, P11ex.Session.decrypt_update/2, and P11ex.Session.decrypt_final/1 if you want to decrypt data in chunks.

Finalize the decryption operation that is in progress for this session (see P11ex.Session.decrypt_init/3).

Initialize a decryption operation involving the specified mechanism and key. Many mechanisms require additional parameters. See P11ex.Lib.encrypt_init/3 for more information on mechanisms and their parameters.

Provide a chunk of ciphertext data to the decryption operation that is in progress for this session (see P11ex.Session.decrypt_init/3).

Destroy the object specified by object.

Get the digest of the data provided to the digest operation. The session must be in the :digest state, so this function must be called after digest_init/2.

Finalize the digest operation. The session must be in the :digest state, so this function must be called after digest_init/2 and digest_update/2. If the operation fails, the session's current operation is reset. The function returns the digest.

Initialize a digest operation involving the specified mechanism. The session's current operation is set to :digest. This operation can be finalized by calling digest_final/1 or digest/1. Also, a failure of digest_update/2 will end this state.

Provide data to the digest operation. The session must be in the :digest state, so this function must be called after digest_init/2. Call this function repeatedly with chunks of data until all data has been provided. If the operation fails, the session's current operation is reset.

Encrypt data using the specified mechanism and key in a single call. See P11ex.Lib.encrypt/4 on how to select an encryption mechanism and set its parameters.

Finalize the encryption operation that is in progress for this session.

Initialize an encryption operation involving the specified mechanism and key. Use P11ex.Session.encrypt_update/2 and P11ex.Session.encrypt_final/1 to provide the data to encrypt and produce the ciphertext chunks. Note that only one encryption operation can be active at a time for a given session. Consider using P11ex.Session.encrypt/4 if you want to encrypt data in a single call and the data is not too large.

Provide a chunk of plaintext data to the encryption operation that is in progress for this session (see P11ex.Session.encrypt_init/3).

Find objects in the session. The attributes is a list of tuples where the first element is the attribute type and the second element is the value to match. The max_hits is the maximum number of hits to return. The result is a list of P11ex.Lib.ObjectHandle.t() objects.

Generate a symmetric key in the session. The key is generated according to the specified mechanism and the key_template. The key_template is a list of attributes that will be used to generate the key. The function returns a handle to the generated key.

Generate a key pair in the session. The key pair is generated according to the specified mechanism and the pub_key_template and priv_key_template.

Generate random data using the token's RNG.

Get information about the session. The result is a map with the following keys

Initialize the session GenServer. This requires the :module (a P11ex.Lib.ModuleHandle.t()) and the :slot_id (an integer) of the slot the session is opened on. Additionally, the :flags keyword argument can be used to pass additional flags to the open_session/3 function.

Log in to the session. The user_type must be either :user or :so. Provide the user's pin for authentication. The P11ex.Session module checks if the session is already logged in and skips the login if so, preventing :cka_already_logged_in errors.

Logout from the session.

Read the attributes of the object identified by object handle object. The type_hint is an optional and can be used to specify the attributes to read. The default is to read the common attributes (e.g. :cka_class, :cka_id). See P11ex.Lib.ObjectAttributes for commonly used attribute sets.

Sign or MAC data. The session must be in the :sign state, so this function must be called after sign_init/3. If the operation fails, the session's current operation is reset. The function returns the signature or MAC.

Finalize the signing operation or MAC computation. The session must be in the :sign state, so this function must be called after sign_init/3 and sign_update/2. If the operation fails, the session's current operation is reset. The function returns the signature or MAC.

Initialize a signing operation or MAC computation involving the specified mechanism and key. The key type must be suitable for the specified mechanism. If the initialization is successful, the session's current operation is set to :sign. This operation can be finalized by calling sign_final/1 or sign/2. Also, a failure of sign_update/2 will end this state.

Provide data to the signing operation or MAC computation. The session must be in the :sign state, so this function must be called after sign_init/3. Call this function repeatedly with chunks of data until all data has been provided. If the operation fails, the session's current operation is reset.

Verify a signature or MAC. The session must be in the :verify state, so this function must be called after verify_init/3. If the operation fails, the session's current operation is reset.

Initialize a verification operation involving the specified mechanism and key. The operation verifies signatures or MACs, depending the mechanism. Some mechanisms require additional parameters. See P11ex.Lib.sign_init/3 for more information on mechanisms and their parameters.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

decrypt(server \\ __MODULE__, mechanism, key, data)

@spec decrypt(
  server :: GenServer.server(),
  mechanism :: P11ex.Lib.mechanism_instance(),
  key :: P11ex.Lib.ObjectHandle.t(),
  data :: binary()
) :: {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Decrypt data using the specified mechanism and key in a single call. See P11ex.Lib.encrypt_init/3 on how to select a decryption mechanism and set its parameters. Consider using P11ex.Session.decrypt_init/3, P11ex.Session.decrypt_update/2, and P11ex.Session.decrypt_final/1 if you want to decrypt data in chunks.

Example: Decrypt data in a single call

{:ok, plaintext} = P11ex.Session.decrypt(session, {:ckm_aes_gcm, %{iv: iv, tag_bits: 128}}, key, ciphertext)

decrypt_final(server \\ __MODULE__)

@spec decrypt_final(server :: GenServer.server()) ::
  {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Finalize the decryption operation that is in progress for this session (see P11ex.Session.decrypt_init/3).

decrypt_init(server \\ __MODULE__, mechanism, key)

@spec decrypt_init(
  server :: GenServer.server(),
  mechanism :: P11ex.Lib.mechanism_instance(),
  key :: P11ex.Lib.ObjectHandle.t()
) :: :ok | {:error, atom()} | {:error, atom(), any()}

Initialize a decryption operation involving the specified mechanism and key. Many mechanisms require additional parameters. See P11ex.Lib.encrypt_init/3 for more information on mechanisms and their parameters.

The function returns :ok if the operation is initialized successfully. The session is now in decryption mode and no other operations can be active at the same time. The ciphertext can be provided in chunks using P11ex.Session.decrypt_update/2 and P11ex.Session.decrypt_final/1.

Consider using P11ex.Session.decrypt/4 if you want to decrypt data in a single call.

Example: Decrypt data in chunks

:ok = P11ex.Session.decrypt_init(session, {:ckm_aes_gcm, %{iv: iv, tag_bits: 128}})
{:ok, plain_chunk1} = P11ex.Session.decrypt_update(session, cipher_chunk1)
{:ok, plain_chunk2} = P11ex.Session.decrypt_update(session, cipher_chunk2)
{:ok, plain_chunk3} = P11ex.Session.decrypt_final(session)
plaintext = plain_chunk1 <> plain_chunk2 <> plain_chunk3

decrypt_update(server \\ __MODULE__, data)

@spec decrypt_update(
  server :: GenServer.server(),
  data :: binary()
) :: {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Provide a chunk of ciphertext data to the decryption operation that is in progress for this session (see P11ex.Session.decrypt_init/3).

destroy_object(server \\ __MODULE__, object)

@spec destroy_object(server :: GenServer.server(), P11ex.Lib.ObjectHandle.t()) ::
  :ok | {:error, atom()} | {:error, atom(), any()}

Destroy the object specified by object.

digest(server \\ __MODULE__, data)

@spec digest(server :: GenServer.server(), binary()) ::
  {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Get the digest of the data provided to the digest operation. The session must be in the :digest state, so this function must be called after digest_init/2.

digest_final(server \\ __MODULE__)

@spec digest_final(server :: GenServer.server()) ::
  {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Finalize the digest operation. The session must be in the :digest state, so this function must be called after digest_init/2 and digest_update/2. If the operation fails, the session's current operation is reset. The function returns the digest.

digest_init(server \\ __MODULE__, mechanism)

@spec digest_init(GenServer.server(), P11ex.Lib.mechanism_instance()) ::
  :ok | {:error, atom()} | {:error, atom(), any()}

Initialize a digest operation involving the specified mechanism. The session's current operation is set to :digest. This operation can be finalized by calling digest_final/1 or digest/1. Also, a failure of digest_update/2 will end this state.

Example: Digest computation in chunks

:ok = P11ex.Session.digest_init(session, {:ckm_sha256})
:ok = P11ex.Session.digest_update(session, data1)
:ok = P11ex.Session.digest_update(session, data2)
{:ok, digest} = P11ex.Session.digest_final(session)

Example: Digest computation in one go

:ok = P11ex.Session.digest_init(session, {:ckm_sha256})
{:ok, digest} = P11ex.Session.digest(session, data)

digest_update(server \\ __MODULE__, data)

@spec digest_update(server :: GenServer.server(), binary()) ::
  :ok | {:error, atom()} | {:error, atom(), any()}

Provide data to the digest operation. The session must be in the :digest state, so this function must be called after digest_init/2. Call this function repeatedly with chunks of data until all data has been provided. If the operation fails, the session's current operation is reset.

encrypt(server \\ __MODULE__, mechanism, key, data)

@spec encrypt(
  server :: GenServer.server(),
  mechanism :: P11ex.Lib.mechanism_instance(),
  key :: P11ex.Lib.ObjectHandle.t(),
  data :: binary()
) :: {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Encrypt data using the specified mechanism and key in a single call. See P11ex.Lib.encrypt/4 on how to select an encryption mechanism and set its parameters.

Example: Encrypt data in a single call

iv = :crypto.strong_rand_bytes(12)
{:ok, ciphertext} = P11ex.Session.encrypt(session, {:ckm_aes_gcm, %{iv: iv, tag_bits: 128}}, key, plaintext)

encrypt_final(server \\ __MODULE__)

Finalize the encryption operation that is in progress for this session.

encrypt_init(server \\ __MODULE__, mechanism, key)

@spec encrypt_init(
  server :: GenServer.server(),
  P11ex.Lib.mechanism_instance(),
  P11ex.Lib.ObjectHandle.t()
) :: :ok | {:error, atom()} | {:error, atom(), any()}

Initialize an encryption operation involving the specified mechanism and key. Use P11ex.Session.encrypt_update/2 and P11ex.Session.encrypt_final/1 to provide the data to encrypt and produce the ciphertext chunks. Note that only one encryption operation can be active at a time for a given session. Consider using P11ex.Session.encrypt/4 if you want to encrypt data in a single call and the data is not too large.

The function returns :ok if the operation is initialized successfully. That is, no other operations (e.g. decryption, signing, etc.) can be active at the same time.

Many mechanisms require additional parameters. See P11ex.Lib.encrypt_init/3 for more information on mechanisms and their parameters.

Example: Encrypt data in chunks

iv = :crypto.strong_rand_bytes(12)
:ok = P11ex.Session.encrypt_init(session, {:ckm_aes_gcm, %{iv: iv, tag_bits: 128}})
{:ok, cipher_chunk1} = P11ex.Session.encrypt_update(session, plain_chunk1)
{:ok, cipher_chunk2} = P11ex.Session.encrypt_update(session, plain_chunk2)
{:ok, cipher_chunk3} = P11ex.Session.encrypt_final(session)
ciphertext = cipher_chunk1 <> cipher_chunk2 <> cipher_chunk3

encrypt_update(server \\ __MODULE__, data)

@spec encrypt_update(
  server :: GenServer.server(),
  data :: binary()
) :: {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Provide a chunk of plaintext data to the encryption operation that is in progress for this session (see P11ex.Session.encrypt_init/3).

find_objects(server \\ __MODULE__, attributes, max_hits)

@spec find_objects(
  server :: GenServer.server(),
  attributes :: [{atom(), any()}],
  max_hits :: non_neg_integer()
) :: {:ok, [P11ex.Lib.ObjectHandle.t()]} | {:error, atom()}

Find objects in the session. The attributes is a list of tuples where the first element is the attribute type and the second element is the value to match. The max_hits is the maximum number of hits to return. The result is a list of P11ex.Lib.ObjectHandle.t() objects.

This example shows how to find all secret keys in the session.

  {:ok, objects} = P11ex.Session.find_objects(session, [{:cka_class, :cko_secret_key}], 10)

To find all secret keys with the label "my_key" use the following:

  {:ok, objects} = P11ex.Session.find_objects(session, [{:cka_class, :cko_secret_key}, {:cka_label, "my_key"}], 10)

generate_key(server \\ __MODULE__, mechanism, key_template)

Generate a symmetric key in the session. The key is generated according to the specified mechanism and the key_template. The key_template is a list of attributes that will be used to generate the key. The function returns a handle to the generated key.

See P11ex.Lib.generate_key/3 for more information on mechanisms and their parameters.

generate_key_pair(server \\ __MODULE__, mechanism, pub_key_template, priv_key_template)

@spec generate_key_pair(
  server :: GenServer.server(),
  P11ex.Lib.mechanism_instance(),
  P11ex.Lib.attributes(),
  P11ex.Lib.attributes()
) ::
  {:ok, {P11ex.Lib.ObjectHandle.t(), P11ex.Lib.ObjectHandle.t()}}
  | {:error, atom()}
  | {:error, atom(), any()}

Generate a key pair in the session. The key pair is generated according to the specified mechanism and the pub_key_template and priv_key_template.

See P11ex.Lib.generate_key_pair/4 for more information on mechanisms and their parameters.

generate_random(server \\ __MODULE__, len)

@spec generate_random(server :: GenServer.server(), len :: non_neg_integer()) ::
  {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}
@spec generate_random(server :: GenServer.server(), len :: non_neg_integer()) ::
  {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Generate random data using the token's RNG.

info(server \\ __MODULE__)

@spec info(server :: GenServer.server()) :: {:ok, map()} | {:error, atom()}

Get information about the session. The result is a map with the following keys:

  • :slot_id - the slot ID of the session
  • :state - the state of the session
  • :flags - the flags of the session
  • :device_error - the device error of the session

init(args)

@spec init(Keyword.t()) :: {:ok, map()} | {:error, atom()}

Initialize the session GenServer. This requires the :module (a P11ex.Lib.ModuleHandle.t()) and the :slot_id (an integer) of the slot the session is opened on. Additionally, the :flags keyword argument can be used to pass additional flags to the open_session/3 function.

login(server \\ __MODULE__, user_type, pin)

@spec login(
  server :: GenServer.server(),
  user_type :: {:user, :so},
  pin :: String.t()
) ::
  :ok | {:error, atom()}

Log in to the session. The user_type must be either :user or :so. Provide the user's pin for authentication. The P11ex.Session module checks if the session is already logged in and skips the login if so, preventing :cka_already_logged_in errors.

logout(server \\ __MODULE__)

@spec logout(server :: GenServer.server()) :: :ok | {:error, atom()}

Logout from the session.

read_object(server \\ __MODULE__, object, type_hint \\ nil)

@spec read_object(
  server :: GenServer.server(),
  object :: P11ex.Lib.ObjectHandle.t(),
  type_hint :: [atom()] | nil
) :: {:ok, map(), [atom()]} | {:error, atom()} | {:error, atom(), any()}

Read the attributes of the object identified by object handle object. The type_hint is an optional and can be used to specify the attributes to read. The default is to read the common attributes (e.g. :cka_class, :cka_id). See P11ex.Lib.ObjectAttributes for commonly used attribute sets.

The function returns a map of the successfully read attributes. The attributes that could not be read (but were requested through the type_hint) are returned as the second element of the tuple. Reasons for not retrieving the attributes are that the attributes are not set or are sensitive.

sign(server \\ __MODULE__, data)

@spec sign(server :: GenServer.server(), binary()) ::
  {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Sign or MAC data. The session must be in the :sign state, so this function must be called after sign_init/3. If the operation fails, the session's current operation is reset. The function returns the signature or MAC.

sign_final(server \\ __MODULE__)

@spec sign_final(server :: GenServer.server()) ::
  {:ok, binary()} | {:error, atom()} | {:error, atom(), any()}

Finalize the signing operation or MAC computation. The session must be in the :sign state, so this function must be called after sign_init/3 and sign_update/2. If the operation fails, the session's current operation is reset. The function returns the signature or MAC.

sign_init(server \\ __MODULE__, mechanism, key)

@spec sign_init(
  server :: GenServer.server(),
  P11ex.Lib.mechanism_instance(),
  P11ex.Lib.ObjectHandle.t()
) :: :ok | {:error, atom()} | {:error, atom(), any()}

Initialize a signing operation or MAC computation involving the specified mechanism and key. The key type must be suitable for the specified mechanism. If the initialization is successful, the session's current operation is set to :sign. This operation can be finalized by calling sign_final/1 or sign/2. Also, a failure of sign_update/2 will end this state.

See P11ex.Lib.sign_init/3 for more information on mechanisms and their parameters.

sign_update(server \\ __MODULE__, data)

@spec sign_update(server :: GenServer.server(), binary()) ::
  :ok | {:error, atom()} | {:error, atom(), any()}

Provide data to the signing operation or MAC computation. The session must be in the :sign state, so this function must be called after sign_init/3. Call this function repeatedly with chunks of data until all data has been provided. If the operation fails, the session's current operation is reset.

start_link(args)

verify(server \\ __MODULE__, data, signature)

@spec verify(server :: GenServer.server(), binary(), binary()) ::
  :ok | {:error, atom()} | {:error, atom(), any()}

Verify a signature or MAC. The session must be in the :verify state, so this function must be called after verify_init/3. If the operation fails, the session's current operation is reset.

The operation return :ok if the signature (or MAC) is valid. Otherwise, it returns an error. Typically, the error reason is :ckr_signature_invalid or :ckr_signature_len_range.

verify_init(server \\ __MODULE__, mechanism, key)

@spec verify_init(
  server :: GenServer.server(),
  P11ex.Lib.mechanism_instance(),
  P11ex.Lib.ObjectHandle.t()
) :: :ok | {:error, atom()} | {:error, atom(), any()}

Initialize a verification operation involving the specified mechanism and key. The operation verifies signatures or MACs, depending the mechanism. Some mechanisms require additional parameters. See P11ex.Lib.sign_init/3 for more information on mechanisms and their parameters.

If successful, the session is in verification mode and no other operations can be active at the same time.