/*
 * FpSdcpDevice - A base class for SDCP enabled devices
 * Copyright (C) 2020 Benjamin Berg <bberg@redhat.com>
 * Copyright (C) 2025 Joshua Grisham <josh@joshuagrisham.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#pragma once

#include "fpi-device.h"
#include "fp-sdcp-device.h"
#include "tod/tod-macros.h"

#define SDCP_RANDOM_SIZE 32
#define SDCP_APPLICATION_SECRET_SIZE 32
#define SDCP_ENROLLMENT_ID_SIZE 32
#define SDCP_NONCE_SIZE 32
#define SDCP_MAC_SIZE 32
#define SDCP_DIGEST_SIZE 32           /* SHA256 */
#define SDCP_CURVE_FIELD_SIZE 32      /* P256 */
#define SDCP_PUBLIC_KEY_SIZE 65       /* 0x04||x||y */
#define SDCP_PRIVATE_KEY_SIZE 32      /* log_2(n)/8 */
#define SDCP_SIGNATURE_SIZE 64        /* r||s */
#define SDCP_ENCRYPTION_BLOCK_SIZE 16 /* AES */
#define SDCP_ENCRYPTION_KEY_SIZE 32   /* AES256 */


#define SDCP_DEFAULT_LABEL_MASTER_SECRET "master secret"
#define SDCP_DEFAULT_LABEL_APPLICATION_KEYS "application keys"
#define SDCP_DEFAULT_LABEL_CONNECT "connect"
#define SDCP_DEFAULT_LABEL_RECONNECT "reconnect"
#define SDCP_DEFAULT_LABEL_SAMPLE "sample"
#define SDCP_DEFAULT_LABEL_ENROLL "enroll"
#define SDCP_DEFAULT_LABEL_IDENTIFY "identify"

/**
 * FpiDeviceSdcpVerificationFlags:
 * @FPI_DEVICE_SDCP_VERIFICATION_IGNORE_CERTIFICATES: Skip validating the
 *   device's #FpiSdcpClaim.model_certificate against the SDCP truststore.
 *   SDCP truststore.
 *   It determines if the model certificate (cert_m) should be parsed and
 *   its trust chain validated as issued from Microsoft's well-known issuers
 * @FPI_DEVICE_SDCP_VERIFICATION_IGNORE_SIGNATURES: Skip validating the
 *   #FpiSdcpClaim.model_signature and #FpiSdcpClaim.device_signature.
 *   It determines if the model signature (s_m) and device signature (s_d)
 *   should be validated against the certificate and keys provided in the claim.
 *
 * @FPI_DEVICE_SDCP_VERIFICATION_CERTIFICATE_NO_CHECK_TIME: Skip validating the
 *   certificate's validity period.
 * @FPI_DEVICE_SDCP_VERIFICATION_CERTIFICATE_NON_X509_STRICT: Use a less strict
 *   validation approach for the certificate, allowing certain non-critical
 *   issues to be ignored.
 * @FPI_DEVICE_SDCP_VERIFICATION_CERTIFICATE_ALLOW_PARTIAL_CHAIN: Allow
 *   validation of the certificate even if the trust chain is incomplete.
 *
 * Bitfield of SDCP verification flags for a device.
 */
typedef enum {
  FPI_DEVICE_SDCP_VERIFICATION_IGNORE_CERTIFICATES = 1 << 0,
  FPI_DEVICE_SDCP_VERIFICATION_IGNORE_SIGNATURES = 1 << 1,

  FPI_DEVICE_SDCP_VERIFICATION_CERTIFICATE_NO_CHECK_TIME = 1 << 8,
  FPI_DEVICE_SDCP_VERIFICATION_CERTIFICATE_NON_X509_STRICT = 1 << 9,
  FPI_DEVICE_SDCP_VERIFICATION_CERTIFICATE_ALLOW_PARTIAL_CHAIN = 1 << 10,
} FpiDeviceSdcpVerificationFlags;

