forked from brl/citadel-tools
Initial implementation of keyring
Keyring is an encrypted file to store secrets. The encryption key is derived from the disk decryption passphrase so that the file can be automatically decrypted and processed during boot. The keys contained in the keyring file are loaded into the kernel key store so that they can later be retrieved by other components. Currenly during installation a signing key is generated and stored in the keyring so that the system can transparently sign RealmFS images when the user modifies or updates them.
This commit is contained in:
parent
0e1a06ae7f
commit
43800cdc6e
@ -1,7 +1,7 @@
|
||||
use std::fs;
|
||||
use std::process::exit;
|
||||
|
||||
use libcitadel::{util,Result,ResourceImage,CommandLine,set_verbose,format_error};
|
||||
use libcitadel::{util,Result,ResourceImage,CommandLine,set_verbose,format_error,KeyRing};
|
||||
|
||||
mod live;
|
||||
mod disks;
|
||||
@ -30,10 +30,21 @@ fn do_rootfs() -> Result<()> {
|
||||
if CommandLine::live_mode() || CommandLine::install_mode() {
|
||||
live::live_rootfs()
|
||||
} else {
|
||||
rootfs::setup_rootfs()
|
||||
rootfs::setup_rootfs()?;
|
||||
if let Err(err) = setup_keyring() {
|
||||
warn!("Failed to setup keyring: {}", err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_keyring() -> Result<()> {
|
||||
ResourceImage::ensure_storage_mounted()?;
|
||||
let keyring = KeyRing::load_with_cryptsetup_passphrase("/sysroot/storage/keyring")?;
|
||||
keyring.add_keys_to_kernel()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
fn do_setup() -> Result<()> {
|
||||
if CommandLine::live_mode() || CommandLine::install_mode() {
|
||||
|
@ -10,6 +10,7 @@ use libcitadel::util::{self,mount,exec_cmdline_with_output};
|
||||
use libcitadel::RealmFS;
|
||||
use libcitadel::Result;
|
||||
use libcitadel::OsRelease;
|
||||
use libcitadel::KeyRing;
|
||||
|
||||
const BLKDEACTIVATE: &str = "/sbin/blkdeactivate";
|
||||
const CRYPTSETUP: &str = "/sbin/cryptsetup";
|
||||
@ -370,6 +371,7 @@ impl Installer {
|
||||
|
||||
fn setup_storage(&self) -> Result<()> {
|
||||
if self._type == InstallType::Install {
|
||||
self.create_keyring()?;
|
||||
self.setup_storage_resources()?;
|
||||
self.setup_base_realmfs()?;
|
||||
}
|
||||
@ -383,6 +385,12 @@ impl Installer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_keyring(&self) -> Result<()> {
|
||||
self.header("Creating initial keyring")?;
|
||||
let keyring = KeyRing::create_new();
|
||||
keyring.write(self.storage().join("keyring"), self.passphrase.as_ref().unwrap())
|
||||
}
|
||||
|
||||
fn setup_base_realmfs(&self) -> Result<()> {
|
||||
let realmfs_dir = self.storage().join("realms/realmfs-images");
|
||||
fs::create_dir_all(&realmfs_dir)?;
|
||||
|
253
libcitadel/src/keyring.rs
Normal file
253
libcitadel/src/keyring.rs
Normal file
@ -0,0 +1,253 @@
|
||||
use std::path::{Path,PathBuf};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{self,Read,Write};
|
||||
use std::fs;
|
||||
use std::ffi::CString;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use libc::{self,c_long,c_ulong, c_int, int32_t};
|
||||
|
||||
use hex;
|
||||
use sodiumoxide::randombytes::randombytes_into;
|
||||
use sodiumoxide::crypto::{
|
||||
sign::{
|
||||
self, SEEDBYTES,
|
||||
},
|
||||
pwhash::{
|
||||
self,SALTBYTES, Salt,
|
||||
},
|
||||
secretbox::{
|
||||
self, NONCEBYTES, Nonce,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{Result,Error};
|
||||
|
||||
#[derive(Serialize,Deserialize,Debug)]
|
||||
pub struct KeyRing {
|
||||
keypairs: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl KeyRing {
|
||||
pub fn create_new() -> KeyRing {
|
||||
let seed = KeyRing::new_random_seed();
|
||||
let mut keypairs = HashMap::new();
|
||||
keypairs.insert("realmfs-user".to_string(), hex::encode(&seed.0));
|
||||
KeyRing { keypairs }
|
||||
}
|
||||
|
||||
pub fn load<P: AsRef<Path>>(path: P, passphrase: &str) -> Result<KeyRing> {
|
||||
let mut sbox = SecretBox::new(path.as_ref());
|
||||
sbox.read()?;
|
||||
let mut bytes = sbox.open(passphrase)?;
|
||||
let keyring = toml::from_slice::<KeyRing>(&bytes)?;
|
||||
bytes.iter_mut().for_each(|b| *b = 0);
|
||||
Ok(keyring)
|
||||
}
|
||||
|
||||
pub fn load_with_cryptsetup_passphrase<P: AsRef<Path>>(path: P) -> Result<KeyRing> {
|
||||
let passphrase = KeyRing::get_cryptsetup_passphrase()?;
|
||||
KeyRing::load(path, &passphrase)
|
||||
}
|
||||
|
||||
fn get_cryptsetup_passphrase() -> Result<String> {
|
||||
let key = KernelKey::request_key("user", "cryptsetup")?;
|
||||
info!("Got key {}", key.0);
|
||||
let buf = key.read()?;
|
||||
match buf.split(|b| *b == 0).map(|bs| String::from_utf8_lossy(bs).to_string()).last() {
|
||||
Some(s) => Ok(s),
|
||||
None => Ok(String::new()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_keys_to_kernel(&self) -> Result<()> {
|
||||
for (k,v) in self.keypairs.iter() {
|
||||
info!("Adding {} to kernel keystore", k.as_str());
|
||||
let _key = KernelKey::add_key("user", k.as_str(), v.as_bytes(), KEY_SPEC_USER_KEYRING)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write<P: AsRef<Path>>(&self, path: P, passphrase: &str) -> Result<()> {
|
||||
let salt = pwhash::gen_salt();
|
||||
let nonce = secretbox::gen_nonce();
|
||||
let key = SecretBox::passphrase_to_key(passphrase, &salt)?;
|
||||
let bytes = toml::to_vec(self)?;
|
||||
let ciphertext = secretbox::seal(&bytes, &nonce, &key);
|
||||
|
||||
let mut file = fs::File::create(path.as_ref())?;
|
||||
file.write_all(&salt.0)?;
|
||||
file.write_all(&nonce.0)?;
|
||||
file.write_all(&ciphertext)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn new_random_seed() -> sign::Seed {
|
||||
let mut seedbuf = [0; SEEDBYTES];
|
||||
randombytes_into(&mut seedbuf);
|
||||
sign::Seed(seedbuf)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Drop for KeyRing {
|
||||
fn drop(&mut self) {
|
||||
for (_,v) in self.keypairs.drain() {
|
||||
v.into_bytes().iter_mut().for_each(|b| *b = 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SecretBox {
|
||||
path: PathBuf,
|
||||
salt: Salt,
|
||||
nonce: Nonce,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl SecretBox {
|
||||
fn new(path: &Path) -> SecretBox {
|
||||
SecretBox {
|
||||
path: path.to_path_buf(),
|
||||
salt: Salt([0; SALTBYTES]),
|
||||
nonce: Nonce([0; NONCEBYTES]),
|
||||
data: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self) -> Result<()> {
|
||||
if !self.data.is_empty() {
|
||||
self.data.clear();
|
||||
}
|
||||
let mut file = fs::File::open(&self.path)?;
|
||||
file.read_exact(&mut self.salt.0)?;
|
||||
file.read_exact(&mut self.nonce.0)?;
|
||||
file.read_to_end(&mut self.data)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn open(&self, passphrase: &str) -> Result<Vec<u8>> {
|
||||
let key = SecretBox::passphrase_to_key(passphrase, &self.salt)?;
|
||||
let result = secretbox::open(&self.data, &self.nonce, &key)
|
||||
.map_err(|_| format_err!("Failed to decrypt {}", self.path.display()))?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn passphrase_to_key(passphrase: &str, salt: &Salt) -> Result<secretbox::Key> {
|
||||
let mut keybuf = [0; secretbox::KEYBYTES];
|
||||
pwhash::derive_key(&mut keybuf, passphrase.as_bytes(), salt, pwhash::OPSLIMIT_INTERACTIVE, pwhash::MEMLIMIT_INTERACTIVE)
|
||||
.map_err(|_| format_err!("Failed to derive key"))?;
|
||||
Ok(secretbox::Key(keybuf))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
const KEYCTL_READ : c_int = 11; // read a key or keyring's contents
|
||||
const KEY_SPEC_USER_KEYRING : c_int = -4; // - key ID for UID-specific keyring
|
||||
|
||||
|
||||
pub struct KernelKey(int32_t);
|
||||
|
||||
impl KernelKey {
|
||||
pub fn request_key(key_type: &str, description: &str) -> Result<KernelKey> {
|
||||
let key_type = CString::new(key_type).unwrap();
|
||||
let description = CString::new(description).unwrap();
|
||||
let serial = _request_key(key_type.as_ptr(), description.as_ptr())?;
|
||||
Ok(KernelKey(serial as i32))
|
||||
}
|
||||
|
||||
pub fn add_key(key_type: &str, description: &str, payload: &[u8], ring_id: c_int) -> Result<KernelKey> {
|
||||
let key_type = CString::new(key_type).unwrap();
|
||||
let description = CString::new(description).unwrap();
|
||||
let serial = _add_key(key_type.as_ptr(), description.as_ptr(), payload.as_ptr(), payload.len(), ring_id)?;
|
||||
Ok(KernelKey(serial as i32))
|
||||
}
|
||||
|
||||
pub fn read(&self) -> Result<Vec<u8>> {
|
||||
let mut size = 0;
|
||||
loop {
|
||||
size = match self.buffer_request(KEYCTL_READ, size) {
|
||||
BufferResult::Err(err) => return Err(err),
|
||||
BufferResult::Ok(buffer) => return Ok(buffer),
|
||||
BufferResult::TooSmall(sz) => sz + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn buffer_request(&self, command: c_int, size: usize) -> BufferResult {
|
||||
if size == 0 {
|
||||
return match keyctl1(command, self.id()) {
|
||||
Err(err) => BufferResult::Err(err),
|
||||
Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctl returned bad size")),
|
||||
Ok(n) => BufferResult::TooSmall(n as usize),
|
||||
};
|
||||
}
|
||||
let mut buffer = vec![0u8; size];
|
||||
match keyctl3(command, self.id(), buffer.as_ptr() as u64, buffer.len() as u64) {
|
||||
Err(err) => BufferResult::Err(err),
|
||||
Ok(n) if n < 0 => BufferResult::Err(format_err!("keyctrl returned bad size {}", n)),
|
||||
Ok(sz) if size >= (sz as usize) => {
|
||||
let sz = sz as usize;
|
||||
if size > sz {
|
||||
buffer.truncate(sz)
|
||||
}
|
||||
BufferResult::Ok(buffer)
|
||||
},
|
||||
Ok(n) => BufferResult::TooSmall(n as usize)
|
||||
}
|
||||
}
|
||||
|
||||
fn id(&self) -> c_ulong {
|
||||
self.0 as c_ulong
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum BufferResult {
|
||||
Ok(Vec<u8>),
|
||||
Err(Error),
|
||||
TooSmall(usize),
|
||||
}
|
||||
|
||||
|
||||
fn keyctl1(command: c_int, arg2: c_ulong) -> Result<c_long> {
|
||||
_keyctl(command, arg2, 0, 0, 0)
|
||||
}
|
||||
|
||||
fn keyctl3(command: c_int, arg2: c_ulong, arg3: c_ulong, arg4: c_ulong) -> Result<c_long> {
|
||||
_keyctl(command, arg2, arg3, arg4, 0)
|
||||
}
|
||||
|
||||
fn _keyctl(command: c_int, arg2: c_ulong, arg3: c_ulong, arg4: c_ulong, arg5: c_ulong) -> Result<c_long> {
|
||||
unsafe {
|
||||
let r = libc::syscall(libc::SYS_keyctl, command, arg2, arg3, arg4, arg5);
|
||||
if r == -1 {
|
||||
Err(io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _request_key(key_type: *const c_char, description: *const c_char) -> Result<c_long> {
|
||||
unsafe {
|
||||
let r = libc::syscall(libc::SYS_request_key, key_type, description, 0, 0);
|
||||
if r == -1 {
|
||||
Err(io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn _add_key(key_type: *const c_char, description: *const c_char, payload: *const u8, plen: usize, ring_id: c_int) -> Result<c_long> {
|
||||
unsafe {
|
||||
let r = libc::syscall(libc::SYS_add_key, key_type, description, payload, plen, ring_id);
|
||||
if r == -1 {
|
||||
Err(io::Error::last_os_error().into())
|
||||
} else {
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
}
|
@ -59,6 +59,7 @@ pub mod util;
|
||||
pub mod verity;
|
||||
mod mount;
|
||||
mod realmfs;
|
||||
mod keyring;
|
||||
|
||||
pub use crate::config::OsRelease;
|
||||
pub use crate::blockdev::BlockDev;
|
||||
@ -69,6 +70,7 @@ pub use crate::resource::ResourceImage;
|
||||
pub use crate::keys::{KeyPair,PublicKey};
|
||||
pub use crate::mount::Mount;
|
||||
pub use crate::realmfs::RealmFS;
|
||||
pub use crate::keyring::KeyRing;
|
||||
|
||||
const DEVKEYS_HEX: &str = "bc02a3a4fd4a0471a8cb2f96d8be0a0a2d060798c024e60d7a98482f23197fc0";
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user