use std::fs;
use std::io::{self, BufReader, ErrorKind};
use std::path::Path;
use nkeys::KeyPair;
use once_cell::sync::Lazy;
use regex::Regex;
use rustls::{Certificate, PrivateKey};
use crate::SecureString;
pub(crate) fn load_creds(path: &Path) -> io::Result<(SecureString, KeyPair)> {
let contents = SecureString::from(fs::read_to_string(path)?);
jwt_kp(&contents)
}
pub(crate) fn jwt_kp(contents: &str) -> io::Result<(SecureString, KeyPair)> {
let jwt = parse_decorated_jwt(contents).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"cannot parse user JWT from the credentials file",
)
})?;
let nkey = parse_decorated_nkey(contents).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
"cannot parse nkey from the credentials file",
)
})?;
let kp =
KeyPair::from_seed(&nkey).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
Ok((jwt, kp))
}
pub(crate) fn sign_nonce(nonce: &[u8], key_pair: &KeyPair) -> io::Result<SecureString> {
let sig = key_pair
.sign(nonce)
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
Ok(SecureString::from(base64_url::encode(&sig)))
}
static USER_CONFIG_RE: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"\s*(?:(?:[-]{3,}.*[-]{3,}\r?\n)([\w\-.=]+)(?:\r?\n[-]{3,}.*[-]{3,}\r?\n))")
.unwrap()
});
fn parse_decorated_jwt(contents: &str) -> Option<SecureString> {
let capture = USER_CONFIG_RE.captures_iter(contents).next()?;
Some(SecureString::from(capture[1].to_string()))
}
fn parse_decorated_nkey(contents: &str) -> Option<SecureString> {
let capture = USER_CONFIG_RE.captures_iter(contents).nth(1)?;
Some(SecureString::from(capture[1].to_string()))
}
pub(crate) fn load_certs(path: &Path) -> io::Result<Vec<Certificate>> {
let file = std::fs::File::open(path)?;
let mut reader = BufReader::new(file);
let certs = rustls_pemfile::certs(&mut reader)?
.iter()
.map(|v| Certificate(v.clone()))
.collect();
Ok(certs)
}
pub(crate) fn load_key(path: &Path) -> io::Result<PrivateKey> {
let file = std::fs::File::open(path)?;
let mut reader = BufReader::new(file);
loop {
let cert = rustls_pemfile::read_one(&mut reader)?;
match cert {
Some(rustls_pemfile::Item::RSAKey(key)) | Some(rustls_pemfile::Item::PKCS8Key(key)) => {
return Ok(PrivateKey(key))
}
Some(rustls_pemfile::Item::X509Certificate(_)) => {}
None => break,
}
}
Err(io::Error::new(
ErrorKind::NotFound,
"could not find client key in the path",
))
}