Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: miscellaneous breaking changes [WPB-5470] [WPB-5473] #103

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions acme/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ p384 = "0.13"
pem = "3.0"
getrandom = { version = "0.2.8", features = ["js"] }

fluvio-wasm-timer = "0.2"

[dev-dependencies]
wasm-bindgen-test = "0.3"
hex = "0.4.3"
6 changes: 5 additions & 1 deletion acme/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ impl RustyAcme {
return Err(CertificateError::DisplayNameMismatch.into());
}

let invalid_handle = cert_identity.handle != identifier.handle.trim_start_matches(ClientId::URI_PREFIX);
let identifier_handle = identifier
.handle
.trim_start_matches(ClientId::URI_PREFIX)
.trim_start_matches(ClientId::HANDLE_PREFIX);
let invalid_handle = cert_identity.handle != identifier_handle;
if invalid_handle {
return Err(CertificateError::HandleMismatch.into());
}
Expand Down
3 changes: 3 additions & 0 deletions acme/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,7 @@ pub enum CertificateError {
/// X509 lacks required standard fields
#[error("X509 lacks required standard fields")]
InvalidFormat,
/// Advertised public key does not match algorithm
#[error("Advertised public key does not match algorithm")]
InvalidPublicKey,
}
89 changes: 74 additions & 15 deletions acme/src/identity.rs → acme/src/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,27 @@ use rusty_jwt_tools::prelude::*;
use crate::error::CertificateError;
use crate::prelude::*;

mod status;
mod thumbprint;

