mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Implement tpm binding of cached password hashes (#1754)
This commit is contained in:
parent
56a2257360
commit
f3080df628
193
Cargo.lock
generated
193
Cargo.lock
generated
|
@ -581,6 +581,12 @@ version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitfield"
|
||||||
|
version = "0.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
|
@ -1430,6 +1436,26 @@ dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2"
|
||||||
|
dependencies = [
|
||||||
|
"enumflags2_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enumflags2_derive"
|
||||||
|
version = "0.7.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.18",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "erased-serde"
|
name = "erased-serde"
|
||||||
version = "0.3.25"
|
version = "0.3.25"
|
||||||
|
@ -2110,6 +2136,12 @@ dependencies = [
|
||||||
"digest 0.9.0",
|
"digest 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hostname-validator"
|
||||||
|
version = "1.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -2484,6 +2516,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"sketching",
|
"sketching",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tss-esapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2573,6 +2606,7 @@ dependencies = [
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tss-esapi",
|
||||||
"users",
|
"users",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
@ -2988,6 +3022,17 @@ dependencies = [
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mbox"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f88d5c34d63aad11aa4321ef55ccb064af58b3ad8091079ae22bf83e5eb75d6"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rustc_version 0.3.3",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -3145,6 +3190,17 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.45"
|
version = "0.1.45"
|
||||||
|
@ -3245,6 +3301,15 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oid"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oid-registry"
|
name = "oid-registry"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -3475,6 +3540,51 @@ version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16833386b02953ca926d19f64af613b9bf742c48dcd5e09b32fbfc9740bf84e2"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror",
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "picky-asn1"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "889bbb26c80acf919e89980dfc8e04eb19df272d8a9893ec9b748d3a1675abde"
|
||||||
|
dependencies = [
|
||||||
|
"oid",
|
||||||
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "picky-asn1-der"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acbbd5390ab967396cc7473e6e0848684aec7166e657c6088604e07b54a73dbe"
|
||||||
|
dependencies = [
|
||||||
|
"picky-asn1",
|
||||||
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "picky-asn1-x509"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3033675030de806aba1d5470949701b7c9f1dbf77e3bb17bd12e5f945e560ba"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.13.1",
|
||||||
|
"oid",
|
||||||
|
"picky-asn1",
|
||||||
|
"picky-asn1-der",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -4024,7 +4134,16 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver",
|
"semver 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||||
|
dependencies = [
|
||||||
|
"semver 0.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4173,7 +4292,16 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver-parser",
|
"semver-parser 0.7.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||||
|
dependencies = [
|
||||||
|
"semver-parser 0.10.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -4182,6 +4310,15 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver-parser"
|
||||||
|
version = "0.10.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.164"
|
version = "1.0.164"
|
||||||
|
@ -4481,6 +4618,12 @@ dependencies = [
|
||||||
"sha2 0.8.2",
|
"sha2 0.8.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "standback"
|
name = "standback"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
@ -4503,7 +4646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
|
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"discard",
|
"discard",
|
||||||
"rustc_version",
|
"rustc_version 0.2.3",
|
||||||
"stdweb-derive",
|
"stdweb-derive",
|
||||||
"stdweb-internal-macros",
|
"stdweb-internal-macros",
|
||||||
"stdweb-internal-runtime",
|
"stdweb-internal-runtime",
|
||||||
|
@ -4659,6 +4802,12 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "target-lexicon"
|
||||||
|
version = "0.12.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.6.0"
|
version = "3.6.0"
|
||||||
|
@ -5100,12 +5249,50 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tss-esapi"
|
||||||
|
version = "7.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "891582e26e83f2cbd608b18cbd7ffb921482740524187a2bca20cf44a286547b"
|
||||||
|
dependencies = [
|
||||||
|
"bitfield",
|
||||||
|
"enumflags2",
|
||||||
|
"hostname-validator",
|
||||||
|
"log",
|
||||||
|
"mbox",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"oid",
|
||||||
|
"picky-asn1",
|
||||||
|
"picky-asn1-x509",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"tss-esapi-sys",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tss-esapi-sys"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7b8be553262e0924410fe96404830252477f175f228081f21cb0bd87f2ccebe"
|
||||||
|
dependencies = [
|
||||||
|
"pkg-config",
|
||||||
|
"target-lexicon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
|
|
|
@ -156,6 +156,8 @@ tracing-subscriber = { version = "^0.3.17", features = ["env-filter"] }
|
||||||
# tracing-forest = { path = "/Users/william/development/tracing-forest/tracing-forest" }
|
# tracing-forest = { path = "/Users/william/development/tracing-forest/tracing-forest" }
|
||||||
tracing-forest = { git = "https://github.com/QnnOkabayashi/tracing-forest.git", rev = "77daf8c8abf010b87d45ece2bf656983c6f8cecb" }
|
tracing-forest = { git = "https://github.com/QnnOkabayashi/tracing-forest.git", rev = "77daf8c8abf010b87d45ece2bf656983c6f8cecb" }
|
||||||
|
|
||||||
|
tss-esapi = "^7.2.0"
|
||||||
|
|
||||||
url = "^2.4.0"
|
url = "^2.4.0"
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
users = "^0.11.0"
|
users = "^0.11.0"
|
||||||
|
|
|
@ -3,6 +3,9 @@ name = "kanidm_lib_crypto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
tpm = ["dep:tss-esapi"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
argon2.workspace = true
|
argon2.workspace = true
|
||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
|
@ -17,6 +20,7 @@ openssl.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
tss-esapi = { workspace = true, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
sketching.workspace = true
|
sketching.workspace = true
|
||||||
|
|
|
@ -28,6 +28,13 @@ use openssl::nid::Nid;
|
||||||
use openssl::pkcs5::pbkdf2_hmac;
|
use openssl::pkcs5::pbkdf2_hmac;
|
||||||
use openssl::sha::Sha512;
|
use openssl::sha::Sha512;
|
||||||
|
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
pub use tss_esapi::{handles::ObjectHandle as TpmHandle, Context as TpmContext, Error as TpmError};
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
pub struct TpmContext {}
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
pub struct TpmHandle {}
|
||||||
|
|
||||||
// NIST 800-63.b salt should be 112 bits -> 14 8u8.
|
// NIST 800-63.b salt should be 112 bits -> 14 8u8.
|
||||||
const PBKDF2_SALT_LEN: usize = 24;
|
const PBKDF2_SALT_LEN: usize = 24;
|
||||||
|
|
||||||
|
@ -48,9 +55,43 @@ const ARGON2_SALT_LEN: usize = 24;
|
||||||
const ARGON2_KEY_LEN: usize = 32;
|
const ARGON2_KEY_LEN: usize = 32;
|
||||||
const ARGON2_MAX_RAM_KIB: u32 = 32 * 1024;
|
const ARGON2_MAX_RAM_KIB: u32 = 32 * 1024;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum CryptoError {
|
||||||
|
Tpm2,
|
||||||
|
Tpm2FeatureMissing,
|
||||||
|
Tpm2InputExceeded,
|
||||||
|
Tpm2ContextMissing,
|
||||||
|
OpenSSL,
|
||||||
|
Md4Disabled,
|
||||||
|
Argon2,
|
||||||
|
Argon2Version,
|
||||||
|
Argon2Parameters,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<OperationError> for CryptoError {
|
||||||
|
fn into(self) -> OperationError {
|
||||||
|
OperationError::CryptographyError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
impl From<TpmError> for CryptoError {
|
||||||
|
fn from(_e: TpmError) -> Self {
|
||||||
|
CryptoError::Tpm2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum DbPasswordV1 {
|
pub enum DbPasswordV1 {
|
||||||
|
TPM_ARGON2ID {
|
||||||
|
m: u32,
|
||||||
|
t: u32,
|
||||||
|
p: u32,
|
||||||
|
v: u32,
|
||||||
|
s: Base64UrlSafeData,
|
||||||
|
k: Base64UrlSafeData,
|
||||||
|
},
|
||||||
ARGON2ID {
|
ARGON2ID {
|
||||||
m: u32,
|
m: u32,
|
||||||
t: u32,
|
t: u32,
|
||||||
|
@ -69,6 +110,14 @@ pub enum DbPasswordV1 {
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
pub enum ReplPasswordV1 {
|
pub enum ReplPasswordV1 {
|
||||||
|
TPM_ARGON2ID {
|
||||||
|
m_cost: u32,
|
||||||
|
t_cost: u32,
|
||||||
|
p_cost: u32,
|
||||||
|
version: u32,
|
||||||
|
salt: Base64UrlSafeData,
|
||||||
|
key: Base64UrlSafeData,
|
||||||
|
},
|
||||||
ARGON2ID {
|
ARGON2ID {
|
||||||
m_cost: u32,
|
m_cost: u32,
|
||||||
t_cost: u32,
|
t_cost: u32,
|
||||||
|
@ -104,6 +153,7 @@ pub enum ReplPasswordV1 {
|
||||||
impl fmt::Debug for DbPasswordV1 {
|
impl fmt::Debug for DbPasswordV1 {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
DbPasswordV1::TPM_ARGON2ID { .. } => write!(f, "TPM_ARGON2ID"),
|
||||||
DbPasswordV1::ARGON2ID { .. } => write!(f, "ARGON2ID"),
|
DbPasswordV1::ARGON2ID { .. } => write!(f, "ARGON2ID"),
|
||||||
DbPasswordV1::PBKDF2(_, _, _) => write!(f, "PBKDF2"),
|
DbPasswordV1::PBKDF2(_, _, _) => write!(f, "PBKDF2"),
|
||||||
DbPasswordV1::PBKDF2_SHA1(_, _, _) => write!(f, "PBKDF2_SHA1"),
|
DbPasswordV1::PBKDF2_SHA1(_, _, _) => write!(f, "PBKDF2_SHA1"),
|
||||||
|
@ -239,6 +289,14 @@ impl CryptoPolicy {
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
enum Kdf {
|
enum Kdf {
|
||||||
|
TPM_ARGON2ID {
|
||||||
|
m_cost: u32,
|
||||||
|
t_cost: u32,
|
||||||
|
p_cost: u32,
|
||||||
|
version: u32,
|
||||||
|
salt: Vec<u8>,
|
||||||
|
key: Vec<u8>,
|
||||||
|
},
|
||||||
//
|
//
|
||||||
ARGON2ID {
|
ARGON2ID {
|
||||||
m_cost: u32,
|
m_cost: u32,
|
||||||
|
@ -272,6 +330,16 @@ impl TryFrom<DbPasswordV1> for Password {
|
||||||
|
|
||||||
fn try_from(value: DbPasswordV1) -> Result<Self, Self::Error> {
|
fn try_from(value: DbPasswordV1) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
|
DbPasswordV1::TPM_ARGON2ID { m, t, p, v, s, k } => Ok(Password {
|
||||||
|
material: Kdf::TPM_ARGON2ID {
|
||||||
|
m_cost: m,
|
||||||
|
t_cost: t,
|
||||||
|
p_cost: p,
|
||||||
|
version: v,
|
||||||
|
salt: s.into(),
|
||||||
|
key: k.into(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
DbPasswordV1::ARGON2ID { m, t, p, v, s, k } => Ok(Password {
|
DbPasswordV1::ARGON2ID { m, t, p, v, s, k } => Ok(Password {
|
||||||
material: Kdf::ARGON2ID {
|
material: Kdf::ARGON2ID {
|
||||||
m_cost: m,
|
m_cost: m,
|
||||||
|
@ -306,6 +374,23 @@ impl TryFrom<&ReplPasswordV1> for Password {
|
||||||
|
|
||||||
fn try_from(value: &ReplPasswordV1) -> Result<Self, Self::Error> {
|
fn try_from(value: &ReplPasswordV1) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
|
ReplPasswordV1::TPM_ARGON2ID {
|
||||||
|
m_cost,
|
||||||
|
t_cost,
|
||||||
|
p_cost,
|
||||||
|
version,
|
||||||
|
salt,
|
||||||
|
key,
|
||||||
|
} => Ok(Password {
|
||||||
|
material: Kdf::TPM_ARGON2ID {
|
||||||
|
m_cost: *m_cost,
|
||||||
|
t_cost: *t_cost,
|
||||||
|
p_cost: *p_cost,
|
||||||
|
version: *version,
|
||||||
|
salt: salt.0.clone(),
|
||||||
|
key: key.0.clone(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
ReplPasswordV1::ARGON2ID {
|
ReplPasswordV1::ARGON2ID {
|
||||||
m_cost,
|
m_cost,
|
||||||
t_cost,
|
t_cost,
|
||||||
|
@ -624,7 +709,7 @@ impl Password {
|
||||||
end.checked_duration_since(start)
|
end.checked_duration_since(start)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_pbkdf2(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, OperationError> {
|
pub fn new_pbkdf2(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
|
||||||
let pbkdf2_cost = policy.pbkdf2_cost;
|
let pbkdf2_cost = policy.pbkdf2_cost;
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.gen()).collect();
|
let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.gen()).collect();
|
||||||
|
@ -642,11 +727,11 @@ impl Password {
|
||||||
// Turn key to a vec.
|
// Turn key to a vec.
|
||||||
Kdf::PBKDF2(pbkdf2_cost, salt, key)
|
Kdf::PBKDF2(pbkdf2_cost, salt, key)
|
||||||
})
|
})
|
||||||
.map_err(|_| OperationError::CryptographyError)
|
.map_err(|_| CryptoError::OpenSSL)
|
||||||
.map(|material| Password { material })
|
.map(|material| Password { material })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_argon2id(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, OperationError> {
|
pub fn new_argon2id(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
|
||||||
let version = Version::V0x13;
|
let version = Version::V0x13;
|
||||||
|
|
||||||
let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
|
let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
|
||||||
|
@ -665,28 +750,72 @@ impl Password {
|
||||||
salt,
|
salt,
|
||||||
key,
|
key,
|
||||||
})
|
})
|
||||||
.map_err(|_| OperationError::CryptographyError)
|
.map_err(|_| CryptoError::Argon2)
|
||||||
|
.map(|material| Password { material })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_argon2id_tpm(
|
||||||
|
policy: &CryptoPolicy,
|
||||||
|
cleartext: &str,
|
||||||
|
tpm_ctx: &mut TpmContext,
|
||||||
|
tpm_key_handle: TpmHandle,
|
||||||
|
) -> Result<Self, CryptoError> {
|
||||||
|
let version = Version::V0x13;
|
||||||
|
|
||||||
|
let argon = Argon2::new(Algorithm::Argon2id, version, policy.argon2id_params.clone());
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let salt: Vec<u8> = (0..ARGON2_SALT_LEN).map(|_| rng.gen()).collect();
|
||||||
|
let mut check_key: Vec<u8> = (0..ARGON2_KEY_LEN).map(|_| 0).collect();
|
||||||
|
|
||||||
|
argon
|
||||||
|
.hash_password_into(
|
||||||
|
cleartext.as_bytes(),
|
||||||
|
salt.as_slice(),
|
||||||
|
check_key.as_mut_slice(),
|
||||||
|
)
|
||||||
|
.map_err(|_| CryptoError::Argon2)
|
||||||
|
.and_then(|()| do_tpm_hmac(check_key, tpm_ctx, tpm_key_handle))
|
||||||
|
.map(|key| Kdf::TPM_ARGON2ID {
|
||||||
|
m_cost: policy.argon2id_params.m_cost(),
|
||||||
|
t_cost: policy.argon2id_params.t_cost(),
|
||||||
|
p_cost: policy.argon2id_params.p_cost(),
|
||||||
|
version: version as u32,
|
||||||
|
salt,
|
||||||
|
key,
|
||||||
|
})
|
||||||
.map(|material| Password { material })
|
.map(|material| Password { material })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, OperationError> {
|
pub fn new(policy: &CryptoPolicy, cleartext: &str) -> Result<Self, CryptoError> {
|
||||||
Self::new_pbkdf2(policy, cleartext)
|
Self::new_pbkdf2(policy, cleartext)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify(&self, cleartext: &str) -> Result<bool, OperationError> {
|
pub fn verify(&self, cleartext: &str) -> Result<bool, CryptoError> {
|
||||||
match &self.material {
|
self.verify_ctx(cleartext, None)
|
||||||
Kdf::ARGON2ID {
|
}
|
||||||
m_cost,
|
|
||||||
t_cost,
|
pub fn verify_ctx<'a>(
|
||||||
p_cost,
|
&self,
|
||||||
version,
|
cleartext: &str,
|
||||||
salt,
|
tpm: Option<(&'a mut TpmContext, TpmHandle)>,
|
||||||
key,
|
) -> Result<bool, CryptoError> {
|
||||||
} => {
|
match (&self.material, tpm) {
|
||||||
|
(
|
||||||
|
Kdf::TPM_ARGON2ID {
|
||||||
|
m_cost,
|
||||||
|
t_cost,
|
||||||
|
p_cost,
|
||||||
|
version,
|
||||||
|
salt,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
Some((tpm_ctx, tpm_key_handle)),
|
||||||
|
) => {
|
||||||
let version: Version = (*version).try_into().map_err(|_| {
|
let version: Version = (*version).try_into().map_err(|_| {
|
||||||
error!("Failed to convert {} to valid argon2id version", version);
|
error!("Failed to convert {} to valid argon2id version", version);
|
||||||
OperationError::CryptographyError
|
CryptoError::Argon2Version
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let key_len = key.len();
|
let key_len = key.len();
|
||||||
|
@ -694,7 +823,7 @@ impl Password {
|
||||||
let params =
|
let params =
|
||||||
Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
|
Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
|
||||||
error!(err = ?e, "invalid argon2id parameters");
|
error!(err = ?e, "invalid argon2id parameters");
|
||||||
OperationError::CryptographyError
|
CryptoError::Argon2Parameters
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let argon = Argon2::new(Algorithm::Argon2id, version, params);
|
let argon = Argon2::new(Algorithm::Argon2id, version, params);
|
||||||
|
@ -708,14 +837,61 @@ impl Password {
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(err = ?e, "unable to perform argon2id hash");
|
error!(err = ?e, "unable to perform argon2id hash");
|
||||||
OperationError::CryptographyError
|
CryptoError::Argon2
|
||||||
|
})
|
||||||
|
.and_then(|()| do_tpm_hmac(check_key, tpm_ctx, tpm_key_handle))
|
||||||
|
.map(|hmac_key| {
|
||||||
|
// Actually compare the outputs.
|
||||||
|
&hmac_key == key
|
||||||
|
})
|
||||||
|
}
|
||||||
|
(Kdf::TPM_ARGON2ID { .. }, None) => {
|
||||||
|
error!("Unable to validate password - not tpm context available");
|
||||||
|
Err(CryptoError::Tpm2ContextMissing)
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Kdf::ARGON2ID {
|
||||||
|
m_cost,
|
||||||
|
t_cost,
|
||||||
|
p_cost,
|
||||||
|
version,
|
||||||
|
salt,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
) => {
|
||||||
|
let version: Version = (*version).try_into().map_err(|_| {
|
||||||
|
error!("Failed to convert {} to valid argon2id version", version);
|
||||||
|
CryptoError::Argon2Version
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let key_len = key.len();
|
||||||
|
|
||||||
|
let params =
|
||||||
|
Params::new(*m_cost, *t_cost, *p_cost, Some(key_len)).map_err(|e| {
|
||||||
|
error!(err = ?e, "invalid argon2id parameters");
|
||||||
|
CryptoError::Argon2Parameters
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let argon = Argon2::new(Algorithm::Argon2id, version, params);
|
||||||
|
let mut check_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
|
||||||
|
|
||||||
|
argon
|
||||||
|
.hash_password_into(
|
||||||
|
cleartext.as_bytes(),
|
||||||
|
salt.as_slice(),
|
||||||
|
check_key.as_mut_slice(),
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(err = ?e, "unable to perform argon2id hash");
|
||||||
|
CryptoError::Argon2
|
||||||
})
|
})
|
||||||
.map(|()| {
|
.map(|()| {
|
||||||
// Actually compare the outputs.
|
// Actually compare the outputs.
|
||||||
&check_key == key
|
&check_key == key
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Kdf::PBKDF2(cost, salt, key) => {
|
(Kdf::PBKDF2(cost, salt, key), _) => {
|
||||||
// We have to get the number of bits to derive from our stored hash
|
// We have to get the number of bits to derive from our stored hash
|
||||||
// as some imported hash types may have variable lengths
|
// as some imported hash types may have variable lengths
|
||||||
let key_len = key.len();
|
let key_len = key.len();
|
||||||
|
@ -728,13 +904,13 @@ impl Password {
|
||||||
MessageDigest::sha256(),
|
MessageDigest::sha256(),
|
||||||
chal_key.as_mut_slice(),
|
chal_key.as_mut_slice(),
|
||||||
)
|
)
|
||||||
.map_err(|_| OperationError::CryptographyError)
|
.map_err(|_| CryptoError::OpenSSL)
|
||||||
.map(|()| {
|
.map(|()| {
|
||||||
// Actually compare the outputs.
|
// Actually compare the outputs.
|
||||||
&chal_key == key
|
&chal_key == key
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Kdf::PBKDF2_SHA1(cost, salt, key) => {
|
(Kdf::PBKDF2_SHA1(cost, salt, key), _) => {
|
||||||
let key_len = key.len();
|
let key_len = key.len();
|
||||||
debug_assert!(key_len >= PBKDF2_SHA1_MIN_KEY_LEN);
|
debug_assert!(key_len >= PBKDF2_SHA1_MIN_KEY_LEN);
|
||||||
let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
|
let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
|
||||||
|
@ -745,13 +921,13 @@ impl Password {
|
||||||
MessageDigest::sha1(),
|
MessageDigest::sha1(),
|
||||||
chal_key.as_mut_slice(),
|
chal_key.as_mut_slice(),
|
||||||
)
|
)
|
||||||
.map_err(|_| OperationError::CryptographyError)
|
.map_err(|_| CryptoError::OpenSSL)
|
||||||
.map(|()| {
|
.map(|()| {
|
||||||
// Actually compare the outputs.
|
// Actually compare the outputs.
|
||||||
&chal_key == key
|
&chal_key == key
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Kdf::PBKDF2_SHA512(cost, salt, key) => {
|
(Kdf::PBKDF2_SHA512(cost, salt, key), _) => {
|
||||||
let key_len = key.len();
|
let key_len = key.len();
|
||||||
debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
|
debug_assert!(key_len >= PBKDF2_MIN_NIST_KEY_LEN);
|
||||||
let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
|
let mut chal_key: Vec<u8> = (0..key_len).map(|_| 0).collect();
|
||||||
|
@ -762,20 +938,20 @@ impl Password {
|
||||||
MessageDigest::sha512(),
|
MessageDigest::sha512(),
|
||||||
chal_key.as_mut_slice(),
|
chal_key.as_mut_slice(),
|
||||||
)
|
)
|
||||||
.map_err(|_| OperationError::CryptographyError)
|
.map_err(|_| CryptoError::OpenSSL)
|
||||||
.map(|()| {
|
.map(|()| {
|
||||||
// Actually compare the outputs.
|
// Actually compare the outputs.
|
||||||
&chal_key == key
|
&chal_key == key
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Kdf::SSHA512(salt, key) => {
|
(Kdf::SSHA512(salt, key), _) => {
|
||||||
let mut hasher = Sha512::new();
|
let mut hasher = Sha512::new();
|
||||||
hasher.update(cleartext.as_bytes());
|
hasher.update(cleartext.as_bytes());
|
||||||
hasher.update(salt);
|
hasher.update(salt);
|
||||||
let r = hasher.finish();
|
let r = hasher.finish();
|
||||||
Ok(key == &(r.to_vec()))
|
Ok(key == &(r.to_vec()))
|
||||||
}
|
}
|
||||||
Kdf::NT_MD4(key) => {
|
(Kdf::NT_MD4(key), _) => {
|
||||||
// We need to get the cleartext to utf16le for reasons.
|
// We need to get the cleartext to utf16le for reasons.
|
||||||
let clear_utf16le: Vec<u8> = cleartext
|
let clear_utf16le: Vec<u8> = cleartext
|
||||||
.encode_utf16()
|
.encode_utf16()
|
||||||
|
@ -786,7 +962,7 @@ impl Password {
|
||||||
let dgst = MessageDigest::from_nid(Nid::MD4).ok_or_else(|| {
|
let dgst = MessageDigest::from_nid(Nid::MD4).ok_or_else(|| {
|
||||||
error!("Unable to access MD4 - fips mode may be enabled, or you may need to activate the legacy provider.");
|
error!("Unable to access MD4 - fips mode may be enabled, or you may need to activate the legacy provider.");
|
||||||
error!("For more details, see https://wiki.openssl.org/index.php/OpenSSL_3.0#Providers");
|
error!("For more details, see https://wiki.openssl.org/index.php/OpenSSL_3.0#Providers");
|
||||||
OperationError::CryptographyError
|
CryptoError::Md4Disabled
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
hash::hash(dgst, &clear_utf16le)
|
hash::hash(dgst, &clear_utf16le)
|
||||||
|
@ -794,7 +970,7 @@ impl Password {
|
||||||
debug!(?e);
|
debug!(?e);
|
||||||
error!("Unable to digest MD4 - fips mode may be enabled, or you may need to activate the legacy provider.");
|
error!("Unable to digest MD4 - fips mode may be enabled, or you may need to activate the legacy provider.");
|
||||||
error!("For more details, see https://wiki.openssl.org/index.php/OpenSSL_3.0#Providers");
|
error!("For more details, see https://wiki.openssl.org/index.php/OpenSSL_3.0#Providers");
|
||||||
OperationError::CryptographyError
|
CryptoError::Md4Disabled
|
||||||
})
|
})
|
||||||
.map(|chal_key| chal_key.as_ref() == key)
|
.map(|chal_key| chal_key.as_ref() == key)
|
||||||
}
|
}
|
||||||
|
@ -803,6 +979,21 @@ impl Password {
|
||||||
|
|
||||||
pub fn to_dbpasswordv1(&self) -> DbPasswordV1 {
|
pub fn to_dbpasswordv1(&self) -> DbPasswordV1 {
|
||||||
match &self.material {
|
match &self.material {
|
||||||
|
Kdf::TPM_ARGON2ID {
|
||||||
|
m_cost,
|
||||||
|
t_cost,
|
||||||
|
p_cost,
|
||||||
|
version,
|
||||||
|
salt,
|
||||||
|
key,
|
||||||
|
} => DbPasswordV1::TPM_ARGON2ID {
|
||||||
|
m: *m_cost,
|
||||||
|
t: *t_cost,
|
||||||
|
p: *p_cost,
|
||||||
|
v: *version,
|
||||||
|
s: salt.clone().into(),
|
||||||
|
k: key.clone().into(),
|
||||||
|
},
|
||||||
Kdf::ARGON2ID {
|
Kdf::ARGON2ID {
|
||||||
m_cost,
|
m_cost,
|
||||||
t_cost,
|
t_cost,
|
||||||
|
@ -834,6 +1025,21 @@ impl Password {
|
||||||
|
|
||||||
pub fn to_repl_v1(&self) -> ReplPasswordV1 {
|
pub fn to_repl_v1(&self) -> ReplPasswordV1 {
|
||||||
match &self.material {
|
match &self.material {
|
||||||
|
Kdf::TPM_ARGON2ID {
|
||||||
|
m_cost,
|
||||||
|
t_cost,
|
||||||
|
p_cost,
|
||||||
|
version,
|
||||||
|
salt,
|
||||||
|
key,
|
||||||
|
} => ReplPasswordV1::TPM_ARGON2ID {
|
||||||
|
m_cost: *m_cost,
|
||||||
|
t_cost: *t_cost,
|
||||||
|
p_cost: *p_cost,
|
||||||
|
version: *version,
|
||||||
|
salt: salt.clone().into(),
|
||||||
|
key: key.clone().into(),
|
||||||
|
},
|
||||||
Kdf::ARGON2ID {
|
Kdf::ARGON2ID {
|
||||||
m_cost,
|
m_cost,
|
||||||
t_cost,
|
t_cost,
|
||||||
|
@ -876,6 +1082,7 @@ impl Password {
|
||||||
|
|
||||||
pub fn requires_upgrade(&self) -> bool {
|
pub fn requires_upgrade(&self) -> bool {
|
||||||
match &self.material {
|
match &self.material {
|
||||||
|
Kdf::TPM_ARGON2ID { .. } => false,
|
||||||
Kdf::ARGON2ID { .. } => false,
|
Kdf::ARGON2ID { .. } => false,
|
||||||
Kdf::PBKDF2_SHA512(cost, salt, hash) | Kdf::PBKDF2(cost, salt, hash) => {
|
Kdf::PBKDF2_SHA512(cost, salt, hash) | Kdf::PBKDF2(cost, salt, hash) => {
|
||||||
*cost < PBKDF2_MIN_NIST_COST
|
*cost < PBKDF2_MIN_NIST_COST
|
||||||
|
@ -885,6 +1092,81 @@ impl Password {
|
||||||
Kdf::PBKDF2_SHA1(_, _, _) | Kdf::SSHA512(_, _) | Kdf::NT_MD4(_) => true,
|
Kdf::PBKDF2_SHA1(_, _, _) | Kdf::SSHA512(_, _) | Kdf::NT_MD4(_) => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
pub fn prepare_tpm_key(tpm_ctx: &mut TpmContext) -> Result<TpmHandle, CryptoError> {
|
||||||
|
use tss_esapi::{
|
||||||
|
attributes::ObjectAttributesBuilder,
|
||||||
|
interface_types::{
|
||||||
|
algorithm::{HashingAlgorithm, PublicAlgorithm},
|
||||||
|
resource_handles::Hierarchy,
|
||||||
|
},
|
||||||
|
structures::{Digest, KeyedHashScheme, PublicBuilder, PublicKeyedHashParameters},
|
||||||
|
};
|
||||||
|
|
||||||
|
let object_attributes = ObjectAttributesBuilder::new()
|
||||||
|
.with_sign_encrypt(true)
|
||||||
|
.with_sensitive_data_origin(true)
|
||||||
|
.with_user_with_auth(true)
|
||||||
|
.build()
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||||
|
CryptoError::Tpm2
|
||||||
|
})?;
|
||||||
|
let key_pub = PublicBuilder::new()
|
||||||
|
.with_public_algorithm(PublicAlgorithm::KeyedHash)
|
||||||
|
.with_name_hashing_algorithm(HashingAlgorithm::Sha256)
|
||||||
|
.with_object_attributes(object_attributes)
|
||||||
|
.with_keyed_hash_parameters(PublicKeyedHashParameters::new(
|
||||||
|
KeyedHashScheme::HMAC_SHA_256,
|
||||||
|
))
|
||||||
|
.with_keyed_hash_unique_identifier(Digest::default())
|
||||||
|
.build()
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||||
|
CryptoError::Tpm2
|
||||||
|
})?;
|
||||||
|
|
||||||
|
tpm_ctx
|
||||||
|
.create_primary(Hierarchy::Owner, key_pub, None, None, None, None)
|
||||||
|
.map(|key| key.key_handle.into())
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||||
|
CryptoError::Tpm2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
fn do_tpm_hmac(
|
||||||
|
data: Vec<u8>,
|
||||||
|
ctx: &mut TpmContext,
|
||||||
|
key_handle: TpmHandle,
|
||||||
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
use tss_esapi::interface_types::algorithm::HashingAlgorithm;
|
||||||
|
use tss_esapi::structures::MaxBuffer;
|
||||||
|
|
||||||
|
let data: MaxBuffer = data.try_into().map_err(|_| {
|
||||||
|
error!("input data exceeds maximum tpm input buffer");
|
||||||
|
CryptoError::Tpm2InputExceeded
|
||||||
|
})?;
|
||||||
|
|
||||||
|
ctx.hmac(key_handle, data.into(), HashingAlgorithm::Sha256)
|
||||||
|
.map(|dgst| dgst.to_vec())
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "unable to proceed, tpm error");
|
||||||
|
CryptoError::Tpm2
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
fn do_tpm_hmac(
|
||||||
|
_data: Vec<u8>,
|
||||||
|
_ctx: &mut TpmContext,
|
||||||
|
_key_handle: TpmHandle,
|
||||||
|
) -> Result<Vec<u8>, CryptoError> {
|
||||||
|
error!("Unable to perform hmac - tpm feature not compiled");
|
||||||
|
Err(CryptoError::Tpm2FeatureMissing)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -1056,4 +1338,76 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
#[test]
|
||||||
|
fn test_password_argon2id_tpm_bind() {
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
sketching::test_init();
|
||||||
|
|
||||||
|
use tss_esapi::{Context, TctiNameConf};
|
||||||
|
|
||||||
|
let mut context =
|
||||||
|
Context::new(TctiNameConf::from_str("device:/dev/tpmrm0").expect("Failed to get TCTI"))
|
||||||
|
.expect("Failed to create Context");
|
||||||
|
|
||||||
|
let key = context
|
||||||
|
.execute_with_nullauth_session(|ctx| Password::prepare_tpm_key(ctx))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let p = CryptoPolicy::minimum();
|
||||||
|
let c = context
|
||||||
|
.execute_with_nullauth_session(|ctx| {
|
||||||
|
Password::new_argon2id_tpm(&p, "password", ctx, key)
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
c.verify("password"),
|
||||||
|
Err(CryptoError::Tpm2ContextMissing)
|
||||||
|
));
|
||||||
|
|
||||||
|
// Assert it fails without the hmac
|
||||||
|
let dup = match &c.material {
|
||||||
|
Kdf::TPM_ARGON2ID {
|
||||||
|
m_cost,
|
||||||
|
t_cost,
|
||||||
|
p_cost,
|
||||||
|
version,
|
||||||
|
salt,
|
||||||
|
key,
|
||||||
|
} => Password {
|
||||||
|
material: Kdf::ARGON2ID {
|
||||||
|
m_cost: *m_cost,
|
||||||
|
t_cost: *t_cost,
|
||||||
|
p_cost: *p_cost,
|
||||||
|
version: *version,
|
||||||
|
salt: salt.clone(),
|
||||||
|
key: key.clone(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(!dup.verify("password").unwrap());
|
||||||
|
|
||||||
|
context
|
||||||
|
.execute_with_nullauth_session(|ctx| {
|
||||||
|
assert!(c.verify_ctx("password", Some((ctx, key))).unwrap());
|
||||||
|
assert!(!c.verify_ctx("password1", Some((ctx, key))).unwrap());
|
||||||
|
assert!(!c.verify_ctx("Password1", Some((ctx, key))).unwrap());
|
||||||
|
|
||||||
|
ctx.flush_context(key).expect("Failed to unload hmac key");
|
||||||
|
|
||||||
|
// Should fail, no key!
|
||||||
|
assert!(matches!(
|
||||||
|
c.verify_ctx("password", Some((ctx, key))),
|
||||||
|
Err(CryptoError::Tpm2)
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok::<(), CryptoError>(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ After=chronyd.service ntpd.service network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
DynamicUser=yes
|
DynamicUser=yes
|
||||||
|
SupplementaryGroups=tss
|
||||||
UMask=0027
|
UMask=0027
|
||||||
CacheDirectory=kanidm-unixd
|
CacheDirectory=kanidm-unixd
|
||||||
RuntimeDirectory=kanidm-unixd
|
RuntimeDirectory=kanidm-unixd
|
||||||
|
@ -23,7 +24,8 @@ ExecStart=/usr/sbin/kanidm_unixd
|
||||||
# SystemCallFilter=@aio @basic-io @chown @file-system @io-event @network-io @sync
|
# SystemCallFilter=@aio @basic-io @chown @file-system @io-event @network-io @sync
|
||||||
NoNewPrivileges=true
|
NoNewPrivileges=true
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
PrivateDevices=true
|
# We have to disable this to allow tpmrm0 access for tpm binding.
|
||||||
|
PrivateDevices=false
|
||||||
ProtectHostname=true
|
ProtectHostname=true
|
||||||
ProtectClock=true
|
ProtectClock=true
|
||||||
ProtectKernelTunables=true
|
ProtectKernelTunables=true
|
||||||
|
|
|
@ -460,7 +460,12 @@ impl Credential {
|
||||||
policy: &CryptoPolicy,
|
policy: &CryptoPolicy,
|
||||||
cleartext: &str,
|
cleartext: &str,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
Password::new(policy, cleartext).map(Self::new_from_password)
|
Password::new(policy, cleartext)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(crypto_err = ?e);
|
||||||
|
e.into()
|
||||||
|
})
|
||||||
|
.map(Self::new_from_password)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new credential that contains a CredentialType::GeneratedPassword
|
/// Create a new credential that contains a CredentialType::GeneratedPassword
|
||||||
|
@ -468,7 +473,12 @@ impl Credential {
|
||||||
policy: &CryptoPolicy,
|
policy: &CryptoPolicy,
|
||||||
cleartext: &str,
|
cleartext: &str,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
Password::new(policy, cleartext).map(Self::new_from_generatedpassword)
|
Password::new(policy, cleartext)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(crypto_err = ?e);
|
||||||
|
e.into()
|
||||||
|
})
|
||||||
|
.map(Self::new_from_generatedpassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the state of the Password on this credential, if a password is present. If possible
|
/// Update the state of the Password on this credential, if a password is present. If possible
|
||||||
|
@ -478,7 +488,12 @@ impl Credential {
|
||||||
policy: &CryptoPolicy,
|
policy: &CryptoPolicy,
|
||||||
cleartext: &str,
|
cleartext: &str,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
Password::new(policy, cleartext).map(|pw| self.update_password(pw))
|
Password::new(policy, cleartext)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(crypto_err = ?e);
|
||||||
|
e.into()
|
||||||
|
})
|
||||||
|
.map(|pw| self.update_password(pw))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extend this credential with another alternate webauthn credential. This is especially
|
/// Extend this credential with another alternate webauthn credential. This is especially
|
||||||
|
@ -634,7 +649,12 @@ impl Credential {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn verify_password(&self, cleartext: &str) -> Result<bool, OperationError> {
|
pub fn verify_password(&self, cleartext: &str) -> Result<bool, OperationError> {
|
||||||
self.password_ref().and_then(|pw| pw.verify(cleartext))
|
self.password_ref().and_then(|pw| {
|
||||||
|
pw.verify(cleartext).map_err(|e| {
|
||||||
|
error!(crypto_err = ?e);
|
||||||
|
e.into()
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract this credential into it's Serialisable Database form, ready for persistence.
|
/// Extract this credential into it's Serialisable Database form, ready for persistence.
|
||||||
|
|
|
@ -458,7 +458,14 @@ impl Account {
|
||||||
self.primary
|
self.primary
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.ok_or(OperationError::InvalidState)
|
.ok_or(OperationError::InvalidState)
|
||||||
.and_then(|cred| cred.password_ref().and_then(|pw| pw.verify(cleartext)))
|
.and_then(|cred| {
|
||||||
|
cred.password_ref().and_then(|pw| {
|
||||||
|
pw.verify(cleartext).map_err(|e| {
|
||||||
|
error!(crypto_err = ?e);
|
||||||
|
e.into()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn regenerate_radius_secret_mod(
|
pub(crate) fn regenerate_radius_secret_mod(
|
||||||
|
|
|
@ -234,7 +234,10 @@ impl UnixUserAccount {
|
||||||
match &self.cred {
|
match &self.cred {
|
||||||
Some(cred) => {
|
Some(cred) => {
|
||||||
cred.password_ref().and_then(|pw| {
|
cred.password_ref().and_then(|pw| {
|
||||||
if pw.verify(cleartext)? {
|
if pw.verify(cleartext).map_err(|e| {
|
||||||
|
error!(crypto_err = ?e);
|
||||||
|
e.into()
|
||||||
|
})? {
|
||||||
security_info!("Successful unix cred handling");
|
security_info!("Successful unix cred handling");
|
||||||
if pw.requires_upgrade() {
|
if pw.requires_upgrade() {
|
||||||
async_tx
|
async_tx
|
||||||
|
@ -270,7 +273,12 @@ impl UnixUserAccount {
|
||||||
|
|
||||||
pub(crate) fn check_existing_pw(&self, cleartext: &str) -> Result<bool, OperationError> {
|
pub(crate) fn check_existing_pw(&self, cleartext: &str) -> Result<bool, OperationError> {
|
||||||
match &self.cred {
|
match &self.cred {
|
||||||
Some(cred) => cred.password_ref().and_then(|pw| pw.verify(cleartext)),
|
Some(cred) => cred.password_ref().and_then(|pw| {
|
||||||
|
pw.verify(cleartext).map_err(|e| {
|
||||||
|
error!(crypto_err = ?e);
|
||||||
|
e.into()
|
||||||
|
})
|
||||||
|
}),
|
||||||
None => Err(OperationError::InvalidState),
|
None => Err(OperationError::InvalidState),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ repository.workspace = true
|
||||||
default = ["unix"]
|
default = ["unix"]
|
||||||
unix = []
|
unix = []
|
||||||
selinux = ["dep:selinux"]
|
selinux = ["dep:selinux"]
|
||||||
|
tpm = ["dep:tss-esapi", "kanidm_lib_crypto/tpm"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "kanidm_unixd"
|
name = "kanidm_unixd"
|
||||||
|
@ -66,6 +67,7 @@ toml.workspace = true
|
||||||
tokio = { workspace = true, features = ["rt", "fs", "macros", "sync", "time", "net", "io-util"] }
|
tokio = { workspace = true, features = ["rt", "fs", "macros", "sync", "time", "net", "io-util"] }
|
||||||
tokio-util = { workspace = true, features = ["codec"] }
|
tokio-util = { workspace = true, features = ["codec"] }
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
|
tss-esapi = { workspace = true, optional = true }
|
||||||
reqwest = { workspace = true, default-features = false }
|
reqwest = { workspace = true, default-features = false }
|
||||||
walkdir.workspace = true
|
walkdir.workspace = true
|
||||||
|
|
||||||
|
|
|
@ -77,8 +77,9 @@ impl CacheLayer {
|
||||||
uid_attr_map: UidAttr,
|
uid_attr_map: UidAttr,
|
||||||
gid_attr_map: UidAttr,
|
gid_attr_map: UidAttr,
|
||||||
allow_id_overrides: Vec<String>,
|
allow_id_overrides: Vec<String>,
|
||||||
|
require_tpm: Option<&str>,
|
||||||
) -> Result<Self, ()> {
|
) -> Result<Self, ()> {
|
||||||
let db = Db::new(path)?;
|
let db = Db::new(path, require_tpm)?;
|
||||||
|
|
||||||
// setup and do a migrate.
|
// setup and do a migrate.
|
||||||
{
|
{
|
||||||
|
|
|
@ -673,6 +673,7 @@ async fn main() -> ExitCode {
|
||||||
cfg.uid_attr_map,
|
cfg.uid_attr_map,
|
||||||
cfg.gid_attr_map,
|
cfg.gid_attr_map,
|
||||||
cfg.allow_local_account_override.clone(),
|
cfg.allow_local_account_override.clone(),
|
||||||
|
cfg.require_tpm.as_deref(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -712,6 +713,7 @@ async fn main() -> ExitCode {
|
||||||
// Start to build the worker tasks
|
// Start to build the worker tasks
|
||||||
let (broadcast_tx, mut broadcast_rx) = broadcast::channel(4);
|
let (broadcast_tx, mut broadcast_rx) = broadcast::channel(4);
|
||||||
let mut c_broadcast_rx = broadcast_tx.subscribe();
|
let mut c_broadcast_rx = broadcast_tx.subscribe();
|
||||||
|
let mut d_broadcast_rx = broadcast_tx.subscribe();
|
||||||
|
|
||||||
let task_b = tokio::spawn(async move {
|
let task_b = tokio::spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
|
@ -739,10 +741,15 @@ async fn main() -> ExitCode {
|
||||||
debug!("A task handler has connected.");
|
debug!("A task handler has connected.");
|
||||||
// It did? Great, now we can wait and spin on that one
|
// It did? Great, now we can wait and spin on that one
|
||||||
// client.
|
// client.
|
||||||
if let Err(e) =
|
|
||||||
handle_task_client(socket, &task_channel_tx, &mut task_channel_rx).await
|
tokio::select! {
|
||||||
{
|
_ = d_broadcast_rx.recv() => {
|
||||||
error!("Task client error occurred; error = {:?}", e);
|
break;
|
||||||
|
}
|
||||||
|
// We have to check for signals here else this tasks waits forever.
|
||||||
|
Err(e) = handle_task_client(socket, &task_channel_tx, &mut task_channel_rx) => {
|
||||||
|
error!("Task client error occurred; error = {:?}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If they DC we go back to accept.
|
// If they DC we go back to accept.
|
||||||
}
|
}
|
||||||
|
@ -754,13 +761,14 @@ async fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
// done
|
// done
|
||||||
}
|
}
|
||||||
|
info!("Stopped task connector");
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Setup a task that handles pre-fetching here.
|
// TODO: Setup a task that handles pre-fetching here.
|
||||||
|
|
||||||
let (inotify_tx, mut inotify_rx) = channel(4);
|
let (inotify_tx, mut inotify_rx) = channel(4);
|
||||||
|
|
||||||
let _watcher =
|
let watcher =
|
||||||
match new_debouncer(Duration::from_secs(2), None, move |_event| {
|
match new_debouncer(Duration::from_secs(2), None, move |_event| {
|
||||||
let _ = inotify_tx.try_send(true);
|
let _ = inotify_tx.try_send(true);
|
||||||
})
|
})
|
||||||
|
@ -798,6 +806,7 @@ async fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
info!("Stopped inotify watcher");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set the umask while we open the path for most clients.
|
// Set the umask while we open the path for most clients.
|
||||||
|
@ -839,6 +848,7 @@ async fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
info!("Stopped resolver");
|
||||||
});
|
});
|
||||||
|
|
||||||
info!("Server started ...");
|
info!("Server started ...");
|
||||||
|
@ -880,12 +890,14 @@ async fn main() -> ExitCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info!("Signal received, shutting down");
|
info!("Signal received, sending down signal to tasks");
|
||||||
// Send a broadcast that we are done.
|
// Send a broadcast that we are done.
|
||||||
if let Err(e) = broadcast_tx.send(true) {
|
if let Err(e) = broadcast_tx.send(true) {
|
||||||
error!("Unable to shutdown workers {:?}", e);
|
error!("Unable to shutdown workers {:?}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drop(watcher);
|
||||||
|
|
||||||
let _ = task_a.await;
|
let _ = task_a.await;
|
||||||
let _ = task_b.await;
|
let _ = task_b.await;
|
||||||
let _ = task_c.await;
|
let _ = task_c.await;
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub struct Db {
|
||||||
pool: Pool<SqliteConnectionManager>,
|
pool: Pool<SqliteConnectionManager>,
|
||||||
lock: Mutex<()>,
|
lock: Mutex<()>,
|
||||||
crypto_policy: CryptoPolicy,
|
crypto_policy: CryptoPolicy,
|
||||||
|
require_tpm: Option<tpm::TpmConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DbTxn<'a> {
|
pub struct DbTxn<'a> {
|
||||||
|
@ -24,10 +25,11 @@ pub struct DbTxn<'a> {
|
||||||
committed: bool,
|
committed: bool,
|
||||||
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
||||||
crypto_policy: &'a CryptoPolicy,
|
crypto_policy: &'a CryptoPolicy,
|
||||||
|
require_tpm: Option<&'a tpm::TpmConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Db {
|
impl Db {
|
||||||
pub fn new(path: &str) -> Result<Self, ()> {
|
pub fn new(path: &str, require_tpm: Option<&str>) -> Result<Self, ()> {
|
||||||
let before = unsafe { umask(0o0027) };
|
let before = unsafe { umask(0o0027) };
|
||||||
let manager = SqliteConnectionManager::file(path);
|
let manager = SqliteConnectionManager::file(path);
|
||||||
let _ = unsafe { umask(before) };
|
let _ = unsafe { umask(before) };
|
||||||
|
@ -42,10 +44,30 @@ impl Db {
|
||||||
|
|
||||||
debug!("Configured {:?}", crypto_policy);
|
debug!("Configured {:?}", crypto_policy);
|
||||||
|
|
||||||
|
// Test a tpm context.
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
let require_tpm = if let Some(tcti_str) = require_tpm {
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
let r = Db::tpm_setup_context(
|
||||||
|
tcti_str,
|
||||||
|
pool.get().expect("Unable to get connection from pool!!!"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
warn!("require_tpm is set, but tpm was not built in. This instance will NOT cache passwords!");
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
let r = tpm::TpmConfig {};
|
||||||
|
|
||||||
|
Some(r)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Db {
|
Ok(Db {
|
||||||
pool,
|
pool,
|
||||||
lock: Mutex::new(()),
|
lock: Mutex::new(()),
|
||||||
crypto_policy,
|
crypto_policy,
|
||||||
|
require_tpm,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +78,7 @@ impl Db {
|
||||||
.pool
|
.pool
|
||||||
.get()
|
.get()
|
||||||
.expect("Unable to get connection from pool!!!");
|
.expect("Unable to get connection from pool!!!");
|
||||||
DbTxn::new(conn, guard, &self.crypto_policy)
|
DbTxn::new(conn, guard, &self.crypto_policy, self.require_tpm.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +93,7 @@ impl<'a> DbTxn<'a> {
|
||||||
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
||||||
guard: MutexGuard<'a, ()>,
|
guard: MutexGuard<'a, ()>,
|
||||||
crypto_policy: &'a CryptoPolicy,
|
crypto_policy: &'a CryptoPolicy,
|
||||||
|
require_tpm: Option<&'a tpm::TpmConfig>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
// Start the transaction
|
// Start the transaction
|
||||||
// debug!("Starting db WR txn ...");
|
// debug!("Starting db WR txn ...");
|
||||||
|
@ -82,6 +105,7 @@ impl<'a> DbTxn<'a> {
|
||||||
conn,
|
conn,
|
||||||
_guard: guard,
|
_guard: guard,
|
||||||
crypto_policy,
|
crypto_policy,
|
||||||
|
require_tpm,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,9 +472,22 @@ impl<'a> DbTxn<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_account_password(&self, a_uuid: &str, cred: &str) -> Result<(), ()> {
|
pub fn update_account_password(&self, a_uuid: &str, cred: &str) -> Result<(), ()> {
|
||||||
let pw = Password::new(self.crypto_policy, cred).map_err(|e| {
|
#[allow(unused_variables)]
|
||||||
error!("password error -> {:?}", e);
|
let pw = if let Some(tcti_str) = self.require_tpm {
|
||||||
})?;
|
// Do nothing.
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
return Ok(());
|
||||||
|
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
let pw = Db::tpm_new(self.crypto_policy, cred, tcti_str)?;
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
pw
|
||||||
|
} else {
|
||||||
|
Password::new(self.crypto_policy, cred).map_err(|e| {
|
||||||
|
error!("password error -> {:?}", e);
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
let dbpw = pw.to_dbpasswordv1();
|
let dbpw = pw.to_dbpasswordv1();
|
||||||
let data = serde_json::to_vec(&dbpw).map_err(|e| {
|
let data = serde_json::to_vec(&dbpw).map_err(|e| {
|
||||||
error!("json error -> {:?}", e);
|
error!("json error -> {:?}", e);
|
||||||
|
@ -471,6 +508,11 @@ impl<'a> DbTxn<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_account_password(&self, a_uuid: &str, cred: &str) -> Result<bool, ()> {
|
pub fn check_account_password(&self, a_uuid: &str, cred: &str) -> Result<bool, ()> {
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
if self.require_tpm.is_some() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
let mut stmt = self
|
let mut stmt = self
|
||||||
.conn
|
.conn
|
||||||
.prepare("SELECT password FROM account_t WHERE uuid = :a_uuid AND password IS NOT NULL")
|
.prepare("SELECT password FROM account_t WHERE uuid = :a_uuid AND password IS NOT NULL")
|
||||||
|
@ -502,20 +544,34 @@ impl<'a> DbTxn<'a> {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let r: Result<bool, ()> = data
|
let pw = data.first().map(|raw| {
|
||||||
.first()
|
// Map the option from data.first.
|
||||||
.map(|raw| {
|
let dbpw: DbPasswordV1 = serde_json::from_slice(raw.as_slice()).map_err(|e| {
|
||||||
// Map the option from data.first.
|
error!("json error -> {:?}", e);
|
||||||
let dbpw: DbPasswordV1 = serde_json::from_slice(raw.as_slice()).map_err(|e| {
|
})?;
|
||||||
error!("json error -> {:?}", e);
|
Password::try_from(dbpw)
|
||||||
})?;
|
});
|
||||||
let pw = Password::try_from(dbpw)?;
|
|
||||||
pw.verify(cred).map_err(|e| {
|
let pw = match pw {
|
||||||
error!("password error -> {:?}", e);
|
Some(Ok(p)) => p,
|
||||||
})
|
_ => return Ok(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
if let Some(tcti_str) = self.require_tpm {
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
let r = Db::tpm_verify(pw, cred, tcti_str);
|
||||||
|
|
||||||
|
// Do nothing.
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
let r = Ok(false);
|
||||||
|
|
||||||
|
r
|
||||||
|
} else {
|
||||||
|
pw.verify(cred).map_err(|e| {
|
||||||
|
error!("password error -> {:?}", e);
|
||||||
})
|
})
|
||||||
.unwrap_or(Ok(false));
|
}
|
||||||
r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_group_data_name(&self, grp_id: &str) -> Result<Vec<(Vec<u8>, i64)>, ()> {
|
fn get_group_data_name(&self, grp_id: &str) -> Result<Vec<(Vec<u8>, i64)>, ()> {
|
||||||
|
@ -726,6 +782,170 @@ impl<'a> Drop for DbTxn<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
pub(crate) mod tpm {
|
||||||
|
pub struct TpmConfig {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
pub(crate) mod tpm {
|
||||||
|
use super::Db;
|
||||||
|
|
||||||
|
use r2d2_sqlite::SqliteConnectionManager;
|
||||||
|
use rusqlite::OptionalExtension;
|
||||||
|
|
||||||
|
use kanidm_lib_crypto::{CryptoError, CryptoPolicy, Password, TpmError};
|
||||||
|
use tss_esapi::{utils::TpmsContext, Context, TctiNameConf};
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
pub struct TpmConfig {
|
||||||
|
tcti: TctiNameConf,
|
||||||
|
ctx: TpmsContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Db {
|
||||||
|
pub fn tpm_setup_context(
|
||||||
|
tcti_str: &str,
|
||||||
|
conn: r2d2::PooledConnection<SqliteConnectionManager>,
|
||||||
|
) -> Result<TpmConfig, ()> {
|
||||||
|
let tcti = TctiNameConf::from_str(tcti_str).map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "Failed to parse tcti name");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut context = Context::new(tcti.clone()).map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "Failed to create tpm context");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TABLE IF NOT EXISTS config_t (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT NOT NULL
|
||||||
|
)
|
||||||
|
",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(sqlite_err = ?e, "update config_t tpm_ctx");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Try and get the db context.
|
||||||
|
let ctx_data: Option<Vec<u8>> = conn
|
||||||
|
.query_row(
|
||||||
|
"SELECT value FROM config_t WHERE key='tpm2_ctx'",
|
||||||
|
[],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.optional()
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(sqlite_err = ?e, "Failed to load tpm2_ctx");
|
||||||
|
})
|
||||||
|
.unwrap_or(None);
|
||||||
|
|
||||||
|
trace!(ctx_data_present = %ctx_data.is_some());
|
||||||
|
|
||||||
|
let ex_ctx = if let Some(ctx_data) = ctx_data {
|
||||||
|
// Test loading, blank it out if it fails.
|
||||||
|
// deserialise
|
||||||
|
let maybe_ctx: TpmsContext =
|
||||||
|
serde_json::from_slice(ctx_data.as_slice()).map_err(|e| {
|
||||||
|
warn!("json error -> {:?}", e);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// can it load?
|
||||||
|
context
|
||||||
|
.execute_with_nullauth_session(|ctx| ctx.context_load(maybe_ctx.clone()))
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "Failed to load tpm context");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Some(maybe_ctx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let ctx = if let Some(existing_ctx) = ex_ctx {
|
||||||
|
existing_ctx
|
||||||
|
} else {
|
||||||
|
// Need to regenerate for some reason
|
||||||
|
info!("Creating new tpm ctx key");
|
||||||
|
context
|
||||||
|
.execute_with_nullauth_session(|ctx| {
|
||||||
|
let key = Password::prepare_tpm_key(ctx)?;
|
||||||
|
|
||||||
|
ctx.context_save(key.into()).map_err(|e| e.into())
|
||||||
|
})
|
||||||
|
.map_err(|e: CryptoError| {
|
||||||
|
error!(tpm_err = ?e, "Failed to create tpm key");
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Serialise it out.
|
||||||
|
let data = serde_json::to_vec(&ctx).map_err(|e| {
|
||||||
|
error!("json error -> {:?}", e);
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Update the tpm ctx str
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR REPLACE INTO config_t (key, value) VALUES ('tpm2_ctx', :data)",
|
||||||
|
named_params! {
|
||||||
|
":data": &data,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(sqlite_err = ?e, "update config_t tpm_ctx");
|
||||||
|
})
|
||||||
|
.map(|_| ())?;
|
||||||
|
|
||||||
|
info!("tpm binding configured");
|
||||||
|
|
||||||
|
Ok(TpmConfig { tcti, ctx })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tpm_new(
|
||||||
|
policy: &CryptoPolicy,
|
||||||
|
cred: &str,
|
||||||
|
tpm_conf: &TpmConfig,
|
||||||
|
) -> Result<Password, ()> {
|
||||||
|
let mut context = Context::new(tpm_conf.tcti.clone()).map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "Failed to create tpm context");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
context
|
||||||
|
.execute_with_nullauth_session(|ctx| {
|
||||||
|
let key = ctx.context_load(tpm_conf.ctx.clone()).map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "Failed to load tpm context");
|
||||||
|
<TpmError as Into<CryptoError>>::into(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Password::new_argon2id_tpm(policy, cred, ctx, key)
|
||||||
|
})
|
||||||
|
.map_err(|e: CryptoError| {
|
||||||
|
error!(tpm_err = ?e, "Failed to create tpm bound password");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tpm_verify(pw: Password, cred: &str, tpm_conf: &TpmConfig) -> Result<bool, ()> {
|
||||||
|
let mut context = Context::new(tpm_conf.tcti.clone()).map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "Failed to create tpm context");
|
||||||
|
})?;
|
||||||
|
|
||||||
|
context
|
||||||
|
.execute_with_nullauth_session(|ctx| {
|
||||||
|
let key = ctx.context_load(tpm_conf.ctx.clone()).map_err(|e| {
|
||||||
|
error!(tpm_err = ?e, "Failed to load tpm context");
|
||||||
|
<TpmError as Into<CryptoError>>::into(e)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
pw.verify_ctx(cred, Some((ctx, key)))
|
||||||
|
})
|
||||||
|
.map_err(|e: CryptoError| {
|
||||||
|
error!(tpm_err = ?e, "Failed to create tpm bound password");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
|
use kanidm_proto::v1::{UnixGroupToken, UnixUserToken};
|
||||||
|
@ -739,7 +959,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_cache_db_account_basic() {
|
async fn test_cache_db_account_basic() {
|
||||||
sketching::test_init();
|
sketching::test_init();
|
||||||
let db = Db::new("").expect("failed to create.");
|
let db = Db::new("", None).expect("failed to create.");
|
||||||
let dbtxn = db.write().await;
|
let dbtxn = db.write().await;
|
||||||
assert!(dbtxn.migrate().is_ok());
|
assert!(dbtxn.migrate().is_ok());
|
||||||
|
|
||||||
|
@ -823,7 +1043,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_cache_db_group_basic() {
|
async fn test_cache_db_group_basic() {
|
||||||
sketching::test_init();
|
sketching::test_init();
|
||||||
let db = Db::new("").expect("failed to create.");
|
let db = Db::new("", None).expect("failed to create.");
|
||||||
let dbtxn = db.write().await;
|
let dbtxn = db.write().await;
|
||||||
assert!(dbtxn.migrate().is_ok());
|
assert!(dbtxn.migrate().is_ok());
|
||||||
|
|
||||||
|
@ -898,7 +1118,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_cache_db_account_group_update() {
|
async fn test_cache_db_account_group_update() {
|
||||||
sketching::test_init();
|
sketching::test_init();
|
||||||
let db = Db::new("").expect("failed to create.");
|
let db = Db::new("", None).expect("failed to create.");
|
||||||
let dbtxn = db.write().await;
|
let dbtxn = db.write().await;
|
||||||
assert!(dbtxn.migrate().is_ok());
|
assert!(dbtxn.migrate().is_ok());
|
||||||
|
|
||||||
|
@ -966,7 +1186,15 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_cache_db_account_password() {
|
async fn test_cache_db_account_password() {
|
||||||
sketching::test_init();
|
sketching::test_init();
|
||||||
let db = Db::new("").expect("failed to create.");
|
|
||||||
|
#[cfg(feature = "tpm")]
|
||||||
|
let tcti_str = Some("device:/dev/tpmrm0");
|
||||||
|
|
||||||
|
#[cfg(not(feature = "tpm"))]
|
||||||
|
let tcti_str = None;
|
||||||
|
|
||||||
|
let db = Db::new("", tcti_str).expect("failed to create.");
|
||||||
|
|
||||||
let dbtxn = db.write().await;
|
let dbtxn = db.write().await;
|
||||||
assert!(dbtxn.migrate().is_ok());
|
assert!(dbtxn.migrate().is_ok());
|
||||||
|
|
||||||
|
@ -1015,7 +1243,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_cache_db_group_rename_duplicate() {
|
async fn test_cache_db_group_rename_duplicate() {
|
||||||
sketching::test_init();
|
sketching::test_init();
|
||||||
let db = Db::new("").expect("failed to create.");
|
let db = Db::new("", None).expect("failed to create.");
|
||||||
let dbtxn = db.write().await;
|
let dbtxn = db.write().await;
|
||||||
assert!(dbtxn.migrate().is_ok());
|
assert!(dbtxn.migrate().is_ok());
|
||||||
|
|
||||||
|
@ -1070,7 +1298,7 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_cache_db_account_rename_duplicate() {
|
async fn test_cache_db_account_rename_duplicate() {
|
||||||
sketching::test_init();
|
sketching::test_init();
|
||||||
let db = Db::new("").expect("failed to create.");
|
let db = Db::new("", None).expect("failed to create.");
|
||||||
let dbtxn = db.write().await;
|
let dbtxn = db.write().await;
|
||||||
assert!(dbtxn.migrate().is_ok());
|
assert!(dbtxn.migrate().is_ok());
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ struct ConfigInt {
|
||||||
selinux: Option<bool>,
|
selinux: Option<bool>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
allow_local_account_override: Vec<String>,
|
allow_local_account_override: Vec<String>,
|
||||||
|
require_tpm: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
@ -92,6 +93,7 @@ pub struct KanidmUnixdConfig {
|
||||||
pub uid_attr_map: UidAttr,
|
pub uid_attr_map: UidAttr,
|
||||||
pub gid_attr_map: UidAttr,
|
pub gid_attr_map: UidAttr,
|
||||||
pub selinux: bool,
|
pub selinux: bool,
|
||||||
|
pub require_tpm: Option<String>,
|
||||||
pub allow_local_account_override: Vec<String>,
|
pub allow_local_account_override: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +128,11 @@ impl Display for KanidmUnixdConfig {
|
||||||
writeln!(f, "gid_attr_map: {}", self.gid_attr_map)?;
|
writeln!(f, "gid_attr_map: {}", self.gid_attr_map)?;
|
||||||
|
|
||||||
writeln!(f, "selinux: {}", self.selinux)?;
|
writeln!(f, "selinux: {}", self.selinux)?;
|
||||||
|
writeln!(
|
||||||
|
f,
|
||||||
|
"require_tpm: {}",
|
||||||
|
self.require_tpm.as_deref().unwrap_or("-")
|
||||||
|
)?;
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"allow_local_account_override: {:#?}",
|
"allow_local_account_override: {:#?}",
|
||||||
|
@ -156,6 +163,7 @@ impl KanidmUnixdConfig {
|
||||||
uid_attr_map: DEFAULT_UID_ATTR_MAP,
|
uid_attr_map: DEFAULT_UID_ATTR_MAP,
|
||||||
gid_attr_map: DEFAULT_GID_ATTR_MAP,
|
gid_attr_map: DEFAULT_GID_ATTR_MAP,
|
||||||
selinux: DEFAULT_SELINUX,
|
selinux: DEFAULT_SELINUX,
|
||||||
|
require_tpm: None,
|
||||||
allow_local_account_override: Vec::default(),
|
allow_local_account_override: Vec::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,6 +276,7 @@ impl KanidmUnixdConfig {
|
||||||
true => selinux_util::supported(),
|
true => selinux_util::supported(),
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
require_tpm: config.require_tpm.or(self.require_tpm),
|
||||||
allow_local_account_override: config.allow_local_account_override,
|
allow_local_account_override: config.allow_local_account_override,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,7 @@ async fn setup_test(fix_fn: Fixture) -> (CacheLayer, KanidmClient) {
|
||||||
DEFAULT_UID_ATTR_MAP,
|
DEFAULT_UID_ATTR_MAP,
|
||||||
DEFAULT_GID_ATTR_MAP,
|
DEFAULT_GID_ATTR_MAP,
|
||||||
vec!["masked_group".to_string()],
|
vec!["masked_group".to_string()],
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to build cache layer.");
|
.expect("Failed to build cache layer.");
|
||||||
|
|
Loading…
Reference in a new issue