• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polyphony-chat / sonata / 16393346128

19 Jul 2025 10:26PM UTC coverage: 86.57% (-4.5%) from 91.038%
16393346128

push

github

bitfl0wer
chore: add tests, fix tests

43 of 92 branches covered (46.74%)

Branch coverage included in aggregate %.

795 of 876 relevant lines covered (90.75%)

2954.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.81
/src/database/idcert.rs
1
use chrono::NaiveDateTime;
2
use log::error;
3
use polyproto::{
4
    certs::{PublicKeyInfo, idcert::IdCert},
5
    der::Encode,
6
    key::PublicKey,
7
    signature::Signature,
8
    types::DomainName,
9
};
10
use sqlx::query;
11

12
use crate::{
13
    database::{AlgorithmIdentifier, Database, Issuer},
14
    errors::{ALGORITHM_IDENTIFER_TO_DER_ERROR_MESSAGE, Context, Error},
15
};
16

17
pub(crate) struct HomeServerCert;
18

19
impl HomeServerCert {
20
    /// Try to get a [HomeServerCert] from the database, filtered by the
21
    /// [DomainName] and a [NaiveDateTime] timestamp, at which the certificate
22
    /// must be valid.
23
    pub(crate) async fn get_idcert_by<S: Signature, P: PublicKey<S>>(
7✔
24
        db: &Database,
7✔
25
        issuer_domain_name: &DomainName,
7✔
26
        timestamp: &NaiveDateTime,
7✔
27
    ) -> Result<Option<IdCert<S, P>>, Error> {
7✔
28
        let issuer_components =
7✔
29
            issuer_domain_name.to_string().split('.').map(|s| s.to_owned()).collect::<Vec<_>>();
15✔
30
        let Some(idcert_table_record) = query!(
7✔
31
            r#"
32
        WITH issuer AS (
33
            SELECT id
34
            FROM issuers
35
            WHERE domain_components = $1
36
        )
37
        SELECT idcert.pem_encoded, idcert.home_server_public_key_id
38
        FROM idcert
39
        JOIN issuer i ON idcert.issuer_info_id = i.id
40
        WHERE (
41
            $2 >= valid_not_before AND $2 <= valid_not_after
42
        )
43
    "#,
44
            issuer_components.as_slice(),
7!
45
            timestamp
7!
46
        )
47
        .fetch_optional(&db.pool)
7✔
48
        .await?
7✔
49
        else {
50
            return Ok(None);
5✔
51
        };
52

53
        let pem_encoded_pubkey_info = query!(
2✔
54
            r#"
55
        SELECT pubkey
56
        FROM public_keys
57
        WHERE id = $1
58
    "#,
59
            idcert_table_record.home_server_public_key_id
2!
60
        )
61
        .fetch_one(&db.pool)
2✔
62
        .await?;
2✔
63
        IdCert::from_pem(
2✔
64
            &idcert_table_record.pem_encoded,
2✔
65
            polyproto::certs::Target::HomeServer,
2✔
66
            timestamp.and_utc().timestamp() as u64,
2✔
67
            &P::try_from_public_key_info(
2✔
68
                PublicKeyInfo::from_pem(&pem_encoded_pubkey_info.pubkey).map_err(|e| {
2✔
69
                    error!("Error parsing public key info: {e}");
×
70
                    Error::new_internal_error(None)
×
71
                })?,
×
72
            )
73
            .map_err(|e| {
2✔
74
                error!("Error creating public key from public key info: {e}");
×
75
                Error::new_internal_error(None)
×
76
            })?,
×
77
        )
78
        .map_err(|e| {
2✔
79
            error!("Error parsing home server certificate: {e}");
2✔
80
            Error::new_internal_error(None)
2✔
81
        })
2✔
82
        .map(Some)
2✔
83
    }
7✔
84

85
    /// Insert an [IdCert] into the database without performing validation
86
    /// checks.
87
    ///
88
    /// This function bypasses certificate validation and directly inserts the
89
    /// certificate into the database. It extracts the signature algorithm
90
    /// information from the certificate and ensures it's supported by the
91
    /// server before insertion.
92
    ///
93
    /// # Parameters
94
    /// * `db` - Database connection reference
95
    /// * `cert` - The [IdCert] to insert into the database
96
    ///
97
    /// # Returns
98
    /// * `Ok(())` if the certificate was successfully inserted
99
    /// * `Err(Error)` if the signature algorithm is unsupported or insertion
100
    ///   fails
101
    pub(crate) async fn insert_idcert_unchecked<S: Signature, P: PublicKey<S>>(
×
102
        db: &Database,
×
103
        cert: IdCert<S, P>,
×
104
    ) -> Result<(), Error> {
×
105
        let oid_signature_algo = S::algorithm_identifier().oid;
×
106
        let params_signature_algo = match S::algorithm_identifier().parameters {
×
107
            Some(params) => params.to_der().map_err(|e| {
×
108
                error!("{ALGORITHM_IDENTIFER_TO_DER_ERROR_MESSAGE} {e}");
×
109
                Error::new_internal_error(None)
×
110
            })?,
×
111
            None => Vec::new(),
×
112
        };
113
        let Some(algorithm_identifier) = AlgorithmIdentifier::get_by_query(
×
114
            db,
×
115
            None,
×
116
            None,
×
117
            Some(&oid_signature_algo),
×
118
            &params_signature_algo,
×
119
        )
×
120
        .await?
×
121
        .first() else {
×
122
            return Err(Error::new(
×
123
                crate::errors::Errcode::IllegalInput,
×
124
                Some(Context::new(
×
125
                    None,
×
126
                    None,
×
127
                    None,
×
128
                    Some("ID-Cert contains cryptographic algorithms not supported by this server"),
×
129
                )),
×
130
            ));
×
131
        };
132
        #[allow(clippy::expect_used)]
133
                // This event should never happen and, as far as I am aware, cannot be triggered by any
134
                // user. As such, I see it ok to unwrap here.
135
                let issuer = Issuer::get_own(db).await?.expect(
×
136
                        "The issuer entry for this sonata instance should have been added to the database on startup!",
×
137
                );
138
        todo!()
×
139
    }
×
140
}
141

