#ifndef CRYPT_MULTIKEY_H
#define CRYPT_MULTIKEY_H

#include <SecretBuffer.h>

/* cmk_pkey may be a struct in the future, but for now that struct would only have one field,
 * so just declare it as a pointer-to-pointer and then it doesn't need a second allocation
 * when stored in MAGIC.  The MAGIC's pointer *is* the "struct" */
typedef EVP_PKEY* cmk_pkey;

/* Store a text-notation UUID / GUID into buf_sv, enlarging it as needed. */
extern SV * cmk_generate_uuid_v4(SV *buf_sv);

/* Return a pointer-to-pointer to EVP_PKEY (which is just a typecast of the
 * MAGIC's mg_ptr) adding the magic if 'autocreate' is true.
 */
#define CMK_MAGIC_AUTOCREATE 1
#define CMK_MAGIC_OR_DIE     2
#define CMK_MAGIC_UNDEF_OK   4
extern cmk_pkey* cmk_pkey_from_magic(SV *sv, int flags);

/* Handy get/set accessors for public and private key which check the type of key actually
 * present in MAGIc and croak with useful error messages. */
extern cmk_pkey* cmk_get_pubkey(SV *objref);
extern cmk_pkey* cmk_get_privkey(SV *objref);

extern void cmk_pkey_get_algorithm_name(cmk_pkey *pk, SV *out);

/* Create a new public/private keypair, store it into MAGIC on the ::PKey object,
 */
extern void cmk_pkey_keygen(cmk_pkey *pk, const char *type_and_params);
extern void cmk_pkey_keygen_params(cmk_pkey *pk, const char *type, const char **params, int param_count);

extern bool cmk_pkey_has_public(cmk_pkey *pkey);
extern bool cmk_pkey_has_private(cmk_pkey *pkey);

extern void cmk_pkey_dup(cmk_pkey *pk, cmk_pkey *orig);

/* Load the public key from the buffer and store it into MAGIC on the ::PKey object.
 * The buffer should contain ASN.1 DER bytes of RFC5280's SubjectPublicKeyInfo structure.
 */
extern void cmk_pkey_import_spki(cmk_pkey *pk, const U8 *buf, STRLEN buf_len);

/* Save the public key from ::PKey MAGIC into the supplied buffer.
 * The buffer receives ASN.1 DER bytes of RFC5280's SubjectPublicKeyInfo structure.
 */
extern void cmk_pkey_export_spki(cmk_pkey *pk, SV *buf_out);

/* Load the private key from the buffer and store it into MAGIC on the ::PKey object.
 * The buffer should contain ASN.1 DER bytes of PKCS#8 which may be storing an encrypted private
 * key that requires a password to decrypt.  It also stores the PDK iterations and other
 * encryption parameters, so only the original password is required.
 * Returns false on incorrect password, and dies on any other type of error.
 */
extern bool cmk_pkey_import_pkcs8(cmk_pkey *pk, const U8 *buf, STRLEN buf_len, const char *pw, STRLEN pw_len);

/* Save the private key from ::PKey MAGIC into the supplied buffer.
 * The pasword parameter is optional, and if supplied results in an encrypted private key.
 */
extern void cmk_pkey_export_pkcs8(cmk_pkey *pk, const char *pass, STRLEN pw_len, int kdf_iter, SV *buf_out);

/* Import a key from OpenSSH public key format (having already decoded the base64).
 */
extern void cmk_pkey_import_openssh_pubkey(cmk_pkey *pk, const U8 *buf, STRLEN buf_len);

/* Import a key from OpenSSH private key format (having already decoded the base64).
 * This can fall back to loading a public key if the key is encrypted and the password is NULL,
 * because the public keys are stored in the unencrypted half of the file.
 * If the password is provided and is incorrect, this return false instead of falling back to
 * loadingthe public key.
 */
extern bool cmk_pkey_import_openssh_privkey(cmk_pkey *pk, const U8 *buf, STRLEN buf_len, const char *pw, STRLEN pw_len);

/* Generate symmetric key material from the public key and store the public data in tumbler_out.
 * This appends bytes to skey_buf, so multiple keys can concatenate to the same buffer
 * before running it through HKDF.
 */
extern void cmk_pkey_generate_key_material(cmk_pkey *pubkey, HV *tumbler_out, secret_buffer *skey_buf);

/* Re-create the symmetric key material from the parameters in 'tumbler' using the private key.
 * This appends bytes to skey_buf, so multiple keys can concatenate to the same buffer
 * before running it through HKDF.
 */
extern void cmk_pkey_recreate_key_material(cmk_pkey *privkey, HV *tumbler, secret_buffer *skey_buf);

/* Run SHA-256 on a list of SV, which may be scalars or SecretBuffer objects or Spans
 * SHA-256 always generates 32 bytes, so write it into a fixed-length array.
 */
extern void cmk_sha256(U8 dest[32], SV **input, size_t n_items);

/* Run HMAC-SHA256 on a list of SV */
extern void cmk_hmac_sha256(U8 dest[32], const U8 *key, size_t key_len, SV **input, size_t n_items);

/* This runs HKDF on the key material to generate an AES key.
 * It reads the cipher from the encryption parameters to know how large to make the key.
 * It also creates a random salt and stores that into the encryption parameters.
 * It uses a default HKDF "info" that can be overridden in the encryption params.
 */
secret_buffer *cmk_hkdf(HV *params, secret_buffer *key_material);

/* Return boolean whether support for Yubico OTP was compiled into Crypt::MultiKey */
extern bool cmk_yubico_otp_available();

/* Run rough equivalent of the 'ykinfo' command over the Yubico OTP API */
extern HV *cmk_yubico_otp_ykinfo(int fd);

/* Run rough equivalent of the 'ykchalresp' command over the Yubico OTP API */
extern int cmk_yubico_otp_ykchalresp(int fd, int slot, int timeout_ms, SV *chal, secret_buffer *resp);

/* Perform symmetric encryption using the supplied AES key and input secret.
 * Ciphertext is written into ciphertext_out, which may be a scalar or SecretBuffer object.
 */
extern void cmk_symmetric_encrypt(HV *params, secret_buffer *aes_key, const U8 *secret, size_t secret_len, SV *ciphertext_out);

/* Perform symmetric decryption using the supplied AES key and ciphertext bytes and parameters in
 * `params`, storing the original secret into secret_out.
 */
extern void cmk_symmetric_decrypt(HV *params, secret_buffer *aes_key, const U8 *ciphertext, size_t ciphertext_len, secret_buffer *secret_out);

#endif /* define CRYPT_MULTIKEY_H */