#[derive(Debug, Clone)]
pub struct WireIdentity {
pub client_id: String,
pub handle: String,
pub display_name: String,
pub domain: String,
pub status: IdentityStatus,
pub thumbprint: String,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum IdentityStatus {
/// All is fine
Valid,
/// The Certificate is expired
Expired,
/// The Certificate is revoked
Revoked,
}

pub trait WireIdentityReader {
Expand All @@ -29,12 +44,16 @@ impl WireIdentityReader for x509_cert::Certificate {
fn extract_identity(&self) -> RustyAcmeResult<WireIdentity> {
let (client_id, handle) = try_extract_san(&self.tbs_certificate)?;
let (display_name, domain) = try_extract_subject(&self.tbs_certificate)?;
let status = status::extract_status(&self.tbs_certificate);
let thumbprint = thumbprint::try_compute_jwk_canonicalized_thumbprint(&self.tbs_certificate)?;

Ok(WireIdentity {
client_id,
handle,
display_name,
domain,
status,
thumbprint,
})
}

Expand Down Expand Up @@ -118,12 +137,16 @@ fn try_extract_san(cert: &x509_cert::TbsCertificate) -> RustyAcmeResult<(String,
_ => None,
})
.try_for_each(|name| -> RustyAcmeResult<()> {
// since both ClientId & handle are in the SAN we first try to parse the element as
// a ClientId (since it's the most characterizable) and else fallback to a handle
if let Ok(cid) = ClientId::try_from_uri(name) {
client_id = Some(cid.to_qualified());
} else if name.starts_with(ClientId::URI_PREFIX) {
let h = name
.strip_prefix(ClientId::URI_PREFIX)
.ok_or(RustyAcmeError::ImplementationError)?
.strip_prefix(ClientId::HANDLE_PREFIX)
.ok_or(RustyAcmeError::ImplementationError)?
.to_string();
handle = Some(h);
}
Expand All @@ -144,17 +167,33 @@ pub mod tests {
wasm_bindgen_test_configure!(run_in_browser);

const CERT: &str = r#"-----BEGIN CERTIFICATE-----
MIICDDCCAbOgAwIBAgIRAPByYiuFhbbYasW+GKz5FBkwCgYIKoZIzj0EAwIwLjEN
MAsGA1UEChMEd2lyZTEdMBsGA1UEAxMUd2lyZSBJbnRlcm1lZGlhdGUgQ0EwHhcN
MjMwNzMxMTQwMjA4WhcNMzMwNzI4MTQwMjA4WjApMREwDwYDVQQKEwh3aXJlLmNv
bTEUMBIGA1UEAxMLQWxpY2UgU21pdGgwKjAFBgMrZXADIQAF/hZvvmRkWMzqZ5jU
LnGKO+y8G/Vz+olfTknk7c/8IqOB5TCB4jAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0l
BAwwCgYIKwYBBQUHAwIwHQYDVR0OBBYEFGhAhRlgprn/FUxPfL+ehHvvAigpMB8G
A1UdIwQYMBaAFB81Yl+jcBh8rnCo9MJtkZ+2vq5YMFwGA1UdEQRVMFOGFWltOndp
cmVhcHA9YWxpY2Vfd2lyZYY6aW06d2lyZWFwcD1UNENveTR2ZFJ6aWFud2ZPZ1hw
bjZBL2EzMzhlOWVhOWU4N2ZlY0B3aXJlLmNvbTAdBgwrBgEEAYKkZMYoQAEEDTAL
AgEGBAR3aXJlBAAwCgYIKoZIzj0EAwIDRwAwRAIgCP+OnliYCy7PKs3rt+x4zUuF
e2grybnLl5fsak6lFPUCIE4T8ZMlKkOZ9xeYdTlrUPT67hc++ZRAtcU03Kqiz8sm
MIICGDCCAb+gAwIBAgIQHhoe3LLRoHP+EPY4KOTgATAKBggqhkjOPQQDAjAuMQ0w
CwYDVQQKEwR3aXJlMR0wGwYDVQQDExR3aXJlIEludGVybWVkaWF0ZSBDQTAeFw0y
MzExMTYxMDM3MjZaFw0zMzExMTMxMDM3MjZaMCkxETAPBgNVBAoTCHdpcmUuY29t
MRQwEgYDVQQDEwtBbGljZSBTbWl0aDAqMAUGAytlcAMhANmHK7rIOLVhj/vmKmK1
qei8Dor8Lu/FPOnXmKLZGKrfo4HyMIHvMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE
DDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQUFlquvWRvc3MxFaLrNgzv+UdGoaswHwYD
VR0jBBgwFoAUz40pQ/qEp4eFDfctCF0jmJB+5xswaQYDVR0RBGIwYIYhaW06d2ly
ZWFwcD0lNDBhbGljZV93aXJlQHdpcmUuY29thjtpbTp3aXJlYXBwPXlsLThBX3da
U2ZhUzJ1VjhWdU1FQncvN2U3OTcyM2E4YmRjNjk0ZkB3aXJlLmNvbTAdBgwrBgEE
AYKkZMYoQAEEDTALAgEGBAR3aXJlBAAwCgYIKoZIzj0EAwIDRwAwRAIgRqbsOAF7
OseMTgkjrKe3UO/UjDUGzW+jlDWOGLZsh5ECIDdNastqkvwOGfbWaeh+IuM6/oBz
flIOs9TQGOVc0YL1
-----END CERTIFICATE-----"#;

const CERT_EXPIRED: &str = r#"-----BEGIN CERTIFICATE-----
MIICGDCCAb+gAwIBAgIQM1JQFaSAmNPtoyWrvmZNGjAKBggqhkjOPQQDAjAuMQ0w
CwYDVQQKEwR3aXJlMR0wGwYDVQQDExR3aXJlIEludGVybWVkaWF0ZSBDQTAeFw0y
MzExMTYxMDQ2MDVaFw0yMzExMTYxMTA2MDVaMCkxETAPBgNVBAoTCHdpcmUuY29t
MRQwEgYDVQQDEwtBbGljZSBTbWl0aDAqMAUGAytlcAMhAEJioXny0jRMd1GAo9aq
ywcUQBJwuc4ym1DxDBuTrFCzo4HyMIHvMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUE
DDAKBggrBgEFBQcDAjAdBgNVHQ4EFgQU3OFsPDRVZrOLHbL7vGiVE9CzyKwwHwYD
VR0jBBgwFoAUusKuRvUWmJgzjSYJL3ndc8W2414waQYDVR0RBGIwYIYhaW06d2ly
ZWFwcD0lNDBhbGljZV93aXJlQHdpcmUuY29thjtpbTp3aXJlYXBwPXlhRld5M3Yt
UUZDZms0X2VkLW9fNEEvNGU4NTI0ZWY0ZTIzMDY4YkB3aXJlLmNvbTAdBgwrBgEE
AYKkZMYoQAEEDTALAgEGBAR3aXJlBAAwCgYIKoZIzj0EAwIDRwAwRAIgPA0RmEYk
k9Jtg4ND98qu7qkUM3vtVVLiZkbCnRlFF04CIGCwhSo/78Kt8h6292SkT8c8eCS6
4PmNd7NrZ71etdKR
-----END CERTIFICATE-----"#;

#[test]
Expand All @@ -163,9 +202,9 @@ e2grybnLl5fsak6lFPUCIE4T8ZMlKkOZ9xeYdTlrUPT67hc++ZRAtcU03Kqiz8sm
let cert_der = pem::parse(CERT).unwrap();
let identity = cert_der.contents().extract_identity().unwrap();

let expected_client_id = "T4Coy4vdRzianwfOgXpn6A:a338e9ea9e87fec@wire.com";
let expected_client_id = "yl-8A_wZSfaS2uV8VuMEBw:7e79723a8bdc694f@wire.com";
assert_eq!(&identity.client_id, expected_client_id);
assert_eq!(&identity.handle, "alice_wire");
assert_eq!(&identity.handle, "alice_wire@wire.com");
assert_eq!(&identity.display_name, "Alice Smith");
assert_eq!(&identity.domain, "wire.com");
}
Expand All @@ -175,7 +214,7 @@ e2grybnLl5fsak6lFPUCIE4T8ZMlKkOZ9xeYdTlrUPT67hc++ZRAtcU03Kqiz8sm
fn should_find_created_at_claim() {
let cert_der = pem::parse(CERT).unwrap();
let created_at = cert_der.contents().extract_created_at().unwrap();
assert_eq!(created_at, 1690812128);
assert_eq!(created_at, 1700131046);
}

#[test]
Expand All @@ -185,7 +224,27 @@ e2grybnLl5fsak6lFPUCIE4T8ZMlKkOZ9xeYdTlrUPT67hc++ZRAtcU03Kqiz8sm
let spki = cert_der.contents().extract_public_key().unwrap();
assert_eq!(
hex::encode(spki),
"05fe166fbe646458ccea6798d42e718a3becbc1bf573fa895f4e49e4edcffc22"
"d9872bbac838b5618ffbe62a62b5a9e8bc0e8afc2eefc53ce9d798a2d918aadf"
);
}

#[test]
#[wasm_bindgen_test]
fn should_have_valid_status() {
let cert_der = pem::parse(CERT).unwrap();
let identity = cert_der.contents().extract_identity().unwrap();
assert_eq!(&identity.status, &IdentityStatus::Valid);

let cert_der = pem::parse(CERT_EXPIRED).unwrap();
let identity = cert_der.contents().extract_identity().unwrap();
assert_eq!(&identity.status, &IdentityStatus::Expired);
}

#[test]
#[wasm_bindgen_test]
fn should_have_thumbprint() {
let cert_der = pem::parse(CERT).unwrap();
let identity = cert_der.contents().extract_identity().unwrap();
assert!(!identity.thumbprint.is_empty());
}
}
30 changes: 30 additions & 0 deletions acme/src/identity/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use super::IdentityStatus;

pub(crate) fn extract_status(cert: &x509_cert::TbsCertificate) -> IdentityStatus {
if is_revoked(cert) {
IdentityStatus::Revoked
} else if !is_time_valid(cert) {
IdentityStatus::Expired
} else {
IdentityStatus::Valid
}
}

fn is_time_valid(cert: &x509_cert::TbsCertificate) -> bool {
// 'not_before' < now < 'not_after'
let x509_cert::time::Validity { not_before, not_after } = cert.validity;

let now = fluvio_wasm_timer::SystemTime::now();
let Ok(now) = now.duration_since(fluvio_wasm_timer::UNIX_EPOCH) else {
return false;
};

let is_nbf = now >= not_before.to_unix_duration();
let is_naf = now < not_after.to_unix_duration();
is_nbf && is_naf
}

// TODO
fn is_revoked(_cert: &x509_cert::TbsCertificate) -> bool {
false
}
33 changes: 33 additions & 0 deletions acme/src/identity/thumbprint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use crate::{
error::CertificateError,
prelude::{RustyAcmeError, RustyAcmeResult},
};
use jwt_simple::prelude::*;
use rusty_jwt_tools::{
jwk::TryIntoJwk,
prelude::{HashAlgorithm, JwkThumbprint},
};
use x509_cert::spki::SubjectPublicKeyInfoOwned;

/// See: https://datatracker.ietf.org/doc/html/rfc8037#appendix-A.3
pub(crate) fn try_compute_jwk_canonicalized_thumbprint(cert: &x509_cert::TbsCertificate) -> RustyAcmeResult<String> {
let jwk = try_into_jwk(&cert.subject_public_key_info)?;
// Hash is always SHA-256
let thumbprint = JwkThumbprint::generate(&jwk, HashAlgorithm::SHA256)?;
Ok(thumbprint.kid)
}

fn try_into_jwk(spki: &SubjectPublicKeyInfoOwned) -> RustyAcmeResult<Jwk> {
let oid = oid_registry::Oid::new(std::borrow::Cow::Borrowed(spki.algorithm.oid.as_bytes()));

// cannot pattern match oid_registry::Oid because it contains a Cow<'_>
if oid == oid_registry::OID_SIG_ED25519 {
Ok(Ed25519PublicKey::from_bytes(spki.subject_public_key.raw_bytes())?.try_into_jwk()?)
} else if oid == oid_registry::OID_SIG_ECDSA_WITH_SHA256 {
Ok(ES256PublicKey::from_bytes(spki.subject_public_key.raw_bytes())?.try_into_jwk()?)
} else if oid == oid_registry::OID_SIG_ECDSA_WITH_SHA384 {
Ok(ES384PublicKey::from_bytes(spki.subject_public_key.raw_bytes())?.try_into_jwk()?)
} else {
Err(RustyAcmeError::InvalidCertificate(CertificateError::InvalidPublicKey))
}
}
2 changes: 1 addition & 1 deletion acme/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub mod prelude {
pub use error::{RustyAcmeError, RustyAcmeResult};
pub use finalize::AcmeFinalize;
pub use identifier::{AcmeIdentifier, WireIdentifier};
pub use identity::{WireIdentity, WireIdentityReader};
pub use identity::{IdentityStatus, WireIdentity, WireIdentityReader};
pub use jws::AcmeJws;
pub use order::AcmeOrder;

Expand Down
2 changes: 1 addition & 1 deletion acme/src/order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ impl RustyAcme {
let acct_url = account.acct_url()?;

let domain = client_id.domain.clone();
let handle = format!("{}{handle}", ClientId::URI_PREFIX);
let handle = format!("{}{}{handle}@{domain}", ClientId::URI_PREFIX, ClientId::HANDLE_PREFIX);
let identifiers = vec![AcmeIdentifier::try_new(
display_name.to_string(),
domain,
Expand Down
Loading
Loading