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

jstedfast / MimeKit / 4.14.0.1801

05 Oct 2025 03:45PM UTC coverage: 94.132% (+0.01%) from 94.122%
4.14.0.1801

push

coveralls.net

jstedfast
Use a bool disposed member variable rather than setting members to null

32370 of 34388 relevant lines covered (94.13%)

0.94 hits per line

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

76.59
/MimeKit/Cryptography/SqlCertificateDatabase.cs
1
//
2
// SqlCertificateDatabase.cs
3
//
4
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
5
//
6
// Copyright (c) 2013-2025 .NET Foundation and Contributors
7
//
8
// Permission is hereby granted, free of charge, to any person obtaining a copy
9
// of this software and associated documentation files (the "Software"), to deal
10
// in the Software without restriction, including without limitation the rights
11
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
// copies of the Software, and to permit persons to whom the Software is
13
// furnished to do so, subject to the following conditions:
14
//
15
// The above copyright notice and this permission notice shall be included in
16
// all copies or substantial portions of the Software.
17
//
18
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
// THE SOFTWARE.
25
//
26

27
using System;
28
using System.Data;
29
using System.Text;
30
using System.Linq;
31
using System.Data.Common;
32
using System.Collections.Generic;
33

34
#if __MOBILE__
35
using Mono.Data.Sqlite;
36
#else
37
using System.Reflection;
38
#endif
39

40
using Org.BouncyCastle.Asn1;
41
using Org.BouncyCastle.X509;
42
using Org.BouncyCastle.Asn1.X509;
43
using Org.BouncyCastle.X509.Store;
44
using Org.BouncyCastle.Security;
45
using Org.BouncyCastle.Utilities.Collections;
46