/**
 * FpiSdcpClaim:
 * @master_secret_label: HMAC label for master secret derivation, or %NULL to
 *   use %SDCP_DEFAULT_LABEL_MASTER_SECRET
 * @application_keys_label: HMAC label for application keys derivation, or %NULL
 *   to use %SDCP_DEFAULT_LABEL_APPLICATION_KEYS
 * @connect_label: HMAC label for connect operations, or %NULL to use
 *   %SDCP_DEFAULT_LABEL_CONNECT
 * @reconnect_label: HMAC label for reconnect operations, or %NULL to use
 *   %SDCP_DEFAULT_LABEL_RECONNECT
 * @enroll_label: HMAC label for enroll operations, or %NULL to use
 *   %SDCP_DEFAULT_LABEL_ENROLL
 * @identify_label: HMAC label for identify operations, or %NULL to use
 *   %SDCP_DEFAULT_LABEL_IDENTIFY
 * @sample_label: HMAC label for sample operations, or %NULL to use
 *   %SDCP_DEFAULT_LABEL_SAMPLE
 * @model_certificate: Microsoft-issued per-model certificate encoded in x509
 *   ASN.1 DER format (`cert_m`)
 * @device_public_key: The per-device ECDSA public key (`pk_d`)
 * @firmware_public_key: The ephemeral public key generated by the device
 *   firmware (`pk_f`)
 * @firmware_hash: Hash of the firmware and firmware public key (`h_f`)
 * @model_signature: Device public key signed by the model key (`s_m`)
 * @device_signature: Firmware hash and public key signed by the device private
 *   key (`s_d`)
 *
 * Structure to hold the claim as produced by the device during a secure
 * connect. See the SDCP specification for more details.
 *
 * Note all of these may simply be memory views into a larger #GBytes created
 * using g_bytes_new_from_bytes().
 */
typedef struct _FpiSdcpClaim
{
  /*< public >*/
  const char *master_secret_label;
  const char *application_keys_label;
  const char *connect_label;
  const char *reconnect_label;
  const char *enroll_label;
  const char *identify_label;
  const char *sample_label;

  GBytes     *model_certificate;   /* cert_m */
  GBytes     *device_public_key;   /* pk_d   */
  GBytes     *firmware_public_key; /* pk_f   */
  GBytes     *firmware_hash;       /* h_f    */
  GBytes     *model_signature;     /* s_m    */
  GBytes     *device_signature;    /* s_d    */

  /*< private >*/
  /* padding for future expansion */
  TOD_PADDING_ALIGNED (16, 0);
} FpiSdcpClaim;

GType          fpi_sdcp_claim_get_type (void) G_GNUC_CONST;

FpiSdcpClaim  *fpi_sdcp_claim_new (void);
FpiSdcpClaim  *fpi_sdcp_claim_copy (FpiSdcpClaim *other);
void           fpi_sdcp_claim_free (FpiSdcpClaim *claim);

G_DEFINE_AUTOPTR_CLEANUP_FUNC (FpiSdcpClaim, fpi_sdcp_claim_free)