142
#[cfg(test)]
143
mod tests {
144
    use chrono::{NaiveDate, Utc};
145
    use sqlx::{Pool, Postgres, query};
146

147
    use super::*;
148
    use crate::crypto::ed25519::{DigitalPublicKey, DigitalSignature, generate_keypair};
149

150
    /// Helper function to update fixture with real ED25519 keys and mock
151
    /// certificates
152
    // TODO: use real certs
153
    async fn setup_real_keys_mock_certs(pool: &Pool<Postgres>) {
6✔
154
        // Generate keypairs functionally and convert to PEM
155
        let public_key_updates: Vec<(i64, String)> = (0..6)
6✔
156
            .map(|_| generate_keypair().1) // Take only public key
36✔
157
            .map(|pubkey| pubkey.public_key_info().to_pem(polyproto::der::pem::LineEnding::LF))
36✔
158
            .collect::<Result<Vec<_>, _>>()
6✔
159
            .expect("Failed to encode public keys to PEM")
6✔
160
            .into_iter()
6✔
161
            .zip([100i64, 101, 102, 103, 200, 201])
6✔
162
            .map(|(pem, id)| (id, pem))
36✔
163
            .collect();
6✔
164

165
        // Apply updates to database
166
        for (id, pem) in public_key_updates {
42✔
167
            query!("UPDATE public_keys SET pubkey = $1 WHERE id = $2", pem, id)
36!
168
                .execute(pool)
36✔
169
                .await
36✔
170
                .unwrap_or_else(|_| panic!("Failed to update public key {}", id));
36✔
171
        }
172

173
        // Generate mock certificates functionally
174
        let mock_cert_data = [
6✔
175
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY",
6✔
176
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1gKdWHX6Zv8ZLNqXwC7D",
6✔
177
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2hLdVGY7Wx9YMNpXzE8G",
6✔
178
        ];
6✔
179

180
        let certificate_updates: Vec<(i64, String)> = mock_cert_data
6✔
181
            .iter()
6✔
182
            .map(|&data| {
18✔
183
                format!("-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----", data)
18✔
184
            })
18✔
185
            .zip([100i64, 101, 102])
6✔
186
            .map(|(pem, id)| (id, pem))
18✔
187
            .collect();
6✔
188

189
        // Apply certificate updates to database
190
        for (id, pem) in certificate_updates {
24✔
191
            query!("UPDATE idcert SET pem_encoded = $1 WHERE idcsr_id = $2", pem, id)
18!
192
                .execute(pool)
18✔
193
                .await
18✔
194
                .unwrap_or_else(|_| panic!("Failed to update certificate {}", id));
18✔
195
        }
196
    }
6✔
197

198
    #[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
199
    async fn test_get_idcert_by_nonexistent_domain(pool: Pool<Postgres>) {
200
        setup_real_keys_mock_certs(&pool).await;
201
        let db = Database { pool };
202

203
        let domain = DomainName::new("nonexistent.com").unwrap();
204
        let timestamp = Utc::now().naive_utc();
205

206
        let result = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
207
            &db, &domain, &timestamp,
208
        )
209
        .await
210
        .unwrap();
211

212
        assert!(result.is_none());
213
    }
