/** @file

  A hook-in library for NetworkPkg/TlsAuthConfigDxe, in order to set volatile
  variables related to TLS configuration, before TlsAuthConfigDxe or HttpDxe
  (which is a UEFI_DRIVER) consume them.

  Copyright (C) 2013, 2015, 2018, Red Hat, Inc.
  Copyright (c) 2008 - 2012, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Uefi/UefiBaseType.h>
#include <Uefi/UefiSpec.h>

#include <Guid/HttpTlsCipherList.h>
#include <Guid/TlsAuthentication.h>

#include <Library/BaseLib.h>
#include <Library/DebugLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/QemuFwCfgLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>

/**
  Read the list of trusted CA certificates from the fw_cfg file
  "etc/edk2/https/cacerts", and store it to
  gEfiTlsCaCertificateGuid:EFI_TLS_CA_CERTIFICATE_VARIABLE.

  The contents are validated (for well-formedness) by NetworkPkg/HttpDxe.
**/
STATIC
VOID
SetCaCerts (
  VOID
  )
{
  EFI_STATUS           Status;
  FIRMWARE_CONFIG_ITEM HttpsCaCertsItem;
  UINTN                HttpsCaCertsSize;
  VOID                 *HttpsCaCerts;

  Status = QemuFwCfgFindFile ("etc/edk2/https/cacerts", &HttpsCaCertsItem,
             &HttpsCaCertsSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VERBOSE, "%a:%a: not touching CA cert list\n",
      gEfiCallerBaseName, __FUNCTION__));
    return;
  }

  //
  // Delete the current EFI_TLS_CA_CERTIFICATE_VARIABLE if it exists. This
  // serves two purposes:
  //
  // (a) If the variable exists with EFI_VARIABLE_NON_VOLATILE attribute, we
  //     cannot make it volatile without deleting it first.
  //
  // (b) If we fail to recreate the variable later, deleting the current one is
  //     still justified if the fw_cfg file exists. Emptying the set of trusted
  //     CA certificates will fail HTTPS boot, which is better than trusting
  //     any certificate that's possibly missing from the fw_cfg file.
  //
  Status = gRT->SetVariable (
                  EFI_TLS_CA_CERTIFICATE_VARIABLE, // VariableName
                  &gEfiTlsCaCertificateGuid,       // VendorGuid
                  0,                               // Attributes
                  0,                               // DataSize
                  NULL                             // Data
                  );
  if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) {
    //
    // This is fatal.
    //
    DEBUG ((DEBUG_ERROR, "%a:%a: failed to delete %g:\"%s\"\n",
      gEfiCallerBaseName, __FUNCTION__, &gEfiTlsCaCertificateGuid,
      EFI_TLS_CA_CERTIFICATE_VARIABLE));
    ASSERT_EFI_ERROR (Status);
    CpuDeadLoop ();
  }

  if (HttpsCaCertsSize == 0) {
    DEBUG ((DEBUG_VERBOSE, "%a:%a: applied empty CA cert list\n",
      gEfiCallerBaseName, __FUNCTION__));
    return;
  }

  HttpsCaCerts = AllocatePool (HttpsCaCertsSize);
  if (HttpsCaCerts == NULL) {
    DEBUG ((DEBUG_ERROR, "%a:%a: failed to allocate HttpsCaCerts\n",
      gEfiCallerBaseName, __FUNCTION__));
    return;
  }

  QemuFwCfgSelectItem (HttpsCaCertsItem);
  QemuFwCfgReadBytes (HttpsCaCertsSize, HttpsCaCerts);

  Status = gRT->SetVariable (
                  EFI_TLS_CA_CERTIFICATE_VARIABLE, // VariableName
                  &gEfiTlsCaCertificateGuid,       // VendorGuid
                  EFI_VARIABLE_BOOTSERVICE_ACCESS, // Attributes
                  HttpsCaCertsSize,                // DataSize
                  HttpsCaCerts                     // Data
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a:%a: failed to set %g:\"%s\": %r\n",
      gEfiCallerBaseName, __FUNCTION__, &gEfiTlsCaCertificateGuid,
      EFI_TLS_CA_CERTIFICATE_VARIABLE, Status));
    goto FreeHttpsCaCerts;
  }

  DEBUG ((DEBUG_VERBOSE, "%a:%a: stored CA cert list (%Lu byte(s))\n",
    gEfiCallerBaseName, __FUNCTION__, (UINT64)HttpsCaCertsSize));

