Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 6253955

Browse files
committed
feat: add *some* tests (read desc)
test currently use mock certs and as such are currently focused on testing aspects which *don't* rely on valid certificates, such as some input validation and so on. i marked some things as TODO.
1 parent 468065e commit 6253955

File tree

4 files changed

+318
-0
lines changed

4 files changed

+318
-0
lines changed

.sqlx/query-6ac8de40e7b49521a3c7f28f81b887384ee1f1c03af69aadec3736356cb94421.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.sqlx/query-de4bfa33f888b7d4a3447c9efd3ce7ef44332dd6b7d492857cc4b63d1ad62fc1.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
-- Fixture for IdCert integration tests
2+
-- Contains data for testing HomeServerCert::get_idcert_by method
3+
4+
-- Algorithm identifiers including ED25519
5+
INSERT INTO algorithm_identifiers (id, algorithm_identifier, common_name, parameters) VALUES
6+
(1, 'rsaEncryption', 'RSA', NULL),
7+
(2, 'id-ecPublicKey', 'EC', NULL),
8+
(3, '1.3.101.112', 'Edwards-curve Digital Signature Algorithm (EdDSA) Ed25519', NULL);
9+
10+
-- Test actors for IdCert scenarios
11+
INSERT INTO actors (uaid, type) VALUES
12+
('00000000-0000-0000-0000-000000000010', 'local'),
13+
('00000000-0000-0000-0000-000000000011', 'local'),
14+
('00000000-0000-0000-0000-000000000012', 'local'),
15+
('00000000-0000-0000-0000-000000000013', 'local');
16+
17+
INSERT INTO local_actors (uaid, local_name, deactivated, joined, password_hash) VALUES
18+
('00000000-0000-0000-0000-000000000010', 'idcert_test_user_1', FALSE, NOW(), 'hash'),
19+
('00000000-0000-0000-0000-000000000011', 'idcert_test_user_2', FALSE, NOW(), 'hash'),
20+
('00000000-0000-0000-0000-000000000012', 'idcert_test_user_3', FALSE, NOW(), 'hash'),
21+
('00000000-0000-0000-0000-000000000013', 'idcert_test_user_4', FALSE, NOW(), 'hash');
22+
23+
-- Test public keys (using placeholders - tests will generate real keys)
24+
INSERT INTO public_keys (id, uaid, pubkey, algorithm_identifier) VALUES
25+
(100, '00000000-0000-0000-0000-000000000010', 'PLACEHOLDER_PEM_KEY_1', 3),
26+
(101, '00000000-0000-0000-0000-000000000011', 'PLACEHOLDER_PEM_KEY_2', 3),
27+
(102, '00000000-0000-0000-0000-000000000012', 'PLACEHOLDER_PEM_KEY_3', 3),
28+
(103, '00000000-0000-0000-0000-000000000013', 'PLACEHOLDER_PEM_KEY_4', 3),
29+
-- Additional home server public keys
30+
(200, NULL, 'PLACEHOLDER_HOMESERVER_KEY_1', 3),
31+
(201, NULL, 'PLACEHOLDER_HOMESERVER_KEY_2', 3);
32+
33+
-- Test ID-CSRs for IdCert tests
34+
INSERT INTO idcsr (
35+
id, serial_number, uaid, subject_public_key_id, subject_signature,
36+
session_id, valid_not_before, valid_not_after, extensions, pem_encoded
37+
) VALUES
38+
(100, 10000000000000000001, '00000000-0000-0000-0000-000000000010', 100, 'test_signature_idcert_1', 'session_idcert_1', NOW() - INTERVAL '1 day', NOW() + INTERVAL '30 days', 'test_extensions_idcert_1', 'test_csr_pem_idcert_1'),
39+
(101, 10000000000000000002, '00000000-0000-0000-0000-000000000011', 101, 'test_signature_idcert_2', 'session_idcert_2', NOW() - INTERVAL '1 day', NOW() + INTERVAL '30 days', 'test_extensions_idcert_2', 'test_csr_pem_idcert_2'),
40+
(102, 10000000000000000003, '00000000-0000-0000-0000-000000000012', 102, 'test_signature_idcert_3', 'session_idcert_3', NOW() - INTERVAL '2 days', NOW() - INTERVAL '1 day', 'test_extensions_idcert_3', 'test_csr_pem_idcert_3'),
41+
(103, 10000000000000000004, '00000000-0000-0000-0000-000000000013', 103, 'test_signature_idcert_4', 'session_idcert_4', NOW() + INTERVAL '1 day', NOW() + INTERVAL '30 days', 'test_extensions_idcert_4', 'test_csr_pem_idcert_4');
42+
43+
-- Test issuers (different domains for testing)
44+
INSERT INTO issuers (id, domain_components) VALUES
45+
(100, ARRAY['example', 'com']),
46+
(101, ARRAY['test', 'org']),
47+
(102, ARRAY['expired', 'net']);
48+
49+
-- Test ID-Certs
50+
-- Valid certificate for example.com
51+
INSERT INTO idcert (
52+
idcsr_id, issuer_info_id, valid_not_before, valid_not_after,
53+
home_server_public_key_id, home_server_signature, pem_encoded
54+
) VALUES
55+
(100, 100, NOW() - INTERVAL '1 day', NOW() + INTERVAL '30 days', 200, 'homeserver_signature_1', 'PLACEHOLDER_CERT_PEM_1'),
56+
-- Valid certificate for test.org
57+
(101, 101, NOW() - INTERVAL '1 day', NOW() + INTERVAL '30 days', 201, 'homeserver_signature_2', 'PLACEHOLDER_CERT_PEM_2'),
58+
-- Expired certificate for expired.net
59+
(102, 102, NOW() - INTERVAL '2 days', NOW() - INTERVAL '1 day', 200, 'homeserver_signature_3', 'PLACEHOLDER_CERT_PEM_3');

