chipsenkbeil / distant

🚧 (Alpha stage software) Library and tooling that supports remote filesystem and process operations. 🚧
https://distant.dev
566 stars 11 forks source link

Add password auth as alternative to static-key #155

Open chipsenkbeil opened 1 year ago

chipsenkbeil commented 1 year ago
chipsenkbeil commented 1 year ago

NOTE: The pam-client and other crate that use the pam sys crate do NOT support OpenPAM. We'd have to write our own bindings!

chipsenkbeil commented 1 year ago

For windows, could use powershell to do this as well: https://serverfault.com/questions/596602/powershell-test-user-credentials-in-ad-with-password-reset

chipsenkbeil commented 1 year ago

For windows using the LogonUserW function:

use std::ptr;
use windows::{ErrorCode, Result};
use windows::win32::security::LogonUserW;
use windows::win32::windows_programming::CloseHandle;

fn authenticate_user(username: &str, password: &str) -> Result<bool> {
    let mut token_handle = ptr::null_mut();

    // NOTE: If we provide NULL to the domain, the username is expected to be in UPN
   //              format, which looks like User@DNSDomainName
   //
   //              We can instead provide a domain of "." for a local account
    let success = unsafe {
        LogonUserW(
            windows::wstring::from_str(username)?.as_ptr(),
            ptr::null_mut(),
            windows::wstring::from_str(password)?.as_ptr(),
            LogonUserW::LOGON32_LOGON_NETWORK,
            LogonUserW::LOGON32_PROVIDER_DEFAULT,
            &mut token_handle,
        ).is_ok()
    };
    let result = success.map_err(|error| error.code())?;
    if success {
        // authentication succeeded, do something with the token handle
        // (e.g. use it to impersonate the authenticated user)
        unsafe { CloseHandle(token_handle) };
    }
    Ok(result)
}
chipsenkbeil commented 1 year ago

For MacOS, we can use the security-framework crate:

use security_framework::{
    auth::*, base::SecKeychainItem, import::SecImportOptions, item::SecItem,
};
use std::os::raw::c_void;

fn authenticate(username: &str, password: &str) -> Result<(), String> {
    let auth_context = AuthContext::new()?;
    let auth_context = auth_context.set_option(AuthOption::UserName(username))?;
    let auth_context = auth_context.set_option(AuthOption::Password(password))?;
    let result = auth_context.evaluate()?;

    if result == AuthResult::Success {
        Ok(())
    } else {
        Err(format!("Authentication failed with result: {:?}", result))
    }
}
chipsenkbeil commented 1 year ago

The security-framework example isn't real (chatGPT failed me). I did figure out a way to do this from the commandline that works for a non-root user is dscl . -authonly {username} {password}. Providing this on the commandline is bad because it'll have the password visible and in the history! You can provide it without the password, but it doesn't appear that I can feed in the password over stdin?

Note that dscl /Login/Default -authonly {username} {password} also works and appears to be what . defaults to on my machine.

chipsenkbeil commented 1 year ago

I've now written https://crates.io/crates/pwcheck to do basic password authentication that leverages su for Unix systems including MacOS, and the LogonUserW via the windows crate for Windows.

It's a bit of a hack and may not be the best in terms of security, but it does work on all three platforms: Linux, MacOS, and Windows. It's untested on FreeBSD, NetBSD, and termux.

I'd like to move the authentication methods (outside of none) to a new crate, distant-auth, which will provide the methods we can use as features. This way, the static key can always be available and we can provide password as a feature to use the pwcheck crate.

Once pam is available, that can be made as a feature as well.