Skip to content

Commit

Permalink
Clean up documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
tgross35 committed Nov 30, 2023
1 parent cebc16d commit 445bfd4
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 48 deletions.
1 change: 0 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ impl fmt::Display for Error {
Error::Base64(e) => write!(f, "base64 error {e}"),
Self::VerifyKey => write!(f, "missing a key marked 'verify'"),
Self::KeyType(v) => write!(f, "unsupported key type {v}"),
// Error::Jose(e) => write!(f, "jose error {e}"),
Error::InvalidPublicKey(key) => write!(f, "invalid public key {key}"),
Error::EllipitcCurve => write!(f, "elliptic curve cryptography"),
Error::MissingPublicKey => write!(f, "could not locate a key with the correct key ID"),
Expand Down
38 changes: 30 additions & 8 deletions src/jose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ impl Advertisment {
///
/// If a thumbprint is specified, only use a verify key with that thumbprint. Otherwise,
/// use any verify key.

fn validate(&self, jwks: &JwkSet, thumbprint: Option<&str>) -> Result<Box<str>> {
let (verify_jwk, thp) = if let Some(thp) = thumbprint {
(jwks.get_key_by_id(thp)?, Box::from(thp))
Expand Down Expand Up @@ -71,7 +70,7 @@ impl Advertisment {
Ok(thp)
}

/// Validate the advertisment and extract its keys
/// Validate the advertisment and extract its keys. Returns an error if validation fails.
pub fn validate_into_keys(self, thumbprint: Option<&str>) -> Result<(JwkSet, Box<str>)> {
let jwks: JwkSet = serde_json::from_str(&self.payload)?;
let thp = self.validate(&jwks, thumbprint)?;
Expand Down Expand Up @@ -99,6 +98,7 @@ impl fmt::Debug for Advertisment {
}
}

/// Our representation of a JSON-serialized JWK
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Jwk {
#[serde(flatten)]
Expand All @@ -107,29 +107,36 @@ pub struct Jwk {
pub key_ops: Option<Vec<Box<str>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alg: Option<Box<str>>,
// FIXME:serde: we don't retain extra fields because our standard fields get duplicated.
// See <https://github.com/serde-rs/serde/issues/2200>
// #[serde(flatten)]
// pub extra: HashMap<Box<str>, serde_json::Value>,
}

/// A JWK type
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(tag = "kty", rename_all = "UPPERCASE")]
pub enum JwkInner {
Ec(EcJwk),
Rsa(RsaJwk),
}

/// JWK representation of an elliptic curve key. Can be private or public.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EcJwk {
/// Curve type
pub crv: Box<str>,
/// Public key `x` coordinate.
pub x: Box<str>,
/// Only required for the `P-` curves
/// Public key `y` coordinate. Only required for the `P-` curves
#[serde(skip_serializing_if = "Option::is_none")]
pub y: Option<Box<str>>,
/// Private key part
/// Private key scalar
#[serde(skip_serializing_if = "Option::is_none")]
pub d: Option<Zeroizing<Box<str>>>,
}

/// RSA key, which we do not yet support
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RsaJwk {
pub e: Box<str>,
Expand Down Expand Up @@ -189,7 +196,7 @@ fn decode_base64url_fe<C: Curve>(s: &str) -> Result<FieldBytes<C>> {
}

impl EcJwk {
/// Convert to a usable `PublicKey`
/// Convert to a usable `PublicKey` from this JWK
pub(crate) fn to_pub<C>(&self) -> Result<PublicKey<C>>
where
C: CurveArithmetic + JwkParameters,
Expand All @@ -209,6 +216,7 @@ impl EcJwk {
PublicKey::from_sec1_bytes(affine.as_bytes()).map_err(Into::into)
}

/// Create a JWK from an EC public key
pub(crate) fn from_pub<C>(key: &PublicKey<C>) -> Self
where
C: CurveArithmetic + JwkParameters,
Expand All @@ -226,6 +234,7 @@ impl EcJwk {
}
}

/// Create a valid private key from this JWK
#[cfg(test)]
pub(crate) fn to_priv<C>(&self) -> Result<SecretKey<C>>
where
Expand All @@ -242,6 +251,7 @@ impl EcJwk {
Ok(result)
}

/// The curve name
pub(crate) fn get_curve(&self) -> Result<JwkCurve> {
match self.crv.as_ref() {
"P-256" => Ok(JwkCurve::P256),
Expand All @@ -251,6 +261,7 @@ impl EcJwk {
}
}

/// Verify a message for a signature
pub(crate) fn verify(&self, message: &[u8], signature: &[u8]) -> Result<()> {
match self.get_curve()? {
JwkCurve::P256 => self.verify_inner::<p256::NistP256>(message, signature),
Expand All @@ -259,6 +270,7 @@ impl EcJwk {
}
}

/// Create a thumbprint of this JWK as specified in the RFC
pub(crate) fn make_thumbprint(&self, alg: ThpHashAlg) -> Box<str> {
let to_enc = json! {{
"crv": &self.crv,
Expand All @@ -269,6 +281,7 @@ impl EcJwk {
alg.hash_data_to_string(to_enc.to_string().as_bytes())
}

/// Curve-eneric implementation of the verification algorithm
fn verify_inner<C>(&self, msg: &[u8], sig: &[u8]) -> Result<()>
where
C: CurveArithmetic + PrimeCurve + JwkParameters,
Expand All @@ -285,7 +298,8 @@ impl EcJwk {
.map_err(|_| Error::FailedVerification)
}

// FIXME: switch these to use generics once p521 uses the `ecdsa` crate traits
/// Verification algorithm specifically for p521
// FIXME:p521: switch these to use generics once p521 uses the `ecdsa` crate traits
fn verify_p521(&self, msg: &[u8], sig: &[u8]) -> Result<()> {
use p521::ecdsa::{Signature, VerifyingKey};
let pubkey = self.to_pub::<p521::NistP521>()?;
Expand Down Expand Up @@ -319,7 +333,7 @@ impl TryFrom<Jwk> for EcJwk {
}
}

/// A verification algorithm
/// An EC algorithm
#[derive(Clone, Copy, Debug)]
pub(crate) enum JwkCurve {
P256,
Expand All @@ -328,6 +342,7 @@ pub(crate) enum JwkCurve {
}

impl RsaJwk {
/// Create a thumbprint as specified in the RFC
fn make_thumbprint(&self, alg: ThpHashAlg) -> Box<str> {
let to_enc = json! {{ "e": &self.e, "kty": "RSA", "n": &self.n }};
alg.hash_data_to_string(to_enc.to_string().as_bytes())
Expand All @@ -353,6 +368,7 @@ impl JwkSet {
.ok_or(Error::MissingKeyOp(op_name.into()))
}

/// Locate a key with a given ID (thumbprint) and return it if it exists
pub(crate) fn get_key_by_id(&self, kid: &str) -> Result<&Jwk> {
for key in &self.keys {
let thp_sha256 = key.make_thumbprint(ThpHashAlg::Sha256);
Expand All @@ -367,6 +383,7 @@ impl JwkSet {
Err(Error::MissingPublicKey)
}

/// Create a tang encryption key
pub(crate) fn make_tang_enc_key<const N: usize>(
&self,
url: &str,
Expand Down Expand Up @@ -399,14 +416,15 @@ impl JwkSet {
}
}

/// The key types we support
/// The hash algorithms we support for thumbprints
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ThpHashAlg {
Sha1,
Sha256,
}

impl ThpHashAlg {
/// Create a thumprint of this data
fn hash_data_to_string(self, data: &[u8]) -> Box<str> {
match self {
ThpHashAlg::Sha1 => {
Expand All @@ -428,6 +446,7 @@ impl ThpHashAlg {
pub struct ProvisionedData<const KEYBYTES: usize> {
/// Use this key to encrypt data
pub encryption_key: EncryptionKey<KEYBYTES>,

/// The thumbprint used for signing. Future keys can be requested using this thumbprint.
pub signing_thumbprint: Box<str>,

Expand Down Expand Up @@ -470,13 +489,15 @@ pub struct KeyMeta {
kid: Box<str>,
}

/// Parameters in the `"clevis"` key
#[derive(Debug, Deserialize, Serialize)]
struct ClevisParams {
/// This is always `tang` I think...
pin: Box<str>,
tang: TangParams,
}

/// Parameters in the `"tang"` key
#[derive(Debug, Deserialize, Serialize)]
struct TangParams {
/// Keys from the initial tang response
Expand Down Expand Up @@ -512,6 +533,7 @@ impl KeyMeta {
&self.clevis.tang.url
}

/// Reciver a key of length N
pub(crate) fn recover_key<const N: usize>(
&self,
server_key_exchange: impl FnOnce(&str, &Jwk) -> Result<Jwk>,
Expand Down
43 changes: 5 additions & 38 deletions src/key_exchange.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub fn create_enc_key<const N: usize>(s_pub_jwk: &EcJwk) -> Result<(EcJwk, Encry
}
}

/// Recover an encryption key when provided a function to do the remote key exchange.
pub fn recover_enc_key<const N: usize>(
c_pub_jwk: &EcJwk,
s_pub_jwk: &EcJwk,
Expand Down Expand Up @@ -187,8 +188,9 @@ where
.map_err(|_| Error::IdentityPointCreated)
}

// FIXME: subtraction doesn't match up with `jose` for some reason, but appears otherwise correct.
// My guess is that it isn't actually doing subtraction, but I am not sure what it is doing instead...
// FIXME:ecmr_sub subtraction doesn't match up with the `jose` CLI for some reason, but appears
// otherwise correct. My guess is that it isn't actually doing subtraction, but I am not sure what
// it is doing instead... See the test file for more comments.
/// Point subtraction, per ECMR in <https://www.mankier.com/1/jose-jwk-exc>
pub fn ecmr_sub<C>(local: &PublicKey<C>, remote: &PublicKey<C>) -> Result<PublicKey<C>>
where
Expand All @@ -200,6 +202,7 @@ where
.map_err(|_| Error::IdentityPointCreated)
}

/// Turn a secret key into a usable encryption key using the concat KDF.
#[allow(clippy::needless_pass_by_value)]
fn secret_to_key<C: Curve, const KEYBYTES: usize>(
secret: SharedSecret<C>,
Expand All @@ -212,42 +215,6 @@ fn secret_to_key<C: Curve, const KEYBYTES: usize>(
enc_key
}

// #[allow(unused)]
// fn jwk_to_priv<C>(jwk: &Jwk) -> Result<SecretKey<C>>
// where
// C: JwkParameters + ValidatePublicKey,
// FieldBytesSize<C>: ModulusSize,
// {
// let errfn = || Error::InvalidPublicKey(jwk.clone());
// let json = serde_json::json! {{
// "crv": jwk.parameter("crv").ok_or_else(errfn)?,
// "kty": jwk.parameter("kty").ok_or_else(errfn)?,
// "d": jwk.parameter("d").ok_or_else(errfn)?,
// "x": jwk.parameter("x").ok_or_else(errfn)?,
// "y": jwk.parameter("y").ok_or_else(errfn)?,
// }};
// SecretKey::from_jwk_str(&json.to_string()).map_err(|_| Error::InvalidPublicKey(jwk.clone()))
// }

// fn jwk_from_pub<C>(key: &PublicKey<C>) -> Jwk
// where
// C: CurveArithmetic + JwkParameters,
// AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
// FieldBytesSize<C>: ModulusSize,
// {
// serde_json::from_str(key.to_jwk_string().as_ref()).expect("dependency generated an invalid JWK")
// }

// #[allow(unused)]
// fn jwk_from_priv<C>(key: &SecretKey<C>) -> Jwk
// where
// C: CurveArithmetic + JwkParameters,
// AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
// FieldBytesSize<C>: ModulusSize,
// {
// serde_json::from_str(key.to_jwk_string().as_ref()).expect("dependency generated an invalid JWK")
// }

#[cfg(test)]
#[path = "key_exchange_tests.rs"]
mod tests;
4 changes: 4 additions & 0 deletions src/key_exchange_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,16 @@ fn test_ecmr_add() {
assert_json_eq(&out.to_jwk_string(), ADDITION);
}

// FIXME:ecmr_sub: this is how ECMR subtraction is supposed to work according to the docs.
// However, I must be missing something because our results don't match.
// #[test]
// fn test_ecmr_sub() {
// let out = ecmr_sub(&pub1_jwk(), &pub2_jwk()).unwrap();
// assert_json_eq(&out.to_jwk_string(), SUBTRACTION);
// }

// FIXME:ecmr_sub: nothing to fix here, but note that this round trip test does
// indeed work, even though the `jose jwk` result above does not match.
#[test]
fn test_ecmr_add_sub() {
let pub1 = pub1_jwk();
Expand Down
10 changes: 9 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! A Rust implementation of the Tang portion of Clevis, specified in
//! <https://github.com/latchset/clevis>.
//!
//! This is still under development, but works reasonibly well.
//! This can be used to create encryption keys with the help of a Tang server, and later recover
//! them using the same server. If you would like to test, the [padhihomelab/tang] Docker image
//! provides an easy way to get started. See [the readme] for more information.
//!
//! ```
//! # fn main() {
Expand Down Expand Up @@ -41,6 +43,12 @@
//! assert_eq!(original_key, new_key);
//! # }
//! ```
//!
//! Please note that the same key length must be used for key creation and recovery (`KEY_BYTES`
//! here) and it is not stored as part of metadata.
//!
//! [padhihomelab/tang]: https://hub.docker.com/r/padhihomelab/tang
//! [the readme]: https://github.com/pluots/clevis-rs/

#![warn(clippy::pedantic)]
#![allow(clippy::missing_panics_doc)]
Expand Down
1 change: 1 addition & 0 deletions src/tang_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ impl TangClient {
adv.validate_into_keys(thumbprint)
}

/// Perform the POST request that requests serverside key exchange to produce a recovery key
fn fetch_recovery_key(&self, kid: &str, x_pub_jwk: &Jwk) -> Result<Jwk> {
let url = format!("{}/rec/{kid}", &self.url);
log::debug!("requesting recovery key from '{url}'");
Expand Down
2 changes: 2 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use base64ct::{Base64UrlUnpadded, Encoding};
use serde::de::Error as DeError;
use serde::{Deserialize, Deserializer};

/// Serde base64 deserializer
pub fn b64_to_str<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
Expand All @@ -11,6 +12,7 @@ where
})
}

/// Serde base64 deserializer for non-string data
pub fn b64_to_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: Deserializer<'de>,
Expand Down

0 comments on commit 445bfd4

Please sign in to comment.