47
namespace MimeKit.Cryptography {
48
        /// <summary>
49
        /// An abstract X.509 certificate database built on generic SQL storage.
50
        /// </summary>
51
        /// <remarks>
52
        /// <para>An X.509 certificate database is used for storing certificates, metdata related to the certificates
53
        /// (such as encryption algorithms supported by the associated client), certificate revocation lists (CRLs),
54
        /// and private keys.</para>
55
        /// <para>This particular database uses SQLite to store the data.</para>
56
        /// </remarks>
57
        public abstract class SqlCertificateDatabase : X509CertificateDatabase
58
        {
59
                bool disposed;
60

61
                /// <summary>
62
                /// Initialize a new instance of the <see cref="SqlCertificateDatabase"/> class.
63
                /// </summary>
64
                /// <remarks>
65
                /// Creates a new <see cref="SqlCertificateDatabase"/> using the provided database connection.
66
                /// </remarks>
67
                /// <param name="connection">The database connection.</param>
68
                /// <param name="password">The password used for encrypting and decrypting the private keys.</param>
69
                /// <exception cref="System.ArgumentNullException">
70
                /// <para><paramref name="connection"/> is <see langword="null"/>.</para>
71
                /// <para>-or-</para>
72
                /// <para><paramref name="password"/> is <see langword="null"/>.</para>
73
                /// </exception>
74
                protected SqlCertificateDatabase (DbConnection connection, string password) : this (connection, password, new SecureRandom ())
1✔
75
                {
1✔
76
                }
1✔
77

78
                /// <summary>
79
                /// Initialize a new instance of the <see cref="SqlCertificateDatabase"/> class.
80
                /// </summary>
81
                /// <remarks>
82
                /// Creates a new <see cref="SqlCertificateDatabase"/> using the provided database connection.
83
                /// </remarks>
84
                /// <param name="connection">The database connection.</param>
85
                /// <param name="password">The password used for encrypting and decrypting the private keys.</param>
86
                /// <param name="random">The secure pseuido-random number generator.</param>
87
                /// <exception cref="System.ArgumentNullException">
88
                /// <para><paramref name="connection"/> is <see langword="null"/>.</para>
89
                /// <para>-or-</para>
90
                /// <para><paramref name="password"/> is <see langword="null"/>.</para>
91
                /// <para>-or-</para>
92
                /// <para><paramref name="random"/> is <see langword="null"/>.</para>
93
                /// </exception>
94
                protected SqlCertificateDatabase (DbConnection connection, string password, SecureRandom random) : base (connection, password, random)
1✔
95
                {
1✔
96
                        if (connection.State != ConnectionState.Open)
1✔
97
                                connection.Open ();
1✔
98

99
                        CertificatesTable = CreateCertificatesDataTable (CertificatesTableName);
1✔
100
                        CrlsTable = CreateCrlsDataTable (CrlsTableName);
1✔
101

102
                        CreateCertificatesTable (connection, CertificatesTable);
1✔
103
                        CreateCrlsTable (connection, CrlsTable);
1✔
104
                }
1✔
105

106
                /// <summary>
107
                /// Get the X.509 certificate table definition.
108
                /// </summary>
109
                /// <remarks>
110
                /// Gets the X.509 certificate table definition.
111
                /// </remarks>
112
                /// <value>The X.509 certificates table definition.</value>
113
                protected DataTable CertificatesTable {
114
                        get; private set;
115
                }
116

117
                /// <summary>
118
                /// Get the X.509 certificate revocation lists (CRLs) table definition.
119
                /// </summary>
120
                /// <remarks>
121
                /// Gets the X.509 certificate revocation lists (CRLs) table definition.
122
                /// </remarks>
123
                /// <value>The X.509 certificate revocation lists table definition.</value>
124
                protected DataTable CrlsTable {
125
                        get; private set;
126
                }
127

128
                static DataTable CreateCertificatesDataTable (string tableName)
129
                {
1✔
130
                        var table = new DataTable (tableName);
1✔
131
                        table.Columns.Add (new DataColumn (CertificateColumnNames.Id, typeof (int)) { AutoIncrement = true });
1✔
132
                        table.Columns.Add (new DataColumn (CertificateColumnNames.Trusted, typeof (bool)) { AllowDBNull = false });
1✔
133
                        table.Columns.Add (new DataColumn (CertificateColumnNames.Anchor, typeof (bool)) { AllowDBNull = false });
1✔
134
                        table.Columns.Add (new DataColumn (CertificateColumnNames.BasicConstraints, typeof (int)) { AllowDBNull = false });
1✔
135
                        table.Columns.Add (new DataColumn (CertificateColumnNames.KeyUsage, typeof (int)) { AllowDBNull = false });
1✔
136
                        table.Columns.Add (new DataColumn (CertificateColumnNames.NotBefore, typeof (long)) { AllowDBNull = false });
1✔
137
                        table.Columns.Add (new DataColumn (CertificateColumnNames.NotAfter, typeof (long)) { AllowDBNull = false });
1✔
138
                        table.Columns.Add (new DataColumn (CertificateColumnNames.IssuerName, typeof (string)) { AllowDBNull = false });
1✔
139
                        table.Columns.Add (new DataColumn (CertificateColumnNames.SerialNumber, typeof (string)) { AllowDBNull = false });
1✔
140
                        table.Columns.Add (new DataColumn (CertificateColumnNames.SubjectName, typeof (string)) { AllowDBNull = false });
1✔
141
                        table.Columns.Add (new DataColumn (CertificateColumnNames.SubjectKeyIdentifier, typeof (string)) { AllowDBNull = true });
1✔
142
                        table.Columns.Add (new DataColumn (CertificateColumnNames.SubjectEmail, typeof (string)) { AllowDBNull = true });
1✔
143
                        table.Columns.Add (new DataColumn (CertificateColumnNames.SubjectDnsNames, typeof (string)) { AllowDBNull = true });
1✔
144
                        table.Columns.Add (new DataColumn (CertificateColumnNames.Fingerprint, typeof (string)) { AllowDBNull = false });
1✔
145
                        table.Columns.Add (new DataColumn (CertificateColumnNames.Algorithms, typeof (string)) { AllowDBNull = true });
1✔
146
                        table.Columns.Add (new DataColumn (CertificateColumnNames.AlgorithmsUpdated, typeof (long)) { AllowDBNull = false });
1✔
147
                        table.Columns.Add (new DataColumn (CertificateColumnNames.Certificate, typeof (byte[])) { AllowDBNull = false, Unique = true });
1✔
148
                        table.Columns.Add (new DataColumn (CertificateColumnNames.PrivateKey, typeof (byte[])) { AllowDBNull = true });
1✔
149
                        table.PrimaryKey = new DataColumn[] { table.Columns[0] };
1✔
150

151
                        return table;
1✔
152
                }
1✔
153

154
                static DataTable CreateCrlsDataTable (string tableName)
155
                {
1✔
156
                        var table = new DataTable (tableName);
1✔
157
                        table.Columns.Add (new DataColumn (CrlColumnNames.Id, typeof (int)) { AutoIncrement = true });
1✔
158
                        table.Columns.Add (new DataColumn (CrlColumnNames.Delta, typeof (bool)) { AllowDBNull = false });
1✔
159
                        table.Columns.Add (new DataColumn (CrlColumnNames.IssuerName, typeof (string)) { AllowDBNull = false });
1✔
160
                        table.Columns.Add (new DataColumn (CrlColumnNames.ThisUpdate, typeof (long)) { AllowDBNull = false });
1✔
161
                        table.Columns.Add (new DataColumn (CrlColumnNames.NextUpdate, typeof (long)) { AllowDBNull = false });
1✔
162
                        table.Columns.Add (new DataColumn (CrlColumnNames.Crl, typeof (byte[])) { AllowDBNull = false });
1✔
163
                        table.PrimaryKey = new DataColumn[] { table.Columns[0] };
1✔
164

165
                        return table;
1✔
166
                }
1✔
167

168
                /// <summary>
169
                /// Gets the columns for the specified table.
170
                /// </summary>
171
                /// <remarks>
172
                /// Gets the list of columns for the specified table.
173
                /// </remarks>
174
                /// <param name="connection">The database connection.</param>
175
                /// <param name="tableName">The name of the table.</param>
176
                /// <returns>The list of columns.</returns>
177
                protected abstract IList<DataColumn> GetTableColumns (DbConnection connection, string tableName);
178

179
                /// <summary>
180
                /// Gets the command to create a table.
181
                /// </summary>
182
                /// <remarks>
183
                /// Constructs the command to create a table.
184
                /// </remarks>
185
                /// <param name="connection">The database connection.</param>
186
                /// <param name="table">The table.</param>
187
                protected abstract void CreateTable (DbConnection connection, DataTable table);
188

189
                /// <summary>
190
                /// Adds a column to a table.
191
                /// </summary>
192
                /// <remarks>
193
                /// Adds a column to a table.
194
                /// </remarks>
195
                /// <param name="connection">The database connection.</param>
196
                /// <param name="table">The table.</param>
197
                /// <param name="column">The column to add.</param>
198
                protected abstract void AddTableColumn (DbConnection connection, DataTable table, DataColumn column);
199

200
                /// <summary>
201
                /// Gets the name of an index based on the table and columns that it is built against.
202
                /// </summary>
203
                /// <remarks>
204
                /// Gets the name of an index based on the table and columns that it is built against.
205
                /// </remarks>
206
                /// <param name="tableName">The name of the table.</param>
207
                /// <param name="columnNames">The names of the columns that are indexed.</param>
208
                /// <returns>The name of the index for the specified table and columns.</returns>
209
                protected static string GetIndexName (string tableName, string[] columnNames)
210
                {
1✔
211
                        return string.Format ("{0}_{1}_INDEX", tableName, string.Join ("_", columnNames));
1✔
212
                }
1✔
213

214
                /// <summary>
215
                /// Creates an index for faster table lookups.
216
                /// </summary>
217
                /// <remarks>
218
                /// Creates an index for faster table lookups.
219
                /// </remarks>
220
                /// <param name="connection">The database connection.</param>
221
                /// <param name="tableName">The name of the table.</param>
222
                /// <param name="columnNames">The names of the columns to index.</param>
223
                protected virtual void CreateIndex (DbConnection connection, string tableName, params string[] columnNames)
224
                {
1✔
225
                        var indexName = GetIndexName (tableName, columnNames);
1✔
226
                        var query = string.Format ("CREATE INDEX IF NOT EXISTS {0} ON {1}({2})", indexName, tableName, string.Join (", ", columnNames));
1✔
227

228
                        using (var command = CreateCommand ()) {
1✔
229
                                command.CommandText = query;
1✔
230
                                command.ExecuteNonQuery ();
1✔
231
                        }
1✔
232
                }
1✔
233

234
                /// <summary>
235
                /// Removes an index that is no longer needed.
236
                /// </summary>
237
                /// <remarks>
238
                /// Removes an index that is no longer needed.
239
                /// </remarks>
240
                /// <param name="connection">The database connection.</param>
241
                /// <param name="tableName">The name of the table.</param>
242
                /// <param name="columnNames">The names of the columns that were indexed.</param>
243
                protected virtual void RemoveIndex (DbConnection connection, string tableName, params string[] columnNames)
244
                {
1✔
245
                        var indexName = GetIndexName (tableName, columnNames);
1✔
246
                        var query = string.Format ("DROP INDEX IF EXISTS {0}", indexName);
1✔
247

248
                        using (var command = CreateCommand ()) {
1✔
249
                                command.CommandText = query;
1✔
250
                                command.ExecuteNonQuery ();
1✔
251
                        }
1✔
252
                }
1✔
253

254
                void CreateCertificatesTable (DbConnection connection, DataTable table)
255
                {
1✔
256
                        CreateTable (connection, table);
1✔
257

258
                        var currentColumns = GetTableColumns (connection, table.TableName);
1✔
259
                        bool hasSubjectDnsNamesColumn = false;
1✔
260
                        bool hasAnchorColumn = false;
1✔
261

262
                        // Figure out which columns are missing...
263
                        for (int i = 0; i < currentColumns.Count; i++) {
1✔
264
                                if (currentColumns[i].ColumnName.Equals (CertificateColumnNames.SubjectDnsNames, StringComparison.Ordinal))
1✔
265
                                        hasSubjectDnsNamesColumn = true;
1✔
266
                                else if (currentColumns[i].ColumnName.Equals (CertificateColumnNames.Anchor, StringComparison.Ordinal))
1✔
267
                                        hasAnchorColumn = true;
1✔
268
                        }
1✔
269

270
                        // Certificates Table Version History:
271
                        //
272
                        // * Version 0: Initial version.
273
                        // * Version 1: v2.5.0 added the ANCHOR, SUBJECTNAME, and SUBJECTKEYIDENTIFIER columns.
274
                        // * Version 2: v4.9.0 added the SUBJECTDNSNAMES column and started canonicalizing the SUBJECTEMAIL and SUBJECTDNSNAMES columns with the IDN-encoded values.
275

276
                        if (!hasAnchorColumn) {
1✔
277
                                // Upgrade from Version 1.
278
                                ExecuteWithinTransaction (() => {
1✔
279
                                        var column = table.Columns[table.Columns.IndexOf (CertificateColumnNames.Anchor)];
1✔
280
                                        AddTableColumn (connection, table, column);
1✔
281

282
                                        column = table.Columns[table.Columns.IndexOf (CertificateColumnNames.SubjectName)];
1✔
283
                                        AddTableColumn (connection, table, column);
1✔
284

285
                                        column = table.Columns[table.Columns.IndexOf (CertificateColumnNames.SubjectKeyIdentifier)];
1✔
286
                                        AddTableColumn (connection, table, column);
1✔
287

288
                                        // Note: The SubjectEmail column exists, but the SubjectDnsNames column was added later, so make sure to add that.
289
                                        column = table.Columns[table.Columns.IndexOf (CertificateColumnNames.SubjectDnsNames)];
1✔
290
                                        AddTableColumn (connection, table, column);
1✔
291

292
                                        // Note: We need to call ToArray() or ToList() here because the Find() method will still have a DataReader open which will block calling new DbCommands until the SELECT command completes.
293
                                        foreach (var record in Find (null, false, X509CertificateRecordFields.Id | X509CertificateRecordFields.Certificate).ToArray ()) {
1✔
294
                                                var statement = $"UPDATE {CertificatesTableName} SET {CertificateColumnNames.Anchor} = @ANCHOR, {CertificateColumnNames.SubjectName} = @SUBJECTNAME, {CertificateColumnNames.SubjectKeyIdentifier} = @SUBJECTKEYIDENTIFIER, {CertificateColumnNames.SubjectEmail} = @SUBJECTEMAIL, {CertificateColumnNames.SubjectDnsNames} = @SUBJECTDNSNAMES WHERE {CertificateColumnNames.Id} = @ID";
1✔
295

296
                                                using (var command = CreateCommand ()) {
1✔
297
                                                        command.AddParameterWithValue ("@ID", record.Id);
1✔
298
                                                        command.AddParameterWithValue ("@ANCHOR", record.IsAnchor);
1✔
299
                                                        command.AddParameterWithValue ("@SUBJECTNAME", record.SubjectName);
1✔
300
                                                        command.AddParameterWithValue ("@SUBJECTKEYIDENTIFIER", record.SubjectKeyIdentifier?.AsHex ());
1✔
301
                                                        command.AddParameterWithValue ("@SUBJECTEMAIL", record.SubjectEmail);
1✔
302
                                                        command.AddParameterWithValue ("@SUBJECTDNSNAMES", EncodeDnsNames (record.SubjectDnsNames));
1✔
303
                                                        command.CommandType = CommandType.Text;
1✔
304
                                                        command.CommandText = statement;
1✔
305

306
                                                        command.ExecuteNonQuery ();
1✔
307
                                                }
1✔
308
                                        }
1✔
309
                                });
1✔
310

311
                                // Remove some old indexes
312
                                RemoveIndex (connection, table.TableName, CertificateColumnNames.Trusted);
1✔
313
                                RemoveIndex (connection, table.TableName, CertificateColumnNames.Trusted, CertificateColumnNames.BasicConstraints, CertificateColumnNames.IssuerName, CertificateColumnNames.SerialNumber);
1✔
314
                                RemoveIndex (connection, table.TableName, CertificateColumnNames.BasicConstraints, CertificateColumnNames.IssuerName, CertificateColumnNames.SerialNumber);
1✔
315
                                RemoveIndex (connection, table.TableName, CertificateColumnNames.BasicConstraints, CertificateColumnNames.Fingerprint);
1✔
316
                                RemoveIndex (connection, table.TableName, CertificateColumnNames.BasicConstraints, CertificateColumnNames.SubjectEmail);
1✔
317
                        } else if (!hasSubjectDnsNamesColumn) {
1✔
318
                                // Upgrade from Version 2.
319
                                ExecuteWithinTransaction (() => {
1✔
320
                                        var column = table.Columns[table.Columns.IndexOf (CertificateColumnNames.SubjectDnsNames)];
1✔
321
                                        AddTableColumn (connection, table, column);
1✔
322

323
                                        // Note: We need to call ToArray() or ToList() here because the Find() method will still have a DataReader open which will block calling new DbCommands until the SELECT command completes.
324
                                        foreach (var record in Find (null, false, X509CertificateRecordFields.Id | X509CertificateRecordFields.Certificate).ToArray ()) {
1✔
325
                                                var statement = $"UPDATE {CertificatesTableName} SET {CertificateColumnNames.SubjectEmail} = @SUBJECTEMAIL, {CertificateColumnNames.SubjectDnsNames} = @SUBJECTDNSNAMES WHERE {CertificateColumnNames.Id} = @ID";
1✔
326

327
                                                using (var command = CreateCommand ()) {
1✔
328
                                                        command.AddParameterWithValue ("@ID", record.Id);
1✔
329
                                                        command.AddParameterWithValue ("@SUBJECTEMAIL", record.SubjectEmail);
1✔
330
                                                        command.AddParameterWithValue ("@SUBJECTDNSNAMES", EncodeDnsNames (record.SubjectDnsNames));
1✔
331
                                                        command.CommandType = CommandType.Text;
1✔
332
                                                        command.CommandText = statement;
1✔
333

334
                                                        command.ExecuteNonQuery ();
1✔
335
                                                }
1✔
336
                                        }
1✔
337
                                });
1✔
338

339
                                // Remove some old indexes
340
                                RemoveIndex (connection, table.TableName, CertificateColumnNames.BasicConstraints, CertificateColumnNames.SubjectEmail, CertificateColumnNames.NotBefore, CertificateColumnNames.NotAfter);
1✔
341
                        }
1✔
342

343
                        // Note: Use "EXPLAIN QUERY PLAN SELECT ... FROM CERTIFICATES WHERE ..." to verify that any indexes we create get used as expected.
344

345
                        // Index for matching against a specific certificate
346
                        CreateIndex (connection, table.TableName, CertificateColumnNames.IssuerName, CertificateColumnNames.SerialNumber, CertificateColumnNames.Fingerprint);
1✔
347

348
                        // Index for searching for a certificate based on a SecureMailboxAddress
349
                        CreateIndex (connection, table.TableName, CertificateColumnNames.BasicConstraints, CertificateColumnNames.Fingerprint, CertificateColumnNames.NotBefore, CertificateColumnNames.NotAfter);
1✔
350

351
                        // Index for searching for a certificate based on a MailboxAddress
352
                        CreateIndex (connection, table.TableName, CertificateColumnNames.BasicConstraints, CertificateColumnNames.SubjectEmail, CertificateColumnNames.SubjectDnsNames, CertificateColumnNames.NotBefore, CertificateColumnNames.NotAfter);
1✔
353

354
                        // Index for gathering a list of Trusted Anchors
355
                        CreateIndex (connection, table.TableName, CertificateColumnNames.Trusted, CertificateColumnNames.Anchor, CertificateColumnNames.KeyUsage);
1✔
356
                }
1✔
357

358
                void CreateCrlsTable (DbConnection connection, DataTable table)
359
                {
1✔
360
                        CreateTable (connection, table);
1✔
361

362
                        CreateIndex (connection, table.TableName, CrlColumnNames.IssuerName);
1✔
363
                        CreateIndex (connection, table.TableName, CrlColumnNames.Delta, CrlColumnNames.IssuerName, CrlColumnNames.ThisUpdate);
1✔
364
                }
1✔
365

366
                /// <summary>
367
                /// Creates a SELECT query string builder for the specified fields of an X.509 certificate record.
368
                /// </summary>
369
                /// <remarks>
370
                /// Creates a SELECT query string builder for the specified fields of an X.509 certificate record.
371
                /// </remarks>
372
                /// <param name="fields">The X.509 certificate fields.</param>
373
                /// <returns>A <see cref="StringBuilder"/> containing a basic SELECT query string.</returns>
374
                protected static StringBuilder CreateSelectQuery (X509CertificateRecordFields fields)
375
                {
1✔
376
                        var query = new StringBuilder ("SELECT ");
1✔
377
                        var columns = GetColumnNames (fields);
1✔
378

379
                        for (int i = 0; i < columns.Length; i++) {
1✔
380
                                if (i > 0)
1✔
381
                                        query = query.Append (", ");
1✔
382

383
                                query = query.Append (columns[i]);
1✔
384
                        }
1✔
385

386
                        return query.Append (" FROM ").Append (CertificatesTableName);
1✔
387
                }
1✔
388

389
                /// <summary>
390
                /// Creates a SELECT query string builder for the specified fields of an X.509 CRL record.
391
                /// </summary>
392
                /// <remarks>
393
                /// Creates a SELECT query string builder for the specified fields of an X.509 CRL record.
394
                /// </remarks>
395
                /// <param name="fields">The X.509 CRL fields.</param>
396
                /// <returns>A <see cref="StringBuilder"/> containing a basic SELECT query string.</returns>
397
                protected static StringBuilder CreateSelectQuery (X509CrlRecordFields fields)
398
                {
1✔
399
                        var query = new StringBuilder ("SELECT ");
1✔
400
                        var columns = GetColumnNames (fields);
1✔
401

402
                        for (int i = 0; i < columns.Length; i++) {
1✔
403
                                if (i > 0)
1✔
404
                                        query = query.Append (", ");
1✔
405

406
                                query = query.Append (columns[i]);
1✔
407
                        }
1✔
408

409
                        return query.Append (" FROM ").Append (CrlsTableName);
1✔
410
                }
1✔
411

412
                /// <summary>
413
                /// Gets the database command to select the record matching the specified certificate.
414
                /// </summary>
415
                /// <remarks>
416
                /// Gets the database command to select the record matching the specified certificate.
417
                /// </remarks>
418
                /// <returns>The database command.</returns>
419
                /// <param name="connection">The database connection.</param>
420
                /// <param name="certificate">The certificate.</param>
421
                /// <param name="fields">The fields to return.</param>
422
                protected override DbCommand GetSelectCommand (DbConnection connection, X509Certificate certificate, X509CertificateRecordFields fields)
423
                {
1✔
424
                        var fingerprint = certificate.GetFingerprint ().ToLowerInvariant ();
1✔
425
                        var serialNumber = certificate.SerialNumber.ToString ();
1✔
426
                        var issuerName = certificate.IssuerDN.ToString ();
1✔
427
                        var command = CreateCommand ();
1✔
428
                        var query = CreateSelectQuery (fields);
1✔
429

430
                        // FIXME: Is this really the best way to query for an exact match of a certificate?
431
                        query = query.Append (" WHERE ")
1✔
432
                                .Append (CertificateColumnNames.IssuerName).Append (" = @ISSUERNAME AND ")
433
                                .Append (CertificateColumnNames.SerialNumber).Append (" = @SERIALNUMBER AND ")
434
                                .Append (CertificateColumnNames.Fingerprint).Append (" = @FINGERPRINT LIMIT 1");
435
                        command.AddParameterWithValue ("@ISSUERNAME", issuerName);
1✔
436
                        command.AddParameterWithValue ("@SERIALNUMBER", serialNumber);
1✔
437
                        command.AddParameterWithValue ("@FINGERPRINT", fingerprint);
1✔
438

439
                        command.CommandText = query.ToString ();
1✔
440
                        command.CommandType = CommandType.Text;
1✔
441

442
                        return command;
1✔
443
                }
1✔
444

445
                /// <summary>
446
                /// Get the database command to select the certificate records for the specified mailbox.
447
                /// </summary>
448
                /// <remarks>
449
                /// Gets the database command to select the certificate records for the specified mailbox.
450
                /// </remarks>
451
                /// <returns>The database command.</returns>
452
                /// <param name="connection">The database connection.</param>
453
                /// <param name="mailbox">The mailbox.</param>
454
                /// <param name="now">The date and time for which the certificate should be valid.</param>
455
                /// <param name="requirePrivateKey">true</param>
456
                /// <param name="fields">The fields to return.</param>
457
                protected override DbCommand GetSelectCommand (DbConnection connection, MailboxAddress mailbox, DateTime now, bool requirePrivateKey, X509CertificateRecordFields fields)
458
                {
1✔
459
                        var command = CreateCommand ();
1✔
460
                        var query = CreateSelectQuery (fields);
1✔
461

462
                        query = query.Append (" WHERE ").Append (CertificateColumnNames.BasicConstraints).Append (" = @BASICCONSTRAINTS ");
1✔
463
                        command.AddParameterWithValue ("@BASICCONSTRAINTS", -1);
1✔
464

465
                        if (mailbox is SecureMailboxAddress secure && !string.IsNullOrEmpty (secure.Fingerprint)) {
1✔
466
                                if (secure.Fingerprint.Length < 40) {
×
467
                                        command.AddParameterWithValue ("@FINGERPRINT", secure.Fingerprint.ToLowerInvariant () + "%");
×
468
                                        query = query.Append ("AND ").Append (CertificateColumnNames.Fingerprint).Append (" LIKE @FINGERPRINT ");
×
469
                                } else {
1✔
470
                                        command.AddParameterWithValue ("@FINGERPRINT", secure.Fingerprint.ToLowerInvariant ());
1✔
471
                                        query = query.Append ("AND ").Append (CertificateColumnNames.Fingerprint).Append (" = @FINGERPRINT ");
1✔
472
                                }
1✔
473
                        } else {
1✔
474
                                var domain = MailboxAddress.IdnMapping.Encode (mailbox.Domain);
1✔
475
                                var address = mailbox.GetAddress (true);
1✔
476

477
                                command.AddParameterWithValue ("@SUBJECTEMAIL", address.ToLowerInvariant ());
1✔
478
                                command.AddParameterWithValue ("@SUBJECTDNSNAME", $"%|{domain.ToLowerInvariant ()}|%");
1✔
479

480
                                query = query.Append ("AND (")
1✔
481
                                        .Append (CertificateColumnNames.SubjectEmail).Append ("= @SUBJECTEMAIL OR ")
482
                                        .Append (CertificateColumnNames.SubjectDnsNames).Append (" LIKE @SUBJECTDNSNAME) ");
483
                        }
1✔
484

485
                        query = query.Append ("AND ")
1✔
486
                                .Append (CertificateColumnNames.NotBefore).Append (" < @NOW AND ")
487
                                .Append (CertificateColumnNames.NotAfter).Append (" > @NOW");
488
                        command.AddParameterWithValue ("@NOW", now.ToUniversalTime ());
1✔
489

490
                        if (requirePrivateKey)
1✔
491
                                query = query.Append (" AND ").Append (CertificateColumnNames.PrivateKey).Append (" IS NOT NULL");
1✔
492

493
                        command.CommandText = query.ToString ();
1✔
494
                        command.CommandType = CommandType.Text;
1✔
495

496
                        return command;
1✔
497
                }
1✔
498

499
                /// <summary>
500
                /// Get the database command to select the requested certificate records.
501
                /// </summary>
502
                /// <remarks>
503
                /// Gets the database command to select the requested certificate records.
504
                /// </remarks>
505
                /// <returns>The database command.</returns>
506
                /// <param name="connection">The database connection.</param>
507
                /// <param name="selector">The certificate selector.</param>
508
                /// <param name="trustedAnchorsOnly"><see langword="true" /> if only trusted anchor certificates should be matched; otherwise, <see langword="false" />.</param>
509
                /// <param name="requirePrivateKey"><see langword="true" /> if the certificate must have a private key; otherwise, <see langword="false" />.</param>
510
                /// <param name="fields">The fields to return.</param>
511
                protected override DbCommand GetSelectCommand (DbConnection connection, ISelector<X509Certificate>? selector, bool trustedAnchorsOnly, bool requirePrivateKey, X509CertificateRecordFields fields)
512
                {
1✔
513
                        var command = CreateCommand ();
1✔
514
                        var query = CreateSelectQuery (fields);
1✔
515
                        int baseQueryLength = query.Length;
1✔
516

517
                        query = query.Append (" WHERE ");
1✔
518

519
                        // FIXME: We could create an X509CertificateDatabaseSelector subclass of X509CertStoreSelector that
520
                        // adds properties like bool Trusted, bool Anchor, and bool HasPrivateKey ? Then we could drop the
521
                        // bool method arguments...
522
                        if (trustedAnchorsOnly) {
1✔
523
                                query = query.Append (CertificateColumnNames.Trusted).Append (" = @TRUSTED AND ")
1✔
524
                                        .Append (CertificateColumnNames.Anchor).Append (" = @ANCHOR");
525
                                command.AddParameterWithValue ("@TRUSTED", true);
1✔
526
                                command.AddParameterWithValue ("@ANCHOR", true);
1✔
527
                        }
1✔
528

529
                        if (selector is X509CertStoreSelector match) {
1✔
530
                                if (match.BasicConstraints >= 0 || match.BasicConstraints == -2) {
1✔
531
                                        if (command.Parameters.Count > 0)
1✔
532
                                                query = query.Append (" AND ");
×
533

534
                                        if (match.BasicConstraints == -2) {
1✔
535
                                                command.AddParameterWithValue ("@BASICCONSTRAINTS", -1);
1✔
536
                                                query = query.Append (CertificateColumnNames.BasicConstraints).Append (" = @BASICCONSTRAINTS");
1✔
537
                                        } else {
1✔
538
                                                command.AddParameterWithValue ("@BASICCONSTRAINTS", match.BasicConstraints);
1✔
539
                                                query = query.Append (CertificateColumnNames.BasicConstraints).Append (" >= @BASICCONSTRAINTS");
1✔
540
                                        }
1✔
541
                                }
1✔
542

543
                                if (match.CertificateValid != null) {
1✔
544
                                        if (command.Parameters.Count > 0)
1✔
545
                                                query = query.Append (" AND ");
×
546

547
                                        command.AddParameterWithValue ("@DATETIME", match.CertificateValid.Value.ToUniversalTime ());
1✔
548
                                        query = query.Append (CertificateColumnNames.NotBefore).Append (" < @DATETIME AND ")
1✔
549
                                                .Append (CertificateColumnNames.NotAfter).Append (" > @DATETIME");
550
                                }
1✔
551

552
                                if (match.Issuer != null || match.Certificate != null) {
×
553
                                        // Note: GetSelectCommand (X509Certificate certificate, X509CertificateRecordFields fields)
554
                                        // queries for ISSUERNAME, SERIALNUMBER, and FINGERPRINT so we'll do the same.
555
                                        var issuer = match.Issuer ?? match.Certificate.IssuerDN;
×
556

557
                                        if (command.Parameters.Count > 0)
×
558
                                                query = query.Append (" AND ");
×
559

560
                                        command.AddParameterWithValue ("@ISSUERNAME", issuer.ToString ());
×
561
                                        query = query.Append (CertificateColumnNames.IssuerName).Append (" = @ISSUERNAME");
×
562
                                }
×
563

564
                                var serialNumber = match.SerialNumber ?? match.Certificate?.SerialNumber;
1✔
565

566
                                if (serialNumber != null) {
×
567
                                        // Note: GetSelectCommand (X509Certificate certificate, X509CertificateRecordFields fields)
568
                                        // queries for ISSUERNAME, SERIALNUMBER, and FINGERPRINT so we'll do the same.
569

570
                                        if (command.Parameters.Count > 0)
×
571
                                                query = query.Append (" AND ");
×
572

573
                                        command.AddParameterWithValue ("@SERIALNUMBER", serialNumber.ToString ());
×
574
                                        query = query.Append (CertificateColumnNames.SerialNumber).Append (" = @SERIALNUMBER");
×
575
                                }
×
576

577
                                if (match.Certificate != null) {
×
578
                                        // Note: GetSelectCommand (X509Certificate certificate, X509CertificateRecordFields fields)
579
                                        // queries for ISSUERNAME, SERIALNUMBER, and FINGERPRINT so we'll do the same.
580
                                        if (command.Parameters.Count > 0)
×
581
                                                query = query.Append (" AND ");
×
582

583
                                        command.AddParameterWithValue ("@FINGERPRINT", match.Certificate.GetFingerprint ());
×
584
                                        query = query.Append (CertificateColumnNames.Fingerprint).Append (" = @FINGERPRINT");
×
585
                                }
×
586

587
                                if (match.Subject != null) {
1✔
588
                                        if (command.Parameters.Count > 0)
1✔
589
                                                query = query.Append (" AND ");
×
590

591
                                        command.AddParameterWithValue ("@SUBJECTNAME", match.Subject.ToString ());
1✔
592
                                        query = query.Append (CertificateColumnNames.SubjectName).Append (" = @SUBJECTNAME");
1✔
593
                                }
1✔
594

595
                                if (match.SubjectKeyIdentifier != null) {
1✔
596
                                        if (command.Parameters.Count > 0)
1✔
597
                                                query = query.Append (" AND ");
×
598

599
                                        var id = (Asn1OctetString) Asn1Object.FromByteArray (match.SubjectKeyIdentifier);
1✔
600
                                        var subjectKeyIdentifier = id.GetOctets ().AsHex ();
1✔
601

602
                                        command.AddParameterWithValue ("@SUBJECTKEYIDENTIFIER", subjectKeyIdentifier);
1✔
603
                                        query = query.Append (CertificateColumnNames.SubjectKeyIdentifier).Append (" = @SUBJECTKEYIDENTIFIER");
1✔
604
                                }
1✔
605

606
                                if (match.KeyUsage != null) {
×
607
                                        var flags = BouncyCastleCertificateExtensions.GetKeyUsageFlags (match.KeyUsage);
×
608

609
                                        if (flags != X509KeyUsageFlags.None) {
×
610
                                                if (command.Parameters.Count > 0)
×
611
                                                        query = query.Append (" AND ");
×
612

613
                                                command.AddParameterWithValue ("@FLAGS", (int) flags);
×
614
                                                query = query.Append ('(').Append (CertificateColumnNames.KeyUsage).Append (" = 0 OR (")
×
615
                                                        .Append (CertificateColumnNames.KeyUsage).Append (" & @FLAGS) = @FLAGS)");
616
                                        }
×
617
                                }
×
618
                        }
1✔
619

620
                        if (requirePrivateKey) {
1✔
621
                                if (command.Parameters.Count > 0)
1✔
622
                                        query = query.Append (" AND ");
×
623

624
                                query = query.Append (CertificateColumnNames.PrivateKey).Append (" IS NOT NULL");
1✔
625
                        } else if (command.Parameters.Count == 0) {
1✔
626
                                query.Length = baseQueryLength;
1✔
627
                        }
1✔
628

629
                        command.CommandText = query.ToString ();
1✔
630
                        command.CommandType = CommandType.Text;
1✔
631

632
                        return command;
1✔
633
                }
1✔
634

635
                /// <summary>
636
                /// Gets the database command to select the CRL records matching the specified issuer.
637
                /// </summary>
638
                /// <remarks>
639
                /// Gets the database command to select the CRL records matching the specified issuer.
640
                /// </remarks>
641
                /// <returns>The database command.</returns>
642
                /// <param name="connection">The database connection.</param>
643
                /// <param name="issuer">The issuer.</param>
644
                /// <param name="fields">The fields to return.</param>
645
                protected override DbCommand GetSelectCommand (DbConnection connection, X509Name issuer, X509CrlRecordFields fields)
646
                {
1✔
647
                        var query = CreateSelectQuery (fields).Append (" WHERE ").Append (CrlColumnNames.IssuerName).Append (" = @ISSUERNAME");
1✔
648
                        var command = CreateCommand ();
1✔
649

650
                        command.CommandText = query.ToString ();
1✔
651
                        command.AddParameterWithValue ("@ISSUERNAME", issuer.ToString ());
1✔
652
                        command.CommandType = CommandType.Text;
1✔
653

654
                        return command;
1✔
655
                }
1✔
656

657
                /// <summary>
658
                /// Gets the database command to select the record for the specified CRL.
659
                /// </summary>
660
                /// <remarks>
661
                /// Gets the database command to select the record for the specified CRL.
662
                /// </remarks>
663
                /// <returns>The database command.</returns>
664
                /// <param name="connection">The database connection.</param>
665
                /// <param name="crl">The X.509 CRL.</param>
666
                /// <param name="fields">The fields to return.</param>
667
                protected override DbCommand GetSelectCommand (DbConnection connection, X509Crl crl, X509CrlRecordFields fields)
668
                {
1✔
669
                        var query = CreateSelectQuery (fields).Append (" WHERE ")
1✔
670
                                .Append (CrlColumnNames.Delta).Append (" = @DELTA AND ")
671
                                .Append (CrlColumnNames.IssuerName).Append ("= @ISSUERNAME AND ")
672
                                .Append (CrlColumnNames.ThisUpdate).Append (" = @THISUPDATE LIMIT 1");
673
                        var issuerName = crl.IssuerDN.ToString ();
1✔
674
                        var command = CreateCommand ();
1✔
675

676
                        command.CommandText = query.ToString ();
1✔
677
                        command.AddParameterWithValue ("@DELTA", crl.IsDelta ());
1✔
678
                        command.AddParameterWithValue ("@ISSUERNAME", issuerName);
1✔
679
                        command.AddParameterWithValue ("@THISUPDATE", crl.ThisUpdate.ToUniversalTime ());
1✔
680
                        command.CommandType = CommandType.Text;
1✔
681

682
                        return command;
1✔
683
                }
1✔
684

685
                /// <summary>
686
                /// Gets the database command to select all CRLs in the table.
687
                /// </summary>
688
                /// <remarks>
689
                /// Gets the database command to select all CRLs in the table.
690
                /// </remarks>
691
                /// <returns>The database command.</returns>
692
                /// <param name="connection">The database connection.</param>
693
                protected override DbCommand GetSelectAllCrlsCommand (DbConnection connection)
694
                {
×
695
                        var command = CreateCommand ();
×
696

697
                        command.CommandText = $"SELECT {CrlColumnNames.Id}, {CrlColumnNames.Crl} FROM {CrlsTableName}";
×
698
                        command.CommandType = CommandType.Text;
×
699

700
                        return command;
×
701
                }
×
702

703
                /// <summary>
704
                /// Gets the database command to delete the specified certificate record.
705
                /// </summary>
706
                /// <remarks>
707
                /// Gets the database command to delete the specified certificate record.
708
                /// </remarks>
709
                /// <returns>The database command.</returns>
710
                /// <param name="connection">The database connection.</param>
711
                /// <param name="record">The certificate record.</param>
712
                protected override DbCommand GetDeleteCommand (DbConnection connection, X509CertificateRecord record)
713
                {
×
714
                        var command = CreateCommand ();
×
715

716
                        command.CommandText = $"DELETE FROM {CertificatesTableName} WHERE {CertificateColumnNames.Id} = @ID";
×
717
                        command.AddParameterWithValue ("@ID", record.Id);
×
718
                        command.CommandType = CommandType.Text;
×
719

720
                        return command;
×
721
                }
×
722

723
                /// <summary>
724
                /// Gets the database command to delete the specified CRL record.
725
                /// </summary>
726
                /// <remarks>
727
                /// Gets the database command to delete the specified CRL record.
728
                /// </remarks>
729
                /// <returns>The database command.</returns>
730
                /// <param name="connection">The database connection.</param>
731
                /// <param name="record">The record.</param>
732
                protected override DbCommand GetDeleteCommand (DbConnection connection, X509CrlRecord record)
733
                {
×
734
                        var command = CreateCommand ();
×
735

736
                        command.CommandText = $"DELETE FROM {CrlsTableName} WHERE {CrlColumnNames.Id} = @ID";
×
737
                        command.AddParameterWithValue ("@ID", record.Id);
×
738
                        command.CommandType = CommandType.Text;
×
739

740
                        return command;
×
741
                }
×
742

743
                /// <summary>
744
                /// Gets the database command to insert the specified certificate record.
745
                /// </summary>
746
                /// <remarks>
747
                /// Gets the database command to insert the specified certificate record.
748
                /// </remarks>
749
                /// <returns>The database command.</returns>
750
                /// <param name="connection">The database connection.</param>
751
                /// <param name="record">The certificate record.</param>
752
                protected override DbCommand GetInsertCommand (DbConnection connection, X509CertificateRecord record)
753
                {
1✔
754
                        var statement = new StringBuilder ("INSERT INTO ").Append (CertificatesTableName).Append ('(');
1✔
755
                        var variables = new StringBuilder ("VALUES(");
1✔
756
                        var command = CreateCommand ();
1✔
757
                        var columns = CertificatesTable.Columns;
1✔
758

759
                        for (int i = 1; i < columns.Count; i++) {
1✔
760
                                if (i > 1) {
1✔
761
                                        statement.Append (", ");
1✔
762
                                        variables.Append (", ");
1✔
763
                                }
1✔
764

765
                                var value = GetValue (record, columns[i].ColumnName);
1✔
766
                                var variable = "@" + columns[i];
1✔
767

768
                                command.AddParameterWithValue (variable, value);
1✔
769
                                statement.Append (columns[i]);
1✔
770
                                variables.Append (variable);
1✔
771
                        }
1✔
772

773
                        statement.Append (')');
1✔
774
                        variables.Append (')');
1✔
775

776
                        command.CommandText = statement + " " + variables;
1✔
777
                        command.CommandType = CommandType.Text;
1✔
778

779
                        return command;
1✔
780
                }
1✔
781

782
                /// <summary>
783
                /// Gets the database command to insert the specified CRL record.
784
                /// </summary>
785
                /// <remarks>
786
                /// Gets the database command to insert the specified CRL record.
787
                /// </remarks>
788
                /// <returns>The database command.</returns>
789
                /// <param name="connection">The database connection.</param>
790
                /// <param name="record">The CRL record.</param>
791
                protected override DbCommand GetInsertCommand (DbConnection connection, X509CrlRecord record)
792
                {
1✔
793
                        var statement = new StringBuilder ("INSERT INTO ").Append (CrlsTableName).Append ('(');
1✔
794
                        var variables = new StringBuilder ("VALUES(");
1✔
795
                        var command = CreateCommand ();
1✔
796
                        var columns = CrlsTable.Columns;
1✔
797

798
                        for (int i = 1; i < columns.Count; i++) {
1✔
799
                                if (i > 1) {
1✔
800
                                        statement.Append (", ");
1✔
801
                                        variables.Append (", ");
1✔
802
                                }
1✔
803

804
                                var value = GetValue (record, columns[i].ColumnName);
1✔
805
                                var variable = "@" + columns[i];
1✔
806

807
                                command.AddParameterWithValue (variable, value);
1✔
808
                                statement.Append (columns[i]);
1✔
809
                                variables.Append (variable);
1✔
810
                        }
1✔
811

812
                        statement.Append (')');
1✔
813
                        variables.Append (')');
1✔
814

815
                        command.CommandText = statement + " " + variables;
1✔
816
                        command.CommandType = CommandType.Text;
1✔
817

818
                        return command;
1✔
819
                }
1✔
820

821
                /// <summary>
822
                /// Gets the database command to update the specified record.
823
                /// </summary>
824
                /// <remarks>
825
                /// Gets the database command to update the specified record.
826
                /// </remarks>
827
                /// <returns>The database command.</returns>
828
                /// <param name="connection">The database connection.</param>
829
                /// <param name="record">The certificate record.</param>
830
                /// <param name="fields">The fields to update.</param>
831
                protected override DbCommand GetUpdateCommand (DbConnection connection, X509CertificateRecord record, X509CertificateRecordFields fields)
832
                {
×
833
                        var statement = new StringBuilder ("UPDATE ").Append (CertificatesTableName).Append (" SET ");
×
834
                        var columns = GetColumnNames (fields & ~X509CertificateRecordFields.Id);
×
835
                        var command = CreateCommand ();
×
836

837
                        for (int i = 0; i < columns.Length; i++) {
×
838
                                var value = GetValue (record, columns[i]);
×
839
                                var variable = "@" + columns[i];
×
840

841
                                if (i > 0)
×
842
                                        statement.Append (", ");
×
843

844
                                statement.Append (columns[i]);
×
845
                                statement.Append (" = ");
×
846
                                statement.Append (variable);
×
847

848
                                command.AddParameterWithValue (variable, value);
×
849
                        }
×
850

851
                        statement.Append (" WHERE ").Append (CertificateColumnNames.Id).Append (" = @ID");
×
852
                        command.AddParameterWithValue ("@ID", record.Id);
×
853

854
                        command.CommandText = statement.ToString ();
×
855
                        command.CommandType = CommandType.Text;
×
856

857
                        return command;
×
858
                }
×
859

860
                /// <summary>
861
                /// Gets the database command to update the specified CRL record.
862
                /// </summary>
863
                /// <remarks>
864
                /// Gets the database command to update the specified CRL record.
865
                /// </remarks>
866
                /// <returns>The database command.</returns>
867
                /// <param name="connection">The database connection.</param>
868
                /// <param name="record">The CRL record.</param>
869
                [Obsolete ("This method is not used and will be removed in a future release.")]
870
                protected override DbCommand GetUpdateCommand (DbConnection connection, X509CrlRecord record)
871
                {
×
872
                        var statement = new StringBuilder ("UPDATE ").Append (CrlsTableName).Append (" SET ");
×
873
                        var command = CreateCommand ();
×
874
                        var columns = CrlsTable.Columns;
×
875

876
                        for (int i = 1; i < columns.Count; i++) {
×
877
                                var value = GetValue (record, columns[i].ColumnName);
×
878
                                var variable = "@" + columns[i];
×
879

880
                                if (i > 1)
×
881
                                        statement.Append (", ");
×
882

883
                                statement.Append (columns[i]);
×
884
                                statement.Append (" = ");
×
885
                                statement.Append (variable);
×
886

887
                                command.AddParameterWithValue (variable, value);
×
888
                        }
×
889

890
                        statement.Append (" WHERE ").Append (CrlColumnNames.Id).Append (" = @ID");
×
891
                        command.AddParameterWithValue ("@ID", record.Id);
×
892

893
                        command.CommandText = statement.ToString ();
×
894
                        command.CommandType = CommandType.Text;
×
895

896
                        return command;
×
897
                }
×
898

899
                /// <summary>
900
                /// Releases the unmanaged resources used by the <see cref="SqlCertificateDatabase"/> and
901
                /// optionally releases the managed resources.
902
                /// </summary>
903
                /// <remarks>
904
                /// Releases the unmanaged resources used by the <see cref="SqlCertificateDatabase"/> and
905
                /// optionally releases the managed resources.
906
                /// </remarks>
907
                /// <param name="disposing"><see langword="true" /> to release both managed and unmanaged resources;
908
                /// <see langword="false" /> to release only the unmanaged resources.</param>
909
                protected override void Dispose (bool disposing)
910
                {
1✔
911
                        if (disposing && !disposed) {
1✔
912
                                CertificatesTable.Dispose ();
1✔
913
                                CrlsTable.Dispose ();
1✔
914
                                disposed = true;
1✔
915
                        }
1✔
916

917
                        base.Dispose (disposing);
1✔
918
                }
1✔
919
        }
920
}
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