Skip to content

Commit 31ff01f

Browse files
committed
[PKI] Client implementation for pki_enrollment_info.
1 parent 63e3994 commit 31ff01f

File tree

4 files changed

+244
-0
lines changed

4 files changed

+244
-0
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod list_frozen_users;
66
mod organization_info;
77
mod pki_enrollment_accept;
88
mod pki_enrollment_finalize;
9+
mod pki_enrollment_info;
910
mod pki_enrollment_list;
1011
mod pki_enrollment_reject;
1112
mod pki_enrollment_submit;
@@ -53,6 +54,7 @@ pub use self::{
5354
};
5455
use crate::{
5556
certif::{CertifPollServerError, CertificateOps},
57+
client::pki_enrollment_info::{PKIInfoItem, PkiEnrollmentInfoError},
5658
config::{ClientConfig, ServerConfig},
5759
event_bus::EventBus,
5860
monitors::{
@@ -692,6 +694,14 @@ impl Client {
692694
pki_enrollment_list::list_enrollments(&self.cmds).await
693695
}
694696

697+
pub async fn pki_enrollment_info(
698+
config: Arc<ClientConfig>,
699+
addr: ParsecPkiEnrollmentAddr,
700+
id: PKIEnrollmentID,
701+
) -> Result<PKIInfoItem, PkiEnrollmentInfoError> {
702+
pki_enrollment_info::info(config, addr, id).await
703+
}
704+
695705
pub async fn pki_enrollment_reject(
696706
&self,
697707
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+
// Deserialized 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: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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::{
7+
client::pki_enrollment_info::{PKIInfoItem, PkiEnrollmentInfoError},
8+
Client,
9+
};
10+
11+
use super::utils::client_factory;
12+
use libparsec_client_connection::test_register_send_hook;
13+
use libparsec_platform_pki::sign_message;
14+
use libparsec_protocol::anonymous_cmds::{self, v5::pki_enrollment_info::PkiEnrollmentInfoStatus};
15+
16+
#[parsec_test(testbed = "minimal")]
17+
#[case("submitted")]
18+
#[case("rejected")]
19+
#[case("cancelled")]
20+
// #[case("accepted")] // TODO #11269 when pki set up in testbed
21+
async fn ok(#[case] status: &str, env: &TestbedEnv) {
22+
let alice_device = env.local_device("alice@dev1");
23+
24+
let alice_client = client_factory(&env.discriminant_dir, alice_device.clone()).await;
25+
26+
let expected_submitted_on = DateTime::from_timestamp_micros(1668594983390001).unwrap();
27+
let enrollment_id = PKIEnrollmentID::from_hex("e1fe88bd0f054261887a6c8039710b40").unwrap();
28+
29+
let pki_enrollment_item = match status {
30+
"accepted" => {
31+
let expected_accepted_on = DateTime::from_timestamp_micros(1668594983390002).unwrap();
32+
33+
let expected_answer = PkiEnrollmentAnswerPayload {
34+
user_id: UserID::from_hex("9268b5acc07711f0ae7c2394da79527f").unwrap(),
35+
device_id: DeviceID::from_hex("a46105b6c07711f09e41f70f2e4e5650").unwrap(),
36+
device_label: DeviceLabel::try_from("new pki device").unwrap(),
37+
human_handle: HumanHandle::new(
38+
EmailAddress::try_from("[email protected]").unwrap(),
39+
"pki",
40+
)
41+
.unwrap(),
42+
profile: UserProfile::Standard,
43+
root_verify_key: alice_device.root_verify_key().clone(),
44+
};
45+
46+
let cert_hash = X509CertificateHash::fake_sha256();
47+
let cert_ref = cert_hash.clone().into();
48+
let signed = sign_message(&expected_answer.dump(), &cert_ref)
49+
.context("Failed to sign message")
50+
.unwrap();
51+
52+
PkiEnrollmentInfoStatus::Accepted {
53+
accepter_der_x509_certificate: cert_hash.to_string().into(),
54+
accept_payload: expected_answer.dump().into(),
55+
accept_payload_signature: signed.signature,
56+
accept_payload_signature_algorithm: signed.algo,
57+
submitted_on: expected_submitted_on,
58+
accepted_on: expected_accepted_on,
59+
}
60+
}
61+
"cancelled" => {
62+
let expected_cancelled_on = DateTime::from_timestamp_micros(1668594983390002).unwrap();
63+
PkiEnrollmentInfoStatus::Cancelled {
64+
cancelled_on: expected_cancelled_on,
65+
submitted_on: expected_submitted_on,
66+
}
67+
}
68+
"rejected" => {
69+
let expected_rejeted_on = DateTime::from_timestamp_micros(1668594983390002).unwrap();
70+
PkiEnrollmentInfoStatus::Rejected {
71+
rejected_on: expected_rejeted_on,
72+
submitted_on: expected_submitted_on,
73+
}
74+
}
75+
"submitted" => PkiEnrollmentInfoStatus::Submitted {
76+
submitted_on: expected_submitted_on,
77+
},
78+
_ => unimplemented!(),
79+
};
80+
81+
test_register_send_hook(&env.discriminant_dir, {
82+
move |_req: anonymous_cmds::latest::pki_enrollment_info::Req| {
83+
anonymous_cmds::latest::pki_enrollment_info::Rep::Ok(pki_enrollment_item)
84+
}
85+
});
86+
87+
let enrollment_info = Client::pki_enrollment_info(
88+
alice_client.config.clone(),
89+
ParsecPkiEnrollmentAddr::new(
90+
alice_client.organization_addr(),
91+
alice_client.organization_id().clone(),
92+
),
93+
enrollment_id,
94+
)
95+
.await
96+
.unwrap();
97+
match (enrollment_info, status) {
98+
(PKIInfoItem::Accepted { .. }, "accepted") => {}
99+
(PKIInfoItem::Cancelled { .. }, "cancelled") => {}
100+
(PKIInfoItem::Submitted { .. }, "submitted") => {}
101+
(PKIInfoItem::Rejected { .. }, "rejected") => {}
102+
_ => panic!("unexpected answer"),
103+
}
104+
}
105+
106+
#[parsec_test(testbed = "coolorg")]
107+
async fn enrollment_not_found(env: &TestbedEnv) {
108+
let mallory_device = env.local_device("mallory@dev1");
109+
let mallory_client = client_factory(&env.discriminant_dir, mallory_device).await;
110+
test_register_send_hook(&env.discriminant_dir, {
111+
move |_req: anonymous_cmds::latest::pki_enrollment_info::Req| {
112+
anonymous_cmds::latest::pki_enrollment_info::Rep::EnrollmentNotFound
113+
}
114+
});
115+
116+
let rep = Client::pki_enrollment_info(
117+
mallory_client.config.clone(),
118+
ParsecPkiEnrollmentAddr::new(
119+
mallory_client.organization_addr(),
120+
mallory_client.organization_id().clone(),
121+
),
122+
PKIEnrollmentID::default(),
123+
)
124+
.await;
125+
p_assert_matches!(rep.unwrap_err(), PkiEnrollmentInfoError::EnrollmentNotFound)
126+
}

0 commit comments

Comments
 (0)