blob: fc556e1e38672dadf34944b9b7b0f34c207953e1 [file] [log] [blame]
// 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 storage
import (
"encoding/binary"
"encoding/hex"
"fmt"
"os"
"syscall"
"git.monogon.dev/source/nexantic.git/core/pkg/devicemapper"
"golang.org/x/sys/unix"
)
func readDataSectors(path string) (uint64, error) {
integrityPartition, err := os.Open(path)
if err != nil {
return 0, err
}
defer integrityPartition.Close()
// Based on structure defined in
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/md/dm-integrity.c#n59
if _, err := integrityPartition.Seek(16, 0); err != nil {
return 0, err
}
var providedDataSectors uint64
if err := binary.Read(integrityPartition, binary.LittleEndian, &providedDataSectors); err != nil {
return 0, err
}
return providedDataSectors, nil
}
// MapEncryptedBlockDevice maps an encrypted device (node) at baseName to a
// decrypted device at /dev/$name using the given encryptionKey
func MapEncryptedBlockDevice(name string, baseName string, encryptionKey []byte) error {
integritySectors, err := readDataSectors(baseName)
if err != nil {
return fmt.Errorf("failed to read the number of usable sectors on the integrity device: %w", err)
}
integrityDevName := fmt.Sprintf("/dev/%v-integrity", name)
integrityDMName := fmt.Sprintf("%v-integrity", name)
integrityDev, err := devicemapper.CreateActiveDevice(integrityDMName, []devicemapper.Target{
devicemapper.Target{
Length: integritySectors,
Type: "integrity",
Parameters: fmt.Sprintf("%v 0 28 J 1 journal_sectors:1024", baseName),
},
})
if err != nil {
return fmt.Errorf("failed to create Integrity device: %w", err)
}
if err := unix.Mknod(integrityDevName, 0600|unix.S_IFBLK, int(integrityDev)); err != nil {
unix.Unlink(integrityDevName)
devicemapper.RemoveDevice(integrityDMName)
return fmt.Errorf("failed to create integrity device node: %w", err)
}
cryptDevName := fmt.Sprintf("/dev/%v", name)
cryptDev, err := devicemapper.CreateActiveDevice(name, []devicemapper.Target{
devicemapper.Target{
Length: integritySectors,
Type: "crypt",
Parameters: fmt.Sprintf("capi:gcm(aes)-random %v 0 %v 0 1 integrity:28:aead", hex.EncodeToString(encryptionKey), integrityDevName),
},
})
if err != nil {
unix.Unlink(integrityDevName)
devicemapper.RemoveDevice(integrityDMName)
return fmt.Errorf("failed to create crypt device: %w", err)
}
if err := unix.Mknod(cryptDevName, 0600|unix.S_IFBLK, int(cryptDev)); err != nil {
unix.Unlink(cryptDevName)
devicemapper.RemoveDevice(name)
unix.Unlink(integrityDevName)
devicemapper.RemoveDevice(integrityDMName)
return fmt.Errorf("failed to create crypt device node: %w", err)
}
return nil
}
// InitializeEncryptedBlockDevice initializes a new encrypted block device. This can take a long
// time since all bytes on the mapped block device need to be zeroed.
func InitializeEncryptedBlockDevice(name, baseName string, encryptionKey []byte) error {
integrityPartition, err := os.OpenFile(baseName, os.O_WRONLY, 0)
if err != nil {
return err
}
defer integrityPartition.Close()
zeroed512BBuf := make([]byte, 4096*128)
if _, err := integrityPartition.Write(zeroed512BBuf); err != nil {
return fmt.Errorf("failed to wipe header: %w", err)
}
integrityPartition.Close()
integrityDMName := fmt.Sprintf("%v-integrity", name)
_, err = devicemapper.CreateActiveDevice(integrityDMName, []devicemapper.Target{
devicemapper.Target{
Length: 1,
Type: "integrity",
Parameters: fmt.Sprintf("%v 0 28 J 1 journal_sectors:1024", baseName),
},
})
if err != nil {
return fmt.Errorf("failed to create discovery integrity device: %w", err)
}
if err := devicemapper.RemoveDevice(integrityDMName); err != nil {
return fmt.Errorf("failed to remove discovery integrity device: %w", err)
}
if err := MapEncryptedBlockDevice(name, baseName, encryptionKey); err != nil {
return err
}
blkdev, err := os.OpenFile(fmt.Sprintf("/dev/%v", name), unix.O_DIRECT|os.O_WRONLY, 0000)
if err != nil {
return fmt.Errorf("failed to open new encrypted device for zeroing: %w", err)
}
defer blkdev.Close()
blockSize, err := unix.IoctlGetUint32(int(blkdev.Fd()), unix.BLKSSZGET)
zeroedBuf := make([]byte, blockSize*100) // Make it faster
for {
_, err := blkdev.Write(zeroedBuf)
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
break
}
if err != nil {
return fmt.Errorf("failed to zero-initalize new encrypted device: %w", err)
}
}
return nil
}