Attestation & Identity & Global Unlock & Enrolment

This changes the node startup sequence significantly. Now the following three startup procedures replace the old setup/join mechanic:
* If no enrolment config is present, automatically bootstrap a new cluster and become master for it.
* If an enrolment config with an enrolment token is present, register with the NodeManagementService.
* If an enrolment config without an enrolment token is present, attempt a normal cluster unlock.

It also completely revamps the GRPC management services:
* NodeManagementService is a master-only service that deals with other nodes and has a cluster-wide identity
* NodeService is only available in unlocked state and keyed with the node identity
* ClusterManagement is now a master-only service that's been spun out of the main NMS since they have very different authentication models and also deals with EnrolmentConfigs

The TPM support library has also been extended by:
* Lots of integrity attestation and verification functions
* Built-in AK management
* Some advanced policy-based authentication stuff

Also contains various enhancements to the network service to make everything work in a proper multi-node environment.

Lots of old code has been thrown out.

Test Plan: Passed a full manual test of all three startup modes (bootstrap, enrolment and normal unlock) including automated EnrolmentConfig generation and consumption in a dual-node configuration on swtpm / OVMF.

Bug: T499

X-Origin-Diff: phab/D291
GitOrigin-RevId: d53755c828218b1df83a1d7ad252c7b3231abca8
diff --git a/core/internal/integrity/tpm2/BUILD.bazel b/core/internal/integrity/tpm2/BUILD.bazel
new file mode 100644
index 0000000..c4c77bb
--- /dev/null
+++ b/core/internal/integrity/tpm2/BUILD.bazel
@@ -0,0 +1,13 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["tpm2.go"],
+    importpath = "git.monogon.dev/source/nexantic.git/core/internal/integrity/tpm2",
+    visibility = ["//core:__subpackages__"],
+    deps = [
+        "//core/api/api:go_default_library",
+        "//core/internal/integrity:go_default_library",
+        "//core/pkg/tpm:go_default_library",
+    ],
+)
diff --git a/core/internal/integrity/tpm2/tpm2.go b/core/internal/integrity/tpm2/tpm2.go
new file mode 100644
index 0000000..8497562
--- /dev/null
+++ b/core/internal/integrity/tpm2/tpm2.go
@@ -0,0 +1,155 @@
+// Copyright 2020 The Monogon Project Authors.
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// 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 tpm2
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"git.monogon.dev/source/nexantic.git/core/generated/api"
+	"git.monogon.dev/source/nexantic.git/core/internal/integrity"
+	"git.monogon.dev/source/nexantic.git/core/pkg/tpm"
+)
+
+type TPM2Agent struct {
+}
+
+func (a *TPM2Agent) Initialize(newNode api.NewNodeInfo, enrolment api.EnrolmentConfig) error {
+	nmsConn, err := integrity.DialNMS(enrolment)
+	nmsClient := api.NewNodeManagementServiceClient(nmsConn)
+	ekPub, ekCert, err := tpm.GetEKPublic()
+	if err != nil {
+		return fmt.Errorf("failed to generate EK: %w", err)
+	}
+
+	akPub, err := tpm.GetAKPublic()
+	if err != nil {
+		return fmt.Errorf("failed to generate AK: %w", err)
+	}
+
+	registerSession, err := nmsClient.NewTPM2NodeRegister(context.Background())
+	if err != nil {
+		return fmt.Errorf("failed to open registration session: %w", err)
+	}
+	defer registerSession.CloseSend()
+	if err := registerSession.Send(&api.TPM2FlowRequest{
+		Stage: &api.TPM2FlowRequest_Register{
+			Register: &api.TPM2RegisterRequest{
+				AkPublic: akPub,
+				EkPubkey: ekPub,
+				EkCert:   ekCert,
+			},
+		},
+	}); err != nil {
+		return fmt.Errorf("failed to send registration: %w", err)
+	}
+
+	res1, err := registerSession.Recv()
+	if err != nil {
+		return fmt.Errorf("failed to receive attest request: %w", err)
+	}
+	attestReqContainer, ok := res1.Stage.(*api.TPM2FlowResponse_AttestRequest)
+	if !ok {
+		return errors.New("protocol violation: after RegisterRequest expected AttestRequest")
+	}
+	attestReq := attestReqContainer.AttestRequest
+	solution, err := tpm.SolveAKChallenge(attestReq.AkChallenge, attestReq.AkChallengeSecret)
+	if err != nil {
+		return fmt.Errorf("failed to solve AK challenge: %w", err)
+	}
+	pcrs, err := tpm.GetPCRs()
+	if err != nil {
+		return fmt.Errorf("failed to get SRTM PCRs: %w", err)
+	}
+	quote, quoteSig, err := tpm.AttestPlatform(attestReq.QuoteNonce)
+	if err != nil {
+		return fmt.Errorf("failed Quote operation: %w", err)
+	}
+	if err := registerSession.Send(&api.TPM2FlowRequest{
+		Stage: &api.TPM2FlowRequest_AttestResponse{
+			AttestResponse: &api.TPM2AttestResponse{
+				AkChallengeSolution: solution,
+				Pcrs:                pcrs,
+				Quote:               quote,
+				QuoteSignature:      quoteSig,
+			},
+		},
+	}); err != nil {
+		return fmt.Errorf("failed to send AttestResponse: %w", err)
+	}
+	if err := registerSession.Send(&api.TPM2FlowRequest{
+		Stage: &api.TPM2FlowRequest_NewNodeInfo{
+			NewNodeInfo: &newNode,
+		},
+	}); err != nil {
+		return fmt.Errorf("failed to send NewNodeInfo: %w", err)
+	}
+	return nil
+}
+
+// Unlock attests the node state to the remote NMS and asks it for the global unlock key
+func (a *TPM2Agent) Unlock(enrolment api.EnrolmentConfig) ([]byte, error) {
+	nmsConn, err := integrity.DialNMS(enrolment)
+	if err != nil {
+		return []byte{}, err
+	}
+	nmsClient := api.NewNodeManagementServiceClient(nmsConn)
+	unlockClient, err := nmsClient.TPM2Unlock(context.Background())
+	if err != nil {
+		return []byte{}, err
+	}
+	defer unlockClient.CloseSend()
+	unlockInitContainer, err := unlockClient.Recv()
+	if err != nil {
+		return []byte{}, err
+	}
+	unlockInitVariant, ok := unlockInitContainer.Stage.(*api.TPM2UnlockFlowResponse_UnlockInit)
+	if !ok {
+		return []byte{}, errors.New("TPM2Unlock protocol violation")
+	}
+	unlockInit := unlockInitVariant.UnlockInit
+	quote, sig, err := tpm.AttestPlatform(unlockInit.Nonce)
+	if err != nil {
+		return []byte{}, fmt.Errorf("failed to attest platform: %w", err)
+	}
+	pcrs, err := tpm.GetPCRs()
+	if err != nil {
+		return []byte{}, fmt.Errorf("failed to get PCRs from TPM: %w", err)
+	}
+	if err := unlockClient.Send(&api.TPM2UnlockFlowRequeset{Stage: &api.TPM2UnlockFlowRequeset_UnlockRequest{
+		UnlockRequest: &api.TPM2UnlockRequest{
+			Pcrs:           pcrs,
+			Quote:          quote,
+			QuoteSignature: sig,
+			NodeId:         enrolment.NodeId,
+		},
+	}}); err != nil {
+		return []byte{}, err
+	}
+	unlockResponseContainer, err := unlockClient.Recv()
+	if err != nil {
+		return []byte{}, err
+	}
+	unlockResponseVariant, ok := unlockResponseContainer.Stage.(*api.TPM2UnlockFlowResponse_UnlockResponse)
+	if !ok {
+		return []byte{}, errors.New("violated TPM2Unlock protocol")
+	}
+	unlockResponse := unlockResponseVariant.UnlockResponse
+
+	return unlockResponse.GlobalUnlockKey, nil
+}