// Copyright 2019 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

package eventlog

import (
	"bytes"
	"crypto"
	"encoding/binary"
	"fmt"
	"io"
	"unicode/utf16"

	"github.com/smallstep/go-attestation/attest"
)

var (
	// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=153
	efiGlobalVariable = efiGUID{
		0x8BE4DF61, 0x93CA, 0x11d2, [8]uint8{0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C}}

	efiGlobalVariableSecureBoot     = "SecureBoot"
	efiGlobalVariablePlatformKey    = "PK"
	efiGlobalVariableKeyExchangeKey = "KEK"

	// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1804
	efiImageSecurityDatabaseGUID = efiGUID{
		0xd719b2cb, 0x3d3a, 0x4596, [8]uint8{0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f}}

	efiImageSecurityDatabase  = "db"
	efiImageSecurityDatabase1 = "dbx"
	efiImageSecurityDatabase2 = "dbt"
	efiImageSecurityDatabase3 = "dbr"
)

type efiGUID struct {
	Data1 uint32
	Data2 uint16
	Data3 uint16
	Data4 [8]uint8
}

func (e efiGUID) String() string {
	if s, ok := efiGUIDString[e]; ok {
		return s
	}
	return fmt.Sprintf("{0x%x,0x%x,0x%x,{%x}}", e.Data1, e.Data2, e.Data3, e.Data4)
}

var efiGUIDString = map[efiGUID]string{
	efiGlobalVariable:            "EFI_GLOBAL_VARIABLE",
	efiImageSecurityDatabaseGUID: "EFI_IMAGE_SECURITY_DATABASE_GUID",
}

type uefiVariableData struct {
	id   efiGUID
	name string
	data []byte
}

func (d *uefiVariableData) String() string {
	return fmt.Sprintf("%s %s data length %d", d.id, d.name, len(d.data))
}

// SecureBoot holds parsed PCR 7 values representing secure boot settings for
// the device.
type SecureBoot struct {
	Enabled bool

	// TODO(ericchiang): parse these as EFI_SIGNATURE_LIST
	//
	// https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_final.pdf#page=1788

	PK  []byte
	KEK []byte

	DB  []byte
	DBX []byte

	DBT []byte
	DBR []byte

	// Authority is the set of certificate that were used during secure boot
	// validation. This will be a subset of the certifiates in DB.
	Authority []byte
}

// ParseSecureBoot parses UEFI secure boot variables (PCR[7) from a verified event log.
//
// See https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=56
func ParseSecureBoot(events []attest.Event) (*SecureBoot, error) {
	var sb SecureBoot
	seenSep := false
	for i, e := range events {
		if e.Index != 7 {
			continue
		}
		t := eventType(e.Type)
		switch t {
		case evEFIVariableDriverConfig:
			if seenSep {
				return nil, fmt.Errorf("event %d %s after %s", i, t, evSeparator)
			}
			data, err := parseUEFIVariableData(e.Data, e.Digest)
			if err != nil {
				return nil, fmt.Errorf("parsing event %d, PCR[%02d] %s: %v", i, e.Index, t, err)
			}

			switch data.id {
			case efiGlobalVariable:
				switch data.name {
				case efiGlobalVariableSecureBoot:
					if len(data.data) != 1 {
						return nil, fmt.Errorf("%s/%s was %d bytes", data.id, data.name, len(data.data))
					}
					switch data.data[0] {
					case 0x0:
						sb.Enabled = false
					case 0x1:
						sb.Enabled = true
					default:
						return nil, fmt.Errorf("invalid %s/%s value 0x%x", data.id, data.name, data.data)
					}
				case efiGlobalVariablePlatformKey:
					sb.PK = data.data
				case efiGlobalVariableKeyExchangeKey:
					sb.KEK = data.data
				}
			case efiImageSecurityDatabaseGUID:
				switch data.name {
				case efiImageSecurityDatabase:
					sb.DB = data.data
				case efiImageSecurityDatabase1:
					sb.DBX = data.data
				case efiImageSecurityDatabase2:
					sb.DBT = data.data
				case efiImageSecurityDatabase3:
					sb.DBR = data.data
				}
			}
		case evEFIVariableAuthority:
			if !seenSep {
				return nil, fmt.Errorf("event %d %s before %s", i, t, evSeparator)
			}
			data, err := parseUEFIVariableData(e.Data, e.Digest)
			if err != nil {
				return nil, fmt.Errorf("parsing event %d, PCR[%02d] %s: %v", i, e.Index, t, err)
			}
			switch data.id {
			case efiImageSecurityDatabaseGUID:
				switch data.name {
				case efiImageSecurityDatabase:
					if !sb.Enabled {
						return nil, fmt.Errorf("%s/%s present when secure boot wasn't enabled", t, data.name)
					}
					if len(sb.Authority) != 0 {
						// If a malicious value is appended to the eventlog,
						// ensure we only trust the first value written by
						// the UEFI firmware.
						return nil, fmt.Errorf("%s/%s was already present earlier in the event log", t, data.name)
					}
					sb.Authority = data.data
				}
			}
		case evSeparator:
			seenSep = true
		}
	}
	return &sb, nil
}

func binaryRead(r io.Reader, i interface{}) error {
	return binary.Read(r, binary.LittleEndian, i)
}

var hashBySize = map[int]crypto.Hash{
	crypto.SHA1.Size():   crypto.SHA1,
	crypto.SHA256.Size(): crypto.SHA256,
}

func verifyDigest(digest, data []byte) bool {
	h, ok := hashBySize[len(digest)]
	if !ok {
		return false
	}
	hash := h.New()
	hash.Write(data)
	return bytes.Equal(digest, hash.Sum(nil))
}

// parseUEFIVariableData parses a UEFI_VARIABLE_DATA struct and validates the
// digest of an event entry.
//
// https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_Specific_Platform_Profile_for_TPM_2p0_1p04_PUBLIC.pdf#page=100
func parseUEFIVariableData(b, digest []byte) (*uefiVariableData, error) {
	r := bytes.NewBuffer(b)
	var hdr struct {
		ID         efiGUID
		NameLength uint64
		DataLength uint64
	}
	if err := binaryRead(r, &hdr); err != nil {
		return nil, err
	}
	name := make([]uint16, hdr.NameLength)
	if err := binaryRead(r, &name); err != nil {
		return nil, fmt.Errorf("parsing name: %v", err)
	}
	if r.Len() != int(hdr.DataLength) {
		return nil, fmt.Errorf("remaining bytes %d doesn't match data length %d", r.Len(), hdr.DataLength)
	}
	data := r.Bytes()
	// TODO(ericchiang): older UEFI firmware (Lenovo Bios version 1.17) logs the
	// digest of the data, which doesn't encapsulate the ID or name. This lets
	// attackers alter keys and we should determine if this is an acceptable risk.
	if !verifyDigest(digest, b) && !verifyDigest(digest, data) {
		return nil, fmt.Errorf("digest didn't match data")
	}
	return &uefiVariableData{id: hdr.ID, name: string(utf16.Decode(name)), data: r.Bytes()}, nil
}
