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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"github.com/snapcore/snapd/osutil"
)
// DO NOT USE THIS FILE as an example to write fde-hooks. It is just
// here for historic reasons and uses the obsolte v1 hook
// format. Please do not change any of the JSON structs in this file,
// it needs to be kept to ensure we keep compatbility with the
// "denver" project and fde-hooks v1.
// This is a very insecure crypto just for demonstration purposes.
// Please delete it you use this for real.
func xor13(bs []byte) []byte {
out := make([]byte, len(bs))
for i := range bs {
out[i] = bs[i] ^ 0x13
}
return out
}
// Note that this does not import the snapd structs to ensure we don't
// accidentally break something in the contract and miss that we broke
// it because we use the internal thing "externally" here
type fdeSetupJSON struct {
Op string `json:"op"`
Key []byte `json:"key,omitempty"`
KeyName string `json:"key-name,omitempty"`
Models []map[string]string `json:"models,omitempty"`
}
// this is the same as fdeSetupJSON, but is more strict in that it decodes Key
// as a string, which _must_ be a base64 encoded version of the same []byte Key
// we have above, the handler below validates this as a test
type fdeSetupJSONStrictBase64 struct {
Key string `json:"key,omitempty"`
}
func runFdeSetup() error {
output, err := exec.Command("snapctl", "fde-setup-request").CombinedOutput()
if err != nil {
return fmt.Errorf("cannot run snapctl fde-setup-request: %v", osutil.OutputErr(output, err))
}
var js fdeSetupJSON
if err := json.Unmarshal(output, &js); err != nil {
return err
}
var jsStrict fdeSetupJSONStrictBase64
if err := json.Unmarshal(output, &jsStrict); err != nil {
return err
}
// verify that the two de-coding mechanisms agree on the key, manually
// decoding the base64 string in the stricter case
decodedBase64Key, err := base64.StdEncoding.DecodeString(jsStrict.Key)
if err != nil {
return fmt.Errorf("fde-setup-request is not valid base64: %v", err)
}
if !bytes.Equal(decodedBase64Key, js.Key) {
return fmt.Errorf("fde-setup-request is not strictly the same base64 decoded as binary decoded")
}
var fdeSetupResult []byte
switch js.Op {
case "features":
// no special features supported by this hook
fdeSetupResult = []byte(`{"features":[]}`)
case "initial-setup":
// "seal"
buf := bytes.NewBufferString("USK$")
buf.Write(xor13(js.Key))
fdeSetupResult = buf.Bytes()
default:
return fmt.Errorf("unsupported op %q", js.Op)
}
cmd := exec.Command("snapctl", "fde-setup-result")
cmd.Stdin = bytes.NewBuffer(fdeSetupResult)
output, err = cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("cannot run snapctl fde-setup-result for op %q: %v", js.Op, osutil.OutputErr(output, err))
}
return nil
}
type fdeRevealJSON struct {
Op string `json:"op"`
SealedKey []byte `json:"sealed-key"`
}
type fdeRevealJSONStrict struct {
SealedKey string `json:"sealed-key"`
}
func runFdeRevealKey() error {
var js fdeRevealJSON
var jsStrict fdeRevealJSONStrict
b, err := io.ReadAll(os.Stdin)
if err != nil {
return err
}
if err := json.Unmarshal(b, &js); err != nil {
return err
}
if err := json.Unmarshal(b, &jsStrict); err != nil {
return err
}
// verify that the two de-coding mechanisms agree on the key, manually
// decoding the base64 string in the stricter case
decodedBase64Key, err := base64.StdEncoding.DecodeString(jsStrict.SealedKey)
if err != nil {
return fmt.Errorf("fde-reveal-key input is not valid base64: %v", err)
}
if !bytes.Equal(decodedBase64Key, js.SealedKey) {
return fmt.Errorf("fde-reveal-key input is not strictly the same base64 decoded as binary decoded")
}
switch js.Op {
case "reveal":
// strip the "USK$" prefix
unsealedKey := xor13(js.SealedKey[len("USK$"):])
fmt.Fprintf(os.Stdout, "%s", unsealedKey)
case "lock":
// nothing right now
// NOTE: when using this file as an example code for implementing a real
// world, production grade FDE hook, the lock operation must be
// implemented here to block decryption operations
case "features":
// XXX: Not used right now but might in the future?
fmt.Fprintf(os.Stdout, `{"features":[]}`)
default:
return fmt.Errorf(`unsupported operations %q`, js.Op)
}
return nil
}
func main() {
var err error
switch filepath.Base(os.Args[0]) {
case "fde-setup":
// run as regular hook
err = runFdeSetup()
case "fde-reveal-key":
// run from initrd
err = runFdeRevealKey()
default:
err = fmt.Errorf("binary needs to be called as fde-setup or fde-reveal-key")
}
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
|