EBICS Key Store
gln_key_store_t is the EBICS key-material abstraction. It lets an embedder
keep subscriber keys and bank-key staging in a file, secure enclave, HSM,
PKCS#11 token, cloud KMS, or application database while exposing one C vtable
to Galanthus.
Functions
GLN_API gln_status_t GLN_CALL gln_create_key_store(
const gln_key_store_vtable_t* in_vtable,
gln_key_store_t** out_store,
gln_error_t* out_error);
GLN_API gln_status_t GLN_CALL gln_create_file_key_store(
const char* in_key_blob_path,
const char* in_sidecar_key_path_or_null,
gln_key_store_t** out_store,
gln_error_t* out_error);
GLN_API void GLN_CALL gln_destroy_key_store(gln_key_store_t* in_store);
gln_open_ebics_backend consumes the key store:
GLN_API gln_status_t GLN_CALL gln_open_ebics_backend(
const gln_ebics_config_t* in_config,
gln_key_store_t* in_key_store,
gln_backend_t** out_backend,
gln_error_t* out_error);
The backend retains the underlying key-store implementation during open, so the
C key-store handle itself can be destroyed after a successful EBICS backend
open. The object referenced by a custom vtable's user_data remains
host-owned; keep it alive until no live key-store handle or retained EBICS
backend can invoke the callbacks.
The bundled file-backed implementation stores an encrypted key blob at
in_key_blob_path. in_sidecar_key_path_or_null selects the sidecar key path;
pass NULL for the implementation default. If the helper is unavailable in the
current build or platform, it returns GLN_ERR_NOT_SUPPORTED.
Vtable
typedef struct {
gln_status_t (GLN_CALL *generate_keys)(void* in_user_data);
gln_status_t (GLN_CALL *has_keys)(
void* in_user_data,
int* out_has_keys);
gln_status_t (GLN_CALL *authentication_public_key_json)(
void* in_user_data,
char** out_json);
gln_status_t (GLN_CALL *encryption_public_key_json)(
void* in_user_data,
char** out_json);
gln_status_t (GLN_CALL *signature_public_key_json)(
void* in_user_data,
char** out_json);
gln_status_t (GLN_CALL *sign_authentication)(
void* in_user_data,
const uint8_t* in_digest,
size_t in_digest_len,
uint8_t** out_signature,
size_t* out_len);
gln_status_t (GLN_CALL *sign_order)(
void* in_user_data,
const uint8_t* in_digest,
size_t in_digest_len,
uint8_t** out_signature,
size_t* out_len);
gln_status_t (GLN_CALL *decrypt_transaction_key)(
void* in_user_data,
const uint8_t* in_encrypted_key,
size_t in_encrypted_key_len,
uint8_t** out_key,
size_t* out_len);
gln_status_t (GLN_CALL *authentication_certificate_der)(
void* in_user_data,
uint8_t** out_data,
size_t* out_len);
gln_status_t (GLN_CALL *encryption_certificate_der)(
void* in_user_data,
uint8_t** out_data,
size_t* out_len);
gln_status_t (GLN_CALL *signature_certificate_der)(
void* in_user_data,
uint8_t** out_data,
size_t* out_len);
gln_status_t (GLN_CALL *store_bank_keys_json)(
void* in_user_data,
const char* in_bank_keys_json);
gln_status_t (GLN_CALL *load_bank_keys_json)(
void* in_user_data,
char** out_bank_keys_json);
gln_status_t (GLN_CALL *store_pending_bank_keys_json)(
void* in_user_data,
const char* in_bank_keys_json);
gln_status_t (GLN_CALL *load_pending_bank_keys_json)(
void* in_user_data,
char** out_bank_keys_json);
gln_status_t (GLN_CALL *promote_pending_bank_keys_json)(
void* in_user_data,
char** out_bank_keys_json);
gln_status_t (GLN_CALL *reset_bank_keys)(void* in_user_data);
gln_status_t (GLN_CALL *clear_pending_bank_keys)(void* in_user_data);
gln_status_t (GLN_CALL *reset_client_keys)(void* in_user_data);
void (GLN_CALL *free_buffer)(void* in_user_data, void* in_buffer);
void* user_data;
} gln_key_store_vtable_t;
Every function pointer is required. user_data may be NULL.
gln_create_key_store returns GLN_ERR_INVALID_ARG when the vtable pointer,
output slot, or a required callback is missing.
Callback Groups
Client key lifecycle:
generate_keyscreates the authentication, encryption, and signature key material if it is not already present. Implement it idempotently where the backing store allows that.has_keyswrites1to*out_has_keysonly when all required client keys are present; otherwise write0.reset_client_keyserases the local client key material.
Public material:
authentication_public_key_json,encryption_public_key_json, andsignature_public_key_jsonreturn freshly allocated JSON strings.authentication_certificate_der,encryption_certificate_der, andsignature_certificate_derreturn freshly allocated DER byte buffers and lengths.
Cryptographic operations:
sign_authenticationsignsin_digest[0..in_digest_len)with the authentication key and writes a freshly allocated signature buffer.sign_ordersigns with the order-signing key.decrypt_transaction_keydecryptsin_encrypted_keywith the encryption private key and writes a freshly allocated transaction key buffer.
Bank key staging:
store_bank_keys_jsonpersists trusted bank keys. The input string is library-owned and valid only for the callback.load_bank_keys_jsonreturns trusted bank keys orGLN_ERR_NOT_FOUND.store_pending_bank_keys_jsonpersists downloaded but not-yet-trusted bank keys.load_pending_bank_keys_jsonreturns pending bank keys orGLN_ERR_NOT_FOUND.promote_pending_bank_keys_jsonatomically promotes pending keys to trusted keys and returns the trusted JSON, or returnsGLN_ERR_NOT_FOUNDif there is nothing to promote.reset_bank_keysclears trusted bank keys.clear_pending_bank_keysclears only pending bank keys.
Bookkeeping:
free_bufferreleases every non-null buffer produced by one of the output callbacks above. It receives bothchar*strings anduint8_t*byte buffers asvoid*.user_datais passed unchanged to each callback.
Ownership Rules
Every output callback allocates with the embedder's allocator and returns the
fresh pointer through an out_ parameter. The library reads that buffer and
later calls free_buffer(in_user_data, pointer) exactly once.
Every const input pointer is borrowed from the library for the duration of
the callback. Do not retain it. Copy it into the backing store if it must live
after the callback returns.
This means the embedder controls allocation for key-store outputs, but also
owns matching deallocation through free_buffer. Cross-allocator release is a
process-heap bug.
Minimal Custom Shape
struct hsm_key_store {
void* session;
const char* trusted_bank_keys_path;
const char* pending_bank_keys_path;
};
static gln_status_t hsm_sign_authentication(
void* in_user_data,
const uint8_t* in_digest,
size_t in_digest_len,
uint8_t** out_signature,
size_t* out_len)
{
struct hsm_key_store* store = (struct hsm_key_store*)in_user_data;
(void)store;
(void)in_digest;
(void)in_digest_len;
/* Allocate *out_signature with malloc or the store allocator. */
*out_signature = NULL;
*out_len = 0;
return GLN_OK;
}
static gln_status_t hsm_load_bank_keys_json(
void* in_user_data,
char** out_bank_keys_json)
{
struct hsm_key_store* store = (struct hsm_key_store*)in_user_data;
(void)store;
/* Return GLN_ERR_NOT_FOUND if no trusted bank keys exist yet. */
*out_bank_keys_json = NULL;
return GLN_ERR_NOT_FOUND;
}
static void hsm_free_buffer(void* in_user_data, void* in_buffer)
{
(void)in_user_data;
free(in_buffer);
}
The real vtable must fill every callback, not only the callbacks shown above.
See Also
- Conventions - status, error, and ownership model.
- Stores - the state and continuation vtable pattern.
- Troubleshooting - key-store allocator and platform issues.