/**
 * FpSdcpDeviceClass:
 * @verification_flags: Bitfield of #FpiDeviceSdcpVerificationFlags to control
 *   verification behavior.
 * @open: Open the device. Similar to #FpDeviceClass.open except that
 *   completion with fpi_sdcp_device_open_complete() will also take care of
 *   executing @connect and @reconnect as necessary.
 * @connect: Establish SDCP connection.
 * @reconnect: Perform a faster reconnect. Drivers do not need to provide this
 *   function. If reconnect fails, then a normal connect will be tried.
 * @list: List prints stored on the device. The driver must create a #GPtrArray
 *   of #GBytes with each enrollment ID stored on the device and use it to call
 *   fpi_sdcp_device_list_complete() in order to complete the operation.
 * @enroll: Start the enrollment procedure and capture all samples. The driver
 *   must report enrollment progress using fpi_device_enroll_progress(). It
 *   should also store available metadata about the print in device memory. The
 *   driver must call fpi_sdcp_device_enroll_commit() when all enrollment stages
 *   are complete and the print is ready to be commited to the device.
 * @enroll_commit: Commit the newly-enrolled print to the device memory using
 *   the passed id. id may be %NULL, in which case the driver must abort the
 *   enrollment process. id is owned by the base class and remains valid
 *   throughout the operation. On completion, the driver must call
 *   fpi_sdcp_device_enroll_commit_complete().
 * @identify: Start identification process. On completion, the driver must call
 *   fpi_sdcp_device_identify_complete(). To request the user to retry the
 *   fpi_sdcp_device_identify_retry() function is used.
 *
 * These are the main entry points for drivers implementing SDCP.
 *
 * Drivers *must* eventually call the corresponding function to finish the
 * operation.
 *
 * The following #FpDeviceClass entry points are also compatible and can be set
 * on the #FpDeviceClass if supported for a given device:
 * - #FpDeviceClass.probe
 * - #FpDeviceClass.close
 * - #FpDeviceClass.delete
 * - #FpDeviceClass.clear_storage
 * - #FpDeviceClass.cancel
 * - #FpDeviceClass.suspend
 * - #FpDeviceClass.resume
 *
 * Drivers *must* also handle cancellation properly for any long running
 * operation (i.e. any operation that requires capturing). It is entirely fine
 * to ignore cancellation requests for short operations (e.g. open/close).
 *
 * This API is solely intended for drivers. It is purely internal and neither
 * API nor ABI stable.
 */
struct _FpSdcpDeviceClass
{
  FpDeviceClass                  parent_class;

  FpiDeviceSdcpVerificationFlags verification_flags;

  void                           (*open)          (FpSdcpDevice *sdcp_device);
  void                           (*connect)       (FpSdcpDevice *sdcp_device);
  void                           (*reconnect)     (FpSdcpDevice *sdcp_device);
  void                           (*list)          (FpSdcpDevice *sdcp_device);
  void                           (*enroll)        (FpSdcpDevice *sdcp_device);
  void                           (*enroll_commit) (FpSdcpDevice *sdcp_device,
                                                   GBytes       *id);
  void                           (*identify)      (FpSdcpDevice *sdcp_device);

  /*< private >*/
  /* padding for future expansion */
  TOD_PADDING_ALIGNED (32, 0);
};

void fpi_sdcp_device_open_complete (FpSdcpDevice *self,
                                    GError       *error);

void fpi_sdcp_device_get_connect_data (FpSdcpDevice *self,
                                       GBytes      **host_random,
                                       GBytes      **host_public_key);
void fpi_sdcp_device_connect_complete (FpSdcpDevice *self,
                                       GBytes       *device_random,
                                       FpiSdcpClaim *claim,
                                       GBytes       *mac,
                                       GError       *error);

void fpi_sdcp_device_get_reconnect_data (FpSdcpDevice *self,
                                         GBytes      **reconnect_random);
void fpi_sdcp_device_reconnect_complete (FpSdcpDevice *self,
                                         GBytes       *mac,
                                         GError       *error);

void fpi_sdcp_device_list_complete (FpSdcpDevice *self,
                                    GPtrArray    *ids,
                                    GError       *error);

void fpi_sdcp_device_enroll_commit (FpSdcpDevice *self,
                                    GBytes       *nonce,
                                    GError       *error);
void fpi_sdcp_device_enroll_commit_complete (FpSdcpDevice *self,
                                             GError       *error);

void fpi_sdcp_device_get_identify_data (FpSdcpDevice *self,
                                        GBytes      **nonce);
void fpi_sdcp_device_set_identify_data (FpSdcpDevice *self,
                                        GBytes       *nonce);
void fpi_sdcp_device_identify_retry (FpSdcpDevice *self,
                                     GError       *error);
void fpi_sdcp_device_identify_complete (FpSdcpDevice *self,
                                        GBytes       *id,
                                        GBytes       *mac,
                                        GError       *error);

void fpi_sdcp_device_get_print_id (FpPrint *print,
                                   GBytes **id);
void fpi_sdcp_device_set_print_id (FpPrint *print,
                                   GBytes  *id);