214

215
    #[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
216
    async fn test_get_idcert_by_expired_certificate(pool: Pool<Postgres>) {
217
        setup_real_keys_mock_certs(&pool).await;
218
        let db = Database { pool };
219

220
        // expired.net has a certificate that's already expired
221
        let domain = DomainName::new("expired.net").unwrap();
222
        let timestamp = Utc::now().naive_utc();
223

224
        let result = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
225
            &db, &domain, &timestamp,
226
        )
227
        .await
228
        .unwrap();
229

230
        assert!(result.is_none());
231
    }
232

233
    #[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
234
    async fn test_get_idcert_by_future_timestamp(pool: Pool<Postgres>) {
235
        setup_real_keys_mock_certs(&pool).await;
236
        let db = Database { pool };
237

238
        let domain = DomainName::new("example.com").unwrap();
239
        // Set timestamp far in the future, beyond certificate validity
240
        let future_timestamp =
241
            NaiveDate::from_ymd_opt(2030, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
242

243
        let result = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
244
            &db,
245
            &domain,
246
            &future_timestamp,
247
        )
248
        .await
249
        .unwrap();
250

251
        assert!(result.is_none());
252
    }
253

254
    #[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
255
    async fn test_get_idcert_by_past_timestamp(pool: Pool<Postgres>) {
256
        setup_real_keys_mock_certs(&pool).await;
257
        let db = Database { pool };
258

259
        let domain = DomainName::new("example.com").unwrap();
260
        // Set timestamp in the past, before certificate validity
261
        let past_timestamp =
262
            NaiveDate::from_ymd_opt(2020, 1, 1).unwrap().and_hms_opt(0, 0, 0).unwrap();
263

264
        let result = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
265
            &db,
266
            &domain,
267
            &past_timestamp,
268
        )
269
        .await
270
        .unwrap();
271

272
        assert!(result.is_none());
273
    }
274

275
    #[tokio::test]
