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:
Bruce Leidl 2019-02-02 20:42:42 -05:00
parent 0e1a06ae7f
commit 43800cdc6e
4 changed files with 276 additions and 2 deletions

View File

@ -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() {

View File

@ -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
View 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)
}
}
}

View File

@ -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";