diff --git a/.gitignore b/.gitignore index d2dc8d2..83a8ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -235,3 +235,4 @@ _Pvt_Extensions # FAKE - F# Make .fake/ /PKI.Test +*.DotSettings diff --git a/PKI/CertificateServices/DB/AdcsDbManager.cs b/PKI/CertificateServices/DB/AdcsDbManager.cs deleted file mode 100644 index 76cb947..0000000 --- a/PKI/CertificateServices/DB/AdcsDbManager.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace PKI.CertificateServices.DB { - class AdcsDbManager { - - } -} diff --git a/PKI/Management/CertificateServices/Database/AdcsDbColumnDataType.cs b/PKI/Management/CertificateServices/Database/AdcsDbColumnDataType.cs new file mode 100644 index 0000000..fd67e62 --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbColumnDataType.cs @@ -0,0 +1,23 @@ +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + /// + /// Contains possible datatypes to store the data in Certification Authority's database. + /// + public enum AdcsDbColumnDataType { + /// + /// Signed long data. + /// + Long = 1, + /// + /// Date/time. + /// + DateTime = 2, + /// + /// Binary data. + /// + Binary = 3, + /// + /// Unicode string data. + /// + String = 4 + } +} diff --git a/PKI/Management/CertificateServices/Database/AdcsDbInternalEnumerator.cs b/PKI/Management/CertificateServices/Database/AdcsDbInternalEnumerator.cs new file mode 100644 index 0000000..cf683ba --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbInternalEnumerator.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using CERTADMINLib; +using PKI.Structs; +using PKI.Utils; + +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + class AdcsDbInternalEnumerator : IDisposable { + readonly String _configString; + readonly AdcsDbTableName _table; + readonly IEnumCERTVIEWROW _dbRow; + + public AdcsDbInternalEnumerator(IEnumCERTVIEWROW dbRow, String configString, AdcsDbTableName table) { + _configString = configString; + _table = table; + _dbRow = dbRow; + } + + public String ConfigString { get; set; } + public AdcsDbViewTableName Table { get; set; } + + + static void enumColumnView(IEnumCERTVIEWROW dbRow, AdcsDbRow row) { + var dbColumn = dbRow.EnumCertViewColumn(); + while (dbColumn.Next() != -1) { + String colName = dbColumn.GetName(); + Object colVal = dbColumn.GetValue(CertAdmConstants.CV_OUT_BASE64); + switch (colName) { + case "RequestID": + case "ExtensionRequestId": + case "AttributeRequestId": + case "CRLRowId": + row.RowId = (Int32)colVal; + break; + } + row.Properties.Add(colName, colVal); + } + CryptographyUtils.ReleaseCom(dbColumn); + } + static void postProcessRow(AdcsDbRow row) { + if (row.Properties.ContainsKey("CertificateTemplate")) { + row.Properties.Add("CertificateTemplateOid", new Oid((String)row.Properties["CertificateTemplate"])); + } + if (row.Properties.ContainsKey("ExtensionName")) { + row.Properties.Add("ExtensionNameOid", new Oid((String)row.Properties["ExtensionName"])); + } + } + + public IEnumerable EnumRows(Int32 skipRows, Int32 takeRows) { + Int32 rowsTaken = 0; + _dbRow.Skip(skipRows); + while (_dbRow.Next() != -1 && rowsTaken < takeRows) { + rowsTaken++; + var row = new AdcsDbRow { + ConfigString = _configString, + Table = _table + }; + enumColumnView(_dbRow, row); + postProcessRow(row); + yield return row; + } + } + void ReleaseUnmanagedResources() { + CryptographyUtils.ReleaseCom(_dbRow); + } + /// + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + /// + ~AdcsDbInternalEnumerator() { + ReleaseUnmanagedResources(); + } + } +} \ No newline at end of file diff --git a/PKI/Management/CertificateServices/Database/AdcsDbQueryFilterEntry.cs b/PKI/Management/CertificateServices/Database/AdcsDbQueryFilterEntry.cs new file mode 100644 index 0000000..9172957 --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbQueryFilterEntry.cs @@ -0,0 +1,56 @@ +using System; + +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + /// + /// A valid column index number for the view or a predefined column specifier + /// + public class AdcsDbQueryFilterEntry { + + public AdcsDbQueryFilterEntry(String columnName, AdcsDbSeekOperator op, Object value) { + if (String.IsNullOrEmpty(columnName)) { + throw new ArgumentNullException(nameof(columnName)); + } + + ColumnName = columnName; + LogicalOperator = op; + QualifierValue = value ?? throw new ArgumentNullException(nameof(value)); + } + + internal Int32 ColumnID { get; set; } + /// + /// A valid column name for the view or a predefined column specifier. + /// + public String ColumnName { get; } + /// + /// Specifies the logical operator of the data-query qualifier for the column. This parameter + /// is used with the property to define the data-query qualifier. + /// + public AdcsDbSeekOperator LogicalOperator { get; } + /// + /// Specifies the data query qualifier applied to this column. This parameter, along with the + /// parameter, determines which data is returned to the Certificate Services view. + /// + public Object QualifierValue { get; } + + /// + public override Boolean Equals(Object obj) { + return !(obj is null) + && (ReferenceEquals(this, obj) + || obj is AdcsDbQueryFilterEntry other && Equals(other)); + } + protected Boolean Equals(AdcsDbQueryFilterEntry other) { + return String.Equals(ColumnName, other.ColumnName, StringComparison.OrdinalIgnoreCase) + && LogicalOperator == other.LogicalOperator + && Equals(QualifierValue, other.QualifierValue); + } + /// + public override Int32 GetHashCode() { + unchecked { + Int32 hashCode = ColumnName != null ? StringComparer.OrdinalIgnoreCase.GetHashCode(ColumnName) : 0; + hashCode = (hashCode * 397) ^ (Int32) LogicalOperator; + hashCode = (hashCode * 397) ^ (QualifierValue != null ? QualifierValue.GetHashCode() : 0); + return hashCode; + } + } + } +} diff --git a/PKI/Management/CertificateServices/Database/AdcsDbReader.cs b/PKI/Management/CertificateServices/Database/AdcsDbReader.cs new file mode 100644 index 0000000..b3c3d7d --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbReader.cs @@ -0,0 +1,375 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CERTADMINLib; +using PKI.CertificateServices; +using PKI.Structs; +using PKI.Utils; + +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + class AdcsDbReader : IDisposable { + #region predefined columns per view table + readonly String[] _revokedColumns = { + "RequestID", + "Request.RevokedWhen", + "Request.RevokedReason", + "CommonName", + "SerialNumber", + "CertificateTemplate" + }; + readonly String[] _issuedColumns = { + "RequestID", + "Request.RequesterName", + "CommonName", + "NotBefore", + "NotAfter", + "SerialNumber", + "CertificateTemplate" + }; + readonly String[] _pendingColumns = { + "RequestID", + "Request.RequesterName", + "Request.SubmittedWhen", + "Request.CommonName", + "CertificateTemplate" + }; + readonly String[] _failedColumns = { + "RequestID", + "Request.StatusCode", + "Request.DispositionMessage", + "Request.SubmittedWhen", + "Request.CommonName", + "CertificateTemplate" + }; + readonly String[] _requestColumns = { + "RequestID", + "Request.StatusCode", + "Request.DispositionMessage", + "Request.RequesterName", + "Request.SubmittedWhen", + "Request.CommonName", + "CertificateTemplate" + }; + readonly String[] _extensionColumns = { + "ExtensionRequestId", + "ExtensionName", + "ExtensionFlags", + "ExtensionRawValue" + }; + readonly String[] _attributeColumns = { + "AttributeRequestId", + "AttributeName", + "AttributeValue" + }; + readonly String[] _crlColumns = { + "CRLRowId", + "CRLNumber", + "CRLThisUpdate", + "CRLNextUpdate", + "CRLPublishStatusCode", + "CRLPublishError" + }; + #endregion + readonly ICertView2 CaView = new CCertViewClass(); + readonly IList _columnIDs = new List(); + readonly IList _columns = new List(); + readonly ISet _filters = new HashSet(); + AdcsDbTableName table; + Boolean isOpenView, outAllColumns; + + /// + /// + /// + /// + public AdcsDbReader(CertificateAuthority certificateAuthority) + : this(certificateAuthority, AdcsDbViewTableName.Issued) { } + /// + /// + /// + /// + /// + public AdcsDbReader(CertificateAuthority certificateAuthority, AdcsDbViewTableName viewTable) { + if (certificateAuthority == null) { throw new ArgumentNullException(nameof(certificateAuthority)); } + + ConfigString = certificateAuthority.ConfigString; + CaView.OpenConnection(ConfigString); + ViewTable = viewTable; + mapTables(); + } + + /// + /// Gets the CA's config string associated with the current database reader instance. + /// + public String ConfigString { get; } + /// + /// Gets an array of database columns added to include in the view. + /// + public String[] Columns => _columns.ToArray(); + /// + /// Gets an array of currently applied query filters. + /// + public AdcsDbQueryFilterEntry[] QueryFilters => _filters.ToArray(); + /// + /// Gets the view table to query. Default is 'Request' view table. + /// + /// + /// Depending on view table, predefined column list is added to a result view. + /// + /// + /// View Table + /// Default columns + /// + /// + /// Revoked + /// + /// + /// RequestID + /// Request.RevokedWhen + /// Request.RevokedReason + /// CommonName + /// SerialNumber + /// CertificateTemplater + /// + /// + /// + /// + /// Issued + /// + /// + /// RequestID + /// Request.RequesterName + /// CommonName + /// NotBefore + /// NotAfter + /// SerialNumber + /// CertificateTemplate + /// + /// + /// + /// + /// Pending + /// + /// + /// RequestID + /// Request.RequesterName + /// Request.SubmittedWhen + /// CommonName + /// CertificateTemplate + /// + /// + /// + /// + /// Failed + /// + /// + /// RequestID + /// Request.StatusCode + /// Request.DispositionMessage + /// Request.SubmittedWhen + /// Request.CommonName + /// CertificateTemplate + /// + /// + /// + /// + /// Request + /// + /// + /// RequestID + /// Request.StatusCode + /// Request.DispositionMessage + /// Request.RequesterName + /// Request.SubmittedWhen + /// Request.CommonName + /// CertificateTemplate + /// + /// + /// + /// + /// Extension + /// + /// + /// ExtensionRequestId + /// ExtensionName + /// ExtensionFlags + /// ExtensionRawValue + /// + /// + /// + /// + /// Attribute + /// + /// + /// AttributeRequestId + /// AttributeName + /// AttributeValue + /// + /// + /// + /// + /// CRL + /// + /// + /// CRLRowId + /// CRLNumber + /// CRLThisUpdate + /// CRLNextUpdate + /// CRLPublishStatusCode + /// CRLPublishError + /// + /// + /// + /// + /// + public AdcsDbViewTableName ViewTable { get; } + + void mapTables() { + var RColumn = CaView.GetColumnIndex(0, "Disposition"); + // map view table to DB table + String[] columns; + switch (ViewTable) { + case AdcsDbViewTableName.Revoked: + columns = _revokedColumns; + table = AdcsDbTableName.Request; + CaView.SetRestriction(RColumn, 1, 0, 21); + break; + case AdcsDbViewTableName.Issued: + columns = _issuedColumns; + table = AdcsDbTableName.Request; + CaView.SetRestriction(RColumn, 1, 0, 20); + break; + case AdcsDbViewTableName.Pending: + columns = _pendingColumns; + table = AdcsDbTableName.Request; + CaView.SetRestriction(RColumn, 1, 0, 9); + break; + case AdcsDbViewTableName.Failed: + columns = _failedColumns; + table = AdcsDbTableName.Request; + CaView.SetRestriction(-3, 0, 0, 0); + break; + case AdcsDbViewTableName.Request: + columns = _requestColumns; + table = AdcsDbTableName.Request; + CaView.SetTable((Int32)AdcsDbTableName.Request); + break; + case AdcsDbViewTableName.Extension: + columns = _extensionColumns; + table = AdcsDbTableName.Extension; + CaView.SetTable((Int32)AdcsDbTableName.Extension); + break; + case AdcsDbViewTableName.Attribute: + columns = _attributeColumns; + table = AdcsDbTableName.Attribute; + CaView.SetTable((Int32)AdcsDbTableName.Attribute); + break; + case AdcsDbViewTableName.CRL: + columns = _crlColumns; + table = AdcsDbTableName.CRL; + CaView.SetTable((Int32)AdcsDbTableName.CRL); + break; + default: + throw new ArgumentOutOfRangeException(); + } + // set columns for preconfigured view tables + foreach (String column in columns) { + AddColumnToView(column); + } + } + void setResultColumns() { + Int32 columnCount; + if (outAllColumns) { + columnCount = CaView.GetColumnCount(CertAdmConstants.CVRC_COLUMN_SCHEMA); + CaView.SetResultColumnCount(columnCount); + _columns.Clear(); + _columns.Add("*"); + foreach (var columnIndex in Enumerable.Range(0, columnCount)) { + CaView.SetResultColumn(columnIndex); + } + } else { + columnCount = _columnIDs.Count; + CaView.SetResultColumnCount(columnCount); + foreach (var columnID in _columnIDs) { + CaView.SetResultColumn(columnID); + } + } + } + void setFilters() { + foreach (AdcsDbQueryFilterEntry filter in _filters) { + CaView.SetRestriction( + filter.ColumnID, + (Int32)filter.LogicalOperator, + CertAdmConstants.CVR_SORT_NONE, + filter.QualifierValue); + } + } + + /// + /// + /// + /// + /// + public Boolean AddColumnToView(String columnName) { + if (isOpenView) { + throw new AccessViolationException(); + } + + // if '*' is specified then all columns are added implicitly, so return false. + if (outAllColumns) { return false; } + if (columnName == "*") { + outAllColumns = true; + return true; + } + Int32 index = CaView.GetColumnIndex(0, columnName); + if (_columnIDs.Contains(index)) { return false; } + _columnIDs.Add(index); + _columns.Add(columnName); + return true; + } + /// + /// Adds filter to requested view. + /// + /// Filter entry to add. + /// + /// + /// + /// True if + /// + public Boolean AddQueryFilter(AdcsDbQueryFilterEntry filter) { + if (filter == null) { throw new ArgumentNullException(nameof(filter)); } + if (isOpenView) { + throw new AccessViolationException(); + } + if (_filters.Contains(filter)) { return false; } + + Int32 index = CaView.GetColumnIndex(0, filter.ColumnName); + filter.ColumnID = index; + return _filters.Add(filter); + } + public IEnumerable GetView(Int32 skipRows, Int32 takeRows) { + isOpenView = true; + setResultColumns(); + setFilters(); + using (var reader = new AdcsDbInternalEnumerator(CaView.OpenView(), ConfigString, table)) { + foreach (AdcsDbRow row in reader.EnumRows(skipRows, takeRows)) { + yield return row; + } + } + } + + #region IDisposable + void ReleaseUnmanagedResources() { + CryptographyUtils.ReleaseCom(CaView); + } + /// + public void Dispose() { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + /// + ~AdcsDbReader() { + ReleaseUnmanagedResources(); + } + #endregion + } +} diff --git a/PKI/Management/CertificateServices/Database/AdcsDbRow.cs b/PKI/Management/CertificateServices/Database/AdcsDbRow.cs new file mode 100644 index 0000000..d1c821d --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbRow.cs @@ -0,0 +1,35 @@ +using System; +using PKI.CertificateServices.DB; + +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + /// + /// Represents Certification Authority database row. This object contains only 4 base properties: + /// , , and . + /// Other properties should be added by using external means (for example, by using Add-Member cmdlet in + /// Windows PowerShell). + /// + public class AdcsDbRow { + /// + /// Gets or sets RowId which corresponds to row number in CA database. + /// + public Int32 RowId { get; set; } + /// + /// Gets or sets RequestId which corresponds to request ID number in CA database. This property is set to zero + /// for non-request tables. + /// + public Int32 RequestId { get; set; } + /// + /// Gets or sets the configuration string of the CA server to which this object is related. + /// + public String ConfigString { get; set; } + /// + /// Gets or sets database table name. + /// + public AdcsDbTableName Table { get; set; } + /// + /// Gets a collection of properties associated with the current row object. + /// + public AdcsDbPropertyCollection Properties { get; } = new AdcsDbPropertyCollection(); + + } +} diff --git a/PKI/Management/CertificateServices/Database/AdcsDbSchemaEntry.cs b/PKI/Management/CertificateServices/Database/AdcsDbSchemaEntry.cs new file mode 100644 index 0000000..aa9ce15 --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbSchemaEntry.cs @@ -0,0 +1,38 @@ +using System; + +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + /// + /// Represents Certification Authority database's table. + /// + public class AdcsDbSchemaEntry { + + internal AdcsDbSchemaEntry(String name, String displayname, AdcsDbColumnDataType datatype, Int32 maxlength, Boolean isindexed) { + Name = name; + DisplayName = displayname; + DataType = datatype; + MaxLength = maxlength; + IsIndexed = isindexed; + } + + /// + /// Gets column's non-localized name. + /// + public String Name { get; } + /// + /// Gets column localized name. + /// + public String DisplayName { get; } + /// + /// Gets data type for the data stored in the column. + /// + public AdcsDbColumnDataType DataType { get; } + /// + /// Gets maximum data capacity for the column in bytes. + /// + public Int32 MaxLength { get; } + /// + /// Indiciates whether the column is indexed. + /// + public Boolean IsIndexed { get; } + } +} diff --git a/PKI/Management/CertificateServices/Database/AdcsDbSeekOperator.cs b/PKI/Management/CertificateServices/Database/AdcsDbSeekOperator.cs new file mode 100644 index 0000000..5ee218d --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbSeekOperator.cs @@ -0,0 +1,28 @@ +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + /// + /// Specifies the logical operator of the data-query qualifier for the column. This parameter is used with the + /// member to define the data-query qualifier. + /// + public enum AdcsDbSeekOperator { + /// + /// Equal to. + /// + EQ = 1, + /// + /// Less or equal to. + /// + LE = 2, + /// + /// Less than. + /// + LT = 4, + /// + /// Greater or equal to. + /// + GE = 8, + /// + /// Greater than. + /// + GT = 16 + } +} \ No newline at end of file diff --git a/PKI/Management/CertificateServices/Database/AdcsDbTableName.cs b/PKI/Management/CertificateServices/Database/AdcsDbTableName.cs new file mode 100644 index 0000000..674cbf1 --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbTableName.cs @@ -0,0 +1,23 @@ +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + /// + /// Contains Certification Authority database table enumeration. + /// + public enum AdcsDbTableName { + /// + /// The table of pending requests, denied requests, issued certificates, and revoked certificates. + /// + Request = 0x0, // 0 + /// + /// Contains certificate extensions associated with particular request. + /// + Extension = 0x3000, // 12288 + /// + /// Contains certificate attributes passed among wth particulat request. + /// + Attribute = 0x4000, // 16384 + /// + /// Contains Certificate Revocation List (CRL) issued by the CA during it's lifetime. + /// + CRL = 0x5000 // 20480 + } +} diff --git a/PKI/Management/CertificateServices/Database/AdcsDbViewTableName.cs b/PKI/Management/CertificateServices/Database/AdcsDbViewTableName.cs new file mode 100644 index 0000000..58a74d3 --- /dev/null +++ b/PKI/Management/CertificateServices/Database/AdcsDbViewTableName.cs @@ -0,0 +1,52 @@ +namespace SysadminsLV.PKI.Management.CertificateServices.Database { + /// + /// Contains enumeration of predefined ADCS database view tables. + /// + public enum AdcsDbViewTableName { + /// + /// Sets view table to display entire request table. Request table includes the following view tables: + /// + /// Revoked + /// Issued + /// Pending + /// Failed + /// + /// + Request, + /// + /// Sets view table to display only revoked certificates. This value reflects 'Revoked Certificates' folder + /// in Certification Authority MMC snap-in. + /// + Revoked, + /// + /// Sets view table to display only issued and non-revoked certificates. This value reflects + /// 'Issued Certificates' folder in Certification Authority MMC snap-in. + /// + Issued, + /// + /// Sets view table to display only pending requests. This value reflects 'Pending Requests' folder + /// in Certification Authority MMC snap-in. + /// + Pending, + /// + /// Sets view table to display only failed or explicitly denied by CA manager request. This value reflects + /// 'Failed Requests' folder in Certification Authority MMC snap-in. + /// + Failed, + /// + /// Sets view table to display extension table. This table contains extensions associated with respective + /// row in Request table. + /// + Extension, + /// + /// Sets view table to display attribute table. This table contains request attributes associated with + /// respective row in Request table. + /// + Attribute, + /// + /// Sets view table to display certificate revocation list (CRL) table. This table holds a history of all + /// ever issued CRLs by particular CA server. + /// + CRL + } +} \ No newline at end of file diff --git a/PKI/PKI.csproj b/PKI/PKI.csproj index a6b68b3..625a200 100644 --- a/PKI/PKI.csproj +++ b/PKI/PKI.csproj @@ -76,9 +76,17 @@ + + + + + + + + + - diff --git a/PKI/Structs/CertAdmConstants.cs b/PKI/Structs/CertAdmConstants.cs index 725604b..1b1c1d9 100644 --- a/PKI/Structs/CertAdmConstants.cs +++ b/PKI/Structs/CertAdmConstants.cs @@ -74,5 +74,27 @@ class CertAdmConstants { public const Int32 KRADispNotloaded = 0x00000004; // The certificate is not loaded public const Int32 KRADispInvalid = 0x00000005; // The certificate is invalid #endregion + + #region ICertView + public const Int32 CVRC_COLUMN_SCHEMA = 0; + public const Int32 CVRC_COLUMN_RESULT = 1; + public const Int32 CVRC_COLUMN_VALUE = 2; + public const Int32 CVRC_COLUMN_MASK = 0xfff; + + public const Int32 CVR_SORT_NONE = 0; + public const Int32 CVR_SORT_ASCEND = 1; + public const Int32 CVR_SORT_DESCEND = 2; + + public const Int32 CV_OUT_BASE64HEADER = 0x0; + public const Int32 CV_OUT_BASE64 = 0x1; + public const Int32 CV_OUT_BINARY = 0x2; + public const Int32 CV_OUT_BASE64REQUESTHEADER = 0x3; + public const Int32 CV_OUT_HEX = 0x4; + public const Int32 CV_OUT_HEXASCII = 0x5; + public const Int32 CV_OUT_BASE64X509CRLHEADER = 0x9; + public const Int32 CV_OUT_HEXADDR = 0xA; + public const Int32 CV_OUT_HEXASCIIADDR = 0xB; + public const Int32 CV_OUT_HEXRAW = 0xC; + #endregion } }