// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2024 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package preinstall

import (
	"bytes"

	"github.com/canonical/tcglog-parser"
	"github.com/pilebones/go-udev/netlink"
	internal_efi "github.com/snapcore/secboot/internal/efi"
)

// discreteTPMPartialResetAttackMitigationStatus indicates whether a partial mitigation against
// discrete TPM reset attacks should be enabled. See the documentation for
// RequestPartialDiscreteTPMResetAttackMitigation
type discreteTPMPartialResetAttackMitigationStatus int

const (
	// dtpmPartialResetAttackMitigationUnknown indicates that it is not known if
	// partial mitigation is required, because of an error.
	dtpmPartialResetAttackMitigationUnknown discreteTPMPartialResetAttackMitigationStatus = iota

	// dtpmPartialResetAttackMitigationNotRequired indicates that no partial mitigation
	// is required.
	dtpmPartialResetAttackMitigationNotRequired

	// dtpmPartialResetAttackMitigationPreferred indicates that a partial mitigation
	// is desired.
	dtpmPartialResetAttackMitigationPreferred

	// dtpmPartialResetAttackMitigationUnavailable indicates that a partial
	// mitigation is desired but not possible.
	dtpmPartialResetAttackMitigationUnavailable
)

// platformFirmwareIntegrityConfig indicates how the root-of-trust provides
// assurances of the platform firmware integrity.
type platformFirmwareIntegrityConfig int

const (
	// platformFirmwareIntegrityNone indicates that no firmware integrity assurances
	// are provided.
	platformFirmwareIntegrityNone platformFirmwareIntegrityConfig = iota

	// platformFirmwareIntegrityMeasured indicates that firmware integrity is provided
	// by measured boot.
	platformFirmwareIntegrityMeasured

	// platformFirmwareIntegrityVerified indicates that firmware integrity is provided
	// by verifying it against a key that is fused into the platform.
	platformFirmwareIntegrityVerified
)

// checkForKernelIOMMU checks that the kernel has enabled some sort of DMA protection.
// On Intel devices, the domains are defined by the DMAR ACPI table. The check is quite
// simple, and based on the fwupd HSI checks. If it is not enabled, a [ErrNoKernelIOMMU]
// error is returned.
// XXX: Figure out whether this is genuinely sufficient, eg:
//   - Should we only mandate this if there are externally facing ports, or internal ports
//     that are accessible to the user
//   - Are all externally facing ports protected?
//   - Are internal ports accessible to the user protected?
//   - Are all addon devices with embedded controllers protected?
//
// This function is going to need some additional work later on.
func checkForKernelIOMMU(env internal_efi.HostEnvironment) error {
	devices, err := env.EnumerateDevices(&netlink.RuleDefinition{
		Env: map[string]string{
			"SUBSYSTEM": "iommu",
		},
	})
	switch {
	case err != nil:
		return err
	case len(devices) == 0:
		return ErrNoKernelIOMMU
	default:
		return nil
	}
}

// checkSecureBootPolicyPCRForDegradedFirmwareSettings checks PCR7 for the indication of degraded
// firmware settings:
//   - Whether a debugging endpoint is enabled, via the presence of a EV_EFI_ACTION event with the
//     "UEFI Debug Mode" string. This is defined in the TCG PC-Client PFP spec. If one is detected,
//     a [ErrUEFIDebuggingEnabled] error is returned, wrapped in [joinError].
//   - Whether DMA protection was disabled at some point, via the presence of a EV_EFI_ACTION event
//     with the "DMA Protection Disabled" string. This is a Windows requirement. If disabled, a
//     [ErrInsufficientDMAProtection] error is returned, wrapped in [joinError].
func checkSecureBootPolicyPCRForDegradedFirmwareSettings(log *tcglog.Log) error {
	var errs []error

	events := log.Events
Loop:
	for len(events) > 0 {
		// Pop next event
		event := events[0]
		events = events[1:]

		if event.PCRIndex != internal_efi.SecureBootPolicyPCR {
			continue
		}

		switch event.EventType {
		case tcglog.EventTypeEFIAction:
			switch {
			case event.Data == tcglog.FirmwareDebuggerEvent:
				// Debugger enabled
				errs = append(errs, ErrUEFIDebuggingEnabled)
			case event.Data == tcglog.DMAProtectionDisabled:
				// DMA protection was disabled bt the firmware at some point
				errs = append(errs, ErrInsufficientDMAProtection)
			case bytes.Equal(event.Data.Bytes(), append([]byte(tcglog.DMAProtectionDisabled), 0x00)):
				// XXX: My Dell NULL terminates this string which causes decoding to fail,
				//  as the TCG PC Client Platform Firmware Profile spec says that the event
				//  data in EV_EFI_ACTION events should not be NULL terminated.
				errs = append(errs, ErrInsufficientDMAProtection)
			}
		case tcglog.EventTypeEFIVariableAuthority:
			break Loop
		}
	}

	// We'll reach here if we encounter an EV_EFI_VARIABLE_AUTHORITY event or
	// we get to the end of the log.
	if len(errs) > 0 {
		return joinErrors(errs...)
	}
	return nil
}
