From 360125b615c31b89a4be6261352cc92a2e8cddb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natalie=20Klestrup=20R=C3=B6ijezon?= Date: Wed, 2 Jul 2025 14:39:41 +0200 Subject: [PATCH] Move krb5 (and -sys) out of tree krb5 and krb5-sys have been moved to their own repo, at https://github.com/stackabletech/krb5-rs. krb5-provision-keytab stays in secret-operator since it's a helper binary for secret-operator specifically, not a component intended for reuse. --- Cargo.lock | 6 +- Cargo.nix | 38 +-- Cargo.toml | 3 +- crate-hashes.json | 2 + rust/krb5-provision-keytab/Cargo.toml | 2 +- rust/krb5-sys/Cargo.toml | 15 - rust/krb5-sys/build.rs | 45 --- rust/krb5-sys/src/lib.rs | 6 - rust/krb5-sys/wrapper.h | 3 - rust/krb5/Cargo.toml | 14 - rust/krb5/src/kadm5.rs | 224 ------------ rust/krb5/src/lib.rs | 468 -------------------------- rust/krb5/src/profile.rs | 78 ----- 13 files changed, 22 insertions(+), 882 deletions(-) delete mode 100644 rust/krb5-sys/Cargo.toml delete mode 100644 rust/krb5-sys/build.rs delete mode 100644 rust/krb5-sys/src/lib.rs delete mode 100644 rust/krb5-sys/wrapper.h delete mode 100644 rust/krb5/Cargo.toml delete mode 100644 rust/krb5/src/kadm5.rs delete mode 100644 rust/krb5/src/lib.rs delete mode 100644 rust/krb5/src/profile.rs diff --git a/Cargo.lock b/Cargo.lock index 501b042a..b29404ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,7 +1611,8 @@ dependencies = [ [[package]] name = "krb5" -version = "0.0.0-dev" +version = "0.1.0" +source = "git+https://github.com/stackabletech/krb5-rs.git?tag=v0.1.0#cba32789257540e31f262647f9b59cb592e2e011" dependencies = [ "krb5-sys", "snafu 0.8.5", @@ -1619,7 +1620,8 @@ dependencies = [ [[package]] name = "krb5-sys" -version = "0.0.0-dev" +version = "0.1.0" +source = "git+https://github.com/stackabletech/krb5-rs.git?tag=v0.1.0#cba32789257540e31f262647f9b59cb592e2e011" dependencies = [ "bindgen", "pkg-config", diff --git a/Cargo.nix b/Cargo.nix index 74ef2859..90017823 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -38,26 +38,6 @@ rec { # You can override the features with # workspaceMembers."${crateName}".build.override { features = [ "default" "feature1" ... ]; }. workspaceMembers = { - "krb5" = rec { - packageId = "krb5"; - build = internal.buildRustCrateWithFeatures { - packageId = "krb5"; - }; - - # Debug support which might change between releases. - # File a bug if you depend on any for non-debug work! - debug = internal.debugCrate { inherit packageId; }; - }; - "krb5-sys" = rec { - packageId = "krb5-sys"; - build = internal.buildRustCrateWithFeatures { - packageId = "krb5-sys"; - }; - - # Debug support which might change between releases. - # File a bug if you depend on any for non-debug work! - debug = internal.debugCrate { inherit packageId; }; - }; "p12" = rec { packageId = "p12"; build = internal.buildRustCrateWithFeatures { @@ -5096,9 +5076,14 @@ rec { }; "krb5" = rec { crateName = "krb5"; - version = "0.0.0-dev"; + version = "0.1.0"; edition = "2021"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/krb5; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech/krb5-rs.git"; + rev = "cba32789257540e31f262647f9b59cb592e2e011"; + sha256 = "148zr0q04163hpirkrff5q7cbxqgwzzxh0091zr4g23x7l64jh39"; + }; authors = [ "Stackable GmbH " ]; @@ -5116,9 +5101,14 @@ rec { }; "krb5-sys" = rec { crateName = "krb5-sys"; - version = "0.0.0-dev"; + version = "0.1.0"; edition = "2021"; - src = lib.cleanSourceWith { filter = sourceFilter; src = ./rust/krb5-sys; }; + workspace_member = null; + src = pkgs.fetchgit { + url = "https://github.com/stackabletech/krb5-rs.git"; + rev = "cba32789257540e31f262647f9b59cb592e2e011"; + sha256 = "148zr0q04163hpirkrff5q7cbxqgwzzxh0091zr4g23x7l64jh39"; + }; libName = "krb5_sys"; authors = [ "Stackable GmbH " diff --git a/Cargo.toml b/Cargo.toml index f30bd428..26cdbb7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,10 @@ repository = "https://github.com/stackabletech/secret-operator" [workspace.dependencies] stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = ["time", "telemetry"], tag = "stackable-operator-0.93.0" } +krb5 = { git = "https://github.com/stackabletech/krb5-rs.git", tag = "v0.1.0" } anyhow = "1.0" async-trait = "0.1" -bindgen = "0.71" built = { version = "0.8", features = ["chrono", "git2"] } byteorder = "1.5" clap = "4.5" @@ -28,7 +28,6 @@ libc = "0.2" native-tls = "0.2" openssl = "0.10" pin-project = "1.1" -pkg-config = "0.3" prost = "0.13" prost-types = "0.13" rand = "0.9" diff --git a/crate-hashes.json b/crate-hashes.json index fe60e4fd..bba52328 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,4 +1,6 @@ { + "git+https://github.com/stackabletech/krb5-rs.git?tag=v0.1.0#krb5-sys@0.1.0": "148zr0q04163hpirkrff5q7cbxqgwzzxh0091zr4g23x7l64jh39", + "git+https://github.com/stackabletech/krb5-rs.git?tag=v0.1.0#krb5@0.1.0": "148zr0q04163hpirkrff5q7cbxqgwzzxh0091zr4g23x7l64jh39", "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.0#k8s-version@0.1.3": "1dv5vgilcpj1h88pdzzb94aj85nrm5bm0qkpplwd5b0m857b6rmp", "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.0#stackable-operator-derive@0.3.1": "1dv5vgilcpj1h88pdzzb94aj85nrm5bm0qkpplwd5b0m857b6rmp", "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.93.0#stackable-operator@0.93.0": "1dv5vgilcpj1h88pdzzb94aj85nrm5bm0qkpplwd5b0m857b6rmp", diff --git a/rust/krb5-provision-keytab/Cargo.toml b/rust/krb5-provision-keytab/Cargo.toml index b90bca3e..b0e9b622 100644 --- a/rust/krb5-provision-keytab/Cargo.toml +++ b/rust/krb5-provision-keytab/Cargo.toml @@ -9,8 +9,8 @@ repository.workspace = true publish = false [dependencies] -krb5 = { path = "../krb5" } stackable-secret-operator-crd-utils = { path = "../crd-utils" } +krb5.workspace = true byteorder.workspace = true futures.workspace = true diff --git a/rust/krb5-sys/Cargo.toml b/rust/krb5-sys/Cargo.toml deleted file mode 100644 index 28820741..00000000 --- a/rust/krb5-sys/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "krb5-sys" -description = "Raw bindings for libkrb5 and libkadm5" -version.workspace = true -authors.workspace = true -license.workspace = true -edition.workspace = true -repository.workspace = true -publish = false - -[dependencies] - -[build-dependencies] -bindgen.workspace = true -pkg-config.workspace = true diff --git a/rust/krb5-sys/build.rs b/rust/krb5-sys/build.rs deleted file mode 100644 index e88cdca6..00000000 --- a/rust/krb5-sys/build.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::{env, path::PathBuf}; - -fn main() { - println!("cargo:rerun-if-changed=wrapper.h"); - let krb5_cfg = pkg_config::probe_library("krb5").expect("Failed to probe pkg-config for krb5"); - let _kadm_cfg = pkg_config::probe_library("kadm-client") - .expect("Failed to probe pkg-config for kadmin-client"); - let bindings = bindgen::builder() - .header("wrapper.h") - .clang_args( - krb5_cfg - .include_paths - .iter() - .map(|path| format!("-I{}", path.display())), - ) - .allowlist_function("^krb5_.*") - .allowlist_function("^kadm5_.*") - .allowlist_function("error_message") - .allowlist_function("^profile_.*") - .allowlist_var("KRB5_.*") - .allowlist_var("KADM5_.*") - .allowlist_var("ENCTYPE_.*") - // Variadic functions generate bindings that rustc on ARM64 considers FFI-unsafe. - // We don't actually use them, so we can just blocklist the types, and any function - // variants that use them. - .blocklist_type("va_list") - .blocklist_type("__builtin_va_list") - .blocklist_type("__va_list_tag") - .blocklist_function(".*_vset_.*") - .blocklist_function(".*_vwrap_.*") - .blocklist_function(".*_vprepend_.*") - .blocklist_function(".*_va") - .new_type_alias("krb5_error_code") - .new_type_alias("kadm5_ret_t") - .must_use_type("krb5_error_code") - .must_use_type("kadm5_ret_t") - // .default_macro_constant_type(bindgen::MacroTypeVariation::Signed) - .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - .generate() - .expect("Unable to generate bindings"); - let out_path = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR not set")); - bindings - .write_to_file(out_path.join("bindings.rs")) - .expect("Unable to write bindings"); -} diff --git a/rust/krb5-sys/src/lib.rs b/rust/krb5-sys/src/lib.rs deleted file mode 100644 index 5a6b3814..00000000 --- a/rust/krb5-sys/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)] -// krb5 docs are not written following the Rust conventions, -// so some annotations are misinterpreted by rustdoc as links. -#![allow(rustdoc::broken_intra_doc_links)] - -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/rust/krb5-sys/wrapper.h b/rust/krb5-sys/wrapper.h deleted file mode 100644 index a46934ea..00000000 --- a/rust/krb5-sys/wrapper.h +++ /dev/null @@ -1,3 +0,0 @@ -#include -#include -#include diff --git a/rust/krb5/Cargo.toml b/rust/krb5/Cargo.toml deleted file mode 100644 index 238fe73c..00000000 --- a/rust/krb5/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "krb5" -description = "Safe wrapper library for libkrb5 and libkadm5" -version.workspace = true -authors.workspace = true -license.workspace = true -edition.workspace = true -repository.workspace = true -publish = false - -[dependencies] -krb5-sys = { path = "../krb5-sys" } - -snafu.workspace = true diff --git a/rust/krb5/src/kadm5.rs b/rust/krb5/src/kadm5.rs deleted file mode 100644 index d45beb9b..00000000 --- a/rust/krb5/src/kadm5.rs +++ /dev/null @@ -1,224 +0,0 @@ -use std::{ - ffi::{CStr, CString, c_char, c_int}, - fmt::Display, - slice, -}; - -use crate::{KeyblockRef, KrbContext, Principal}; - -/// An error generated by libkadm5 -#[derive(Debug)] -pub struct Error { - pub code: krb5_sys::kadm5_ret_t, -} -impl Error { - fn from_ret(code: krb5_sys::kadm5_ret_t) -> Result<(), Self> { - if code.0 == krb5_sys::kadm5_ret_t(krb5_sys::KADM5_OK.into()).0 { - Ok(()) - } else { - Err(Self { code }) - } - } -} -impl std::error::Error for Error {} -impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let msg = unsafe { CStr::from_ptr(krb5_sys::error_message(self.code.0)) }; - f.write_str(&msg.to_string_lossy()) - } -} - -/// Well-known error codes. This is not exhaustive. -pub mod error_code { - pub use krb5_sys::kadm5_ret_t; - pub const DUP: i64 = krb5_sys::KADM5_DUP as _; -} - -/// Credentials that can be used to authenticate to kadm5. -pub enum Credential { - /// A key stored in a keytab. - ServiceKey { - /// The path to the keytab containing the key. - keytab: CString, - }, -} - -#[derive(Default)] -/// Configuration fields for initializing libkadm5. -pub struct ConfigParams { - /// The default realm. - pub default_realm: Option, - /// The hostname of the kadmin5 server. - pub admin_server: Option, - /// The port of the kadmin5 server. - pub kadmind_port: Option, -} -impl ConfigParams { - /// Return a [`krb5_sys::kadm5_config_params`] view of `self` - /// - /// The returned `kadm5_config_params` has the same lifetime as `&self`. It - /// should be considered unusable as soon as `self` is moved, modified, - /// or dropped. - fn as_c(&self) -> krb5_sys::kadm5_config_params { - let mut c = unsafe { std::mem::zeroed::() }; - if let Some(default_realm) = &self.default_realm { - c.realm = default_realm.as_ptr() as *mut c_char; - c.mask |= i64::from(krb5_sys::KADM5_CONFIG_REALM); - } - if let Some(admin_server) = &self.admin_server { - c.admin_server = admin_server.as_ptr() as *mut c_char; - c.mask |= i64::from(krb5_sys::KADM5_CONFIG_ADMIN_SERVER); - } - if let Some(kadmind_port) = self.kadmind_port { - c.kadmind_port = kadmind_port; - c.mask |= i64::from(krb5_sys::KADM5_CONFIG_KADMIND_PORT); - } - c - } -} - -/// A kadmin5 client. -pub struct ServerHandle<'a> { - ctx: &'a KrbContext, - raw: *mut std::ffi::c_void, -} -impl<'a> ServerHandle<'a> { - /// Create a new kadmin5 client. - /// - /// `client_name`: The principal of the kadmin5 client. - /// `service_name`: The expected principal name of the kadmin5 server. Leave `None` to use the default principal. - /// `credential`: The client credentials to be used. - /// `params`: Any optional settings. - pub fn new( - ctx: &'a KrbContext, - client_name: &CStr, - service_name: Option<&CStr>, - credential: &Credential, - params: &ConfigParams, - ) -> Result { - let mut server_handle = std::ptr::null_mut(); - let mut params = params.as_c(); - - match credential { - Credential::ServiceKey { keytab } => unsafe { - Error::from_ret(krb5_sys::kadm5_init_with_skey( - ctx.raw, - client_name.as_ptr().cast_mut(), - keytab.as_ptr().cast_mut(), - service_name - .as_ref() - .map_or(std::ptr::null_mut(), |sn| sn.as_ptr().cast_mut()), - &mut params, - krb5_sys::KADM5_STRUCT_VERSION_1, - krb5_sys::KADM5_API_VERSION_4, - std::ptr::null_mut(), - &mut server_handle, - ))?; - }, - } - Ok(Self { - ctx, - raw: server_handle, - }) - } - - /// Create a new principal. - pub fn create_principal(&self, principal: &Principal) -> Result<(), Error> { - unsafe { - let mut ent: krb5_sys::_kadm5_principal_ent_t = std::mem::zeroed(); - let mask = krb5_sys::KADM5_PRINCIPAL; - ent.principal = principal.raw; - Error::from_ret(krb5_sys::kadm5_create_principal( - self.raw, - &mut ent, - mask.into(), - std::ptr::null_mut(), - )) - } - } - - /// Get the keys of a principal. - /// - /// `kvno` may specify a specific key version to retrieve. Set to [`KVNO_ALL`] to retrieve all keys. - pub fn get_principal_keys( - &self, - principal: &Principal, - kvno: krb5_sys::krb5_kvno, - ) -> Result { - let mut key_data = std::ptr::null_mut(); - let mut key_count = 0; - unsafe { - Error::from_ret(krb5_sys::kadm5_get_principal_keys( - self.raw, - principal.raw, - kvno, - &mut key_data, - &mut key_count, - ))?; - } - Ok(KeyDataVec { - ctx: self.ctx, - raw: key_data, - key_count, - }) - } -} -impl Drop for ServerHandle<'_> { - fn drop(&mut self) { - unsafe { - Error::from_ret(krb5_sys::kadm5_destroy(self.raw)) - .expect("failed to destroy kadmin5 server handle"); - } - } -} -/// Parameter for [`ServerHandle::get_principal_keys`] that returns all keys, regardless of KVNO. -pub const KVNO_ALL: krb5_sys::krb5_kvno = 0; - -/// An unowned reference to a [`Principal`]'s key. -// SAFETY: 'a must not outlive the object that owns the `KeyDataRef` -pub struct KeyDataRef<'a> { - pub kvno: krb5_sys::krb5_kvno, - pub keyblock: KeyblockRef<'a>, - // salt: krb5_sys::krb5_keysalt, -} -/// An owned reference to a set of keys associated with a [`Principal`]. -pub struct KeyDataVec<'a> { - ctx: &'a KrbContext, - raw: *mut krb5_sys::kadm5_key_data, - key_count: c_int, -} -impl KeyDataVec<'_> { - // SAFETY: returned &kadm_key_data must not outlive &self - fn as_slice(&self) -> &[krb5_sys::kadm5_key_data] { - unsafe { - slice::from_raw_parts( - self.raw, - self.key_count - .try_into() - .expect("keydata vec must have a non-negative number of keys"), - ) - } - } - - /// Iterate over all associated keys. - #[allow(clippy::needless_lifetimes)] - // SAFETY: returned KeyDataRef must not outlive &self - pub fn keys<'a>(&'a self) -> impl Iterator> { - self.as_slice().iter().map(|raw| KeyDataRef { - kvno: raw.kvno, - keyblock: KeyblockRef { - ctx: self.ctx, - raw: &raw.key, - }, - // salt: raw.salt, - }) - } -} -impl Drop for KeyDataVec<'_> { - fn drop(&mut self) { - Error::from_ret(unsafe { - krb5_sys::kadm5_free_kadm5_key_data(self.ctx.raw, self.key_count, self.raw) - }) - .expect("failed to destroy keydata vector") - } -} diff --git a/rust/krb5/src/lib.rs b/rust/krb5/src/lib.rs deleted file mode 100644 index 405fdcf1..00000000 --- a/rust/krb5/src/lib.rs +++ /dev/null @@ -1,468 +0,0 @@ -//! Safe wrapper library for libkrb5 and libkadm5 -//! -//! The primary entry point is [`KrbContext`]. - -use std::{ - ffi::{CStr, c_char, c_int}, - fmt::{Debug, Display}, - ops::Deref, -}; - -use krb5_sys::krb5_kt_resolve; -use profile::Profile; -use snafu::{ResultExt, Snafu}; - -pub mod kadm5; -pub mod profile; - -/// An error generated by libkrb5, or from interacting with it -#[derive(Debug, Snafu)] -pub enum Error { - #[snafu(display("{reason}"))] - Krb5 { reason: Krb5Error }, - - #[snafu(display("{string_name} is too long"))] - StringTooLong { - source: std::num::TryFromIntError, - string_name: &'static str, - }, -} -/// An error generated by libkrb5 -#[derive(Debug)] -pub struct Krb5Error { - message: String, - pub code: krb5_sys::krb5_error_code, -} -impl Error { - // SAFETY: must be called exactly once, immediately after each potentially - // error-generating call that interacts with ctx - // ctx should be None iff the error happened during ctx init - unsafe fn from_call_result( - ctx: Option<&KrbContext>, - code: krb5_sys::krb5_error_code, - ) -> Result<(), Self> { - if code.0 == 0 { - Ok(()) - } else { - let message = { - // copy message into rust str, to avoid keeping a dependency on ctx - // also, krb5_get_error_message may only be called once per error - let raw_ctx = ctx.map_or(std::ptr::null_mut(), |c| c.raw); - let c_msg = unsafe { krb5_sys::krb5_get_error_message(raw_ctx, code) }; - let rust_msg = CStr::from_ptr(c_msg).to_string_lossy().into_owned(); - unsafe { krb5_sys::krb5_free_error_message(raw_ctx, c_msg) } - rust_msg - }; - Krb5Snafu { - reason: Krb5Error { message, code }, - } - .fail() - } - } -} -impl Display for Krb5Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result where { - f.write_str(&self.message) - } -} - -/// An instance of the krb5 client -/// -/// Most other `krb5` data structures are linked to a specific `KrbContext`, -/// and must not be mixed between them or used past the lifetime of the owning `KrbContext`. -/// In Rust-land we represent this as them taking a borrow on their `KrbContext`. -/// -/// `KrbContext` is _not_ thread-safe, since it is mutated internally by libkrb5. -pub struct KrbContext { - raw: krb5_sys::krb5_context, -} -impl KrbContext { - /// Create a new context using the default configuration sources. - pub fn new() -> Result { - let mut ctx = std::ptr::null_mut(); - unsafe { Error::from_call_result(None, krb5_sys::krb5_init_context(&mut ctx)) }?; - Ok(Self { raw: ctx }) - } - - /// Create a new context from a given [`Profile`]. - /// `profile` will be copied into the created `Context`. - pub fn from_profile(profile: &Profile) -> Result { - let mut ctx = std::ptr::null_mut(); - unsafe { - Error::from_call_result( - None, - krb5_sys::krb5_init_context_profile(profile.raw, 0, &mut ctx), - ) - }?; - Ok(Self { raw: ctx }) - } - - /// Parse a Kerberos principal into a [`Principal`]. - /// - /// This will be done in the scope of the context, for example the context's default realm will be used if - /// none is specified in `princ_name`. - pub fn parse_principal_name(&self, princ_name: &CStr) -> Result { - let mut principal = std::ptr::null_mut(); - unsafe { - Error::from_call_result( - None, - krb5_sys::krb5_parse_name(self.raw, princ_name.as_ptr(), &mut principal), - ) - }?; - Ok(Principal { - ctx: self, - raw: principal, - }) - } - - /// Get the default realm configured for this context. - pub fn default_realm(&self) -> Result { - let mut realm: *mut c_char = std::ptr::null_mut(); - unsafe { - Error::from_call_result( - Some(self), - krb5_sys::krb5_get_default_realm(self.raw, &mut realm), - )?; - Ok(DefaultRealm { - ctx: self, - raw: realm, - }) - } - } -} -impl Drop for KrbContext { - fn drop(&mut self) { - unsafe { - krb5_sys::krb5_free_context(self.raw); - } - } -} - -/// The default realm name for a [`KrbContext`]. -/// -/// Created by [`KrbContext::default_realm`]. -pub struct DefaultRealm<'a> { - ctx: &'a KrbContext, - raw: *const c_char, -} -impl Deref for DefaultRealm<'_> { - type Target = CStr; - - fn deref(&self) -> &Self::Target { - unsafe { CStr::from_ptr(self.raw) } - } -} -impl Drop for DefaultRealm<'_> { - fn drop(&mut self) { - unsafe { krb5_sys::krb5_free_default_realm(self.ctx.raw, self.raw.cast_mut()) } - } -} - -/// A parsed Kerberos principal name. -/// -/// Created by [`KrbContext::parse_principal_name`]. -pub struct Principal<'a> { - ctx: &'a KrbContext, - raw: krb5_sys::krb5_principal, -} -impl<'a> Principal<'a> { - /// The default salt when deriving keys for this principal. - pub fn default_salt(&self) -> Result, Error> { - unsafe { - let mut salt = std::mem::zeroed::(); - Error::from_call_result( - Some(self.ctx), - krb5_sys::krb5_principal2salt(self.ctx.raw, self.raw, &mut salt), - )?; - Ok(KrbData { - ctx: self.ctx, - raw: salt, - }) - } - } - - /// Converts the parsed principal back into a string representation. - /// - /// The [`Display`] instance is equivalent to `self.unparse(PrincipalUnparseOptions::default())`. - pub fn unparse(&self, options: PrincipalUnparseOptions) -> Result { - let mut raw_name = std::ptr::null_mut(); - unsafe { - Error::from_call_result( - Some(self.ctx), - krb5_sys::krb5_unparse_name_flags( - self.ctx.raw, - self.raw, - options.to_flags(), - &mut raw_name, - ), - )?; - }; - // We need to take ownership before freeing it - let name: String = unsafe { CStr::from_ptr(raw_name) } - .to_string_lossy() - .into_owned(); - unsafe { krb5_sys::krb5_free_unparsed_name(self.ctx.raw, raw_name) } - Ok(name) - } -} -impl Drop for Principal<'_> { - fn drop(&mut self) { - unsafe { - krb5_sys::krb5_free_principal(self.ctx.raw, self.raw); - } - } -} -impl Display for Principal<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let name = self.unparse(PrincipalUnparseOptions::default()); - f.write_str(name.as_deref().unwrap_or("(invalid)")) - } -} -impl From<&Principal<'_>> for String { - fn from(princ: &Principal<'_>) -> Self { - princ.to_string() - } -} - -/// Optional settings for [`Principal::unparse`]. -#[derive(Default, Clone, Copy)] -pub struct PrincipalUnparseOptions { - /// Controls whether the realm is included. - pub realm: PrincipalRealmDisplayMode, - /// Special characters are not quoted in display mode, even if this would generate a principal string that cannot be parsed. - pub for_display: bool, -} - -/// See [`PrincipalUnparseOptions::realm`]. -#[derive(Default, Clone, Copy)] -pub enum PrincipalRealmDisplayMode { - /// The realm is always included. - #[default] - Always, - /// The realm is only included if it is not the default realm. - IfForeign, - /// The realm is never included. This may create ambiguity in multi-realm configurations. - Never, -} -impl PrincipalUnparseOptions { - fn to_flags(self) -> c_int { - let realm = match self.realm { - PrincipalRealmDisplayMode::Always => 0, - PrincipalRealmDisplayMode::IfForeign => krb5_sys::KRB5_PRINCIPAL_UNPARSE_SHORT as c_int, - PrincipalRealmDisplayMode::Never => krb5_sys::KRB5_PRINCIPAL_UNPARSE_NO_REALM as c_int, - }; - let for_display = match self.for_display { - true => krb5_sys::KRB5_PRINCIPAL_UNPARSE_DISPLAY as c_int, - false => 0, - }; - realm | for_display - } -} - -/// A reference to a Kerberos keyblock. -// SAFETY: 'a must not outlive the object that owns the `KeyblockRef` -pub struct KeyblockRef<'a> { - // We need to constrain the lifetime to the owning KrbContext even if it is never actually used - #[allow(dead_code)] - ctx: &'a KrbContext, - raw: *const krb5_sys::krb5_keyblock, -} - -/// An owned reference to a Kerberos keyblock. -pub struct Keyblock<'a> { - ctx: &'a KrbContext, - raw: *mut krb5_sys::krb5_keyblock, -} -impl<'a> Keyblock<'a> { - /// Create a new zero-initialized keyblock of a given size. - pub fn new( - ctx: &'a KrbContext, - enctype: krb5_sys::krb5_enctype, - len: usize, - ) -> Result { - unsafe { - let mut keyblock: *mut krb5_sys::krb5_keyblock = std::ptr::null_mut(); - Error::from_call_result( - Some(ctx), - krb5_sys::krb5_init_keyblock(ctx.raw, enctype, len, &mut keyblock), - )?; - let mut kb = Self { ctx, raw: keyblock }; - // krb5_init_keyblock does not guarantee that the keyblock is zeroed, so let's clear it ourselves to avoid leaks - kb.contents_mut()?.fill(0); - Ok(kb) - } - } - - /// Derive a key from a given password. - /// - /// Some well-known `enctype` values are available in [`enctype`]. - /// - /// `salt` may be generated using [`Principal::default_salt`]. - pub fn from_password( - ctx: &'a KrbContext, - enctype: krb5_sys::krb5_enctype, - password: &CStr, - salt: &KrbData, - ) -> Result { - let kb = Self::new( - ctx, enctype, - // not that we have a reason to use a preinitialized keyblock, - // but `krb5_c_string_to_key` doesn't free or reuse an existing - // (non-null) keyblock's contents - 0, - )?; - let password_data = krb5_sys::krb5_data { - magic: krb5_sys::krb5_error_code(0), - length: password - .to_bytes() - .len() - .try_into() - .context(StringTooLongSnafu { - string_name: "password", - })?, - data: password.as_ptr().cast::().cast_mut(), - }; - unsafe { - Error::from_call_result( - Some(ctx), - krb5_sys::krb5_c_string_to_key(ctx.raw, enctype, &password_data, &salt.raw, kb.raw), - )?; - } - Ok(kb) - } - - // SAFETY: we own raw, so it is valid for as long as the reference to &śelf - pub fn contents_mut(&mut self) -> Result<&mut [u8], Error> { - unsafe { - let raw = *self.raw; - if raw.length > 0 { - Ok(std::slice::from_raw_parts_mut( - raw.contents, - raw.length.try_into().context(StringTooLongSnafu { - string_name: "keyblock", - })?, - )) - } else { - // contents are not allocated for length=0, but slice requires that the ptr is non-null and "valid" - Ok(&mut []) - } - } - } - - // Ideally this would be a Deref impl, but we don't have a KeyblockRef we can borrow - // SAFETY: the KeyblockRef must not outlive the &self-ref - #[allow(clippy::needless_lifetimes)] - pub fn as_ref<'b>(&'b self) -> KeyblockRef<'b> { - KeyblockRef { - ctx: self.ctx, - raw: self.raw, - } - } -} -impl Drop for Keyblock<'_> { - fn drop(&mut self) { - unsafe { - krb5_sys::krb5_free_keyblock(self.ctx.raw, self.raw); - } - } -} - -/// Well-known encryption types. This is not exhaustive. -pub mod enctype { - pub const AES256_CTS_HMAC_SHA1_96: krb5_sys::krb5_enctype = - krb5_sys::ENCTYPE_AES256_CTS_HMAC_SHA1_96 as i32; -} - -/// A Kerberos keytab. -pub struct Keytab<'a> { - ctx: &'a KrbContext, - raw: krb5_sys::krb5_keytab, -} -impl<'a> Keytab<'a> { - /// Create a `Keytab` for a given name. - /// - /// `name` should follow the format `{type}:{residual}`, such as `FILE:/foo/bar`. - /// Known types are: - /// - `FILE`: A keytab serialized to a file. - /// - `MEMORY`: An in-memory keytab. - /// - /// The file, if used, does not need to exist. It will be created as required. - pub fn resolve(ctx: &'a KrbContext, name: &CStr) -> Result { - let mut raw = std::ptr::null_mut(); - unsafe { - Error::from_call_result(Some(ctx), krb5_kt_resolve(ctx.raw, name.as_ptr(), &mut raw))? - } - Ok(Self { ctx, raw }) - } - - /// Add the specified key to the keytab. - pub fn add( - &mut self, - principal: &Principal, - kvno: krb5_sys::krb5_kvno, - keyblock: &KeyblockRef, - ) -> Result<(), Error> { - unsafe { - let mut entry: krb5_sys::krb5_keytab_entry = std::mem::zeroed(); - entry.principal = principal.raw; - entry.vno = kvno; - entry.key = keyblock.raw.read(); - // SAFETY: krb5_kt_add_entry is responsible for copying entry as needed - Error::from_call_result( - Some(self.ctx), - krb5_sys::krb5_kt_add_entry(self.ctx.raw, self.raw, &mut entry), - ) - } - } - - /// Remove the specified key from the keytab. - pub fn remove( - &mut self, - principal: &Principal, - kvno: krb5_sys::krb5_kvno, - ) -> Result<(), Error> { - unsafe { - let mut entry: krb5_sys::krb5_keytab_entry = std::mem::zeroed(); - entry.principal = principal.raw; - entry.vno = kvno; - Error::from_call_result( - Some(self.ctx), - krb5_sys::krb5_kt_remove_entry(self.ctx.raw, self.raw, &mut entry), - ) - } - } -} -impl Drop for Keytab<'_> { - fn drop(&mut self) { - unsafe { - Error::from_call_result( - Some(self.ctx), - krb5_sys::krb5_kt_close(self.ctx.raw, self.raw), - ) - .unwrap() - } - } -} - -/// Opaque Kerberos data -pub struct KrbData<'a> { - ctx: &'a KrbContext, - raw: krb5_sys::krb5_data, -} -impl Debug for KrbData<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let slice = unsafe { - std::slice::from_raw_parts( - self.raw.data.cast::(), - self.raw.length.try_into().unwrap(), - ) - }; - let s = std::str::from_utf8(slice).unwrap(); - Debug::fmt(s, f) - } -} -impl Drop for KrbData<'_> { - fn drop(&mut self) { - unsafe { krb5_sys::krb5_free_data_contents(self.ctx.raw, &mut self.raw) } - } -} diff --git a/rust/krb5/src/profile.rs b/rust/krb5/src/profile.rs deleted file mode 100644 index 744f1b7a..00000000 --- a/rust/krb5/src/profile.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::{ - ffi::{CStr, CString, c_char}, - fmt::Display, -}; - -#[derive(Debug)] -pub struct ProfileError { - code: i64, -} -impl ProfileError { - fn from_code(code: i64) -> Result<(), Self> { - if code == 0 { - Ok(()) - } else { - Err(Self { code }) - } - } -} -impl std::error::Error for ProfileError {} -impl Display for ProfileError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let msg = unsafe { CStr::from_ptr(krb5_sys::error_message(self.code)) }; - f.write_str(&msg.to_string_lossy()) - } -} - -/// A Kerberos configuration profile. This is equivalent to a krb5.conf file. -/// -/// Any modifications made are lost when dropped. In other words, [`Drop::drop`] is equivalent to -/// [`krb5_sys::profile_abandon`], _not_ [`krb5_sys::profile_release`]. To save any changes, use -/// [`Self::flush`]. -pub struct Profile { - pub(super) raw: *mut krb5_sys::_profile_t, -} -impl Profile { - /// Create a new empty profile. - pub fn new() -> Result { - // profile segfaults on writes if there isn't at least one file specified - Self::from_path(&CString::new("/dev/null").unwrap()) - } - - /// Load a profile from a file. - pub fn from_path(path: &CStr) -> Result { - let mut files = [ - path.as_ptr(), - // list of strings is null-terminated - std::ptr::null(), - ]; - let mut profile = std::ptr::null_mut::(); - ProfileError::from_code(unsafe { - krb5_sys::profile_init(files.as_mut_ptr(), &mut profile) - })?; - Ok(Self { raw: profile }) - } - - /// Set a configuration value. - pub fn set(&mut self, key_path: &[&CStr], value: &CStr) -> Result<(), ProfileError> { - let mut key_path = key_path - .iter() - .map(|s| s.as_ptr()) - // Path is terminated by null pointer - .chain([std::ptr::null()]) - .collect::>(); - ProfileError::from_code(unsafe { - krb5_sys::profile_add_relation(self.raw, key_path.as_mut_ptr(), value.as_ptr()) - }) - } - - /// Save any modifications made to the file, if it was created using [`Self::from_path`]. - pub fn flush(&mut self) -> Result<(), ProfileError> { - ProfileError::from_code(unsafe { krb5_sys::profile_flush(self.raw) }) - } -} -impl Drop for Profile { - fn drop(&mut self) { - unsafe { krb5_sys::profile_abandon(self.raw) } - } -}