src/database/idcert.rs

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,232 @@ impl HomeServerCert {
7676
.map(Some)
7777
}
7878
}
79+
80+
#[cfg(test)]
81+
mod tests {
82+
use chrono::{NaiveDate, Utc};
83+
use sqlx::{Pool, Postgres, query};
84+
85+
use super::*;
86+
use crate::crypto::ed25519::{DigitalPublicKey, DigitalSignature, generate_keypair};
87+
88+
/// Helper function to update fixture with real ED25519 keys and mock
89+
/// certificates
90+
// TODO: use real certs
91+
async fn setup_real_keys_mock_certs(pool: &Pool<Postgres>) {
92+
// Generate keypairs functionally and convert to PEM
93+
let public_key_updates: Vec<(i64, String)> = (0..6)
94+
.map(|_| generate_keypair().1) // Take only public key
95+
.map(|pubkey| pubkey.public_key_info().to_pem(polyproto::der::pem::LineEnding::LF))
96+
.collect::<Result<Vec<_>, _>>()
97+
.expect("Failed to encode public keys to PEM")
98+
.into_iter()
99+
.zip([100i64, 101, 102, 103, 200, 201])
100+
.map(|(pem, id)| (id, pem))
101+
.collect();
102+
103+
// Apply updates to database
104+
for (id, pem) in public_key_updates {
105+
query!("UPDATE public_keys SET pubkey = $1 WHERE id = $2", pem, id)
106+
.execute(pool)
107+
.await
108+
.unwrap_or_else(|_| panic!("Failed to update public key {}", id));
109+
}
110+
111+
// Generate mock certificates functionally
112+
let mock_cert_data = [
113+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY",
114+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1gKdWHX6Zv8ZLNqXwC7D",
115+
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2hLdVGY7Wx9YMNpXzE8G",
116+
];
117+
118+
let certificate_updates: Vec<(i64, String)> = mock_cert_data
119+
.iter()
120+
.map(|&data| {
121+
format!("-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----", data)
122+
})
123+
.zip([100i64, 101, 102])
124+
.map(|(pem, id)| (id, pem))
125+
.collect();
126+
127+
// Apply certificate updates to database
128+
for (id, pem) in certificate_updates {
129+
query!("UPDATE idcert SET pem_encoded = $1 WHERE idcsr_id = $2", pem, id)
130+
.execute(pool)
131+
.await
132+
.unwrap_or_else(|_| panic!("Failed to update certificate {}", id));
133+
}
134+
}
135+
136+
#[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
137+
async fn test_get_idcert_by_nonexistent_domain(pool: Pool<Postgres>) {
138+
setup_real_keys_mock_certs(&pool).await;
139+
let db = Database { pool };
140+
141+
let domain = DomainName::new("nonexistent.com").unwrap();
142+
let timestamp = Utc::now().naive_utc();
143+
144+
let result = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
145+
&db, &domain, &timestamp,
146+
)
147+
.await
148+
.unwrap();
149+
150+
assert!(result.is_none());
151+
}
152+
153+
#[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
154+
async fn test_get_idcert_by_expired_certificate(pool: Pool<Postgres>) {
155+
setup_real_keys_mock_certs(&pool).await;
156+
let db = Database { pool };
157+
158+
// expired.net has a certificate that's already expired
159+
let domain = DomainName::new("expired.net").unwrap();
160+
let timestamp = Utc::now().naive_utc();
161+
162+
let result = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
163+
&db, &domain, &timestamp,
164+
)
165+
.await
166+
.unwrap();
167+
168+
assert!(result.is_none());
169+
}
170+
171+
#[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
172+
async fn test_get_idcert_by_future_timestamp(pool: Pool<Postgres>) {
173+
setup_real_keys_mock_certs(&pool).await;
174+
let db = Database { pool };
175+
176+
let domain = DomainName::new("example.com").unwrap();
177+
// Set timestamp far in the future, beyond certificate validity
178+
let future_timestamp =
179+
NaiveDate::from_ymd_opt(2030, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
180+
181+
let result = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
182+
&db,
183+
&domain,
184+
&future_timestamp,
185+
)
186+
.await
187+
.unwrap();
188+
189+
assert!(result.is_none());
190+
}
191+
192+
#[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
193+
async fn test_get_idcert_by_past_timestamp(pool: Pool<Postgres>) {
194+
setup_real_keys_mock_certs(&pool).await;
195+
let db = Database { pool };
196+
197+
let domain = DomainName::new("example.com").unwrap();
198+
// Set timestamp in the past, before certificate validity
199+
let past_timestamp =
200+
NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
201+
202+
let result = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
203+
&db,
204+
&domain,
205+
&past_timestamp,
206+
)
207+
.await
208+
.unwrap();
209+
210+
assert!(result.is_none());
211+
}
212+
213+
#[tokio::test]
214+
async fn test_get_idcert_by_domain_case_sensitivity() {
215+
// Test domain validation behavior
216+
let domain_exact = DomainName::new("example.com");
217+
assert!(domain_exact.is_ok(), "Lowercase domain should be valid");
218+
219+
// Test that uppercase domain names are invalid per domain validation rules
220+
let domain_upper_result = DomainName::new("EXAMPLE.COM");
221+
assert!(
222+
domain_upper_result.is_err(),
223+
"Uppercase domain names should be rejected by DomainName validation"
224+
);
225+
226+
println!("Domain case sensitivity validation works correctly");
227+
}
228+
229+
#[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
230+
async fn test_get_idcert_by_multiple_domains(pool: Pool<Postgres>) {
231+
setup_real_keys_mock_certs(&pool).await;
232+
let db = Database { pool };
233+
234+
let timestamp = Utc::now().naive_utc();
235+
236+
// Test example.com
237+
let domain1 = DomainName::new("example.com").unwrap();
238+
let result1 = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
239+
&db, &domain1, &timestamp,
240+
)
241+
.await;
242+
243+
// Test test.org
244+
let domain2 = DomainName::new("test.org").unwrap();
245+
let result2 = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
246+
&db, &domain2, &timestamp,
247+
)
248+
.await;
249+
250+
// Both should find database records but fail on certificate parsing for now
251+
// TODO
252+
assert!(result1.is_err());
253+
assert!(result2.is_err());
254+
}
255+
256+
#[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
257+
async fn test_get_idcert_by_database_edge_cases(pool: Pool<Postgres>) {
258+
setup_real_keys_mock_certs(&pool).await;
259+
let db = Database { pool };
260+
261+
// Test with subdomain that doesn't exist
262+
let subdomain = DomainName::new("sub.example.com").unwrap();
263+
let timestamp = Utc::now().naive_utc();
264+
265+
let result_subdomain = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
266+
&db, &subdomain, &timestamp,
267+
)
268+
.await
269+
.unwrap();
270+
271+
assert!(result_subdomain.is_none());
272+
273+
// Test with empty domain components (this should fail domain creation)
274+
let empty_domain_result = DomainName::new("");
275+
assert!(empty_domain_result.is_err());
276+
}
277+
278+
#[tokio::test]
279+
async fn test_real_ed25519_key_generation_and_pem_encoding() {
280+
let (_private_key, public_key) = generate_keypair();
281+
282+
// Test PEM encoding/decoding pipeline functionally
283+
let pem_data = public_key
284+
.public_key_info()
285+
.to_pem(polyproto::der::pem::LineEnding::LF)
286+
.expect("Failed to encode public key to PEM");
287+
288+
// Verify PEM structure functionally
289+
[
290+
("-----BEGIN PUBLIC KEY-----", pem_data.starts_with("-----BEGIN PUBLIC KEY-----")),
291+
("-----END PUBLIC KEY-----\n", pem_data.ends_with("-----END PUBLIC KEY-----\n")),
292+
]
293+
.iter()
294+
.for_each(|(expected, valid)| {
295+
assert!(*valid, "PEM structure validation failed for: {}", expected);
296+
});
297+
298+
// Test round-trip: PEM -> PublicKeyInfo -> DigitalPublicKey -> bytes
299+
let original_bytes = public_key.key.to_bytes();
300+
let reconstructed_bytes = PublicKeyInfo::from_pem(&pem_data)
301+
.and_then(|info| DigitalPublicKey::try_from_public_key_info(info))
302+
.map(|key| key.key.to_bytes())
303+
.expect("Failed to reconstruct key from PEM");
304+
305+
assert_eq!(original_bytes, reconstructed_bytes, "Round-trip key conversion failed");
306+
}
307+
}

0 commit comments

Comments
 (0)