feat: added verification for the ssh keys #42

Merged
silver merged 5 commits from #15-set-email-via-ssh-key into main 2023-08-19 20:44:58 +00:00
5 changed files with 104 additions and 7 deletions
Showing only changes of commit 82d7ca48bb - Show all commits

4
Cargo.lock generated
View file

@ -2690,9 +2690,9 @@ dependencies = [
[[package]] [[package]]
name = "ssh-key" name = "ssh-key"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0a17fec6ea344bfa1cda3aed2f0696fddc6295cfcc8c454a3bf58b8ffaabeb" checksum = "728fdf5286c394f12d83eaad51190af629aade5494813ce6b57f7425f1ca51b7"
dependencies = [ dependencies = [
"dsa", "dsa",
"ed25519-dalek", "ed25519-dalek",

View file

@ -42,5 +42,4 @@ lettre = "0.10.4"
maud = "0.25.0" maud = "0.25.0"
# verifying ssh keys # verifying ssh keys
ssh-key = { version = "0.6.0", features = ["ecdsa", "rand_core", "std", "dsa", "ed25519", "rsa" ] } ssh-key = { version = "0.6.1", features = ["crypto", "dsa"] }

View file

@ -0,0 +1,3 @@
# --- BEGIN PVE ---
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBHbsOjxIcLasz+CHA8gUg1pvc8dPHwMKdWoIwNvPxLp Desktop_WSL
# --- END PVE ---

View file

@ -32,6 +32,7 @@ async fn main() -> tide::Result<()> {
app.at("/ldap/recover/password/auth").post(account_recover::password::auth); app.at("/ldap/recover/password/auth").post(account_recover::password::auth);
app.at("/ldap/recover/username").post(account_recover::username::submit); app.at("/ldap/recover/username").post(account_recover::username::submit);
app.at("/ldap/recover/ssh/request").post(account_recover::ssh::request); app.at("/ldap/recover/ssh/request").post(account_recover::ssh::request);
app.at("/ldap/recover/ssh/verify").post(account_recover::ssh::verify);
app.listen(host_port).await?; app.listen(host_port).await?;
Ok(()) Ok(())

View file

@ -426,7 +426,9 @@ pub mod username {
pub mod ssh { pub mod ssh {
use super::*; use super::*;
use crate::AccountsSSH; use crate::AccountsSSH;
use ssh_key::AuthorizedKeys; use ldap3::Mod;
use ssh_key::{AuthorizedKeys, SshSig};
use std::collections::HashSet;
use std::fs; use std::fs;
// this is for a legacy member who has forgotten their account password to be able to set an email. // this is for a legacy member who has forgotten their account password to be able to set an email.
@ -479,7 +481,7 @@ pub mod ssh {
// check if there is ane listing entry, use that auth if exists // check if there is ane listing entry, use that auth if exists
if let Ok(result) = sqlx::query_as::<_, AccountsSSH>( if let Ok(result) = sqlx::query_as::<_, AccountsSSH>(
r#" r#"
SELECT * SELECT *
FROM accounts_ssh FROM accounts_ssh
WHERE user == ? WHERE user == ?
@ -495,7 +497,7 @@ pub mod ssh {
// not in db, generate auth and save // not in db, generate auth and save
let auth = random_string(50); let auth = random_string(50);
if sqlx::query_as::<_, AccountsSSH>( if sqlx::query_as::<_, AccountsSSH>(
" "
INSERT OR REPLACE INTO accounts_ssh (user, auth_code, email) INSERT OR REPLACE INTO accounts_ssh (user, auth_code, email)
VALUES (?1, ?2, ?3) VALUES (?1, ?2, ?3)
", ",
@ -514,4 +516,96 @@ pub mod ssh {
// return the full thing // return the full thing
Ok(json!({"result": "success","success": { "auth": auth,"keys": keys }}).into()) Ok(json!({"result": "success","success": { "auth": auth,"keys": keys }}).into())
} }
#[derive(Debug, Deserialize)]
struct RequestVerify {
user: String,
auth_signed: String,
}
// echo "auth code" | ssh-keygen -Y sign -n file -f /path/to/key
pub async fn verify(mut req: Request<State>) -> tide::Result {
let RequestVerify {
user,
auth_signed,
} = req.body_json().await?;
let db = &req.state().db;
let config = &req.state().config;
let details = if let Ok(result) = sqlx::query_as::<_, AccountsSSH>(
r#"
SELECT *
FROM accounts_ssh
WHERE user == ?
"#,
)
.bind(&user)
.fetch_one(db)
.await
{
result
} else {
return Ok(json!({ "result": "error"}).into());
};
// check if <root>/<user>/.ssh/authorized_keys exists
//let root = "/skynet_old";
let root = ".";
let path = format!("{}/{}/.ssh/authorized_keys", root, user);
let sig = match SshSig::from_pem(auth_signed) {
Ok(x) => x,
Err(_) => {
return Ok(json!({ "result": "error", "error": "Incorrect signed format"}).into());
}
};
// when ye echo it adds a newline to the end of the "file", need to duplicate it here.
let msg_tmp = format!("{}\n", details.auth_code);
let msg = msg_tmp.as_bytes();
let mut valid = false;
if fs::read_to_string(&path).is_ok() {
if let Ok(x) = AuthorizedKeys::read_file(path) {
for entry in x {
let key = entry.public_key();
match key.verify("file", msg, &sig) {
Ok(_) => {
valid = true;
break;
}
Err(_) => {}
}
}
}
}
if !valid {
return Ok(json!({"result": "error", "error": "no valid key"}).into());
}
// add to ldap
let mut ldap = LdapConn::new(&config.ldap_host)?;
ldap.simple_bind(&config.ldap_admin, &config.ldap_admin_pw)?.success()?;
let mods = vec![Mod::Replace(String::from("mail"), HashSet::from([details.email]))];
let dn = format!("uid={},ou=users,dc=skynet,dc=ie", &user);
ldap.modify(&dn, mods)?.success()?;
ldap.unbind()?;
// delete from tmp
sqlx::query_as::<_, AccountsSSH>(
r#"
DELETE FROM accounts_ssh
WHERE user == ?
"#,
)
.bind(&user)
.fetch_optional(db)
.await?;
// return the full thing
Ok(json!({"result": "success", "success": "key valid"}).into())
}
} }