Skip to content
Open
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
10 changes: 10 additions & 0 deletions libparsec/crates/client/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod list_frozen_users;
mod organization_info;
mod pki_enrollment_accept;
mod pki_enrollment_finalize;
mod pki_enrollment_info;
mod pki_enrollment_list;
mod pki_enrollment_reject;
mod pki_enrollment_submit;
Expand Down Expand Up @@ -53,6 +54,7 @@ pub use self::{
};
use crate::{
certif::{CertifPollServerError, CertificateOps},
client::pki_enrollment_info::{PKIInfoItem, PkiEnrollmentInfoError},
config::{ClientConfig, ServerConfig},
event_bus::EventBus,
monitors::{
Expand Down Expand Up @@ -692,6 +694,14 @@ impl Client {
pki_enrollment_list::list_enrollments(&self.cmds).await
}

pub async fn pki_enrollment_info(
config: Arc<ClientConfig>,
addr: ParsecPkiEnrollmentAddr,
id: PKIEnrollmentID,
) -> Result<PKIInfoItem, PkiEnrollmentInfoError> {
pki_enrollment_info::info(config, addr, id).await
}

pub async fn pki_enrollment_reject(
&self,
enrollment_id: PKIEnrollmentID,
Expand Down
107 changes: 107 additions & 0 deletions libparsec/crates/client/src/client/pki_enrollment_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

use crate::ClientConfig;
pub use anonymous_cmds::latest::pki_enrollment_info::PkiEnrollmentInfoStatus;
use libparsec_client_connection::{protocol::anonymous_cmds, AnonymousCmds, ConnectionError};
use libparsec_platform_pki::{load_answer_payload, SignedMessage};
use libparsec_types::prelude::*;
use std::sync::Arc;

#[derive(Debug, thiserror::Error)]
pub enum PkiEnrollmentInfoError {
#[error("Cannot communicate with the server: {0}")]
Offline(#[from] ConnectionError),
#[error("No enrollment found with that id")]
EnrollmentNotFound,
#[error("Invalid accept payload")]
InvalidAcceptPayload,
#[error(transparent)]
Internal(#[from] anyhow::Error),
}

#[derive(Debug)]
pub enum PKIInfoItem {
Accepted {
// Deserialized version of the provided payload
// signature should have been checked before loading it
answer: PkiEnrollmentAnswerPayload,
accepted_on: DateTime,
submitted_on: DateTime,
},
Submitted {
submitted_on: DateTime,
},
Rejected {
rejected_on: DateTime,
submitted_on: DateTime,
},
Cancelled {
submitted_on: DateTime,
cancelled_on: DateTime,
},
}

pub async fn info(
config: Arc<ClientConfig>,
addr: ParsecPkiEnrollmentAddr,
enrollment_id: PKIEnrollmentID,
) -> Result<PKIInfoItem, PkiEnrollmentInfoError> {
use anonymous_cmds::latest::pki_enrollment_info::{Rep, Req};
let cmds = AnonymousCmds::new(
&config.config_dir,
ParsecAnonymousAddr::ParsecPkiEnrollmentAddr(addr.clone()),
config.proxy.clone(),
)?;
let rep = cmds.send(Req { enrollment_id }).await?;

let status = match rep {
Rep::Ok(status) => status,
Rep::EnrollmentNotFound => return Err(PkiEnrollmentInfoError::EnrollmentNotFound),
rep @ Rep::UnknownStatus { .. } => {
return Err(anyhow::anyhow!("Unexpected server response: {:?}", rep).into())
}
};

// Check that the payload is valid
let answer = match status {
PkiEnrollmentInfoStatus::Submitted { submitted_on } => {
PKIInfoItem::Submitted { submitted_on }
}
PkiEnrollmentInfoStatus::Rejected {
rejected_on,
submitted_on,
} => PKIInfoItem::Rejected {
rejected_on,
submitted_on,
},
PkiEnrollmentInfoStatus::Cancelled {
submitted_on,
cancelled_on,
} => PKIInfoItem::Cancelled {
submitted_on,
cancelled_on,
},
PkiEnrollmentInfoStatus::Accepted {
accept_payload,
accept_payload_signature,
accepted_on,
accepter_der_x509_certificate,
submitted_on,
accept_payload_signature_algorithm,
} => {
let message = SignedMessage {
algo: accept_payload_signature_algorithm,
signature: accept_payload_signature.to_vec(),
message: accept_payload.to_vec(),
};
let answer = load_answer_payload(&accepter_der_x509_certificate, &message, accepted_on)
.map_err(|_| PkiEnrollmentInfoError::InvalidAcceptPayload)?;
PKIInfoItem::Accepted {
answer,
accepted_on,
submitted_on,
}
}
};
Ok(answer)
}
1 change: 1 addition & 0 deletions libparsec/crates/client/tests/unit/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod list_users;
mod list_workspace_users;
mod list_workspaces;
mod organization_info;
mod pki_enrollment_info;
mod pki_enrollment_list;
mod pki_enrollment_reject;
mod process_workspaces_needs;
Expand Down
126 changes: 126 additions & 0 deletions libparsec/crates/client/tests/unit/client/pki_enrollment_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS

use libparsec_tests_fixtures::prelude::*;
use libparsec_types::prelude::*;

use crate::{
client::pki_enrollment_info::{PKIInfoItem, PkiEnrollmentInfoError},
Client,
};

use super::utils::client_factory;
use libparsec_client_connection::test_register_send_hook;
use libparsec_platform_pki::sign_message;
use libparsec_protocol::anonymous_cmds::{self, v5::pki_enrollment_info::PkiEnrollmentInfoStatus};

#[parsec_test(testbed = "minimal")]
#[case("submitted")]
#[case("rejected")]
#[case("cancelled")]
// #[case("accepted")] // TODO #11269 when pki set up in testbed
async fn ok(#[case] status: &str, env: &TestbedEnv) {
let alice_device = env.local_device("alice@dev1");

let alice_client = client_factory(&env.discriminant_dir, alice_device.clone()).await;

let expected_submitted_on = DateTime::from_timestamp_micros(1668594983390001).unwrap();
let enrollment_id = PKIEnrollmentID::from_hex("e1fe88bd0f054261887a6c8039710b40").unwrap();

let pki_enrollment_item = match status {
"accepted" => {
let expected_accepted_on = DateTime::from_timestamp_micros(1668594983390002).unwrap();

let expected_answer = PkiEnrollmentAnswerPayload {
user_id: UserID::from_hex("9268b5acc07711f0ae7c2394da79527f").unwrap(),
device_id: DeviceID::from_hex("a46105b6c07711f09e41f70f2e4e5650").unwrap(),
device_label: DeviceLabel::try_from("new pki device").unwrap(),
human_handle: HumanHandle::new(
EmailAddress::try_from("[email protected]").unwrap(),
"pki",
)
.unwrap(),
profile: UserProfile::Standard,
root_verify_key: alice_device.root_verify_key().clone(),
};

let cert_hash = X509CertificateHash::fake_sha256();
let cert_ref = cert_hash.clone().into();
let signed = sign_message(&expected_answer.dump(), &cert_ref)
.context("Failed to sign message")
.unwrap();

PkiEnrollmentInfoStatus::Accepted {
accepter_der_x509_certificate: cert_hash.to_string().into(),
accept_payload: expected_answer.dump().into(),
accept_payload_signature: signed.signature,
accept_payload_signature_algorithm: signed.algo,
submitted_on: expected_submitted_on,
accepted_on: expected_accepted_on,
}
}
"cancelled" => {
let expected_cancelled_on = DateTime::from_timestamp_micros(1668594983390002).unwrap();
PkiEnrollmentInfoStatus::Cancelled {
cancelled_on: expected_cancelled_on,
submitted_on: expected_submitted_on,
}
}
"rejected" => {
let expected_rejected_on = DateTime::from_timestamp_micros(1668594983390002).unwrap();
PkiEnrollmentInfoStatus::Rejected {
rejected_on: expected_rejected_on,
submitted_on: expected_submitted_on,
}
}
"submitted" => PkiEnrollmentInfoStatus::Submitted {
submitted_on: expected_submitted_on,
},
_ => unimplemented!(),
};

test_register_send_hook(&env.discriminant_dir, {
move |_req: anonymous_cmds::latest::pki_enrollment_info::Req| {
anonymous_cmds::latest::pki_enrollment_info::Rep::Ok(pki_enrollment_item)
}
});

let enrollment_info = Client::pki_enrollment_info(
alice_client.config.clone(),
ParsecPkiEnrollmentAddr::new(
alice_client.organization_addr(),
alice_client.organization_id().clone(),
),
enrollment_id,
)
.await
.unwrap();
match (enrollment_info, status) {
(PKIInfoItem::Accepted { .. }, "accepted") => {}
(PKIInfoItem::Cancelled { .. }, "cancelled") => {}
(PKIInfoItem::Submitted { .. }, "submitted") => {}
(PKIInfoItem::Rejected { .. }, "rejected") => {}
_ => panic!("unexpected answer"),
}
}

#[parsec_test(testbed = "coolorg")]
async fn enrollment_not_found(env: &TestbedEnv) {
let mallory_device = env.local_device("mallory@dev1");
let mallory_client = client_factory(&env.discriminant_dir, mallory_device).await;
test_register_send_hook(&env.discriminant_dir, {
move |_req: anonymous_cmds::latest::pki_enrollment_info::Req| {
anonymous_cmds::latest::pki_enrollment_info::Rep::EnrollmentNotFound
}
});

let rep = Client::pki_enrollment_info(
mallory_client.config.clone(),
ParsecPkiEnrollmentAddr::new(
mallory_client.organization_addr(),
mallory_client.organization_id().clone(),
),
PKIEnrollmentID::default(),
)
.await;
p_assert_matches!(rep.unwrap_err(), PkiEnrollmentInfoError::EnrollmentNotFound)
}
Loading