tpm2 integration

This commit is contained in:
Vivian 2023-12-18 22:29:13 +01:00
parent cbf4e9d62f
commit 83a4181842
7 changed files with 1249 additions and 53 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
/target /target
.idea/ .idea/
.direnv

1062
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -6,4 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
color-eyre = "0.6.2" color-eyre = "0.6.2"
serde_json = "1.0.108"
serde = { version = "1.0.193", features = ["derive"] }
clevis-pin-tpm2 = { path = "../clevis-pin-tpm2" }
clap = { version = "4", features = ["derive"] }
rpassword = "7.3.1"

60
flake.lock Normal file
View file

@ -0,0 +1,60 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1702933230,
"narHash": "sha256-xi8AZ3noIXrgmKLR+ij+CeYFoUTKiQuTLL+aA7FRdRQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "4e2c5373180ecd17e41e879420be69dc642a6349",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

46
flake.nix Normal file
View file

@ -0,0 +1,46 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) stdenv lib;
in {
devShell = pkgs.mkShell {
shellHook = ''
export BINDGEN_EXTRA_CLANG_ARGS="$(< ${stdenv.cc}/nix-support/libc-crt1-cflags) \
$(< ${stdenv.cc}/nix-support/libc-cflags) \
$(< ${stdenv.cc}/nix-support/cc-cflags) \
$(< ${stdenv.cc}/nix-support/libcxx-cxxflags) \
${
lib.optionalString stdenv.cc.isClang
"-idirafter ${stdenv.cc.cc}/lib/clang/${
lib.getVersion stdenv.cc.cc
}/include"
} \
${
lib.optionalString stdenv.cc.isGNU
"-isystem ${stdenv.cc.cc}/include/c++/${
lib.getVersion stdenv.cc.cc
} -isystem ${stdenv.cc.cc}/include/c++/${
lib.getVersion stdenv.cc.cc
}/${stdenv.hostPlatform.config} -idirafter ${stdenv.cc.cc}/lib/gcc/${stdenv.hostPlatform.config}/${
lib.getVersion stdenv.cc.cc
}/include"
} \
"
'';
nativeBuildInputs = with pkgs; [
llvmPackages.libclang
llvmPackages.libcxxClang
clang
];
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
buildInputs = with pkgs; [ libclang pkg-config openssl tpm2-tss ];
};
});
}

View file

