Skip to content

Commit c55c703

Browse files
committed
[PKI] Client implementation for pki_enrollment_info.
1 parent 7380236 commit c55c703

File tree

4 files changed

+214
-0
lines changed

4 files changed

+214
-0
lines changed

libparsec/crates/client/src/client/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
mod list_frozen_users;
66
mod organization_info;
77
mod pki_enrollment_accept;
8+
mod pki_enrollment_info;
89
mod pki_enrollment_list;
910
mod pki_enrollment_reject;
1011
mod pki_enrollment_submit;
@@ -51,6 +52,7 @@ pub use self::{
5152
};
5253
use crate::{
5354
certif::{CertifPollServerError, CertificateOps},
55+
client::pki_enrollment_info::{PKIInfoItem, PkiEnrollmentInfoError},
5456
config::{ClientConfig, ServerConfig},
5557
event_bus::EventBus,
5658
monitors::{
@@ -690,6 +692,14 @@ impl Client {
690692
pki_enrollment_list::list_enrollments(&self.cmds).await
691693
}
692694

695+
pub async fn pki_enrollment_info(
696+
config: Arc<ClientConfig>,
697+
addr: ParsecPkiEnrollmentAddr,
698+
id: PKIEnrollmentID,
699+
) -> Result<PKIInfoItem, PkiEnrollmentInfoError> {
700+
pki_enrollment_info::info(config, addr, id).await
701+
}
702+
693703
pub async fn pki_enrollment_reject(
694704
&self,
695705
enrollment_id: PKIEnrollmentID,
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS
2+
3+
use crate::ClientConfig;
4+
pub use anonymous_cmds::latest::pki_enrollment_info::PkiEnrollmentInfoStatus;
5+
use libparsec_client_connection::{protocol::anonymous_cmds, AnonymousCmds, ConnectionError};
6+
use libparsec_platform_pki::{load_answer_payload, SignedMessage};
7+
use libparsec_types::prelude::*;
8+
use std::sync::Arc;
9+
10+
#[derive(Debug, thiserror::Error)]
11+
pub enum PkiEnrollmentInfoError {
12+
#[error("Cannot communicate with the server: {0}")]
13+
Offline(#[from] ConnectionError),
14+
#[error("No enrollment found with that id")]
15+
EnrollmentNotFound,
16+
#[error("Invalid accept payload")]
17+
InvalidAcceptPayload,
18+
#[error(transparent)]
19+
Internal(#[from] anyhow::Error),
20+
}
21+
22+
#[derive(Debug)]
23+
pub enum PKIInfoItem {
24+
Accepted {
25+
// Serialized version of the provided payload
26+
// signature should have been checked before loading it
27+
answer: PkiEnrollmentAnswerPayload,
28+
accepted_on: DateTime,
29+
submitted_on: DateTime,
30+
},
31+
Submitted {
32+
submitted_on: DateTime,
33+
},
34+
Rejected {
35+
rejected_on: DateTime,
36+
submitted_on: DateTime,
37+
},
38+
Cancelled {
39+
submitted_on: DateTime,
40+
cancelled_on: DateTime,
41+
},
42+
}
43+
44+
pub async fn info(
45+
config: Arc<ClientConfig>,
46+
addr: ParsecPkiEnrollmentAddr,
47+
enrollment_id: PKIEnrollmentID,
48+
) -> Result<PKIInfoItem, PkiEnrollmentInfoError> {
49+
use anonymous_cmds::latest::pki_enrollment_info::{Rep, Req};
50+
let cmds = AnonymousCmds::new(
51+
&config.config_dir,
52+
ParsecAnonymousAddr::ParsecPkiEnrollmentAddr(addr.clone()),
53+
config.proxy.clone(),
54+
)?;
55+
let rep = cmds.send(Req { enrollment_id }).await?;
56+
57+
let status = match rep {
58+
Rep::Ok(status) => status,
59+
Rep::EnrollmentNotFound => return Err(PkiEnrollmentInfoError::EnrollmentNotFound),
60+
rep @ Rep::UnknownStatus { .. } => {
61+
return Err(anyhow::anyhow!("Unexpected server response: {:?}", rep).into())
62+
}
63+
};
64+
65+
// Check that the payload is valid
66+
let answer = match status {
67+
PkiEnrollmentInfoStatus::Submitted { submitted_on } => {
68+
PKIInfoItem::Submitted { submitted_on }
69+
}
70+
PkiEnrollmentInfoStatus::Rejected {
71+
rejected_on,
72+
submitted_on,
73+
} => PKIInfoItem::Rejected {
74+
rejected_on,
75+
submitted_on,
76+
},
77+
PkiEnrollmentInfoStatus::Cancelled {
78+
submitted_on,
79+
cancelled_on,
80+
} => PKIInfoItem::Cancelled {
81+
submitted_on,
82+
cancelled_on,
83+
},
84+
PkiEnrollmentInfoStatus::Accepted {
85+
accept_payload,
86+
accept_payload_signature,
87+
accepted_on,
88+
accepter_der_x509_certificate,
89+
submitted_on,
90+
accept_payload_signature_algorithm,
91+
} => {
92+
let message = SignedMessage {
93+
algo: accept_payload_signature_algorithm,
94+
signature: accept_payload_signature.to_vec(),
95+
message: accept_payload.to_vec(),
96+
};
97+
let answer = load_answer_payload(&accepter_der_x509_certificate, &message, accepted_on)
98+
.map_err(|_| PkiEnrollmentInfoError::InvalidAcceptPayload)?;
99+
PKIInfoItem::Accepted {
100+
answer,
101+
accepted_on,
102+
submitted_on,
103+
}
104+
}
105+
};
106+
Ok(answer)
107+
}

libparsec/crates/client/tests/unit/client/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod list_users;
88
mod list_workspace_users;
99
mod list_workspaces;
1010
mod organization_info;
11+
mod pki_enrollment_info;
1112
mod pki_enrollment_list;
1213
mod pki_enrollment_reject;
1314
mod process_workspaces_needs;
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Parsec Cloud (https://parsec.cloud) Copyright (c) BUSL-1.1 2016-present Scille SAS
2+
3+
use libparsec_tests_fixtures::prelude::*;
4+
use libparsec_types::prelude::*;
5+
6+
use crate::{client::pki_enrollment_info::PkiEnrollmentInfoError, Client};
7+
8+
use super::utils::client_factory;
9+
10+
#[parsec_test(testbed = "minimal")]
11+
#[cfg(target_os = "windows")] // because we are signing data with a cert, it needs to be on windows
12+
async fn ok(env: &TestbedEnv) {
13+
use libparsec_client_connection::test_register_send_hook;
14+
use libparsec_platform_pki::sign_message;
15+
use libparsec_protocol::anonymous_cmds::{
16+
self, v5::pki_enrollment_info::PkiEnrollmentInfoStatus,
17+
};
18+
19+
use crate::pki_enrollment_info::PKIInfoItem;
20+
let alice_device = env.local_device("alice@dev1");
21+
22+
let alice_client = client_factory(&env.discriminant_dir, alice_device.clone()).await;
23+
24+
let expected_accepted_on = DateTime::from_timestamp_micros(1668594983390001).unwrap();
25+
let expected_submitted_on = DateTime::from_timestamp_micros(1668594983390002).unwrap();
26+
let enrollment_id = PKIEnrollmentID::from_hex("e1fe88bd0f054261887a6c8039710b40").unwrap();
27+
let expected_answer = PkiEnrollmentAnswerPayload {
28+
user_id: UserID::from_hex("9268b5acc07711f0ae7c2394da79527f").unwrap(),
29+
device_id: DeviceID::from_hex("a46105b6c07711f09e41f70f2e4e5650").unwrap(),
30+
device_label: DeviceLabel::try_from("new pki device").unwrap(),
31+
human_handle: HumanHandle::new(EmailAddress::try_from("[email protected]").unwrap(), "pki")
32+
.unwrap(),
33+
profile: UserProfile::Standard,
34+
root_verify_key: alice_device.root_verify_key().clone(),
35+
};
36+
37+
let cert_hash = X509CertificateHash::fake_sha256();
38+
let cert_ref = cert_hash.clone().into();
39+
let signed = sign_message(&expected_answer.dump(), &cert_ref)
40+
.context("Failed to sign message")
41+
.unwrap();
42+
43+
let pki_enrollment_item = PkiEnrollmentInfoStatus::Accepted {
44+
accepter_der_x509_certificate: cert_hash.to_string().into(),
45+
accept_payload: expected_answer.dump().into(),
46+
accept_payload_signature: signed.signature,
47+
accept_payload_signature_algorithm: signed.algo,
48+
submitted_on: expected_submitted_on,
49+
accepted_on: expected_accepted_on,
50+
};
51+
52+
test_register_send_hook(&env.discriminant_dir, {
53+
move |_req: anonymous_cmds::latest::pki_enrollment_info::Req| {
54+
anonymous_cmds::latest::pki_enrollment_info::Rep::Ok(pki_enrollment_item)
55+
}
56+
});
57+
58+
let enrollment_info = Client::pki_enrollment_info(
59+
alice_client.config.clone(),
60+
ParsecPkiEnrollmentAddr::new(
61+
alice_client.organization_addr(),
62+
alice_client.organization_id().clone(),
63+
),
64+
enrollment_id,
65+
)
66+
.await
67+
.unwrap();
68+
match enrollment_info {
69+
PKIInfoItem::Accepted {
70+
answer,
71+
accepted_on,
72+
submitted_on,
73+
} => {
74+
assert_eq!(answer, expected_answer);
75+
assert_eq!(accepted_on, expected_accepted_on);
76+
assert_eq!(submitted_on, expected_submitted_on);
77+
}
78+
_ => panic!("unexpected answer"),
79+
}
80+
}
81+
82+
#[parsec_test(testbed = "coolorg")]
83+
async fn enrollment_not_found(env: &TestbedEnv) {
84+
let mallory_device = env.local_device("mallory@dev1");
85+
let mallory_client = client_factory(&env.discriminant_dir, mallory_device).await;
86+
let rep = Client::pki_enrollment_info(
87+
mallory_client.config.clone(),
88+
ParsecPkiEnrollmentAddr::new(
89+
mallory_client.organization_addr(),
90+
mallory_client.organization_id().clone(),
91+
),
92+
PKIEnrollmentID::default(),
93+
)
94+
.await;
95+
p_assert_matches!(rep.unwrap_err(), PkiEnrollmentInfoError::EnrollmentNotFound)
96+
}

0 commit comments

Comments
 (0)