Skip to content

Commit

Permalink
Add certificates and certificate checking for IDevID and IAK keys (#669)
Browse files Browse the repository at this point in the history
* Add certificates and certificate checking for IDevID and IAK keys

Signed-off-by: Isaac Matthews <[email protected]>
  • Loading branch information
Isaac-Matthews authored Nov 14, 2023
1 parent 9c7c6fa commit 33d624d
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 2 deletions.
18 changes: 17 additions & 1 deletion keylime-agent.conf
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,23 @@ ek_handle = "generate"
enable_iak_idevid = false
iak_idevid_asymmetric_alg = "rsa"
iak_idevid_name_alg = "sha256"
iak_idevid_template = ""
iak_idevid_template = "H-1"

# The name of the file containing the X509 IAK certificate.
# If set as "default", the "iak-cert.crt" value is used
# If a relative path is set, it will be considered relative from the keylime_dir.
# If an absolute path is set, it is used without change.
#
# To override iak_cert, set KEYLIME_AGENT_IAK_CERT environment variable.
iak_cert = "default"

# The name of the file containing the X509 IDevID certificate.
# If set as "default", the "idevid-cert.crt" value is used
# If a relative path is set, it will be considered relative from the keylime_dir.
# If an absolute path is set, it is used without change.
#
# To override idevid_cert, set KEYLIME_AGENT_IDEVID_CERT environment variable.
idevid_cert = "default"

# Use this option to state the existing TPM ownerpassword.
# This option should be set only when a password is set for the Endorsement
Expand Down
40 changes: 39 additions & 1 deletion keylime-agent/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ pub static DEFAULT_ENABLE_AGENT_MTLS: bool = true;
pub static DEFAULT_KEYLIME_DIR: &str = "/var/lib/keylime";
pub static DEFAULT_SERVER_KEY: &str = "server-private.pem";
pub static DEFAULT_SERVER_CERT: &str = "server-cert.crt";
pub static DEFAULT_IAK_CERT: &str = "iak-cert.crt";
pub static DEFAULT_IDEVID_CERT: &str = "idevid-cert.crt";
pub static DEFAULT_SERVER_KEY_PASSWORD: &str = "";
// The DEFAULT_TRUSTED_CLIENT_CA is relative from KEYLIME_DIR
pub static DEFAULT_TRUSTED_CLIENT_CA: &str = "cv_ca/cacert.crt";
Expand Down Expand Up @@ -58,7 +60,7 @@ pub static DEFAULT_EK_HANDLE: &str = "generate";
pub static DEFAULT_ENABLE_IAK_IDEVID: bool = true;
pub static DEFAULT_IAK_IDEVID_ASYMMETRIC_ALG: &str = "rsa";
pub static DEFAULT_IAK_IDEVID_NAME_ALG: &str = "sha256";
pub static DEFAULT_IAK_IDEVID_TEMPLATE: &str = "";
pub static DEFAULT_IAK_IDEVID_TEMPLATE: &str = "H-1";
pub static DEFAULT_RUN_AS: &str = "keylime:tss";
pub static DEFAULT_AGENT_DATA_PATH: &str = "agent_data.json";
pub static DEFAULT_CONFIG: &str = "/etc/keylime/agent.conf";
Expand All @@ -78,6 +80,8 @@ pub(crate) struct EnvConfig {
pub keylime_dir: Option<String>,
pub server_key: Option<String>,
pub server_cert: Option<String>,
pub iak_cert: Option<String>,
pub idevid_cert: Option<String>,
pub server_key_password: Option<String>,
pub trusted_client_ca: Option<String>,
pub enc_keyname: Option<String>,
Expand Down Expand Up @@ -120,6 +124,8 @@ pub(crate) struct AgentConfig {
pub keylime_dir: String,
pub server_key: String,
pub server_cert: String,
pub iak_cert: String,
pub idevid_cert: String,
pub server_key_password: String,
pub trusted_client_ca: String,
pub enc_keyname: String,
Expand Down Expand Up @@ -199,6 +205,12 @@ impl EnvConfig {
if let Some(ref v) = self.server_cert {
_ = agent.insert("server_cert".to_string(), v.to_string().into());
}
if let Some(ref v) = self.iak_cert {
_ = agent.insert("iak_cert".to_string(), v.to_string().into());
}
if let Some(ref v) = self.idevid_cert {
_ = agent.insert("idevid_cert".to_string(), v.to_string().into());
}
if let Some(ref v) = self.trusted_client_ca {
_ = agent.insert(
"trusted_client_ca".to_string(),
Expand Down Expand Up @@ -395,6 +407,14 @@ impl Source for KeylimeConfig {
"server_cert".to_string(),
self.agent.server_cert.to_string().into(),
);
_ = m.insert(
"iak_cert".to_string(),
self.agent.iak_cert.to_string().into(),
);
_ = m.insert(
"idevid_cert".to_string(),
self.agent.idevid_cert.to_string().into(),
);
_ = m.insert(
"trusted_client_ca".to_string(),
self.agent.trusted_client_ca.to_string().into(),
Expand Down Expand Up @@ -544,6 +564,8 @@ impl Default for AgentConfig {
server_key: "default".to_string(),
server_key_password: DEFAULT_SERVER_KEY_PASSWORD.to_string(),
server_cert: "default".to_string(),
iak_cert: "default".to_string(),
idevid_cert: "default".to_string(),
trusted_client_ca: "default".to_string(),
revocation_actions: DEFAULT_REVOCATION_ACTIONS.to_string(),
revocation_actions_dir: DEFAULT_REVOCATION_ACTIONS_DIR
Expand Down Expand Up @@ -724,6 +746,20 @@ fn config_translate_keywords(
.collect::<Vec<_>>()
.join(", ");

let mut iak_cert = config_get_file_path(
"iak_cert",
&config.agent.iak_cert,
keylime_dir,
DEFAULT_IAK_CERT,
);

let mut idevid_cert = config_get_file_path(
"idevid_cert",
&config.agent.idevid_cert,
keylime_dir,
DEFAULT_IDEVID_CERT,
);

let ek_handle = match config.agent.ek_handle.as_ref() {
"generate" => "".to_string(),
"" => "".to_string(),
Expand Down Expand Up @@ -764,6 +800,8 @@ fn config_translate_keywords(
uuid,
server_key,
server_cert,
iak_cert,
idevid_cert,
trusted_client_ca,
ek_handle,
agent_data_path,
Expand Down
62 changes: 62 additions & 0 deletions keylime-agent/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use openssl::{
x509::store::X509StoreBuilder,
x509::{X509Name, X509},
};
use picky_asn1_x509::SubjectPublicKeyInfo;
use std::{
fs::{read_to_string, set_permissions, File, Permissions},
io::{Read, Write},
Expand All @@ -30,6 +31,19 @@ use crate::{
Error, Result, AES_128_KEY_LEN, AES_256_KEY_LEN, AES_BLOCK_SIZE,
};

// Read a X509 cert in DER format from path
pub(crate) fn load_x509_der(input_cert_path: &Path) -> Result<X509> {
let contents = std::fs::read(input_cert_path).map_err(Error::from)?;

X509::from_der(&contents).map_err(Error::Crypto)
}

pub(crate) fn load_x509_pem(input_cert_path: &Path) -> Result<X509> {
let contents = std::fs::read(input_cert_path).map_err(Error::from)?;

X509::from_pem(&contents).map_err(Error::Crypto)
}

// Read a X509 cert or cert chain and outputs the first certificate
pub(crate) fn load_x509(input_cert_path: &Path) -> Result<X509> {
let mut cert_chain = load_x509_cert_chain(input_cert_path)?;
Expand Down Expand Up @@ -75,6 +89,54 @@ pub(crate) fn write_x509(cert: &X509, file_path: &Path) -> Result<()> {
Ok(())
}

/// Check an x509 certificate contains a specific public key
pub(crate) fn check_x509_key(
cert: &X509,
tpm_key: tss_esapi::structures::Public,
) -> Result<bool> {
// Id:RSA_PSS only added in rust-openssl from v0.10.59; remove this let and use Id::RSA_PSS after update
// Id taken from https://boringssl.googlesource.com/boringssl/+/refs/heads/master/include/openssl/nid.h#4039
let id_rsa_pss: Id = Id::from_raw(912);
match cert.public_key()?.id() {
Id::RSA => {
let cert_n = cert.public_key()?.rsa()?.n().to_vec();
let mut cert_n_str = format!("{:?}", cert_n);
_ = cert_n_str.pop();
_ = cert_n_str.remove(0);
let key = SubjectPublicKeyInfo::try_from(tpm_key)?;
let key_der = picky_asn1_der::to_vec(&key)?;
let key_der_str = format!("{:?}", key_der);

Ok(key_der_str.contains(&cert_n_str))
}
cert_id if cert_id == id_rsa_pss => {
let cert_n = cert.public_key()?.rsa()?.n().to_vec();
let mut cert_n_str = format!("{:?}", cert_n);
_ = cert_n_str.pop();
_ = cert_n_str.remove(0);
let key = SubjectPublicKeyInfo::try_from(tpm_key)?;
let key_der = picky_asn1_der::to_vec(&key)?;
let key_der_str = format!("{:?}", key_der);

Ok(key_der_str.contains(&cert_n_str))
}
Id::EC => {
let cert_n = cert.public_key()?.ec_key()?.public_key_to_der()?;
let mut cert_n_str = format!("{:?}", cert_n);
_ = cert_n_str.pop();
_ = cert_n_str.remove(0);
let key = SubjectPublicKeyInfo::try_from(tpm_key)?;
let key_der = picky_asn1_der::to_vec(&key)?;
let key_der_str = format!("{:?}", key_der);

Ok(key_der_str.contains(&cert_n_str))
}
_ => Err(Error::Other(
"Certificate does not seem to have an RSA or EC key".to_string(),
)),
}
}

/// Read a PEM file and returns the public and private keys
pub(crate) fn load_key_pair(
key_path: &Path,
Expand Down
86 changes: 86 additions & 0 deletions keylime-agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,88 @@ async fn main() -> Result<()> {
(None, None)
};

let iak_cert: Option<X509>;
let idevid_cert: Option<X509>;
// Attempt to load the IAK and IDevID certificates
// Check the certificates contain the keys that have been regenerated by the TPM
// If they do not there is likely a configuration issue where the template has been set to the wrong value
if config.agent.enable_iak_idevid {
iak_cert = match config.agent.iak_cert.as_ref() {
"" => {
debug!("The iak_cert option was not set in the configuration file");
None
}
path => {
let iak_path = Path::new(&path);
if iak_path.exists() {
debug!(
"Loading IAK certificate from {}",
iak_path.display()
);
let iakcert = match crypto::load_x509_der(iak_path) {
Ok(cert) => cert,
Err(error) => crypto::load_x509_pem(iak_path)?,
};
if crypto::check_x509_key(
&iakcert,
iak.clone()
.expect(
"IAK could not be used in cert key check.",
)
.public,
)? {
Some(iakcert)
} else {
error!("IAK template does not match certificate. Check template in configuration.");
return Err(Error::Configuration("IAK template does not match certificate. Check template in configuration.".to_string()));
}
} else {
debug!("Can not find IAK certificate");
None
}
}
};
idevid_cert = match config.agent.idevid_cert.as_ref() {
"" => {
debug!("The idevid_cert option was not set in the configuration file");
None
}
path => {
let idevid_path = Path::new(&path);
if idevid_path.exists() {
debug!(
"Loading IDevID certificate from {}",
idevid_path.display()
);
let idevcert = match crypto::load_x509_der(idevid_path) {
Ok(cert) => cert,
Err(error) => crypto::load_x509_pem(idevid_path)?,
};
if crypto::check_x509_key(
&idevcert,
idevid
.clone()
.expect(
"IDevID could not be used in cert key check.",
)
.public,
)? {
Some(idevcert)
} else {
error!("IDevID template does not match certificate. Check template in configuration.");
return Err(Error::Configuration("IDevID template does not match certificate. Check template in configuration.".to_string()));
}
} else {
debug!("Can not find IDevID certificate");
None
}
}
};
} else {
iak_cert = None;
idevid_cert = None;
}

// Gather EK values and certs
let ek_result = match config.agent.ek_handle.as_ref() {
"" => ctx.create_ek(tpm_encryption_alg, None)?,
Expand Down Expand Up @@ -562,6 +644,8 @@ async fn main() -> Result<()> {
&PublicBuffer::try_from(idevid.public.clone())?
.marshall()?,
),
idevid_cert,
iak_cert,
Some(attest.marshall()?),
Some(signature.marshall()?),
mtls_cert,
Expand All @@ -582,6 +666,8 @@ async fn main() -> Result<()> {
None,
None,
None,
None,
None,
mtls_cert,
config.agent.contact_ip.as_ref(),
config.agent.contact_port,
Expand Down
24 changes: 24 additions & 0 deletions keylime-agent/src/registrar_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ struct Register<'a> {
skip_serializing_if = "Option::is_none"
)]
idevid_tpm: Option<&'a [u8]>,
#[serde(serialize_with = "serialize_maybe_base64")]
idevid_cert: Option<Vec<u8>>,
#[serde(serialize_with = "serialize_maybe_base64")]
iak_cert: Option<Vec<u8>>,
#[serde(
serialize_with = "serialize_maybe_base64",
skip_serializing_if = "Option::is_none"
Expand Down Expand Up @@ -116,6 +120,8 @@ pub(crate) async fn do_register_agent(
aik_tpm: &[u8],
iak_tpm: Option<&[u8]>,
idevid_tpm: Option<&[u8]>,
idevid_cert_x509: Option<X509>,
iak_cert_x509: Option<X509>,
iak_attest: Option<Vec<u8>>,
iak_sign: Option<Vec<u8>>,
mtls_cert_x509: Option<&X509>,
Expand All @@ -127,6 +133,16 @@ pub(crate) async fn do_register_agent(
None => Some("disabled".to_string()),
};

let idevid_cert = match idevid_cert_x509 {
Some(cert) => Some(cert.to_der()?),
None => None,
};

let iak_cert = match iak_cert_x509 {
Some(cert) => Some(cert.to_der()?),
None => None,
};

let ip = if ip.is_empty() {
None
} else {
Expand All @@ -139,6 +155,8 @@ pub(crate) async fn do_register_agent(
aik_tpm,
iak_tpm,
idevid_tpm,
idevid_cert,
iak_cert,
iak_attest,
iak_sign,
mtls_cert,
Expand Down Expand Up @@ -227,6 +245,8 @@ mod tests {
None,
None,
None,
None,
None,
Some(&cert),
"",
0,
Expand Down Expand Up @@ -273,6 +293,8 @@ mod tests {
None,
None,
None,
None,
None,
Some(&cert),
"",
0,
Expand Down Expand Up @@ -315,6 +337,8 @@ mod tests {
None,
None,
None,
None,
None,
Some(&cert),
"",
0,
Expand Down

0 comments on commit 33d624d

Please sign in to comment.