@ -1,8 +1,12 @@
use clap::{Parser, Subcommand};
use clevis_pin_tpm2::tpm_objects::TPM2Config;
use color_eyre::eyre::{bail, eyre, WrapErr}; use color_eyre::eyre::{bail, eyre, WrapErr};
use std::env; use std::env;
use std::io::{Read, Write}; use std::fs::{read_to_string, File};
use std::io::{stdin, Read, Write};
use std::os::unix::net::UnixStream; use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::exit;
fn get_control_socket() -> Option<PathBuf> { fn get_control_socket() -> Option<PathBuf> {
let gnome_var = env::var("GNOME_KEYRING_CONTROL") let gnome_var = env::var("GNOME_KEYRING_CONTROL")
@ -31,7 +35,7 @@ impl ControlOp {
} }
} }
#[derive(Debug)] #[derive(Debug, PartialEq, Eq)]
enum ControlResult { enum ControlResult {
Ok = 0, Ok = 0,
Denied = 1, Denied = 1,
@ -40,100 +44,117 @@ enum ControlResult {
} }
impl ControlResult { impl ControlResult {
fn from_u32(n: u32) -> Option<ControlResult> { fn from_bytes(bytes: [u8; 4]) -> Option<Self> {
match n { let num = u32::from_be_bytes(bytes);
0 => Some(ControlResult::Ok), match num {
1 => Some(ControlResult::Denied), 0 => Some(Self::Ok),
2 => Some(ControlResult::Failed), 1 => Some(Self::Denied),
3 => Some(ControlResult::NoDaemon), 3 => Some(Self::NoDaemon),
_ => None, _ => None,
} }
} }
} }
fn unlock_keyring(password: &str) -> color_eyre::Result<ControlResult> { fn unlock_keyring(password: &[u8]) -> color_eyre::Result<ControlResult> {
let socket = get_control_socket() let socket = get_control_socket()
.ok_or_else(|| eyre!("Could not find gnome keyring control socket path"))?; .ok_or_else(|| eyre!("Could not find gnome keyring control socket path"))?;
let mut stream = UnixStream::connect(socket) let mut stream = UnixStream::connect(socket)
.wrap_err("Could not connect to the gnome keyring unix socket")?; .wrap_err("Could not connect to the gnome keyring unix socket")?;
let ret = stream stream
.write(&[0]) .write_all(&[0])
.wrap_err("could not write credential byte")?; .wrap_err("could not write credential byte")?;
if ret != 1 {
bail!("writing cred byte failed")
}
// oplen is // oplen is
// 8 = packet size + op code // 8 = packet size + op code
// 4 size of length of pw byte // 4 size of length of pw byte
let oplen: u32 = 8 + 4 + password.len() as u32; let oplen: u32 = 8 + 4 + password.len() as u32;
// write length // write length
let ret = stream stream
.write(&oplen.to_be_bytes()) .write_all(&oplen.to_be_bytes())
.wrap_err("could not write oplen")?; .wrap_err("could not write oplen")?;
if ret != 4 {
bail!("writing oplen failed")
}
// write unlock // write unlock
let ret = stream stream
.write(&ControlOp::Unlock.to_bytes()) .write_all(&ControlOp::Unlock.to_bytes())
.wrap_err("could not write unlock")?; .wrap_err("could not write unlock")?;
if ret != 4 {
bail!("writing unlock failed")
}
// write pw len // write pw len
let ret = stream stream
.write(&(password.len() as u32).to_be_bytes()) .write_all(&(password.len() as u32).to_be_bytes())
.wrap_err("could not write password length")?; .wrap_err("could not write password length")?;
if ret != 4 { stream.write_all(password).wrap_err("writing pass failed")?;
bail!("writing pwlen failed")
}
let mut pw_buf = password.as_bytes();
while !pw_buf.is_empty() {
let ret = stream.write(pw_buf).wrap_err("writing password failed")?;
pw_buf = &pw_buf[ret..]
}
let mut buf = [0; 4]; let mut buf = [0; 4];
let val = stream stream
.read(&mut buf) .read_exact(&mut buf)
.wrap_err("could not read response length")?; .wrap_err("could not read response length")?;
if val != 4 {
bail!("invalid response length length")
}
let len = u32::from_be_bytes(buf); let len = u32::from_be_bytes(buf);
if len != 8 { if len != 8 {
bail!("invalid response length"); bail!("invalid response length");
} }
let val = stream.read(&mut buf).wrap_err("could not read response")?; stream
if val != 4 { .read_exact(&mut buf)
bail!("invalid response length (2)") .wrap_err("could not read response")?;
}
let resp = u32::from_be_bytes(buf); let code = ControlResult::from_bytes(buf).ok_or_else(|| eyre!("invalid control result"))?;
let code = ControlResult::from_u32(resp).ok_or_else(|| eyre!("invalid resp"))?;
Ok(code) Ok(code)
} }
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Unlock,
Enroll,
}
fn main() -> color_eyre::Result<()> { fn main() -> color_eyre::Result<()> {
color_eyre::install().unwrap(); color_eyre::install().unwrap();
let cli = Cli::parse();
let res = unlock_keyring("example")?; match cli.command {
Commands::Unlock => {
let file = PathBuf::from("/home/vivian/.config/gnome_password.token");
if file.exists() {
let token = read_to_string(file)?;
let password = clevis_pin_tpm2::perform_decrypt(token.as_bytes())
.map_err(|err| eyre!("{err:?}"))?;
let res = unlock_keyring(password.as_slice())?;
if res != ControlResult::Ok {
eprintln!("Failed to unlock keyring: {res:?}");
exit(2);
}
} else {
bail!("password token file not found")
}
println!("Unlocked keyring successfully")
}
Commands::Enroll => {
let password = rpassword::prompt_password("Password: ")?;
dbg!(res); if unlock_keyring(password.as_bytes())? != ControlResult::Ok {
eprintln!("invalid password");
exit(3);
}
let token =
clevis_pin_tpm2::perform_encrypt(TPM2Config::default(), password.as_bytes())
.map_err(|err| eyre!("{err:?}"))?;
let mut file = File::create("/home/vivian/.config/gnome_password.token")?;
file.write_all(token.as_bytes())?;
println!("Password enrolled successfully")
}
}
Ok(()) Ok(())
} }