Skip to content

Commit bf7bc51

Browse files
author
Emily Ehlert
committed
Switch to structured "Name" type for certificates and CAs
Replaced plain strings with a new `Name` type containing `cn` (common name) and optional `ou` (organizational unit). Updated frontend components and backend handling to reflect this change, including database, API, and test adjustments. Added support for displaying and optionally editing the `ou` field in relevant forms. Fixes: #114
1 parent 3b32863 commit bf7bc51

File tree

15 files changed

+186
-59
lines changed

15 files changed

+186
-59
lines changed

backend/src/api.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::constants::VAULTLS_VERSION;
1414
use crate::data::api::{CallbackQuery, ChangePasswordRequest, CreateCARequest, CreateUserCertificateRequest, CreateUserRequest, DownloadResponse, IsSetupResponse, LoginRequest, SetupRequest};
1515
use crate::data::enums::{CAType, CertificateType, PasswordRule, TimespanUnit, UserRole};
1616
use crate::data::error::ApiError;
17-
use crate::data::objects::{AppState, User};
17+
use crate::data::objects::{AppState, Name, User};
1818
use crate::notification::mail::{MailMessage, Mailer};
1919
use crate::settings::{FrontendSettings, InnerSettings};
2020

@@ -83,8 +83,12 @@ pub(crate) async fn setup(
8383

8484
let cert_validity = setup_req.validity_duration.unwrap_or(5);
8585
let cert_validity_unit = setup_req.validity_unit.unwrap_or(TimespanUnit::Year);
86+
let name = Name {
87+
cn: setup_req.ca_name.clone(),
88+
ou: None
89+
};
8690
let mut ca = TLSCertificateBuilder::new()?
87-
.set_name(&setup_req.ca_name)?
91+
.set_name(name)?
8892
.set_valid_until(cert_validity, cert_validity_unit)?
8993
.build_ca()?;
9094
ca = state.db.insert_ca(ca).await?;
@@ -292,13 +296,13 @@ pub(crate) async fn create_ca(
292296
let cert_validity = payload.validity_duration.unwrap_or(5);
293297
let cert_validity_unit = payload.validity_unit.unwrap_or(TimespanUnit::Year);
294298
TLSCertificateBuilder::new()?
295-
.set_name(&payload.ca_name)?
299+
.set_name(payload.ca_name.clone())?
296300
.set_valid_until(cert_validity, cert_validity_unit)?
297301
.build_ca()?
298302
},
299303
CAType::SSH => {
300304
SSHCertificateBuilder::new()?
301-
.set_name(&payload.ca_name)?
305+
.set_name(&payload.ca_name.cn)?
302306
.build_ca()?
303307
}
304308
};
@@ -330,7 +334,7 @@ pub(crate) async fn create_user_certificate(
330334

331335
cert = state.db.insert_user_cert(cert).await?;
332336

333-
info!(cert=cert.name, "New certificate created.");
337+
info!(cert=cert.name.cn, "New certificate created.");
334338
trace!("{:?}", cert);
335339

336340
if payload.notify_user == Some(true) {
@@ -430,7 +434,7 @@ fn build_ssh_cert(
430434
is_client: bool,
431435
) -> Result<Certificate, ApiError> {
432436
let mut cert_builder = SSHCertificateBuilder::new()?
433-
.set_name(&payload.cert_name)?
437+
.set_name(&payload.cert_name.cn)?
434438
.set_valid_until(validity_duration, validity_unit)?
435439
.set_renew_method(payload.renew_method.unwrap_or_default())?
436440
.set_ca(ca)?
@@ -461,7 +465,7 @@ async fn build_tls_cert(
461465
is_client: bool,
462466
) -> Result<Certificate, ApiError> {
463467
let mut cert_builder = TLSCertificateBuilder::new()?
464-
.set_name(&payload.cert_name)?
468+
.set_name(payload.cert_name.clone())?
465469
.set_valid_until(validity_duration, validity_unit)?
466470
.set_renew_method(payload.renew_method.unwrap_or_default())?
467471
.set_password(pkcs12_password)?

backend/src/certs/common.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ use rocket::serde::{Deserialize, Serialize};
22
use rocket_okapi::JsonSchema;
33
use passwords::PasswordGenerator;
44
use crate::data::enums::{CAType, CertificateRenewMethod, CertificateType};
5+
use crate::data::objects::Name;
56

67
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
78
/// Certificate can be either SSH or TLS certificate.
89
pub struct Certificate {
910
pub id: i64,
10-
pub name: String,
11+
pub name: Name,
1112
pub created_on: i64,
1213
pub valid_until: i64,
1314
pub certificate_type: CertificateType,
@@ -23,7 +24,7 @@ pub struct Certificate {
2324
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
2425
pub struct CA {
2526
pub id: i64,
26-
pub name: String,
27+
pub name: Name,
2728
pub created_on: i64,
2829
pub valid_until: i64,
2930
pub ca_type: CAType,

backend/src/certs/ssh_cert.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl SSHCertificateBuilder {
6363
self.principals = principals
6464
.iter()
6565
.filter(|principal| !principal.is_empty())
66-
.map(|principal| principal.clone())
66+
.cloned()
6767
.collect();
6868
Ok(self)
6969
}
@@ -96,9 +96,11 @@ impl SSHCertificateBuilder {
9696
let ca_key = PrivateKey::random(&mut OsRng, Algorithm::Ed25519)?;
9797
let key = ca_key.to_bytes()?.to_vec();
9898

99+
let name = self.name.unwrap_or_else(|| "CA".to_string()).into();
100+
99101
Ok(CA{
100102
id: -1,
101-
name: self.name.unwrap_or_else(|| "CA".to_string()),
103+
name,
102104
created_on: self.created_on,
103105
valid_until: -1,
104106
ca_type: SSH,
@@ -146,7 +148,7 @@ impl SSHCertificateBuilder {
146148

147149
Ok(Certificate {
148150
id: -1,
149-
name,
151+
name: name.into(),
150152
created_on: self.created_on,
151153
valid_until,
152154
certificate_type: CertificateType::SSHClient,
@@ -189,7 +191,7 @@ impl SSHCertificateBuilder {
189191

190192
Ok(Certificate {
191193
id: -1,
192-
name,
194+
name: name.into(),
193195
created_on: self.created_on,
194196
valid_until,
195197
certificate_type: CertificateType::SSHServer,

backend/src/certs/tls_cert.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ use crate::data::enums::CertificateType::{TLSClient, TLSServer};
2222
use crate::ApiError;
2323
use crate::certs::common::{Certificate, CA};
2424
use crate::data::enums::CAType::TLS;
25+
use crate::data::objects::Name;
2526

2627
pub struct TLSCertificateBuilder {
2728
x509: X509Builder,
2829
private_key: PKey<Private>,
2930
created_on: i64,
3031
valid_until: Option<i64>,
31-
name: Option<String>,
32+
name: Option<Name>,
3233
pkcs12_password: String,
3334
ca: Option<(i64, X509, PKey<Private>)>,
3435
user_id: Option<i64>,
@@ -70,7 +71,7 @@ impl TLSCertificateBuilder {
7071
let validity_d = ((old_cert.valid_until - old_cert.created_on) / 1000 / 60 / 60 / 24).max(14);
7172

7273
Self::new()?
73-
.set_name(&old_cert.name)?
74+
.set_name(old_cert.name.clone())?
7475
.set_valid_until(validity_d as u64, TimespanUnit::Day)?
7576
.set_password(&old_cert.password)?
7677
.set_renew_method(old_cert.renew_method)?
@@ -84,16 +85,16 @@ impl TLSCertificateBuilder {
8485
let validity_h = ((old_ca.valid_until - old_ca.created_on) / 1000 / 60 / 60 / 24).max(14);
8586

8687
Self::new()?
87-
.set_name(&old_ca.name)?
88+
.set_name(old_ca.name.clone())?
8889
.set_valid_until(validity_h as u64, TimespanUnit::Day)?
8990
.build_ca()
9091

9192
}
9293

93-
pub fn set_name(mut self, name: &str) -> Result<Self, anyhow::Error> {
94-
self.name = Some(name.to_string());
95-
let common_name = create_cn(name)?;
94+
pub fn set_name(mut self, name: Name) -> Result<Self, anyhow::Error> {
95+
let common_name = create_cn(&name)?;
9696
self.x509.set_subject_name(&common_name)?;
97+
self.name = Some(name);
9798
Ok(self)
9899
}
99100

@@ -230,7 +231,7 @@ impl TLSCertificateBuilder {
230231
ca_stack.push(ca_cert.clone())?;
231232

232233
let pkcs12 = Pkcs12::builder()
233-
.name(&name)
234+
.name(&name.cn)
234235
.ca(ca_stack)
235236
.cert(&cert)
236237
.pkey(&self.private_key)
@@ -298,9 +299,12 @@ fn generate_private_key() -> Result<PKey<Private>, ErrorStack> {
298299
Ok(server_key)
299300
}
300301

301-
fn create_cn(ca_name: &str) -> Result<X509Name, ErrorStack> {
302+
fn create_cn(name: &Name) -> Result<X509Name, ErrorStack> {
302303
let mut name_builder = X509NameBuilder::new()?;
303-
name_builder.append_entry_by_text("CN", ca_name)?;
304+
name_builder.append_entry_by_text("CN", &name.cn)?;
305+
if let Some(ref ou) = name.ou {
306+
name_builder.append_entry_by_text("OU", ou)?;
307+
}
304308
let name = name_builder.build();
305309
Ok(name)
306310
}

backend/src/data/api.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rocket_okapi::{okapi, JsonSchema, OpenApiError};
99
use rocket_okapi::okapi::openapi3::{Responses, Response as OAResponse, MediaType, RefOr};
1010
use rocket_okapi::response::OpenApiResponderInner;
1111
use crate::data::enums::{CAType, CertificateRenewMethod, CertificateType, TimespanUnit, UserRole};
12+
use crate::data::objects::Name;
1213

1314
#[derive(Serialize, Deserialize, JsonSchema)]
1415
pub struct IsSetupResponse {
@@ -47,15 +48,15 @@ pub struct CallbackQuery {
4748

4849
#[derive(Serialize, Deserialize, JsonSchema)]
4950
pub struct CreateCARequest {
50-
pub ca_name: String,
51+
pub ca_name: Name,
5152
pub ca_type: CAType,
5253
pub validity_duration: Option<u64>,
5354
pub validity_unit: Option<TimespanUnit>,
5455
}
5556

5657
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
5758
pub struct CreateUserCertificateRequest {
58-
pub cert_name: String,
59+
pub cert_name: Name,
5960
pub validity_duration: Option<u64>,
6061
pub validity_unit: Option<TimespanUnit>,
6162
pub user_id: i64,

backend/src/data/objects.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
use std::fmt;
12
use crate::helper;
23
use std::sync::Arc;
34
use rocket::serde::{Deserialize, Serialize};
45
use rocket_okapi::JsonSchema;
6+
use rusqlite::ToSql;
7+
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
58
use tokio::sync::Mutex;
69
use crate::auth::oidc_auth::OidcAuth;
710
use crate::auth::password_auth::Password;
@@ -29,4 +32,56 @@ pub struct User {
2932
#[serde(skip)]
3033
pub oidc_id: Option<String>,
3134
pub role: UserRole
35+
}
36+
37+
#[derive(Default, Deserialize, Serialize, JsonSchema, Debug, Clone, PartialEq)]
38+
pub struct Name {
39+
pub cn: String,
40+
pub ou: Option<String>
41+
}
42+
43+
impl ToSql for Name {
44+
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
45+
let mut serialized = self.cn.clone();
46+
serialized.push('\0');
47+
if let Some(ou) = &self.ou {
48+
serialized.push_str(ou);
49+
}
50+
Ok(ToSqlOutput::from(serialized))
51+
}
52+
}
53+
54+
impl FromSql for Name {
55+
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
56+
let bytes = value.as_str()?;
57+
let parts: Vec<&str> = bytes.split('\0').collect();
58+
59+
if parts.is_empty() {
60+
return Err(FromSqlError::InvalidType);
61+
}
62+
63+
let cn = parts[0].to_string();
64+
let ou = parts.get(1).and_then(|s| if s.is_empty() { None } else { Some(s.to_string()) });
65+
66+
67+
Ok(Name { cn, ou })
68+
}
69+
}
70+
71+
impl fmt::Display for Name {
72+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73+
write!(f, "{}", self.cn)
74+
}
75+
}
76+
77+
impl From<String> for Name {
78+
fn from(value: String) -> Self {
79+
Self{cn: value, ou: None}
80+
}
81+
}
82+
83+
impl From<&str> for Name {
84+
fn from(value: &str) -> Self {
85+
value.to_string().into()
86+
}
3287
}

backend/src/db.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::constants::{DB_FILE_PATH, TEMP_DB_FILE_PATH};
22
use crate::data::enums::{CAType, CertificateRenewMethod, CertificateType, UserRole};
3-
use crate::data::objects::User;
3+
use crate::data::objects::{Name, User};
44
use crate::helper::get_secret;
55
use anyhow::anyhow;
66
use anyhow::Result;
@@ -301,7 +301,7 @@ impl VaulTLSDB {
301301

302302
/// Retrieve the certificate's cert data with id from the database
303303
/// Returns the id of the user the certificate belongs to and the cert data
304-
pub(crate) async fn get_user_cert_data(&self, id: i64) -> Result<(i64, String, Vec<u8>, CertificateType)> {
304+
pub(crate) async fn get_user_cert_data(&self, id: i64) -> Result<(i64, Name, Vec<u8>, CertificateType)> {
305305
db_do!(self.pool, |conn: &Connection| {
306306
let mut stmt = conn.prepare("SELECT user_id, name, data, type FROM user_certificates WHERE id = ?1")?;
307307

backend/tests/api/api_test_functionality.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ async fn test_create_client_certificate() -> Result<()> {
147147
let valid_until = get_timestamp_ms(1);
148148

149149
assert_eq!(cert.id, 1);
150-
assert_eq!(cert.name, TEST_CLIENT_CERT_NAME);
150+
assert_eq!(cert.name, TEST_CLIENT_CERT_NAME.into());
151151
assert!(now > cert.created_on && cert.created_on > now - 10000 /* 10 seconds */);
152152
assert!(valid_until > cert.valid_until && cert.valid_until > valid_until - 10000 /* 10 seconds */);
153153
assert_eq!(cert.certificate_type, CertificateType::TLSClient);
@@ -175,7 +175,7 @@ async fn test_fetch_client_certificates() -> Result<()> {
175175
let valid_until = get_timestamp_ms(1);
176176

177177
assert_eq!(cert.id, 1);
178-
assert_eq!(cert.name, TEST_CLIENT_CERT_NAME);
178+
assert_eq!(cert.name, TEST_CLIENT_CERT_NAME.into());
179179
assert!(now > cert.created_on && cert.created_on > now - 10000 /* 10 seconds */);
180180
assert!(valid_until > cert.valid_until && cert.valid_until > valid_until - 10000 /* 10 seconds */);
181181
assert_eq!(cert.certificate_type, CertificateType::TLSClient);
@@ -364,7 +364,7 @@ async fn test_create_new_ca() -> Result<()> {
364364
let valid_until = get_timestamp_ms(15);
365365

366366
assert_eq!(new_cas.len(), 2);
367-
assert_eq!(new_cas[1].name, TEST_SECOND_CA_NAME.to_string());
367+
assert_eq!(new_cas[1].name, TEST_SECOND_CA_NAME.to_string().into());
368368
assert_eq!(new_cas[1].id, 2);
369369
assert!(now >= new_cas[1].created_on && new_cas[1].created_on > now - 10000 /* 10 seconds */);
370370
assert!(valid_until >= new_cas[1].valid_until && new_cas[1].valid_until > valid_until - 10000 /* 10 seconds */);
@@ -404,7 +404,7 @@ async fn test_create_certificate_with_short_lived_ca() -> Result<()> {
404404
let client = VaulTLSClient::new_authenticated().await;
405405

406406
let cert_req = CreateUserCertificateRequest {
407-
cert_name: TEST_CLIENT_CERT_NAME.to_string(),
407+
cert_name: TEST_CLIENT_CERT_NAME.into(),
408408
validity_duration: Some(2),
409409
validity_unit: Some(TimespanUnit::Year),
410410
user_id: 1,
@@ -457,7 +457,7 @@ async fn test_create_ssh_ca() -> Result<()> {
457457
let now = get_timestamp_ms(0);
458458

459459
assert_eq!(ca.id, 2);
460-
assert_eq!(ca.name, TEST_SSH_CA_NAME);
460+
assert_eq!(ca.name, TEST_SSH_CA_NAME.into());
461461
assert!(now > ca.created_on && ca.created_on > now - 10000 /* 10 seconds */);
462462
assert_eq!(ca.ca_type, CAType::SSH);
463463
Ok(())
@@ -473,7 +473,7 @@ async fn test_create_ssh_client_certificate() -> Result<()> {
473473
let valid_until = get_timestamp_ms(1);
474474

475475
assert_eq!(cert.id, 1);
476-
assert_eq!(cert.name, TEST_SSH_CLIENT_CERT_NAME);
476+
assert_eq!(cert.name, TEST_SSH_CLIENT_CERT_NAME.into());
477477
assert!(now > cert.created_on && cert.created_on > now - 10000 /* 10 seconds */);
478478
assert!(valid_until > cert.valid_until && cert.valid_until > valid_until - 10000 /* 10 seconds */);
479479
assert_eq!(cert.certificate_type, CertificateType::SSHClient);

0 commit comments

Comments
 (0)