FreeHttpsCaCerts:
  FreePool (HttpsCaCerts);
}

/**
  Read the list of trusted cipher suites from the fw_cfg file
  "etc/edk2/https/ciphers", and store it to
  gEdkiiHttpTlsCipherListGuid:EDKII_HTTP_TLS_CIPHER_LIST_VARIABLE.

  The contents are propagated by NetworkPkg/HttpDxe to NetworkPkg/TlsDxe; the
  list is processed by the latter.
**/
STATIC
VOID
SetCipherSuites (
  VOID
  )
{
  EFI_STATUS           Status;
  FIRMWARE_CONFIG_ITEM HttpsCiphersItem;
  UINTN                HttpsCiphersSize;
  VOID                 *HttpsCiphers;

  Status = QemuFwCfgFindFile ("etc/edk2/https/ciphers", &HttpsCiphersItem,
             &HttpsCiphersSize);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VERBOSE, "%a:%a: not touching cipher suites\n",
      gEfiCallerBaseName, __FUNCTION__));
    return;
  }
  //
  // From this point on, any failure is fatal. An ordered cipher preference
  // list is available from QEMU, thus we cannot let the firmware attempt HTTPS
  // boot with either pre-existent or non-existent preferences. An empty set of
  // cipher suites does not fail HTTPS boot automatically; the default cipher
  // suite preferences would take effect, and we must prevent that.
  //
  // Delete the current EDKII_HTTP_TLS_CIPHER_LIST_VARIABLE if it exists. If
  // the variable exists with EFI_VARIABLE_NON_VOLATILE attribute, we cannot
  // make it volatile without deleting it first.
  //
  Status = gRT->SetVariable (
                  EDKII_HTTP_TLS_CIPHER_LIST_VARIABLE, // VariableName
                  &gEdkiiHttpTlsCipherListGuid,        // VendorGuid
                  0,                                   // Attributes
                  0,                                   // DataSize
                  NULL                                 // Data
                  );
  if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) {
    DEBUG ((DEBUG_ERROR, "%a:%a: failed to delete %g:\"%s\"\n",
      gEfiCallerBaseName, __FUNCTION__, &gEdkiiHttpTlsCipherListGuid,
      EDKII_HTTP_TLS_CIPHER_LIST_VARIABLE));
    goto Done;
  }

  if (HttpsCiphersSize == 0) {
    DEBUG ((DEBUG_ERROR, "%a:%a: list of cipher suites must not be empty\n",
      gEfiCallerBaseName, __FUNCTION__));
    Status = EFI_INVALID_PARAMETER;
    goto Done;
  }

  HttpsCiphers = AllocatePool (HttpsCiphersSize);
  if (HttpsCiphers == NULL) {
    DEBUG ((DEBUG_ERROR, "%a:%a: failed to allocate HttpsCiphers\n",
      gEfiCallerBaseName, __FUNCTION__));
    Status = EFI_OUT_OF_RESOURCES;
    goto Done;
  }

  QemuFwCfgSelectItem (HttpsCiphersItem);
  QemuFwCfgReadBytes (HttpsCiphersSize, HttpsCiphers);

  Status = gRT->SetVariable (
                  EDKII_HTTP_TLS_CIPHER_LIST_VARIABLE, // VariableName
                  &gEdkiiHttpTlsCipherListGuid,        // VendorGuid
                  EFI_VARIABLE_BOOTSERVICE_ACCESS,     // Attributes
                  HttpsCiphersSize,                    // DataSize
                  HttpsCiphers                         // Data
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_ERROR, "%a:%a: failed to set %g:\"%s\"\n",
      gEfiCallerBaseName, __FUNCTION__, &gEdkiiHttpTlsCipherListGuid,
      EDKII_HTTP_TLS_CIPHER_LIST_VARIABLE));
    goto FreeHttpsCiphers;
  }

  DEBUG ((DEBUG_VERBOSE, "%a:%a: stored list of cipher suites (%Lu byte(s))\n",
    gEfiCallerBaseName, __FUNCTION__, (UINT64)HttpsCiphersSize));

FreeHttpsCiphers:
  FreePool (HttpsCiphers);

Done:
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
    CpuDeadLoop ();
  }
}

RETURN_STATUS
EFIAPI
TlsAuthConfigInit (
  VOID
  )
{
  SetCaCerts ();
  SetCipherSuites ();

  return RETURN_SUCCESS;
}
