diff --git a/citadel-image/src/build.rs b/citadel-image/src/build.rs index 4d0752c..3218c4d 100644 --- a/citadel-image/src/build.rs +++ b/citadel-image/src/build.rs @@ -150,6 +150,11 @@ impl UpdateBuilder { let metainfo = self.generate_metainfo(); fs::write(self.config.workdir_path("metainfo"), &metainfo)?; hdr.set_metainfo_bytes(&metainfo); + + if self.config.channel() == "dev" { + let sig = devkeys().sign(&metainfo); + hdr.set_signature(sig.to_bytes())?; + } Ok(hdr) } diff --git a/citadel-mount/src/rootfs.rs b/citadel-mount/src/rootfs.rs index ce6e180..39ffce2 100644 --- a/citadel-mount/src/rootfs.rs +++ b/citadel-mount/src/rootfs.rs @@ -1,18 +1,17 @@ use std::process::Command; -use libcitadel::{BlockDev,CommandLine,Config,ImageHeader,Partition,Result,verity}; +use libcitadel::{BlockDev,CommandLine,Partition,Result,verity}; use std::path::Path; use std::process::Stdio; use BootSelection; use ResourceImage; pub struct Rootfs { - config: Config, } impl Rootfs { - pub fn new(config: Config) -> Rootfs { - Rootfs { config } + pub fn new() -> Rootfs { + Rootfs {} } pub fn setup(&self) -> Result<()> { @@ -53,17 +52,8 @@ impl Rootfs { self.setup_linear_mapping(&loopdev) } - fn maybe_check_signature(&self, hdr: &ImageHeader) -> Result<()> { - if !CommandLine::nosignatures() { - let signature = hdr.signature(); - let metainfo = hdr.metainfo()?; - metainfo.verify(&self.config, &signature)?; - } - Ok(()) - } - fn setup_resource_verified(&self, img: &ResourceImage) -> Result<()> { - let _ = img.setup_verity_device(&self.config)?; + let _ = img.setup_verity_device()?; Ok(()) } @@ -74,7 +64,10 @@ impl Rootfs { fn setup_partition_verified(&self, partition: &Partition) -> Result<()> { info!("Creating /dev/mapper/rootfs dm-verity device"); - self.maybe_check_signature(partition.header())?; + if !CommandLine::nosignatures() { + partition.header().verify_signature()?; + info!("Image signature is valid for channel {}", partition.metainfo().channel()); + } verity::setup_partition_device(partition)?; Ok(()) } diff --git a/libcitadel/src/header.rs b/libcitadel/src/header.rs index 0913590..b802a1b 100644 --- a/libcitadel/src/header.rs +++ b/libcitadel/src/header.rs @@ -8,7 +8,7 @@ use failure::ResultExt; use toml; use blockdev::AlignedBuffer; -use {BlockDev, Channel, Config, Result}; +use {BlockDev,Result,public_key_for_channel}; /// Expected magic value in header const MAGIC: &[u8] = b"SGOS"; @@ -217,24 +217,25 @@ impl ImageHeader { self.read_bytes(METAINFO_OFFSET + mlen, SIGNATURE_LENGTH) } - pub fn sign_metainfo(&self, channel: &Channel) -> Result<()> { + pub fn set_signature(&self, signature: &[u8]) -> Result<()> { + if signature.len() != SIGNATURE_LENGTH { + bail!("Signature has invalid length: {}", signature.len()); + } let mlen = self.metainfo_len(); - // XXX assert mlen is good - let sig = channel.sign(&self.0.borrow()[8..8 + mlen])?; - self.write_bytes(8 + mlen, sig.to_bytes()); + self.write_bytes(8 + mlen, signature); Ok(()) } - pub fn verify_signature(&self, config: &Config) -> Result<()> { + pub fn verify_signature(&self) -> Result<()> { let metainfo = self.metainfo()?; - let channel = match config.channel(metainfo.channel()) { - Some(channel) => channel, - None => bail!("Cannot verify signature for channel '{}' because it does not exist in configuration file", metainfo.channel()), - }; - channel - .verify(metainfo.bytes(), &self.signature()) - .context("failed to verify header signature")?; - Ok(()) + + if let Some(pubkey) = public_key_for_channel(metainfo.channel())? { + if !pubkey.verify(&self.metainfo_bytes(), &self.signature()) { + bail!("Header signature verification failed"); + } + return Ok(()) + } + Err(format_err!("Cannot verify signature because no public key found for channel '{}'", metainfo.channel())) } pub fn write_header(&self, mut writer: W) -> Result<()> { @@ -315,10 +316,6 @@ impl MetaInfo { } } - fn bytes(&self) -> &[u8] { - &self.bytes - } - pub fn parse_toml(&mut self) -> Result<()> { if !self.is_parsed { self.is_parsed = true; @@ -329,18 +326,6 @@ impl MetaInfo { Ok(()) } - pub fn verify(&self, config: &Config, signature: &[u8]) -> Result<()> { - let channel = match config.channel(self.channel()) { - Some(channel) => channel, - None => bail!("Channel '{}' not found in config file", self.channel()), - }; - channel - .verify(&self.bytes, signature) - .context("Bad metainfo signature in header")?; - - Ok(()) - } - fn toml(&self) -> &MetaInfoToml { self.toml.as_ref().unwrap() } diff --git a/libcitadel/src/keys.rs b/libcitadel/src/keys.rs index 7757803..5cef4c3 100644 --- a/libcitadel/src/keys.rs +++ b/libcitadel/src/keys.rs @@ -16,17 +16,34 @@ pub struct KeyPair([u8; ED25519_PKCS8_V2_LEN]); pub struct Signature(signature::Signature); impl PublicKey { - pub fn from_bytes(bytes: &[u8]) -> Result { + + pub fn from_hex(hex: &str) -> Result { + let bytes = hex.from_hex()?; + if bytes.len() != ED25519_PUBLIC_KEY_LEN { + bail!("Hex encoded public key has invalid length: {}", bytes.len()); + } + Ok(PublicKey::from_bytes(&bytes)) + } + + pub fn to_hex(&self) -> String { + self.0.to_hex() + } + + fn from_bytes(bytes: &[u8]) -> PublicKey { let mut key = [0u8; ED25519_PUBLIC_KEY_LEN]; key.copy_from_slice(bytes); - Ok(PublicKey(key)) + PublicKey(key) } - pub fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> { + + pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool { let signature = Input::from(signature); let data = Input::from(data); let pubkey = Input::from(&self.0); - signature::verify(&signature::ED25519, pubkey, data, signature)?; - Ok(()) + + match signature::verify(&signature::ED25519, pubkey, data, signature) { + Ok(()) => true, + Err(_) => false, + } } } @@ -40,7 +57,6 @@ impl KeyPair { let rng = rand::SystemRandom::new(); let bytes = Ed25519KeyPair::generate_pkcs8(&rng)?; KeyPair::from_bytes(&bytes) - } pub fn from_hex(hex: &str) -> Result { @@ -50,30 +66,32 @@ impl KeyPair { fn from_bytes(bytes: &[u8]) -> Result { let mut pair = [0u8; ED25519_PKCS8_V2_LEN]; pair.copy_from_slice(bytes); + let _ = Ed25519KeyPair::from_pkcs8(Input::from(&pair))?; Ok(KeyPair(pair)) } - pub fn public_key_bytes(&self) -> Vec { - let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0)).expect("failed to parse pkcs8 key"); - pair.public_key_bytes().to_vec() + fn get_keys(&self) -> Ed25519KeyPair { + Ed25519KeyPair::from_pkcs8(Input::from(&self.0)) + .expect("failed to parse pkcs8 key") } - pub fn private_key_bytes(&self) -> Vec { - self.0.to_vec() + pub fn public_key(&self) -> PublicKey { + let keys = self.get_keys(); + PublicKey::from_bytes(keys.public_key_bytes()) } pub fn private_key_hex(&self) -> String { self.0.to_hex() } - pub fn public_key_hex(&self) -> String { - let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0)).expect("failed to parse pkcs8 key"); - pair.public_key_bytes().to_hex() + + pub fn sign(&self, data: &[u8]) -> Signature { + let keys = self.get_keys(); + let signature = keys.sign(data); + Signature(signature) } - pub fn sign(&self, data: &[u8]) -> Result { - let pair = Ed25519KeyPair::from_pkcs8(Input::from(&self.0))?; - let signature = pair.sign(data); - Ok(Signature(signature)) + pub fn verify(&self, data: &[u8], signature: &[u8]) -> bool { + self.public_key().verify(data, signature) } } diff --git a/libcitadel/src/lib.rs b/libcitadel/src/lib.rs index ed8756a..c341f4c 100644 --- a/libcitadel/src/lib.rs +++ b/libcitadel/src/lib.rs @@ -66,16 +66,46 @@ pub mod util; pub mod verity; mod mount; -pub use config::Config; -pub use config::Channel; +pub use config::OsRelease; pub use blockdev::BlockDev; pub use cmdline::CommandLine; pub use header::{ImageHeader,MetaInfo}; pub use partition::Partition; pub use resource::ResourceImage; -pub use keys::KeyPair; +pub use keys::{KeyPair,PublicKey}; pub use mount::Mount; +const DEVKEYS_HEX: &str = + "3053020101300506032b6570042204206ed2849c6c5168e1aebc50005ac3d4a4e84af4889e4e0189bb4c787e6ee0be49a1230321006b652764c62a1de35e7e37af2b743e9a5b82cee2211cf3091d2514441b417f5f"; + +pub fn devkeys() -> KeyPair { + KeyPair::from_hex(&DEVKEYS_HEX) + .expect("Error parsing built in dev channel keys") +} + +pub fn public_key_for_channel(channel: &str) -> Result> { + if channel == "dev" { + return Ok(Some(devkeys().public_key())); + } + + // Look in /etc/os-release + if Some(channel) == OsRelease::citadel_channel() { + if let Some(hex) = OsRelease::citadel_image_pubkey() { + let pubkey = PublicKey::from_hex(hex)?; + return Ok(Some(pubkey)); + } + } + + // Does kernel command line have citadel.channel=name:[hex encoded pubkey] + if Some(channel) == CommandLine::channel_name() { + if let Some(hex) = CommandLine::channel_pubkey() { + let pubkey = PublicKey::from_hex(hex)?; + return Ok(Some(pubkey)) + } + } + + Ok(None) +} pub type Result = result::Result;