NAME

Crypt::MultiKey::LockMechanism - Implementation of a key-wrapping-scheme used by Coffer and Vault

DESCRIPTION

This class implements the "Multi Key" behavior of Coffer and Vault. The basic structure of the LockMechanism is an array of "locks", each of which has an array of "tumblers".

# conceptually:
[
  [ $key1, $key2 ],        # lock 0
  [ $key3 ],               # lock 1
  [ $key4, $key5, $key6 ]  # lock 2
]

Each lock independently provides access to the primary symmetric key secret. A lock can be opened by inserting one PKey (with private key available) into each of its tumblers, and calling "unlock".

Since each key is a public/private keypair, you can also encrypt a new primary symmetric key secret when only the public halves of the PKeys are available. You can also take an existing LockMechanism and add new locks to it so long as you have the primary symmetric key decrypted.

This class is only the lock mechanism, and does not handle serialization.

CONSTRUCTORS

new

Basic constructor. Attributes locks and primary_skey can be provided.

ATTRIBUTES

primary_skey

A Crypt::SecretBuffer holding the symmetric key used to derive "cipher_skey" and "hmac_skey". This is the main secret that is encrypted and decrypted by the locks.

The LockMechanism is logically "locked" when locks are defined but primary_skey is not. A fresh, uninitialized object is not considered locked.

locked

Convenience accessor that returns true if locks exist and primary_skey is not defined.

initialized

Convenience accessor that returns true if primary_skey is defined, or if any "locks" are defined.

cipher_skey

A Crypt::SecretBuffer holding the symmetric key to be used for AES encryption of content.

hmac_skey

A Crypt::SecretBuffer holding the symmetric key to be used for authenticating metadata.

locks

An arrayref of lock definitions, each of which is sufficient to open the LockMechanism. Locks are created with "add_access" method to emphasize that each one adds access to the secret rather than restricting it.

Each lock is a key-wrapping-scheme that contains an encryption of the "primary_skey" using a symmetric key derived from the public half of a list of component public/private keypairs. During the creation process, one "tumbler" is generated for each public/private keypair such that the original symmetric key can only be recreated by engaging the tumbler with the private half of its key.

locks => [
  { cipher        => 'AES-256-GCM',
    ciphertext    => $encrypted_bytes,
    tumblers      => [
      { key_fingerprint    => $hashname_and_base64,
        ephemeral_pubkey   => $pubkey_bytes,
        rsa_key_ciphertext => $encrypted_bytes,
      },
      ...
    ],
  },
  ...
]

The private half must be present to open the tumbler, but only the public half is needed to create the tumbler. This means the primary_skey can be updated even when the private half of the keys are not available.

METHODS

generate_primary_skey

Generate a new "primary_skey". This can only be done when all locks (if any) have at least their public keys inserted.

add_access

$lockmech->add_access($key1, ... $keyN);

This creates a new "locks" entry, which provides a new way to access the Coffer/Vault independent of other locks. The new lock can be opened when the private half of all N keys are passed to the "unlock" method (or "insert_keys" method).

insert_keys

(\@complete_locks, \@incomplete_locks)= $lockmech->insert_keys(@pkeys);

When deserialized from a PEM file, the tumblers of the "locks" attribute reference keys by their SHA-256 fingerprint. Those need matched to PKey objects before "unlock" can run (though you can also provide the objects at that time). This method adds PKey objects to the tumblers so that they are already available when you call "unlock", very analogous to inserting a physical key into a keyhole but not turning it.

The references persist in the "locks" attribute, allowing you to call insert_keys multiple times if desired to populate additional tumblers. If a tumbler already contains a PKey object, it will be replaced by the new object in this list unless the previous included the private half and the new one lacks the private half.

The return value is a pair of arrayrefs, one with the locks which have all PKeys inserted, and the other of any locks still lacking a PKey object. This is unrelated to whether the PKey objects include their private half, needed for "unlock".

unlock

$lockmech->unlock($key1, ... $keyN);
# or if you used insert_keys,
$lockmech->unlock;

This attempts to find a lock which can be unlocked by this list of keys, or a subset of them. If found, the "primary_skey" attribute is set, after which decryption and encryption methods can be used. All keys provided as parameters must have the private halves available.

interactive_unlock

$bool= $lockmech->interactive_unlock(%options);

Shortcut for

Crypt::MultiKey::InteractiveUnlock
  ->new(target => $lockmech, %options)
  ->run

lock

Delete the "primary_skey" attribute and any attributes holding unencrypted secrets. If this mechanism is part of a Coffer or Vault, you should call the method of the same name on that object to purge any other unencrypted secrets that object might be holding.

VERSION

version 0.000_001

AUTHOR

Michael Conrad <mike@nrdvana.net>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Michael Conrad.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.