File: fde.go

package info (click to toggle)
snapd 2.71-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 79,536 kB
  • sloc: ansic: 16,114; sh: 16,105; python: 9,941; makefile: 1,890; exp: 190; awk: 40; xml: 22
file content (152 lines) | stat: -rw-r--r-- 4,896 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2021-2023  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 fde implements helper used by low level parts like secboot
// in snap-bootstrap and high level parts like DeviceManager in snapd.
//
// Note that it must never import anything overlord related itself
// to avoid increasing the size of snap-bootstrap.
package fde

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os/exec"
)

// HasRevealKey return true if the current system has a "fde-reveal-key"
// binary (usually used in the initrd).
//
// This will be setup by devicestate to support device-specific full
// disk encryption implementations.
func HasRevealKey() bool {
	// XXX: should we record during initial sealing that the fde-setup
	//      was used and only use fde-reveal-key in that case?
	_, err := exec.LookPath("fde-reveal-key")
	return err == nil
}

func isV1Hook(hookOutput []byte) bool {
	// This is the prefix of a tpm secboot v1 key as used in the
	// "denver" project. So if we see this prefix we know it's
	// v1 hook output.
	return bytes.HasPrefix(hookOutput, []byte("USK$"))
}

func unmarshalInitialSetupResult(hookOutput []byte) (*InitialSetupResult, error) {
	// We expect json output that fits InitalSetupResult
	// hook at this point. However the "denver" project
	// uses the old and deprecated v1 API that returns raw
	// bytes and we still need to support this.
	var res InitialSetupResult
	if err := json.Unmarshal(hookOutput, &res); err != nil {
		// If the outout is not json and looks like va
		if !isV1Hook(hookOutput) {
			return nil, fmt.Errorf("cannot decode hook output %q: %v", hookOutput, err)
		}
		// v1 hooks do not support a handle
		handle := json.RawMessage(v1NoHandle)
		res.Handle = &handle
		res.EncryptedKey = hookOutput
	}

	return &res, nil
}

// TODO: unexport this because how the hook is driven is an implemenation
//
//	detail. It creates quite a bit of churn unfortunately, see
//	https://github.com/snapcore/snapd/compare/master...mvo5:ice/refactor-fde?expand=1
//
// SetupRequest carries the operation and parameters for the fde-setup hooks
// made available to them via the snapctl fde-setup-request command.
type SetupRequest struct {
	Op string `json:"op"`

	// This needs to be a []byte so that Go's standard library will base64
	// encode it automatically for us
	Key []byte `json:"key,omitempty"`

	// Only used when called with "initial-setup"
	KeyName string `json:"key-name,omitempty"`

	// Name of the partition
	PartitionName string `json:"partition-name,omitempty"`
}

// A RunSetupHookFunc implements running the fde-setup kernel hook.
type RunSetupHookFunc func(req *SetupRequest) ([]byte, error)

// InitialSetupParams contains the inputs for the fde-setup hook
type InitialSetupParams struct {
	Key     []byte
	KeyName string
}

// InitalSetupResult contains the outputs of the fde-setup hook
type InitialSetupResult struct {
	// result when called with "initial-setup"
	// XXX call this encrypted-key if possible?
	EncryptedKey []byte           `json:"sealed-key"`
	Handle       *json.RawMessage `json:"handle"`
}

// InitialSetup invokes the initial-setup op running the kernel hook via runSetupHook.
func InitialSetup(runSetupHook RunSetupHookFunc, params *InitialSetupParams) (*InitialSetupResult, error) {
	req := &SetupRequest{
		Op:      "initial-setup",
		Key:     params.Key,
		KeyName: params.KeyName,
	}
	hookOutput, err := runSetupHook(req)
	if err != nil {
		return nil, err
	}
	res, err := unmarshalInitialSetupResult(hookOutput)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// CheckFeatures returns the features of fde-setup hook.
func CheckFeatures(runSetupHook RunSetupHookFunc) ([]string, error) {
	req := &SetupRequest{
		Op: "features",
	}
	output, err := runSetupHook(req)
	if err != nil {
		return nil, err
	}
	var res struct {
		Features []string `json:"features"`
		Error    string   `json:"error"`
	}
	if err := json.Unmarshal(output, &res); err != nil {
		return nil, fmt.Errorf("cannot parse hook output %q: %v", output, err)
	}
	if res.Features == nil && res.Error == "" {
		return nil, fmt.Errorf(`cannot use hook: neither "features" nor "error" returned`)
	}
	if res.Error != "" {
		return nil, fmt.Errorf("cannot use hook: it returned error: %v", res.Error)
	}
	return res.Features, nil
}