tpm2 integration
This commit is contained in:
parent
1893d4c90b
commit
19a7f20162
7 changed files with 1249 additions and 53 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
.idea/
|
.idea/
|
||||||
|
.direnv
|
1062
Cargo.lock
generated
1062
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -7,3 +7,8 @@ edition = "2021"
|
||||||
|
|
||||||
[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
60
flake.lock
Normal 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
46
flake.nix
Normal 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 ];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
125
src/main.rs
125
src/main.rs
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue