An X.509 certificate record.
Represents an X.509 certificate record loaded from a database.
Exemple #1
0
        /// <summary>
        /// Add the specified certificate record.
        /// </summary>
        /// <remarks>
        /// Adds the specified certificate record to the database.
        /// </remarks>
        /// <param name="record">The certificate record.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="record"/> is <c>null</c>.
        /// </exception>
        public void Add(X509CertificateRecord record)
        {
            if (record == null)
            {
                throw new ArgumentNullException("record");
            }

            using (var command = GetInsertCommand(record)) {
                command.ExecuteNonQuery();
            }
        }
Exemple #2
0
        /// <summary>
        /// Remove the specified certificate record.
        /// </summary>
        /// <remarks>
        /// Removes the specified certificate record from the database.
        /// </remarks>
        /// <param name="record">The certificate record.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="record"/> is <c>null</c>.
        /// </exception>
        public void Remove(X509CertificateRecord record)
        {
            if (record == null)
            {
                throw new ArgumentNullException(nameof(record));
            }

            using (var command = GetDeleteCommand(record)) {
                command.ExecuteNonQuery();
            }
        }
Exemple #3
0
        /// <summary>
        /// Update the specified certificate record.
        /// </summary>
        /// <remarks>
        /// Updates the specified fields of the record in the database.
        /// </remarks>
        /// <param name="record">The certificate record.</param>
        /// <param name="fields">The fields to update.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="record"/> is <c>null</c>.
        /// </exception>
        public void Update(X509CertificateRecord record, X509CertificateRecordFields fields)
        {
            if (record == null)
            {
                throw new ArgumentNullException(nameof(record));
            }

            using (var command = GetUpdateCommand(record, fields)) {
                command.ExecuteNonQuery();
            }
        }
Exemple #4
0
        /// <summary>
        /// Updates the known S/MIME capabilities of the client used by the recipient that owns the specified certificate.
        /// </summary>
        /// <remarks>
        /// Updates the known S/MIME capabilities of the client used by the recipient that owns the specified certificate.
        /// </remarks>
        /// <param name="certificate">The certificate.</param>
        /// <param name="algorithms">The encryption algorithm capabilities of the client (in preferred order).</param>
        /// <param name="timestamp">The timestamp in coordinated universal time (UTC).</param>
        protected override void UpdateSecureMimeCapabilities(X509Certificate certificate, EncryptionAlgorithm[] algorithms, DateTime timestamp)
        {
            X509CertificateRecord record;

            if ((record = dbase.Find(certificate, AlgorithmFields)) == null)
            {
                record = new X509CertificateRecord(certificate);
                record.AlgorithmsUpdated = timestamp;
                record.Algorithms        = algorithms;

                dbase.Add(record);
            }
            else if (timestamp > record.AlgorithmsUpdated)
            {
                record.AlgorithmsUpdated = timestamp;
                record.Algorithms        = algorithms;

                dbase.Update(record, AlgorithmFields);
            }
        }
Exemple #5
0
        /// <summary>
        /// Gets the database command to insert the specified certificate record.
        /// </summary>
        /// <remarks>
        /// Gets the database command to insert the specified certificate record.
        /// </remarks>
        /// <returns>The database command.</returns>
        /// <param name="record">The certificate record.</param>
        protected override DbCommand GetInsertCommand(X509CertificateRecord record)
        {
            var statement = new StringBuilder("INSERT INTO CERTIFICATES(");
            var variables = new StringBuilder("VALUES(");
            var command   = Connection.CreateCommand();
            var columns   = CertificatesTable.Columns;

            for (int i = 1; i < columns.Count; i++)
            {
                if (i > 1)
                {
                    statement.Append(", ");
                    variables.Append(", ");
                }

                var value = GetValue(record, columns[i].ColumnName);
                if (value is DateTime dateTime && dateTime < DateUtils.UnixEpoch)
                {
                    value = DateUtils.UnixEpoch;
                }

                if (columns[i].ColumnName == "PRIVATEKEY" && value is DBNull)
                {
                    value = new byte[0];
                }

                var variable = "@" + columns[i];

                command.AddParameterWithValue(variable, value);
                statement.Append(columns[i]);
                variables.Append(variable);
            }

            statement.Append(')');
            variables.Append(')');

            command.CommandText = statement + " " + variables;
            command.CommandType = CommandType.Text;

            return(command);
        }
        /// <summary>
        /// Imports a DER-encoded certificate stream.
        /// </summary>
        /// <remarks>
        /// Imports all of the certificates in the DER-encoded stream.
        /// </remarks>
        /// <param name="stream">The raw certificate(s).</param>
        /// <param name="trusted"><c>true</c> if the certificates are trusted.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="stream"/> is <c>null</c>.
        /// </exception>
        public void Import(Stream stream, bool trusted)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            var parser = new X509CertificateParser();

            foreach (X509Certificate certificate in parser.ReadCertificates(stream))
            {
                if (dbase.Find(certificate, X509CertificateRecordFields.Id) != null)
                {
                    continue;
                }

                var record = new X509CertificateRecord(certificate);
                record.IsTrusted = trusted;
                dbase.Add(record);
            }
        }
Exemple #7
0
        /// <summary>
        /// Gets the value for the specified column.
        /// </summary>
        /// <remarks>
        /// Gets the value for the specified column.
        /// </remarks>
        /// <returns>The value.</returns>
        /// <param name="record">The certificate record.</param>
        /// <param name="columnName">The column name.</param>
        /// <exception cref="System.ArgumentException">
        /// <paramref name="columnName"/> is not a known column name.
        /// </exception>
        protected object GetValue(X509CertificateRecord record, string columnName)
        {
            switch (columnName)
            {
            //case "ID": return record.Id;
            case "BASICCONSTRAINTS": return(record.BasicConstraints);

            case "TRUSTED": return(record.IsTrusted);

            case "ANCHOR": return(record.IsAnchor);

            case "KEYUSAGE": return((int)record.KeyUsage);

            case "NOTBEFORE": return(record.NotBefore.ToUniversalTime());

            case "NOTAFTER": return(record.NotAfter.ToUniversalTime());

            case "ISSUERNAME": return(record.IssuerName);

            case "SERIALNUMBER": return(record.SerialNumber);

            case "SUBJECTNAME": return(record.SubjectName);

            case "SUBJECTKEYIDENTIFIER": return(record.SubjectKeyIdentifier?.AsHex());

            case "SUBJECTEMAIL": return(record.SubjectEmail != null?record.SubjectEmail.ToLowerInvariant() : string.Empty);

            case "FINGERPRINT": return(record.Fingerprint.ToLowerInvariant());

            case "ALGORITHMS": return(EncodeEncryptionAlgorithms(record.Algorithms));

            case "ALGORITHMSUPDATED": return(record.AlgorithmsUpdated);

            case "CERTIFICATE": return(record.Certificate.GetEncoded());

            case "PRIVATEKEY": return(EncodePrivateKey(record.PrivateKey));

            default: throw new ArgumentException(string.Format("Unknown column name: {0}", columnName), nameof(columnName));
            }
        }
Exemple #8
0
        /// <summary>
        /// Imports a certificate.
        /// </summary>
        /// <remarks>
        /// <para>Imports the certificate.</para>
        /// <para>If the certificate already exists in the database and <paramref name="trusted"/> is <c>true</c>,
        /// then the IsTrusted state is updated otherwise the certificate is added to the database with the
        /// specified trust.</para>
        /// </remarks>
        /// <param name="certificate">The certificate.</param>
        /// <param name="trusted"><c>true</c> if the certificate is trusted; otherwise, <c>false</c>.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <paramref name="certificate"/> is <c>null</c>.
        /// </exception>
        public void Import(X509Certificate certificate, bool trusted)
        {
            if (certificate == null)
            {
                throw new ArgumentNullException(nameof(certificate));
            }

            X509CertificateRecord record;

            if ((record = dbase.Find(certificate, X509CertificateRecordFields.Id | X509CertificateRecordFields.Trusted)) != null)
            {
                if (trusted && !record.IsTrusted)
                {
                    record.IsTrusted = trusted;
                    dbase.Update(record, X509CertificateRecordFields.Trusted);
                }

                return;
            }

            record           = new X509CertificateRecord(certificate);
            record.IsTrusted = trusted;
            dbase.Add(record);
        }
        X509CertificateRecord LoadCertificateRecord(DbDataReader reader, X509CertificateParser parser, ref byte[] buffer)
        {
            var record = new X509CertificateRecord();

            for (int i = 0; i < reader.FieldCount; i++)
            {
                switch (reader.GetName(i).ToUpperInvariant())
                {
                case "CERTIFICATE":
                    record.Certificate = DecodeCertificate(reader, parser, i, ref buffer);
                    break;

                case "PRIVATEKEY":
                    record.PrivateKey = DecodePrivateKey(reader, i, ref buffer);
                    break;

                case "ALGORITHMS":
                    record.Algorithms = DecodeEncryptionAlgorithms(reader, i);
                    break;

                case "ALGORITHMSUPDATED":
                    record.AlgorithmsUpdated = DateTime.SpecifyKind(reader.GetDateTime(i), DateTimeKind.Utc);
                    break;

                case "TRUSTED":
                    record.IsTrusted = reader.GetBoolean(i);
                    break;

                case "ID":
                    record.Id = reader.GetInt32(i);
                    break;
                }
            }

            return(record);
        }
		/// <summary>
		/// Remove the specified certificate record.
		/// </summary>
		/// <remarks>
		/// Removes the specified certificate record from the database.
		/// </remarks>
		/// <param name="record">The certificate record.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="record"/> is <c>null</c>.
		/// </exception>
		public void Remove (X509CertificateRecord record)
		{
			if (record == null)
				throw new ArgumentNullException ("record");

			using (var command = GetDeleteCommand (record)) {
				command.ExecuteNonQuery ();
			}
		}
		/// <summary>
		/// Gets the database command to update the specified record.
		/// </summary>
		/// <remarks>
		/// Gets the database command to update the specified record.
		/// </remarks>
		/// <returns>The database command.</returns>
		/// <param name="record">The certificate record.</param>
		/// <param name="fields">The fields to update.</param>
		protected abstract IDbCommand GetUpdateCommand (X509CertificateRecord record, X509CertificateRecordFields fields);
		/// <summary>
		/// Gets the database command to insert the specified certificate record.
		/// </summary>
		/// <remarks>
		/// Gets the database command to insert the specified certificate record.
		/// </remarks>
		/// <returns>The database command.</returns>
		/// <param name="record">The certificate record.</param>
		protected abstract IDbCommand GetInsertCommand (X509CertificateRecord record);
		/// <summary>
		/// Gets the value for the specified column.
		/// </summary>
		/// <remarks>
		/// Gets the value for the specified column.
		/// </remarks>
		/// <returns>The value.</returns>
		/// <param name="record">The certificate record.</param>
		/// <param name="columnName">The column name.</param>
		/// <exception cref="System.ArgumentException">
		/// <paramref name="columnName"/> is not a known column name.
		/// </exception>
		protected object GetValue (X509CertificateRecord record, string columnName)
		{
			switch (columnName) {
			case "ID": return record.Id;
			case "BASICCONSTRAINTS": return record.BasicConstraints;
			case "TRUSTED": return record.IsTrusted;
			case "KEYUSAGE": return (int) record.KeyUsage;
			case "NOTBEFORE": return record.NotBefore;
			case "NOTAFTER": return record.NotAfter;
			case "ISSUERNAME": return record.IssuerName;
			case "SERIALNUMBER": return record.SerialNumber;
			case "SUBJECTEMAIL": return record.SubjectEmail != null ? record.SubjectEmail.ToLowerInvariant () : string.Empty;
			case "FINGERPRINT": return record.Fingerprint.ToLowerInvariant ();
			case "ALGORITHMS": return EncodeEncryptionAlgorithms (record.Algorithms);
			case "ALGORITHMSUPDATED": return record.AlgorithmsUpdated;
			case "CERTIFICATE": return record.Certificate.GetEncoded ();
			case "PRIVATEKEY": return EncodePrivateKey (record.PrivateKey);
			default: throw new ArgumentException (string.Format ("Unknown column name: {0}", columnName), "columnName");
			}
		}
		/// <summary>
		/// Gets the database command to delete the specified certificate record.
		/// </summary>
		/// <remarks>
		/// Gets the database command to delete the specified certificate record.
		/// </remarks>
		/// <returns>The database command.</returns>
		/// <param name="record">The certificate record.</param>
		protected abstract IDbCommand GetDeleteCommand (X509CertificateRecord record);
		X509CertificateRecord LoadCertificateRecord (IDataRecord reader, X509CertificateParser parser, ref byte[] buffer)
		{
			var record = new X509CertificateRecord ();

			for (int i = 0; i < reader.FieldCount; i++) {
				switch (reader.GetName (i).ToUpperInvariant ()) {
				case "CERTIFICATE":
					record.Certificate = DecodeCertificate (reader, parser, i, ref buffer);
					break;
				case "PRIVATEKEY":
					record.PrivateKey = DecodePrivateKey (reader, i, ref buffer);
					break;
				case "ALGORITHMS":
					record.Algorithms = DecodeEncryptionAlgorithms (reader, i);
					break;
				case "ALGORITHMSUPDATED":
					record.AlgorithmsUpdated = reader.GetDateTime (i);
					break;
				case "TRUSTED":
					record.IsTrusted = reader.GetBoolean (i);
					break;
				case "ID":
					record.Id = reader.GetInt32 (i);
					break;
				}
			}

			return record;
		}
 /// <summary>
 /// Gets the database command to insert the specified certificate record.
 /// </summary>
 /// <remarks>
 /// Gets the database command to insert the specified certificate record.
 /// </remarks>
 /// <returns>The database command.</returns>
 /// <param name="record">The certificate record.</param>
 protected abstract DbCommand GetInsertCommand(X509CertificateRecord record);
Exemple #17
0
        /// <summary>
        /// Imports certificates and keys from a pkcs12-encoded stream.
        /// </summary>
        /// <remarks>
        /// Imports all of the certificates and keys from the pkcs12-encoded stream.
        /// </remarks>
        /// <param name="stream">The raw certificate and key data.</param>
        /// <param name="password">The password to unlock the data.</param>
        /// <exception cref="System.ArgumentNullException">
        /// <para><paramref name="stream"/> is <c>null</c>.</para>
        /// <para>-or-</para>
        /// <para><paramref name="password"/> is <c>null</c>.</para>
        /// </exception>
        /// <exception cref="Org.BouncyCastle.Cms.CmsException">
        /// An error occurred in the cryptographic message syntax subsystem.
        /// </exception>
        public override void Import(Stream stream, string password)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            if (password == null)
            {
                throw new ArgumentNullException(nameof(password));
            }

            var pkcs12            = new Pkcs12Store(stream, password.ToCharArray());
            var enabledAlgorithms = EnabledEncryptionAlgorithms;
            X509CertificateRecord record;

            foreach (string alias in pkcs12.Aliases)
            {
                if (pkcs12.IsKeyEntry(alias))
                {
                    var chain      = pkcs12.GetCertificateChain(alias);
                    var entry      = pkcs12.GetKey(alias);
                    int startIndex = 0;

                    if (entry.Key.IsPrivate)
                    {
                        if ((record = dbase.Find(chain[0].Certificate, ImportPkcs12Fields)) == null)
                        {
                            record = new X509CertificateRecord(chain[0].Certificate, entry.Key);
                            record.AlgorithmsUpdated = DateTime.UtcNow;
                            record.Algorithms        = enabledAlgorithms;
                            record.IsTrusted         = true;
                            dbase.Add(record);
                        }
                        else
                        {
                            record.AlgorithmsUpdated = DateTime.UtcNow;
                            record.Algorithms        = enabledAlgorithms;
                            if (record.PrivateKey == null)
                            {
                                record.PrivateKey = entry.Key;
                            }
                            record.IsTrusted = true;
                            dbase.Update(record, ImportPkcs12Fields);
                        }

                        startIndex = 1;
                    }

                    for (int i = startIndex; i < chain.Length; i++)
                    {
                        Import(chain[i].Certificate, true);
                    }
                }
                else if (pkcs12.IsCertificateEntry(alias))
                {
                    var entry = pkcs12.GetCertificate(alias);

                    Import(entry.Certificate, true);
                }
            }
        }
        SqliteCommand GetDeleteCommand(X509CertificateRecord record)
        {
            var command = sqlite.CreateCommand ();

            command.CommandText = "DELETE FROM CERTIFICATES WHERE ID = @ID";
            command.Parameters.AddWithValue ("@ID", record.Id);
            command.CommandType = CommandType.Text;

            return command;
        }
		/// <summary>
		/// Updates the known S/MIME capabilities of the client used by the recipient that owns the specified certificate.
		/// </summary>
		/// <remarks>
		/// Updates the known S/MIME capabilities of the client used by the recipient that owns the specified certificate.
		/// </remarks>
		/// <param name="certificate">The certificate.</param>
		/// <param name="algorithms">The encryption algorithm capabilities of the client (in preferred order).</param>
		/// <param name="timestamp">The timestamp in coordinated universal time (UTC).</param>
		protected override void UpdateSecureMimeCapabilities (X509Certificate certificate, EncryptionAlgorithm[] algorithms, DateTime timestamp)
		{
			X509CertificateRecord record;

			if ((record = dbase.Find (certificate, AlgorithmFields)) == null) {
				record = new X509CertificateRecord (certificate);
				record.AlgorithmsUpdated = timestamp;
				record.Algorithms = algorithms;

				dbase.Add (record);
			} else if (timestamp > record.AlgorithmsUpdated) {
				record.AlgorithmsUpdated = timestamp;
				record.Algorithms = algorithms;

				dbase.Update (record, AlgorithmFields);
			}
		}
		/// <summary>
		/// Imports certificates and keys from a pkcs12-encoded stream.
		/// </summary>
		/// <remarks>
		/// Imports all of the certificates and keys from the pkcs12-encoded stream.
		/// </remarks>
		/// <param name="stream">The raw certificate and key data.</param>
		/// <param name="password">The password to unlock the data.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <para><paramref name="stream"/> is <c>null</c>.</para>
		/// <para>-or-</para>
		/// <para><paramref name="password"/> is <c>null</c>.</para>
		/// </exception>
		/// <exception cref="Org.BouncyCastle.Cms.CmsException">
		/// An error occurred in the cryptographic message syntax subsystem.
		/// </exception>
		public override void Import (Stream stream, string password)
		{
			if (stream == null)
				throw new ArgumentNullException ("stream");

			if (password == null)
				throw new ArgumentNullException ("password");

			var pkcs12 = new Pkcs12Store (stream, password.ToCharArray ());
			var enabledAlgorithms = EnabledEncryptionAlgorithms;
			X509CertificateRecord record;

			foreach (string alias in pkcs12.Aliases) {
				if (pkcs12.IsKeyEntry (alias)) {
					var chain = pkcs12.GetCertificateChain (alias);
					var entry = pkcs12.GetKey (alias);
					int startIndex = 0;

					if (entry.Key.IsPrivate) {
						if ((record = dbase.Find (chain[0].Certificate, ImportPkcs12Fields)) == null) {
							record = new X509CertificateRecord (chain[0].Certificate, entry.Key);
							record.AlgorithmsUpdated = DateTime.UtcNow;
							record.Algorithms = enabledAlgorithms;
							record.IsTrusted = true;
							dbase.Add (record);
						} else {
							record.AlgorithmsUpdated = DateTime.UtcNow;
							record.Algorithms = enabledAlgorithms;
							if (record.PrivateKey == null)
								record.PrivateKey = entry.Key;
							record.IsTrusted = true;
							dbase.Update (record, ImportPkcs12Fields);
						}

						startIndex = 1;
					}

					for (int i = startIndex; i < chain.Length; i++) {
						if ((record = dbase.Find (chain[i].Certificate, X509CertificateRecordFields.Id)) == null)
							dbase.Add (new X509CertificateRecord (chain[i].Certificate));
					}
				} else if (pkcs12.IsCertificateEntry (alias)) {
					var entry = pkcs12.GetCertificate (alias);

					if ((record = dbase.Find (entry.Certificate, X509CertificateRecordFields.Id)) == null)
						dbase.Add (new X509CertificateRecord (entry.Certificate));
				}
			}
		}
		/// <summary>
		/// Update the specified certificate record.
		/// </summary>
		/// <remarks>
		/// Updates the specified fields of the record in the database.
		/// </remarks>
		/// <param name="record">The certificate record.</param>
		/// <param name="fields">The fields to update.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="record"/> is <c>null</c>.
		/// </exception>
		public void Update (X509CertificateRecord record, X509CertificateRecordFields fields)
		{
			if (record == null)
				throw new ArgumentNullException ("record");

			using (var command = GetUpdateCommand (record, fields)) {
				command.ExecuteNonQuery ();
			}
		}
 object GetValue(X509CertificateRecord record, string columnName)
 {
     switch (columnName) {
     case "ID": return record.Id;
     case "BASICCONSTRAINTS": return record.BasicConstraints;
     case "TRUSTED": return record.IsTrusted;
     case "KEYUSAGE": return record.KeyUsage;
     case "NOTBEFORE": return record.NotBefore;
     case "NOTAFTER": return record.NotAfter;
     case "ISSUERNAME": return record.IssuerName;
     case "SERIALNUMBER": return record.SerialNumber;
     case "SUBJECTEMAIL": return record.SubjectEmail;
     case "FINGERPRINT": return record.Fingerprint;
     case "ALGORITHMS": return EncodeEncryptionAlgorithms (record.Algorithms);
     case "ALGORITHMSUPDATED": return record.AlgorithmsUpdated;
     case "CERTIFICATE": return record.Certificate.GetEncoded ();
     case "PRIVATEKEY": return EncodePrivateKey (record.PrivateKey);
     default: throw new ArgumentOutOfRangeException ();
     }
 }
		/// <summary>
		/// Imports a DER-encoded certificate stream.
		/// </summary>
		/// <remarks>
		/// Imports all of the certificates in the DER-encoded stream.
		/// </remarks>
		/// <param name="stream">The raw certificate(s).</param>
		/// <param name="trusted"><c>true</c> if the certificates are trusted.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="stream"/> is <c>null</c>.
		/// </exception>
		public void Import (Stream stream, bool trusted)
		{
			if (stream == null)
				throw new ArgumentNullException ("stream");

			var parser = new X509CertificateParser ();

			foreach (X509Certificate certificate in parser.ReadCertificates (stream)) {
				if (dbase.Find (certificate, X509CertificateRecordFields.Id) != null)
					continue;

				var record = new X509CertificateRecord (certificate);
				record.IsTrusted = trusted;
				dbase.Add (record);
			}
		}
		/// <summary>
		/// Gets the database command to delete the specified certificate record.
		/// </summary>
		/// <remarks>
		/// Gets the database command to delete the specified certificate record.
		/// </remarks>
		/// <returns>The database command.</returns>
		/// <param name="record">The certificate record.</param>
		protected override DbCommand GetDeleteCommand (X509CertificateRecord record)
		{
			var command = connection.CreateCommand ();

			command.CommandText = "DELETE FROM CERTIFICATES WHERE ID = @ID";
			command.AddParameterWithValue ("@ID", record.Id);
			command.CommandType = CommandType.Text;

			return command;
		}
		/// <summary>
		/// Gets the database command to insert the specified certificate record.
		/// </summary>
		/// <remarks>
		/// Gets the database command to insert the specified certificate record.
		/// </remarks>
		/// <returns>The database command.</returns>
		/// <param name="record">The certificate record.</param>
		protected override DbCommand GetInsertCommand (X509CertificateRecord record)
		{
			var statement = new StringBuilder ("INSERT INTO CERTIFICATES(");
			var columns = X509CertificateRecord.ColumnNames;
			var variables = new StringBuilder ("VALUES(");
			var command = connection.CreateCommand ();

			for (int i = 1; i < columns.Length; i++) {
				if (i > 1) {
					statement.Append (", ");
					variables.Append (", ");
				}

				var value = GetValue (record, columns[i]);
				var variable = "@" + columns[i];

				command.AddParameterWithValue (variable, value);
				statement.Append (columns[i]);
				variables.Append (variable);
			}

			statement.Append (')');
			variables.Append (')');

			command.CommandText = statement + " " + variables;
			command.CommandType = CommandType.Text;

			return command;
		}
 /// <summary>
 /// Gets the database command to delete the specified certificate record.
 /// </summary>
 /// <remarks>
 /// Gets the database command to delete the specified certificate record.
 /// </remarks>
 /// <returns>The database command.</returns>
 /// <param name="record">The certificate record.</param>
 protected abstract DbCommand GetDeleteCommand(X509CertificateRecord record);
		/// <summary>
		/// Gets the database command to update the specified record.
		/// </summary>
		/// <remarks>
		/// Gets the database command to update the specified record.
		/// </remarks>
		/// <returns>The database command.</returns>
		/// <param name="record">The certificate record.</param>
		/// <param name="fields">The fields to update.</param>
		protected override DbCommand GetUpdateCommand (X509CertificateRecord record, X509CertificateRecordFields fields)
		{
			var statement = new StringBuilder ("UPDATE CERTIFICATES SET ");
			var columns = GetColumnNames (fields & ~X509CertificateRecordFields.Id);
			var command = connection.CreateCommand ();

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

				if (i > 0)
					statement.Append (", ");

				statement.Append (columns[i]);
				statement.Append (" = ");
				statement.Append (variable);

				command.AddParameterWithValue (variable, value);
			}

			statement.Append (" WHERE ID = @ID");
			command.AddParameterWithValue ("@ID", record.Id);

			command.CommandText = statement.ToString ();
			command.CommandType = CommandType.Text;

			return command;
		}
 /// <summary>
 /// Gets the database command to update the specified record.
 /// </summary>
 /// <remarks>
 /// Gets the database command to update the specified record.
 /// </remarks>
 /// <returns>The database command.</returns>
 /// <param name="record">The certificate record.</param>
 /// <param name="fields">The fields to update.</param>
 protected abstract DbCommand GetUpdateCommand(X509CertificateRecord record, X509CertificateRecordFields fields);
		/// <summary>
		/// Add the specified certificate record.
		/// </summary>
		/// <remarks>
		/// Adds the specified certificate record to the database.
		/// </remarks>
		/// <param name="record">The certificate record.</param>
		/// <exception cref="System.ArgumentNullException">
		/// <paramref name="record"/> is <c>null</c>.
		/// </exception>
		public void Add (X509CertificateRecord record)
		{
			if (record == null)
				throw new ArgumentNullException (nameof (record));

			using (var command = GetInsertCommand (record)) {
				command.ExecuteNonQuery ();
			}
		}