mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
* many: remove usages of deprecated io/ioutil package Signed-off-by: Miguel Pires <miguel.pires@canonical.com> * .golangci.yml: remove errcheck ignore rule for io/ioutil Signed-off-by: Miguel Pires <miguel.pires@canonical.com> * run-checks: prevent new usages of io/ioutil Signed-off-by: Miguel Pires <miguel.pires@canonical.com> --------- Signed-off-by: Miguel Pires <miguel.pires@canonical.com>
229 lines
7.2 KiB
Go
229 lines
7.2 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
//go:build !nosecboot
|
|
|
|
/*
|
|
* Copyright (C) 2021 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 secboot
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
sb "github.com/snapcore/secboot"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/snapcore/snapd/kernel/fde"
|
|
"github.com/snapcore/snapd/logger"
|
|
"github.com/snapcore/snapd/osutil"
|
|
)
|
|
|
|
var fdeHasRevealKey = fde.HasRevealKey
|
|
|
|
const fdeHooksPlatformName = "fde-hook-v2"
|
|
|
|
func init() {
|
|
handler := &fdeHookV2DataHandler{}
|
|
sb.RegisterPlatformKeyDataHandler(fdeHooksPlatformName, handler)
|
|
}
|
|
|
|
// SealKeysWithFDESetupHook protects the given keys through using the
|
|
// fde-setup hook and saves each protected key to the KeyFile
|
|
// indicated in the key SealKeyRequest.
|
|
func SealKeysWithFDESetupHook(runHook fde.RunSetupHookFunc, keys []SealKeyRequest, params *SealKeysWithFDESetupHookParams) error {
|
|
auxKey := params.AuxKey[:]
|
|
for _, skr := range keys {
|
|
payload := sb.MarshalKeys([]byte(skr.Key), auxKey)
|
|
keyParams := &fde.InitialSetupParams{
|
|
Key: payload,
|
|
KeyName: skr.KeyName,
|
|
}
|
|
res, err := fde.InitialSetup(runHook, keyParams)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := writeKeyData(skr.KeyFile, res, auxKey, params.Model); err != nil {
|
|
return fmt.Errorf("cannot store key: %v", err)
|
|
}
|
|
}
|
|
if params.AuxKeyFile != "" {
|
|
if err := osutil.AtomicWriteFile(params.AuxKeyFile, auxKey, 0600, 0); err != nil {
|
|
return fmt.Errorf("cannot write the aux key file: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeKeyData(path string, keySetup *fde.InitialSetupResult, auxKey []byte, model sb.SnapModel) error {
|
|
var handle []byte
|
|
if keySetup.Handle == nil {
|
|
// this will reach fde-reveal-key as null but should be ok
|
|
handle = []byte("null")
|
|
} else {
|
|
handle = *keySetup.Handle
|
|
}
|
|
kd, err := sb.NewKeyData(&sb.KeyCreationData{
|
|
PlatformKeyData: sb.PlatformKeyData{
|
|
EncryptedPayload: keySetup.EncryptedKey,
|
|
Handle: handle,
|
|
},
|
|
PlatformName: fdeHooksPlatformName,
|
|
AuxiliaryKey: auxKey,
|
|
SnapModelAuthHash: crypto.SHA256,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("cannot create key data: %v", err)
|
|
}
|
|
if err := kd.SetAuthorizedSnapModels(auxKey, model); err != nil {
|
|
return fmt.Errorf("cannot set model %s/%s as authorized: %v", model.BrandID(), model.Model(), err)
|
|
}
|
|
f := sb.NewFileKeyDataWriter(path)
|
|
if err := kd.WriteAtomic(f); err != nil {
|
|
return fmt.Errorf("cannot write key data: %v", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isV1EncryptedKeyFile(p string) bool {
|
|
// XXX move some of this to kernel/fde
|
|
var v1KeyPrefix = []byte("USK$")
|
|
|
|
f, err := os.Open(p)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
defer f.Close()
|
|
|
|
buf := make([]byte, len(v1KeyPrefix))
|
|
if _, err := io.ReadFull(f, buf); err != nil {
|
|
return false
|
|
}
|
|
return bytes.HasPrefix(buf, v1KeyPrefix)
|
|
}
|
|
|
|
// We have to deal with the following cases:
|
|
// 1. Key created with v1 data-format on disk (raw encrypted key), v1 hook reads the data
|
|
// 2. Key created with v2 data-format on disk (json), v1 hook created the data (no handle) and reads the data (hook output not json but raw binary data)
|
|
// 3. Key created with v1 data-format on disk (raw), v2 hook
|
|
// 4. Key created with v2 data-format on disk (json), v2 hook [easy]
|
|
func unlockVolumeUsingSealedKeyFDERevealKey(sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) {
|
|
// deal with v1 keys
|
|
if isV1EncryptedKeyFile(sealedEncryptionKeyFile) {
|
|
return unlockVolumeUsingSealedKeyFDERevealKeyV1(sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName)
|
|
}
|
|
return unlockVolumeUsingSealedKeyFDERevealKeyV2(sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName, opts)
|
|
}
|
|
|
|
func unlockVolumeUsingSealedKeyFDERevealKeyV1(sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string) (UnlockResult, error) {
|
|
res := UnlockResult{IsEncrypted: true, PartDevice: sourceDevice}
|
|
|
|
sealedKey, err := os.ReadFile(sealedEncryptionKeyFile)
|
|
if err != nil {
|
|
return res, fmt.Errorf("cannot read sealed key file: %v", err)
|
|
}
|
|
|
|
p := fde.RevealParams{
|
|
SealedKey: sealedKey,
|
|
}
|
|
output, err := fde.Reveal(&p)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
|
|
// the output of fde-reveal-key is the unsealed key
|
|
unsealedKey := output
|
|
if err := unlockEncryptedPartitionWithKey(mapperName, sourceDevice, unsealedKey); err != nil {
|
|
return res, fmt.Errorf("cannot unlock encrypted partition: %v", err)
|
|
}
|
|
res.FsDevice = targetDevice
|
|
res.UnlockMethod = UnlockedWithSealedKey
|
|
return res, nil
|
|
}
|
|
|
|
func unlockVolumeUsingSealedKeyFDERevealKeyV2(sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (res UnlockResult, err error) {
|
|
res = UnlockResult{IsEncrypted: true, PartDevice: sourceDevice}
|
|
|
|
f, err := sb.NewFileKeyDataReader(sealedEncryptionKeyFile)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
keyData, err := sb.ReadKeyData(f)
|
|
if err != nil {
|
|
fmt := "cannot read key data: %w"
|
|
return res, xerrors.Errorf(fmt, err)
|
|
}
|
|
|
|
// the output of fde-reveal-key is the unsealed key
|
|
options := activateVolOpts(opts.AllowRecoveryKey)
|
|
modChecker, err := sbActivateVolumeWithKeyData(mapperName, sourceDevice, keyData, options)
|
|
if err == sb.ErrRecoveryKeyUsed {
|
|
logger.Noticef("successfully activated encrypted device %q using a fallback activation method", sourceDevice)
|
|
res.FsDevice = targetDevice
|
|
res.UnlockMethod = UnlockedWithRecoveryKey
|
|
return res, nil
|
|
}
|
|
if err != nil {
|
|
return res, fmt.Errorf("cannot unlock encrypted partition: %v", err)
|
|
}
|
|
// ensure we close the open volume under any error condition
|
|
defer func() {
|
|
if err != nil {
|
|
if err := sbDeactivateVolume(mapperName); err != nil {
|
|
logger.Noticef("cannot deactivate volume %q: %v", mapperName, err)
|
|
}
|
|
}
|
|
}()
|
|
// ensure that the model is authorized to open the volume
|
|
model, err := opts.WhichModel()
|
|
if err != nil {
|
|
return res, fmt.Errorf("cannot retrieve which model to unlock for: %v", err)
|
|
}
|
|
ok, err := modChecker.IsModelAuthorized(model)
|
|
if err != nil {
|
|
return res, fmt.Errorf("cannot check if model is authorized to unlock disk: %v", err)
|
|
}
|
|
if !ok {
|
|
return res, fmt.Errorf("cannot unlock volume: model %s/%s not authorized", model.BrandID(), model.Model())
|
|
}
|
|
|
|
logger.Noticef("successfully activated encrypted device %q using FDE kernel hooks", sourceDevice)
|
|
res.FsDevice = targetDevice
|
|
res.UnlockMethod = UnlockedWithSealedKey
|
|
return res, nil
|
|
}
|
|
|
|
type fdeHookV2DataHandler struct{}
|
|
|
|
func (fh *fdeHookV2DataHandler) RecoverKeys(data *sb.PlatformKeyData) (sb.KeyPayload, error) {
|
|
var handle *json.RawMessage
|
|
if len(data.Handle) != 0 {
|
|
rawHandle := json.RawMessage(data.Handle)
|
|
handle = &rawHandle
|
|
}
|
|
p := fde.RevealParams{
|
|
SealedKey: data.EncryptedPayload,
|
|
Handle: handle,
|
|
V2Payload: true,
|
|
}
|
|
return fde.Reveal(&p)
|
|
}
|