Skip to content

Commit

Permalink
kadmin: add allowed_keysalts (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
rissson authored Dec 4, 2024
1 parent c3bd8f3 commit e9d4188
Show file tree
Hide file tree
Showing 18 changed files with 680 additions and 12 deletions.
1 change: 0 additions & 1 deletion .clippy.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
avoid-breaking-exported-api = true
disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"]
msrv = "1.77"
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions kadmin-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ fn main() {
.allowlist_var("KADM5_.*")
.allowlist_var("KRB5_NT_SRV_HST")
.allowlist_var("KRB5_OK")
.allowlist_var("ENCTYPE_.*")
.allowlist_var("KRB5_KDB_SALTTYPE_.*")
.allowlist_function("krb5_init_context")
.allowlist_function("krb5_free_context")
.allowlist_function("krb5_get_error_message")
Expand All @@ -25,6 +27,10 @@ fn main() {
.allowlist_function("krb5_cc_close")
.allowlist_function("krb5_get_default_realm")
.allowlist_function("krb5_free_default_realm")
.allowlist_function("krb5_string_to_enctype")
.allowlist_function("krb5_string_to_salttype")
.allowlist_function("krb5_enctype_to_string")
.allowlist_function("krb5_salttype_to_string")
.clang_arg("-fparse-all-comments")
.derive_default(true)
.generate_cstr(true)
Expand Down
5 changes: 4 additions & 1 deletion kadmin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@ categories = ["api-bindings", "authentication"]
default = ["client"]
client = ["kadmin-sys/client"]
local = ["kadmin-sys/server"]
python = ["dep:pyo3", "dep:indoc"]
python = ["dep:pyo3", "dep:indoc", "dep:either"]

[dependencies]
chrono = "0.4"
either = { version = "1.13", optional = true }
getset = "0.1"
indoc = { version = "2", optional = true }
kadmin-sys = { path = "../kadmin-sys", version = "0.2.0", default-features = false }
pyo3 = { version = "0.23", features = [
"extension-module",
"chrono",
"chrono-tz",
"either",
], optional = true }
strum = { version = "0.26", features = ["derive"] }
thiserror = "2"

[dev-dependencies]
Expand Down
8 changes: 8 additions & 0 deletions kadmin/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ pub enum Error {
message: String,
},

/// Conversion to an encryption type failed
#[error("Conversion to encryption type failed")]
EncryptionTypeConversion,

/// Conversion to a salt type failed
#[error("Conversion to salt type failed")]
SaltTypeConversion,

/// When converting a `*c_char` to a [`String`], if the provided pointer was `NULL`, this error
/// is returned
#[error("NULL pointer dereference error")]
Expand Down
251 changes: 251 additions & 0 deletions kadmin/src/keysalt_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
//! Kerberos keysalt lists
use std::{
collections::HashSet,
ffi::{CStr, CString},
str::FromStr,
};

use kadmin_sys::*;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use strum::FromRepr;

use crate::error::{Error, Result};

/// Kerberos encryption type
// In MIT krb5: src/lib/crypto/krb/etypes.c
#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq, Hash)]
#[allow(clippy::exhaustive_enums)]
#[repr(i32)]
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
pub enum EncryptionType {
/// Triple DES cbc mode raw (weak, deprecated)
Des3CbcRaw = ENCTYPE_DES3_CBC_RAW as i32,
/// Triple DES cbc mode with HMAC/sha1 (deprecated)
Des3CbcSha1 = ENCTYPE_DES3_CBC_SHA1 as i32,
/// ArcFour with HMAC/md5 (deprecated)
ArcfourHmac = ENCTYPE_ARCFOUR_HMAC as i32,
/// Exportable ArcFour with HMAC/md5 (weak, deprecated)
ArcfourHmacExp = ENCTYPE_ARCFOUR_HMAC_EXP as i32,
/// AES-128 CTS mode with 96-bit SHA-1 HMAC
Aes128CtsHmacSha196 = ENCTYPE_AES128_CTS_HMAC_SHA1_96 as i32,
/// AES-256 CTS mode with 96-bit SHA-1 HMAC
Aes256CtsHmacSha196 = ENCTYPE_AES256_CTS_HMAC_SHA1_96 as i32,
/// Camellia-128 CTS mode with CMAC
Camellia128CtsCmac = ENCTYPE_CAMELLIA128_CTS_CMAC as i32,
/// Camellia-256 CTS mode with CMAC
Camellia256CtsCmac = ENCTYPE_CAMELLIA256_CTS_CMAC as i32,
/// AES-128 CTS mode with 128-bit SHA-256 HMAC
Aes128CtsHmacSha256128 = ENCTYPE_AES128_CTS_HMAC_SHA256_128 as i32,
/// AES-256 CTS mode with 192-bit SHA-384 HMAC
Aes256CtsHmacSha384192 = ENCTYPE_AES256_CTS_HMAC_SHA384_192 as i32,
}

impl From<EncryptionType> for i32 {
fn from(enctype: EncryptionType) -> Self {
enctype as Self
}
}

impl TryFrom<i32> for EncryptionType {
type Error = Error;

fn try_from(enctype: i32) -> Result<Self> {
Self::from_repr(enctype).ok_or(Error::EncryptionTypeConversion)
}
}

impl FromStr for EncryptionType {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
let s = CString::new(s)?;
let mut enctype = -1;
let code = unsafe { krb5_string_to_enctype(s.as_ptr().cast_mut(), &mut enctype) };
if code != KRB5_OK {
Err(Error::EncryptionTypeConversion)
} else {
enctype.try_into()
}
}
}

impl TryFrom<&str> for EncryptionType {
type Error = Error;

fn try_from(s: &str) -> Result<Self> {
Self::from_str(s)
}
}

impl TryFrom<EncryptionType> for String {
type Error = Error;

fn try_from(enctype: EncryptionType) -> Result<Self> {
let buffer = [0_u8; 100];
let code = unsafe {
let mut b: [i8; 100] = std::mem::transmute(buffer);
krb5_enctype_to_string(enctype.into(), b.as_mut_ptr(), 100)
};
if code != KRB5_OK {
return Err(Error::EncryptionTypeConversion);
}
let s = CStr::from_bytes_until_nul(&buffer).map_err(|_| Error::EncryptionTypeConversion)?;
Ok(s.to_owned().into_string()?)
}
}

/// Kerberos salt type
// In MIT krb5: src/lib/krb5/krb/str_conv.c
#[derive(Copy, Clone, Debug, FromRepr, PartialEq, Eq, Hash)]
#[allow(clippy::exhaustive_enums)]
#[repr(i32)]
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
pub enum SaltType {
/// Default for Kerberos Version 5
Normal = KRB5_KDB_SALTTYPE_NORMAL as i32,
/// Same as the default, without using realm information
NoRealm = KRB5_KDB_SALTTYPE_NOREALM as i32,
/// Uses only realm information as the salt
OnlyRealm = KRB5_KDB_SALTTYPE_ONLYREALM as i32,
/// Generate a random salt
Special = KRB5_KDB_SALTTYPE_SPECIAL as i32,
}

impl Default for SaltType {
fn default() -> Self {
Self::Normal
}
}

impl From<SaltType> for i32 {
fn from(salttype: SaltType) -> Self {
salttype as Self
}
}

impl TryFrom<i32> for SaltType {
type Error = Error;

fn try_from(salttype: i32) -> Result<Self> {
Self::from_repr(salttype).ok_or(Error::SaltTypeConversion)
}
}

impl FromStr for SaltType {
type Err = Error;

fn from_str(s: &str) -> Result<Self> {
if s.is_empty() {
return Ok(SaltType::Normal);
}
let s = CString::new(s)?;
let mut salttype = 0;
let code = unsafe { krb5_string_to_salttype(s.as_ptr().cast_mut(), &mut salttype) };
if code != KRB5_OK {
Err(Error::SaltTypeConversion)
} else {
salttype.try_into()
}
}
}

impl TryFrom<&str> for SaltType {
type Error = Error;

fn try_from(s: &str) -> Result<Self> {
Self::from_str(s)
}
}

impl TryFrom<Option<&str>> for SaltType {
type Error = Error;

fn try_from(s: Option<&str>) -> Result<Self> {
if let Some(s) = s {
s.try_into()
} else {
Ok(SaltType::Normal)
}
}
}

impl TryFrom<SaltType> for String {
type Error = Error;

fn try_from(salttype: SaltType) -> Result<Self> {
let buffer = [0_u8; 100];
let code = unsafe {
let mut b: [i8; 100] = std::mem::transmute(buffer);
krb5_enctype_to_string(salttype.into(), b.as_mut_ptr(), 100)
};
if code != KRB5_OK {
return Err(Error::SaltTypeConversion);
}
let s = CStr::from_bytes_until_nul(&buffer).map_err(|_| Error::SaltTypeConversion)?;
Ok(s.to_owned().into_string()?)
}
}

/// Kerberos keysalt
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[allow(clippy::exhaustive_structs)]
#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
pub struct KeySalt {
/// Encryption type
pub enctype: EncryptionType,
/// Salt type
pub salttype: SaltType,
}

impl TryFrom<KeySalt> for String {
type Error = Error;

fn try_from(ks: KeySalt) -> Result<Self> {
let enctype: String = ks.enctype.try_into()?;
let salttype: String = ks.salttype.try_into()?;
Ok(enctype + ":" + &salttype)
}
}

/// Kerberos keysalt list
#[derive(Clone, Debug, PartialEq, Eq)]
#[allow(clippy::exhaustive_structs)]
#[cfg_attr(feature = "python", pyclass(get_all, set_all))]
pub struct KeySaltList {
/// Keysalt list
pub keysalts: HashSet<KeySalt>,
}

impl TryFrom<KeySaltList> for String {
type Error = Error;

fn try_from(ksl: KeySaltList) -> Result<Self> {
Ok(ksl
.keysalts
.into_iter()
.map(|ks| ks.try_into())
.collect::<Result<Vec<String>>>()?
.join(","))
}
}

impl KeySaltList {
pub(crate) fn from_str(s: &str) -> Result<Self> {
let mut keysalts = HashSet::new();
for ks in s.split([',', ' ', '\t']) {
let (enctype, salttype) = if let Some((enctype, salttype)) = ks.split_once(":") {
(enctype.try_into()?, salttype.try_into()?)
} else {
(ks.try_into()?, Default::default())
};
keysalts.insert(KeySalt { enctype, salttype });
}

Ok(Self { keysalts })
}

pub(crate) fn to_cstring(&self) -> Result<CString> {
Ok(CString::new("")?)
}
}
3 changes: 3 additions & 0 deletions kadmin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ pub use db_args::DbArgs;
pub mod tl_data;
pub use tl_data::{TlData, TlDataEntry};

pub mod keysalt_list;
pub use keysalt_list::EncryptionType;

pub mod kadmin;
pub use kadmin::{KAdmin, KAdminImpl};

Expand Down
Loading

0 comments on commit e9d4188

Please sign in to comment.