276
    async fn test_get_idcert_by_domain_case_sensitivity() {
1✔
277
        // Test domain validation behavior
278
        let domain_exact = DomainName::new("example.com");
1✔
279
        assert!(domain_exact.is_ok(), "Lowercase domain should be valid");
1✔
280

281
        // Test that uppercase domain names are invalid per domain validation rules
282
        let domain_upper_result = DomainName::new("EXAMPLE.COM");
1✔
283
        assert!(
1✔
284
            domain_upper_result.is_err(),
1✔
285
            "Uppercase domain names should be rejected by DomainName validation"
×
286
        );
287

288
        println!("Domain case sensitivity validation works correctly");
1✔
289
    }
1✔
290

291
    #[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
292
    async fn test_get_idcert_by_multiple_domains(pool: Pool<Postgres>) {
293
        setup_real_keys_mock_certs(&pool).await;
294
        let db = Database { pool };
295

296
        let timestamp = Utc::now().naive_utc();
297

298
        // Test example.com
299
        let domain1 = DomainName::new("example.com").unwrap();
300
        let result1 = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
301
            &db, &domain1, &timestamp,
302
        )
303
        .await;
304

305
        // Test test.org
306
        let domain2 = DomainName::new("test.org").unwrap();
307
        let result2 = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
308
            &db, &domain2, &timestamp,
309
        )
310
        .await;
311

312
        // Both should find database records but fail on certificate parsing for now
313
        // TODO
314
        assert!(result1.is_err());
315
        assert!(result2.is_err());
316
    }
317

318
    #[sqlx::test(fixtures("../../fixtures/idcert_integration_tests.sql"))]
319
    async fn test_get_idcert_by_database_edge_cases(pool: Pool<Postgres>) {
320
        setup_real_keys_mock_certs(&pool).await;
321
        let db = Database { pool };
322

323
        // Test with subdomain that doesn't exist
324
        let subdomain = DomainName::new("sub.example.com").unwrap();
325
        let timestamp = Utc::now().naive_utc();
326

327
        let result_subdomain = HomeServerCert::get_idcert_by::<DigitalSignature, DigitalPublicKey>(
328
            &db, &subdomain, &timestamp,
329
        )
330
        .await
331
        .unwrap();
332

333
        assert!(result_subdomain.is_none());
334

335
        // Test with empty domain components (this should fail domain creation)
336
        let empty_domain_result = DomainName::new("");
337
        assert!(empty_domain_result.is_err());
338
    }
339

340
    #[tokio::test]
341
    async fn test_real_ed25519_key_generation_and_pem_encoding() {
1✔
342
        let (_private_key, public_key) = generate_keypair();
1✔
343

344
        // Test PEM encoding/decoding pipeline functionally
345
        let pem_data = public_key
1✔
346
            .public_key_info()
1✔
347
            .to_pem(polyproto::der::pem::LineEnding::LF)
1✔
348
            .expect("Failed to encode public key to PEM");
1✔
349

350
        // Verify PEM structure functionally
351
        [
1✔
352
            ("-----BEGIN PUBLIC KEY-----", pem_data.starts_with("-----BEGIN PUBLIC KEY-----")),
1✔
353
            ("-----END PUBLIC KEY-----\n", pem_data.ends_with("-----END PUBLIC KEY-----\n")),
1✔
354
        ]
1✔
355
        .iter()
1✔
356
        .for_each(|(expected, valid)| {
2✔
357
            assert!(*valid, "PEM structure validation failed for: {}", expected);
2✔
358
        });
2✔
359

360
        // Test round-trip: PEM -> PublicKeyInfo -> DigitalPublicKey -> bytes
361
        let original_bytes = public_key.key.to_bytes();
1✔
362
        let reconstructed_bytes = PublicKeyInfo::from_pem(&pem_data)
1✔
363
            .and_then(|info| DigitalPublicKey::try_from_public_key_info(info))
1✔
364
            .map(|key| key.key.to_bytes())
1✔
365
            .expect("Failed to reconstruct key from PEM");
1✔
366

367
        assert_eq!(original_bytes, reconstructed_bytes, "Round-trip key conversion failed");
1✔
368
    }
1✔
369
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc