Ugochukwu Mmaduekwe hace 2 semanas
padre
commit
e23ac3a8e1

+ 3 - 0
CryptoLib.Tests/Delphi.Tests/CryptoLib.Tests.dpr

@@ -483,6 +483,8 @@ uses
   ClpX509SignatureUtilities in '..\..\CryptoLib\src\Asn1\X509\ClpX509SignatureUtilities.pas',
   ClpX509Utilities in '..\..\CryptoLib\src\Asn1\X509\ClpX509Utilities.pas',
   ClpAsn1Dumper in '..\..\CryptoLib\src\Asn1\ClpAsn1Dumper.pas',
+  ClpX509CertificateParser in '..\..\CryptoLib\src\Asn1\X509\ClpX509CertificateParser.pas',
+  ClpIX509CertificateParser in '..\..\CryptoLib\src\Interfaces\ClpIX509CertificateParser.pas',
   ClpFixedSecureRandom in '..\src\Utils\ClpFixedSecureRandom.pas',
   ClpIFixedSecureRandom in '..\src\Utils\ClpIFixedSecureRandom.pas',
   ClpIShortenedDigest in '..\src\Utils\ClpIShortenedDigest.pas',
@@ -577,6 +579,7 @@ uses
   PrivateKeyInfoTests in '..\src\Asn1\PKCS\PrivateKeyInfoTests.pas',
   IPAddressUtilitiesTests in '..\src\Utils\Net\IPAddressUtilitiesTests.pas',
   PemReaderTests in '..\src\Utils\Pem\PemReaderTests.pas',
+  CertificateTest in '..\src\Asn1\X509\CertificateTest.pas',
   CryptoLibTestBase in '..\src\CryptoLibTestBase.pas';
 
 begin

+ 441 - 0
CryptoLib.Tests/src/Asn1/X509/CertificateTest.pas

@@ -0,0 +1,441 @@
+{ *********************************************************************************** }
+{ *                              CryptoLib Library                                  * }
+{ *                Copyright (c) 2018 - 20XX Ugochukwu Mmaduekwe                    * }
+{ *                 Github Repository <https://github.com/Xor-el>                   * }
+
+{ *  Distributed under the MIT software license, see the accompanying file LICENSE  * }
+{ *          or visit http://www.opensource.org/licenses/mit-license.php.           * }
+
+{ *                              Acknowledgements:                                  * }
+{ *                                                                                 * }
+{ *      Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring     * }
+{ *                           development of this library                           * }
+
+{ * ******************************************************************************* * }
+
+(* &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& *)
+
+unit CertificateTest;
+
+interface
+
+{$IFDEF FPC}
+{$MODE DELPHI}
+{$ENDIF FPC}
+
+uses
+  SysUtils,
+{$IFDEF FPC}
+  fpcunit,
+  testregistry,
+{$ELSE}
+  TestFramework,
+{$ENDIF FPC}
+  ClpX509Asn1Objects,
+  ClpIX509Asn1Objects,
+  ClpAsn1Objects,
+  ClpIAsn1Objects,
+  ClpIX509Extension,
+  ClpEncoders,
+  ClpCryptoLibTypes,
+  CryptoLibTestBase;
+
+type
+
+  TCertificateTest = class(TCryptoLibAlgorithmTestCase)
+  strict private
+    var
+      FCert1, FCert2, FCert3, FCert4, FCert5, FCert6, FCert7, FDudCert, FBangerCert: TCryptoLibByteArray;
+      FSubjects: TCryptoLibStringArray;
+
+    procedure SetUpTestData;
+    procedure CheckCertificate(AId: Int32; const ACert: TCryptoLibByteArray);
+    procedure CheckDudCertificate;
+    procedure CheckMalformed;
+
+  protected
+    procedure SetUp; override;
+
+  published
+    procedure TestCertificate1;
+    procedure TestCertificate2;
+    procedure TestCertificate3;
+    procedure TestCertificate4;
+    procedure TestCertificate5;
+    procedure TestCertificate6;
+    procedure TestCertificate7;
+    procedure TestDudCertificate;
+    procedure TestMalformedCertificate;
+
+  end;
+
+implementation
+
+{ TCertificateTest }
+
+procedure TCertificateTest.SetUpTestData;
+begin
+  // server.crt
+  FCert1 := DecodeBase64(
+    'MIIDXjCCAsegAwIBAgIBBzANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx' +
+    'ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY' +
+    'BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB' +
+    'dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ' +
+    'd2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU2MjFaFw0wMTA2' +
+    'MDIwNzU2MjFaMIG4MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW' +
+    'BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM' +
+    'dGQxFzAVBgNVBAsTDldlYnNlcnZlciBUZWFtMR0wGwYDVQQDExR3d3cyLmNvbm5l' +
+    'Y3Q0LmNvbS5hdTEoMCYGCSqGSIb3DQEJARYZd2VibWFzdGVyQGNvbm5lY3Q0LmNv' +
+    'bS5hdTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArvDxclKAhyv7Q/Wmr2re' +
+    'Gw4XL9Cnh9e+6VgWy2AWNy/MVeXdlxzd7QAuc1eOWQkGQEiLPy5XQtTY+sBUJ3AO' +
+    'Rvd2fEVJIcjf29ey7bYua9J/vz5MG2KYo9/WCHIwqD9mmG9g0xLcfwq/s8ZJBswE' +
+    '7sb85VU+h94PTvsWOsWuKaECAwEAAaN3MHUwJAYDVR0RBB0wG4EZd2VibWFzdGVy' +
+    'QGNvbm5lY3Q0LmNvbS5hdTA6BglghkgBhvhCAQ0ELRYrbW9kX3NzbCBnZW5lcmF0' +
+    'ZWQgY3VzdG9tIHNlcnZlciBjZXJ0aWZpY2F0ZTARBglghkgBhvhCAQEEBAMCBkAw' +
+    'DQYJKoZIhvcNAQEEBQADgYEAotccfKpwSsIxM1Hae8DR7M/Rw8dg/RqOWx45HNVL' +
+    'iBS4/3N/TO195yeQKbfmzbAA2jbPVvIvGgTxPgO1MP4ZgvgRhasaa0qCJCkWvpM4' +
+    'yQf33vOiYQbpv4rTwzU8AmRlBG45WdjyNIigGV+oRc61aKCTnLq7zB8N3z1TF/bF' +
+    '5/8=');
+
+  // ca.crt
+  FCert2 := DecodeBase64(
+    'MIIDbDCCAtWgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBtzELMAkGA1UEBhMCQVUx' +
+    'ETAPBgNVBAgTCFZpY3RvcmlhMRgwFgYDVQQHEw9Tb3V0aCBNZWxib3VybmUxGjAY' +
+    'BgNVBAoTEUNvbm5lY3QgNCBQdHkgTHRkMR4wHAYDVQQLExVDZXJ0aWZpY2F0ZSBB' +
+    'dXRob3JpdHkxFTATBgNVBAMTDENvbm5lY3QgNCBDQTEoMCYGCSqGSIb3DQEJARYZ' +
+    'd2VibWFzdGVyQGNvbm5lY3Q0LmNvbS5hdTAeFw0wMDA2MDIwNzU1MzNaFw0wMTA2' +
+    'MDIwNzU1MzNaMIG3MQswCQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExGDAW' +
+    'BgNVBAcTD1NvdXRoIE1lbGJvdXJuZTEaMBgGA1UEChMRQ29ubmVjdCA0IFB0eSBM' +
+    'dGQxHjAcBgNVBAsTFUNlcnRpZmljYXRlIEF1dGhvcml0eTEVMBMGA1UEAxMMQ29u' +
+    'bmVjdCA0IENBMSgwJgYJKoZIhvcNAQkBFhl3ZWJtYXN0ZXJAY29ubmVjdDQuY29t' +
+    'LmF1MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgs5ptNG6Qv1ZpCDuUNGmv' +
+    'rhjqMDPd3ri8JzZNRiiFlBA4e6/ReaO1U8ASewDeQMH6i9R6degFdQRLngbuJP0s' +
+    'xcEE+SksEWNvygfzLwV9J/q+TQDyJYK52utb++lS0b48A1KPLwEsyL6kOAgelbur' +
+    'ukwxowprKUIV7Knf1ajetQIDAQABo4GFMIGCMCQGA1UdEQQdMBuBGXdlYm1hc3Rl' +
+    'ckBjb25uZWN0NC5jb20uYXUwDwYDVR0TBAgwBgEB/wIBADA2BglghkgBhvhCAQ0E' +
+    'KRYnbW9kX3NzbCBnZW5lcmF0ZWQgY3VzdG9tIENBIGNlcnRpZmljYXRlMBEGCWCG' +
+    'SAGG+EIBAQQEAwICBDANBgkqhkiG9w0BAQQFAAOBgQCsGvfdghH8pPhlwm1r3pQk' +
+    'msnLAVIBb01EhbXm2861iXZfWqGQjrGAaA0ZpXNk9oo110yxoqEoSJSzniZa7Xtz' +
+    'soTwNUpE0SLHvWf/SlKdFWlzXA+vOZbzEv4UmjeelekTm7lc01EEa5QRVzOxHFtQ' +
+    'DhkaJ8VqOMajkQFma2r9iA==');
+
+  // testx509.pem
+  FCert3 := DecodeBase64(
+    'MIIBWzCCAQYCARgwDQYJKoZIhvcNAQEEBQAwODELMAkGA1UEBhMCQVUxDDAKBgNV' +
+    'BAgTA1FMRDEbMBkGA1UEAxMSU1NMZWF5L3JzYSB0ZXN0IENBMB4XDTk1MDYxOTIz' +
+    'MzMxMloXDTk1MDcxNzIzMzMxMlowOjELMAkGA1UEBhMCQVUxDDAKBgNVBAgTA1FM' +
+    'RDEdMBsGA1UEAxMUU1NMZWF5L3JzYSB0ZXN0IGNlcnQwXDANBgkqhkiG9w0BAQEF' +
+    'AANLADBIAkEAqtt6qS5GTxVxGZYWa0/4u+IwHf7p2LNZbcPBp9/OfIcYAXBQn8hO' +
+    '/Re1uwLKXdCjIoaGs4DLdG88rkzfyK5dPQIDAQABMAwGCCqGSIb3DQIFBQADQQAE' +
+    'Wc7EcF8po2/ZO6kNCwK/ICH6DobgLekA5lSLr5EvuioZniZp5lFzAw4+YzPQ7XKJ' +
+    'zl9HYIMxATFyqSiD9jsx');
+
+  // v3-cert1.pem
+  FCert4 := DecodeBase64(
+    'MIICjTCCAfigAwIBAgIEMaYgRzALBgkqhkiG9w0BAQQwRTELMAkGA1UEBhMCVVMx' +
+    'NjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFuZCBTcGFjZSBBZG1pbmlz' +
+    'dHJhdGlvbjAmFxE5NjA1MjgxMzQ5MDUrMDgwMBcROTgwNTI4MTM0OTA1KzA4MDAw' +
+    'ZzELMAkGA1UEBhMCVVMxNjA0BgNVBAoTLU5hdGlvbmFsIEFlcm9uYXV0aWNzIGFu' +
+    'ZCBTcGFjZSBBZG1pbmlzdHJhdGlvbjEgMAkGA1UEBRMCMTYwEwYDVQQDEwxTdGV2' +
+    'ZSBTY2hvY2gwWDALBgkqhkiG9w0BAQEDSQAwRgJBALrAwyYdgxmzNP/ts0Uyf6Bp' +
+    'miJYktU/w4NG67ULaN4B5CnEz7k57s9o3YY3LecETgQ5iQHmkwlYDTL2fTgVfw0C' +
+    'AQOjgaswgagwZAYDVR0ZAQH/BFowWDBWMFQxCzAJBgNVBAYTAlVTMTYwNAYDVQQK' +
+    'Ey1OYXRpb25hbCBBZXJvbmF1dGljcyBhbmQgU3BhY2UgQWRtaW5pc3RyYXRpb24x' +
+    'DTALBgNVBAMTBENSTDEwFwYDVR0BAQH/BA0wC4AJODMyOTcwODEwMBgGA1UdAgQR' +
+    'MA8ECTgzMjk3MDgyM4ACBSAwDQYDVR0KBAYwBAMCBkAwCwYJKoZIhvcNAQEEA4GB' +
+    'AH2y1VCEw/A4zaXzSYZJTTUi3uawbbFiS2yxHvgf28+8Js0OHXk1H1w2d6qOHH21' +
+    'X82tZXd/0JtG0g1T9usFFBDvYK8O0ebgz/P5ELJnBL2+atObEuJy1ZZ0pBDWINR3' +
+    'WkDNLCGiTkCKp0F5EWIrVDwh54NNevkCQRZita+z4IBO');
+
+  // v3-cert2.pem
+  FCert5 := DecodeBase64(
+    'MIICiTCCAfKgAwIBAgIEMeZfHzANBgkqhkiG9w0BAQQFADB9MQswCQYDVQQGEwJD' +
+    'YTEPMA0GA1UEBxMGTmVwZWFuMR4wHAYDVQQLExVObyBMaWFiaWxpdHkgQWNjZXB0' +
+    'ZWQxHzAdBgNVBAoTFkZvciBEZW1vIFB1cnBvc2VzIE9ubHkxHDAaBgNVBAMTE0Vu' +
+    'dHJ1c3QgRGVtbyBXZWIgQ0EwHhcNOTYwNzEyMTQyMDE1WhcNOTYxMDEyMTQyMDE1' +
+    'WjB0MSQwIgYJKoZIhvcNAQkBExVjb29rZUBpc3NsLmF0bC5ocC5jb20xCzAJBgNV' +
+    'BAYTAlVTMScwJQYDVQQLEx5IZXdsZXR0IFBhY2thcmQgQ29tcGFueSAoSVNTTCkx' +
+    'FjAUBgNVBAMTDVBhdWwgQS4gQ29va2UwXDANBgkqhkiG9w0BAQEFAANLADBIAkEA' +
+    '6ceSq9a9AU6g+zBwaL/yVmW1/9EE8s5you1mgjHnj0wAILuoB3L6rm6jmFRy7QZT' +
+    'G43IhVZdDua4e+5/n1ZslwIDAQABo2MwYTARBglghkgBhvhCAQEEBAMCB4AwTAYJ' +
+    'YIZIAYb4QgENBD8WPVRoaXMgY2VydGlmaWNhdGUgaXMgb25seSBpbnRlbmRlZCBm' +
+    'b3IgZGVtb25zdHJhdGlvbiBwdXJwb3Nlcy4wDQYJKoZIhvcNAQEEBQADgYEAi8qc' +
+    'F3zfFqy1sV8NhjwLVwOKuSfhR/Z8mbIEUeSTlnH3QbYt3HWZQ+vXI8mvtZoBc2Fz' +
+    'lexKeIkAZXCesqGbs6z6nCt16P6tmdfbZF3I3AWzLquPcOXjPf4HgstkyvVBn0Ap' +
+    'jAFN418KF/Cx4qyHB4cjdvLrRjjQLnb2+ibo7QU=');
+
+  FCert6 := DecodeBase64(
+    'MIIEDjCCAvagAwIBAgIEFAAq2jANBgkqhkiG9w0BAQUFADBLMSowKAYDVQQDEyFT' +
+    'dW4gTWljcm9zeXN0ZW1zIEluYyBDQSAoQ2xhc3MgQikxHTAbBgNVBAoTFFN1biBN' +
+    'aWNyb3N5c3RlbXMgSW5jMB4XDTA0MDIyOTAwNDMzNFoXDTA5MDMwMTAwNDMzNFow' +
+    'NzEdMBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJbmMxFjAUBgNVBAMTDXN0b3Jl' +
+    'LnN1bi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAP9ErzFT7MPg2bVV' +
+    'LNmHTgN4kmiRNlPpuLGWS7EDIXYBbLeSSOCp/e1ANcOGnsuf0WIq9ejd/CPyEfh4' +
+    'sWoVvQzpOfHZ/Jyei29PEuxzWT+4kQmCx3+sLK25lAnDFsz1KiFmB6Y3GJ/JSjpp' +
+    'L0Yy1R9YlIc82I8gSw44y5JDABW5AgMBAAGjggGQMIIBjDAOBgNVHQ8BAf8EBAMC' +
+    'BaAwHQYDVR0OBBYEFG1WB3PApZM7OPPVWJ31UrERaoKWMEcGA1UdIARAMD4wPAYL' +
+    'YIZIAYb3AIN9k18wLTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5zdW4uY29tL3Br' +
+    'aS9jcHMuaHRtbDCBhQYDVR0fBH4wfDB6oCegJYYjaHR0cDovL3d3dy5zdW4uY29t' +
+    'L3BraS9wa2lzbWljYS5jcmyiT6RNMEsxKjAoBgNVBAMTIVN1biBNaWNyb3N5c3Rl' +
+    'bXMgSW5jIENBIChDbGFzcyBCKTEdMBsGA1UEChMUU3VuIE1pY3Jvc3lzdGVtcyBJ' +
+    'bmMwHwYDVR0jBBgwFoAUT7ZnqR/EEBSgG6h1wdYMI5RiiWswVAYIKwYBBQUHAQEE' +
+    'SDBGMB0GCCsGAQUFBzABhhFodHRwOi8vdmEuc3VuLmNvbTAlBggrBgEFBQcwAYYZ' +
+    'aHR0cDovL3ZhLmNlbnRyYWwuc3VuLmNvbTATBgNVHSUEDDAKBggrBgEFBQcDATAN' +
+    'BgkqhkiG9w0BAQUFAAOCAQEAq3byQgyU24tBpR07iQK7agm1zQyzDQ6itdbji0ln' +
+    'T7fOd5Pnp99iig8ovwWliNtXKAmgtJY60jWz7nEuk38AioZJhS+RPWIWX/+2PRV7' +
+    's2aWTzM3n43BypD+jU2qF9c9kDWP/NW9K9IcrS7SfU/2MZVmiCMD/9FEL+CWndwE' +
+    'JJQ/oenXm44BFISI/NjV7fMckN8EayPvgtzQkD5KnEiggOD6HOrwTDFR+tmAEJ0K' +
+    'ZttQNwOzCOcEdxXTg6qBHUbONdL7bjTT5NzV+JR/bnfiCqHzdnGwfbHzhmrnXw8j' +
+    'QCVXcfBfL9++nmpNNRlnJMRdYGeCY6OAfh/PRo8/fXak1Q==');
+
+  FCert7 := DecodeBase64(
+    'MIIFJDCCBAygAwIBAgIKEcJZuwAAAAAABzANBgkqhkiG9w0BAQUFADAPMQ0wCwYD' +
+    'VQQDEwRNU0NBMB4XDTA0MDUyMjE2MTM1OFoXDTA1MDUyMjE2MjM1OFowaTEbMBkG' +
+    'CSqGSIb3DQEJCBMMMTkyLjE2OC4xLjMzMScwJQYJKoZIhvcNAQkCExhwaXhmaXJl' +
+    'd2FsbC5jaXNjb3BpeC5jb20xITAfBgNVBAMTGHBpeGZpcmV3YWxsLmNpc2NvcGl4' +
+    'LmNvbTB8MA0GCSqGSIb3DQEBAQUAA2sAMGgCYQCbcsY7vrjweXZiFQdhUafEjJV+' +
+    'HRy5UKmuCy0237ffmYrN+XNLw0h90cdCSK6KPZebd2E2Bc2UmTikc/FY8meBT3/E' +
+    'O/Osmywzi++Ur8/IrDvtuR1zd0c/xEPnV1ZRezkCAwEAAaOCAs4wggLKMAsGA1Ud' +
+    'DwQEAwIFoDAdBgNVHQ4EFgQUzJBSxkQiN9TKvhTMQ1/Aq4gZnHswHwYDVR0jBBgw' +
+    'FoAUMsxzXVh+5UKMNpwNHmqSfcRYfJ4wgfcGA1UdHwSB7zCB7DCB6aCB5qCB44aB' +
+    'r2xkYXA6Ly8vQ049TVNDQSxDTj1NQVVELENOPUNEUCxDTj1QdWJsaWMlMjBLZXkl' +
+    'MjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPWludCxE' +
+    'Qz1wcmltZWtleSxEQz1zZT9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0P2Jhc2U/' +
+    'b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnSGL2h0dHA6Ly9tYXVkLmlu' +
+    'dC5wcmltZWtleS5zZS9DZXJ0RW5yb2xsL01TQ0EuY3JsMIIBEAYIKwYBBQUHAQEE' +
+    'ggECMIH/MIGqBggrBgEFBQcwAoaBnWxkYXA6Ly8vQ049TVNDQSxDTj1BSUEsQ049' +
+    'UHVibGljJTIwS2V5JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJh' +
+    'dGlvbixEQz1pbnQsREM9cHJpbWVrZXksREM9c2U/Y0FDZXJ0aWZpY2F0ZT9iYXNl' +
+    'P29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkwUAYIKwYBBQUHMAKG' +
+    'RGh0dHA6Ly9tYXVkLmludC5wcmltZWtleS5zZS9DZXJ0RW5yb2xsL01BVUQuaW50' +
+    'LnByaW1la2V5LnNlX01TQ0EuY3J0MCwGA1UdEQEB/wQiMCCCGHBpeGZpcmV3YWxs' +
+    'LmNpc2NvcGl4LmNvbYcEwKgBITA/BgkrBgEEAYI3FAIEMh4wAEkAUABTAEUAQwBJ' +
+    'AG4AdABlAHIAbQBlAGQAaQBhAHQAZQBPAGYAZgBsAGkAbgBlMA0GCSqGSIb3DQEB' +
+    'BQUAA4IBAQCa0asiPbObLJjpSz6ndJ7y4KOWMiuuBc/VQBnLr7RBCF3ZlZ6z1+e6' +
+    'dmv8se/z11NgateKfxw69IhLCriA960HEgX9Z61MiVG+DrCFpbQyp8+hPFHoqCZN' +
+    'b7upc8k2OtJW6KPaP9k0DW52YQDIky4Vb2rZeC4AMCorWN+KlndHhr1HFA14HxwA' +
+    '4Mka0FM6HNWnBV2UmTjBZMDr/OrGH1jLYIceAaZK0X2R+/DWXeeqIga8jwP5empq' +
+    'JetYnkXdtTbEh3xL0BX+mZl8vDI+/PGcwox/7YjFmyFWphRMxk9CZ3rF2/FQWMJP' +
+    'YqQpKiQOmQg5NAhcwffLAuVjVVibPYqi');
+
+  // bad issuer certificate
+  FDudCert := DecodeBase64(
+    'MIICLzCCAZgCBFp/9TowDQYJKoZIhvcNAQEFBQAwAjEAMB4XDTA4MDcyNTEzNTQ0' +
+    'MFoXDTEzMDgyNTA1MDAwMFowgboxCzAJBgNVBAYTAlVTMQ0wCwYDVQQIEwRJb3dh' +
+    'MRMwEQYDVQQHEwpEZXMgTW9pbmVzMT0wOwYDVQQKEzRTdGF0ZSBvZiBJb3dhLCBE' +
+    'ZXBhcnRtZW50IG9mIEFkbWluaXN0cmF0aXZlIFNlcnZpY2VzMSowKAYDVQQLEyFJ' +
+    'bmZvcm1hdGlvbiBUZWNobm9sb2d5IEVudGVycHJpc2UxHDAaBgNVBAMTE3d3dy5k' +
+    'b20uc3RhdGUuaWEudXMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAK0C7Jca' +
+    'C0RiD0hcBcPUdGc78y815yPuHGmF/A2K+3LbwfFXDhsY7ebRxHVfL7gt+nFBvJ2r' +
+    'MqDBIMHFB3vYdSnGbND41eso6cLnzkMVtSisG25Tat3F8BF/js54sa0mFEn4qMQ+' +
+    '6T6jxyPflsjKpmi6L7lfRdPNbBbKSmK9ik2lAgMBAAEwDQYJKoZIhvcNAQEFBQAD' +
+    'gYEAc9Rx95MiPzJiCn3nOoP+3PPQCGTyUcUWZfYKXuC7aOzMYUXes71Q3K1/W6Vy' +
+    'V2Tlrbj0KT8j2/kBmy8+7d5whnUklJNsH6VJMst3V4Uxvk3os+uaW0FHsW389sNY' +
+    '/5LdslDjfqV2nUc2GqDPn38PATL26SRJKlCvU2NagdID3WM=');
+
+  // malformed cert
+  FBangerCert := DecodeBase64(
+    'MIIBSKADAgECAgECMA0GCSqGSIb3DQEEBAUAMCUxCzAJBgNVBAMMAkFVMRYwFAYD' +
+    'VQQKDA1CaXVuYHkgQGFzdGtlMB4XDTcwMDExMTQyNjAwMVoXDTcwMDEwNzAwMDAw' +
+    'MlowNjELMQkGA1UBAwwCQVUxFjAUBgNVAQwMDUJsdW5jeSZDY3Nzb2UxDzANBgNV' +
+    'AQsMBlRlc3cgNTAYMBAGBisOBwMDATAGAgEBAgECAwQAAgEDoYGVMIGSMGEGA1Yd' +
+    'IwEB/wRXNVWAFDZPdpTPzKi7o8EJokoQU2uqCHRRoTqkOzA2NAs2CgYDVQYDDAJ' +
+    'HVTEWMBQGA1QECQwNQmhwbmR5J0Ngc3RsYDAPMA0CA1UECwwGUWVzdyA0hQECMCA' +
+    'GA1UdDgEB/wQWBBQ2T3OSzciou6PBCqRJEFNrqgh2UTALBgNVHQkEBAMGBBE=');
+
+  System.SetLength(FSubjects, 7);
+  FSubjects[0] := 'C=AU,ST=Victoria,L=South Melbourne,O=Connect 4 Pty Ltd,OU=Webserver Team,CN=www2.connect4.com.au,[email protected]';
+  FSubjects[1] := 'C=AU,ST=Victoria,L=South Melbourne,O=Connect 4 Pty Ltd,OU=Certificate Authority,CN=Connect 4 CA,[email protected]';
+  FSubjects[2] := 'C=AU,ST=QLD,CN=SSLeay/rsa test cert';
+  FSubjects[3] := 'C=US,O=National Aeronautics and Space Administration,SERIALNUMBER=16+CN=Steve Schoch';
+  FSubjects[4] := '[email protected],C=US,OU=Hewlett Packard Company (ISSL),CN=Paul A. Cooke';
+  FSubjects[5] := 'O=Sun Microsystems Inc,CN=store.sun.com';
+  FSubjects[6] := 'unstructuredAddress=192.168.1.33,unstructuredName=pixfirewall.ciscopix.com,CN=pixfirewall.ciscopix.com';
+end;
+
+procedure TCertificateTest.SetUp;
+begin
+  inherited SetUp;
+  SetUpTestData;
+end;
+
+procedure TCertificateTest.CheckCertificate(AId: Int32; const ACert: TCryptoLibByteArray);
+var
+  LObj: IX509CertificateStructure;
+  LTbsCert: ITbsCertificateStructure;
+  LExt: IX509Extensions;
+  LOid: IDerObjectIdentifier;
+  LExtVal: IX509Extension;
+  LExtObj: IAsn1Object;
+  LExtBytes: TCryptoLibByteArray;
+  LExtendedKeyUsage: IExtendedKeyUsage;
+  LSeq: IAsn1Sequence;
+  I: Int32;
+  LGeneralNames: IGeneralNames;
+  LCrlDistPoint: ICrlDistPoint;
+  LPoints: TCryptoLibGenericArray<IDistributionPoint>;
+  LPolicyInfo: IPolicyInformation;
+  LPolicySeq: IAsn1Sequence;
+begin
+  LObj := TX509CertificateStructure.GetInstance(ACert);
+  LTbsCert := LObj.TbsCertificate;
+
+  if LTbsCert.Subject.ToString() <> FSubjects[AId - 1] then
+  begin
+    Fail(Format('failed subject test for certificate id %d got %s', [AId, LTbsCert.Subject.ToString()]));
+  end;
+
+  if LTbsCert.Version >= 3 then
+  begin
+    LExt := LTbsCert.Extensions;
+    if LExt <> nil then
+    begin
+      for LOid in LExt.ExtensionOids do
+      begin
+        LExtVal := LExt.GetExtension(LOid);
+        LExtBytes := LExtVal.Value.GetOctets();
+        LExtObj := TAsn1Object.FromByteArray(LExtBytes);
+
+        if LOid.Equals(TX509Extensions.SubjectKeyIdentifier) then
+        begin
+          TSubjectKeyIdentifier.GetInstance(LExtObj as TObject);
+        end
+        else if LOid.Equals(TX509Extensions.KeyUsage) then
+        begin
+          //TKeyUsage.GetInstance(LExtObj as TObject);
+        end
+        else if LOid.Equals(TX509Extensions.ExtendedKeyUsage) then
+        begin
+          LExtendedKeyUsage := TExtendedKeyUsage.GetInstance(LExtObj as TObject);
+          LSeq := LExtendedKeyUsage.ToAsn1Object() as IAsn1Sequence;
+          for I := 0 to LSeq.Count - 1 do
+          begin
+            TDerObjectIdentifier.GetInstance(LSeq[I] as TObject);
+          end;
+        end
+        else if LOid.Equals(TX509Extensions.SubjectAlternativeName) then
+        begin
+          LGeneralNames := TGeneralNames.GetInstance(LExtObj as TObject);
+          LSeq := LGeneralNames.ToAsn1Object() as IAsn1Sequence;
+          for I := 0 to LSeq.Count - 1 do
+          begin
+            TGeneralName.GetInstance(LSeq[I] as TObject);
+          end;
+        end
+        else if LOid.Equals(TX509Extensions.IssuerAlternativeName) then
+        begin
+          LGeneralNames := TGeneralNames.GetInstance(LExtObj as TObject);
+          LSeq := LGeneralNames.ToAsn1Object() as IAsn1Sequence;
+          for I := 0 to LSeq.Count - 1 do
+          begin
+            TGeneralName.GetInstance(LSeq[I] as TObject);
+          end;
+        end
+        else if LOid.Equals(TX509Extensions.CrlDistributionPoints) then
+        begin
+          LCrlDistPoint := TCrlDistPoint.GetInstance(LExtObj as TObject);
+          LPoints := LCrlDistPoint.GetDistributionPoints();
+          // do nothing - just verify it parses
+        end
+        else if LOid.Equals(TX509Extensions.CertificatePolicies) then
+        begin
+          LPolicySeq := LExtObj as IAsn1Sequence;
+          for I := 0 to LPolicySeq.Count - 1 do
+          begin
+            LPolicyInfo := TPolicyInformation.GetInstance(LPolicySeq[I] as TObject);
+          end;
+        end
+        else if LOid.Equals(TX509Extensions.AuthorityKeyIdentifier) then
+        begin
+          TAuthorityKeyIdentifier.GetInstance(LExtObj as TObject);
+        end
+        else if LOid.Equals(TX509Extensions.BasicConstraints) then
+        begin
+          TBasicConstraints.GetInstance(LExtObj as TObject);
+        end;
+      end;
+    end;
+  end;
+end;
+
+procedure TCertificateTest.CheckDudCertificate;
+var
+  LCert: IX509CertificateStructure;
+begin
+  LCert := TX509CertificateStructure.GetInstance(FDudCert);
+
+  if LCert.Issuer.ToString() <> '' then
+  begin
+    Fail('empty issuer not recognised correctly');
+  end;
+end;
+
+procedure TCertificateTest.CheckMalformed;
+begin
+  try
+    TTbsCertificateStructure.GetInstance(FBangerCert);
+    Fail('Expected exception for malformed certificate');
+  except
+    on E: EArgumentCryptoLibException do
+    begin
+      // expected - anything else is not!
+    end;
+  end;
+end;
+
+procedure TCertificateTest.TestCertificate1;
+begin
+  CheckCertificate(1, FCert1);
+end;
+
+procedure TCertificateTest.TestCertificate2;
+begin
+  CheckCertificate(2, FCert2);
+end;
+
+procedure TCertificateTest.TestCertificate3;
+begin
+  CheckCertificate(3, FCert3);
+end;
+
+procedure TCertificateTest.TestCertificate4;
+begin
+  CheckCertificate(4, FCert4);
+end;
+
+procedure TCertificateTest.TestCertificate5;
+begin
+  CheckCertificate(5, FCert5);
+end;
+
+procedure TCertificateTest.TestCertificate6;
+begin
+  CheckCertificate(6, FCert6);
+end;
+
+procedure TCertificateTest.TestCertificate7;
+begin
+  CheckCertificate(7, FCert7);
+end;
+
+procedure TCertificateTest.TestDudCertificate;
+begin
+  CheckDudCertificate();
+end;
+
+procedure TCertificateTest.TestMalformedCertificate;
+begin
+  CheckMalformed();
+end;
+
+initialization
+
+// Register any test cases with the test runner
+
+{$IFDEF FPC}
+  RegisterTest(TCertificateTest);
+{$ELSE}
+  RegisterTest(TCertificateTest.Suite);
+{$ENDIF FPC}
+
+end.

+ 1 - 4
CryptoLib/src/Asn1/ClpAsn1Objects.pas

@@ -6413,11 +6413,8 @@ end;
 constructor TAsn1Set.Create(AIsSorted: Boolean; const AElements: TCryptoLibGenericArray<IAsn1Encodable>);
 begin
   inherited Create;
-  if AElements = nil then
-    raise EArgumentNilCryptoLibException.Create('elements');
-
   System.Assert(not AIsSorted);
-  FElements := System.Copy(AElements);
+  FElements := AElements;
   FSortedDerEncodings := nil;
 end;
 

+ 368 - 1
CryptoLib/src/Asn1/Pkcs/ClpPkcsAsn1Objects.pas

@@ -33,7 +33,8 @@ uses
   ClpPkcsObjectIdentifiers,
   ClpOiwObjectIdentifiers,
   ClpCryptoLibTypes,
-  ClpAsn1Utilities;
+  ClpAsn1Utilities,
+  ClpPlatform;
 
 resourcestring
   SBadSequenceSize = 'Bad sequence size: %d';
@@ -53,6 +54,84 @@ resourcestring
   SVersionNil = 'version';
 
 type
+  /// <summary>
+  /// The ContentInfo object (PKCS#7).
+  /// </summary>
+  TContentInfo = class(TAsn1Encodable, IContentInfo)
+  strict private
+  var
+    FContentType: IDerObjectIdentifier;
+    FContent: IAsn1Encodable;
+
+  strict protected
+    function GetContentType: IDerObjectIdentifier;
+    function GetContent: IAsn1Encodable;
+
+  public
+    class function GetInstance(AObj: TObject): IContentInfo; overload; static;
+    class function GetInstance(const AObj: IAsn1Object): IContentInfo; overload; static;
+    class function GetInstance(const AEncoded: TCryptoLibByteArray): IContentInfo; overload; static;
+    class function GetInstance(const AObj: IAsn1TaggedObject;
+      AExplicitly: Boolean): IContentInfo; overload; static;
+    class function GetTagged(const ATaggedObject: IAsn1TaggedObject;
+      ADeclaredExplicit: Boolean): IContentInfo; static;
+
+    constructor Create(const ASeq: IAsn1Sequence); overload;
+    constructor Create(const AContentType: IDerObjectIdentifier;
+      const AContent: IAsn1Encodable); overload;
+
+    function ToAsn1Object: IAsn1Object; override;
+
+    property ContentType: IDerObjectIdentifier read GetContentType;
+    property Content: IAsn1Encodable read GetContent;
+  end;
+
+  /// <summary>
+  /// The SignedData object (PKCS#7).
+  /// </summary>
+  TSignedData = class(TAsn1Encodable, ISignedData)
+  strict private
+  var
+    FVersion: IDerInteger;
+    FDigestAlgorithms: IAsn1Set;
+    FContentInfo: IContentInfo;
+    FCertificates: IAsn1Set;
+    FCrls: IAsn1Set;
+    FSignerInfos: IAsn1Set;
+
+  strict protected
+    function GetVersion: IDerInteger;
+    function GetDigestAlgorithms: IAsn1Set;
+    function GetContentInfo: IContentInfo;
+    function GetCertificates: IAsn1Set;
+    function GetCrls: IAsn1Set;
+    function GetSignerInfos: IAsn1Set;
+
+  public
+    class function GetInstance(AObj: TObject): ISignedData; overload; static;
+    class function GetInstance(const AObj: IAsn1Object): ISignedData; overload; static;
+    class function GetInstance(const AEncoded: TCryptoLibByteArray): ISignedData; overload; static;
+    class function GetInstance(const AObj: IAsn1TaggedObject;
+      AExplicitly: Boolean): ISignedData; overload; static;
+    class function GetTagged(const ATaggedObject: IAsn1TaggedObject;
+      ADeclaredExplicit: Boolean): ISignedData; static;
+
+    constructor Create(const ASeq: IAsn1Sequence); overload;
+    constructor Create(const AVersion: IDerInteger;
+      const ADigestAlgorithms: IAsn1Set; const AContentInfo: IContentInfo;
+      const ACertificates: IAsn1Set; const ACrls: IAsn1Set;
+      const ASignerInfos: IAsn1Set); overload;
+
+    function ToAsn1Object: IAsn1Object; override;
+
+    property Version: IDerInteger read GetVersion;
+    property DigestAlgorithms: IAsn1Set read GetDigestAlgorithms;
+    property ContentInfo: IContentInfo read GetContentInfo;
+    property Certificates: IAsn1Set read GetCertificates;
+    property Crls: IAsn1Set read GetCrls;
+    property SignerInfos: IAsn1Set read GetSignerInfos;
+  end;
+
   /// <summary>
   /// The AttributePkcs object.
   /// </summary>
@@ -1441,4 +1520,292 @@ begin
   Result := TDerSequence.Create(LV);
 end;
 
+{ TContentInfo }
+
+class function TContentInfo.GetInstance(AObj: TObject): IContentInfo;
+var
+  LAsn1Obj: IAsn1Object;
+  LConvertible: IAsn1Convertible;
+begin
+  if AObj = nil then
+  begin
+    Result := nil;
+    Exit;
+  end;
+
+  if Supports(AObj, IContentInfo, Result) then
+    Exit;
+
+  if Supports(AObj, IAsn1Object, LAsn1Obj) then
+  begin
+    Result := GetInstance(LAsn1Obj);
+    Exit;
+  end;
+
+  if Supports(AObj, IAsn1Convertible, LConvertible) then
+  begin
+    LAsn1Obj := LConvertible.ToAsn1Object();
+    Result := GetInstance(LAsn1Obj);
+    Exit;
+  end;
+
+  raise EArgumentCryptoLibException.CreateFmt('illegal object in GetInstance: %s', [TPlatform.GetTypeName(AObj)]);
+end;
+
+class function TContentInfo.GetInstance(const AObj: IAsn1Object): IContentInfo;
+var
+  LSeq: IAsn1Sequence;
+begin
+  if AObj = nil then
+  begin
+    Result := nil;
+    Exit;
+  end;
+
+  if Supports(AObj, IContentInfo, Result) then
+    Exit;
+
+  LSeq := TAsn1Sequence.GetInstance(AObj);
+  Result := TContentInfo.Create(LSeq);
+end;
+
+class function TContentInfo.GetInstance(const AEncoded: TCryptoLibByteArray): IContentInfo;
+begin
+  Result := TContentInfo.Create(TAsn1Sequence.GetInstance(AEncoded));
+end;
+
+class function TContentInfo.GetInstance(const AObj: IAsn1TaggedObject;
+  AExplicitly: Boolean): IContentInfo;
+begin
+  Result := GetInstance(TAsn1Sequence.GetInstance(AObj, AExplicitly));
+end;
+
+class function TContentInfo.GetTagged(const ATaggedObject: IAsn1TaggedObject;
+  ADeclaredExplicit: Boolean): IContentInfo;
+begin
+  Result := GetInstance(TAsn1Sequence.GetTagged(ATaggedObject, ADeclaredExplicit));
+end;
+
+constructor TContentInfo.Create(const ASeq: IAsn1Sequence);
+var
+  LCount: Int32;
+  LTagged: IAsn1TaggedObject;
+begin
+  Inherited Create();
+  LCount := ASeq.Count;
+  if (LCount < 1) or (LCount > 2) then
+    raise EArgumentCryptoLibException.CreateFmt('Bad sequence size: %d', [LCount]);
+
+  FContentType := TDerObjectIdentifier.GetInstance(ASeq[0] as TObject);
+
+  if ASeq.Count > 1 then
+  begin
+    LTagged := TAsn1TaggedObject.GetInstance(ASeq[1] as TObject, TAsn1Tags.ContextSpecific, 0);
+    FContent := LTagged.GetExplicitBaseObject();
+  end
+  else
+  begin
+    FContent := nil;
+  end;
+end;
+
+constructor TContentInfo.Create(const AContentType: IDerObjectIdentifier;
+  const AContent: IAsn1Encodable);
+begin
+  Inherited Create();
+  if AContentType = nil then
+    raise EArgumentNilCryptoLibException.Create('contentType');
+  FContentType := AContentType;
+  FContent := AContent;
+end;
+
+function TContentInfo.GetContentType: IDerObjectIdentifier;
+begin
+  Result := FContentType;
+end;
+
+function TContentInfo.GetContent: IAsn1Encodable;
+begin
+  Result := FContent;
+end;
+
+function TContentInfo.ToAsn1Object: IAsn1Object;
+var
+  LV: IAsn1EncodableVector;
+begin
+  LV := TAsn1EncodableVector.Create(2);
+  LV.Add(FContentType);
+  if FContent <> nil then
+  begin
+    LV.Add(TBerTaggedObject.Create(True, 0, FContent));
+  end;
+  Result := TBerSequence.Create(LV);
+end;
+
+{ TSignedData }
+
+class function TSignedData.GetInstance(AObj: TObject): ISignedData;
+var
+  LAsn1Obj: IAsn1Object;
+  LConvertible: IAsn1Convertible;
+begin
+  if AObj = nil then
+  begin
+    Result := nil;
+    Exit;
+  end;
+
+  if Supports(AObj, ISignedData, Result) then
+    Exit;
+
+  if Supports(AObj, IAsn1Object, LAsn1Obj) then
+  begin
+    Result := GetInstance(LAsn1Obj);
+    Exit;
+  end;
+
+  if Supports(AObj, IAsn1Convertible, LConvertible) then
+  begin
+    LAsn1Obj := LConvertible.ToAsn1Object();
+    Result := GetInstance(LAsn1Obj);
+    Exit;
+  end;
+
+  raise EArgumentCryptoLibException.CreateFmt('illegal object in GetInstance: %s', [TPlatform.GetTypeName(AObj)]);
+end;
+
+class function TSignedData.GetInstance(const AObj: IAsn1Object): ISignedData;
+var
+  LSeq: IAsn1Sequence;
+begin
+  if AObj = nil then
+  begin
+    Result := nil;
+    Exit;
+  end;
+
+  if Supports(AObj, ISignedData, Result) then
+    Exit;
+
+  LSeq := TAsn1Sequence.GetInstance(AObj);
+  Result := TSignedData.Create(LSeq);
+end;
+
+class function TSignedData.GetInstance(const AEncoded: TCryptoLibByteArray): ISignedData;
+begin
+  Result := TSignedData.Create(TAsn1Sequence.GetInstance(AEncoded));
+end;
+
+class function TSignedData.GetInstance(const AObj: IAsn1TaggedObject;
+  AExplicitly: Boolean): ISignedData;
+begin
+  Result := GetInstance(TAsn1Sequence.GetInstance(AObj, AExplicitly));
+end;
+
+class function TSignedData.GetTagged(const ATaggedObject: IAsn1TaggedObject;
+  ADeclaredExplicit: Boolean): ISignedData;
+begin
+  Result := GetInstance(TAsn1Sequence.GetTagged(ATaggedObject, ADeclaredExplicit));
+end;
+
+constructor TSignedData.Create(const ASeq: IAsn1Sequence);
+var
+  LCount, LPos: Int32;
+begin
+  Inherited Create();
+  LCount := ASeq.Count;
+  LPos := 0;
+  if (LCount < 4) or (LCount > 6) then
+    raise EArgumentCryptoLibException.CreateFmt(SBadSequenceSize, [LCount]);
+
+  FVersion := TDerInteger.GetInstance(ASeq[LPos] as TObject);
+  System.Inc(LPos);
+  FDigestAlgorithms := TAsn1Set.GetInstance(ASeq[LPos] as TObject);
+  System.Inc(LPos);
+  FContentInfo := TContentInfo.GetInstance(ASeq[LPos] as TObject);
+  System.Inc(LPos);
+  FCertificates := TAsn1Utilities.ReadOptionalContextTagged<IAsn1Sequence, IAsn1Set>(
+    ASeq, LPos, 0, ASeq,
+    function(ATagged: IAsn1TaggedObject; AState: IAsn1Sequence): IAsn1Set
+    begin
+      Result := TAsn1Set.GetTagged(ATagged, False);
+    end);
+  FCrls := TAsn1Utilities.ReadOptionalContextTagged<IAsn1Sequence, IAsn1Set>(
+    ASeq, LPos, 1, ASeq,
+    function(ATagged: IAsn1TaggedObject; AState: IAsn1Sequence): IAsn1Set
+    begin
+      Result := TAsn1Set.GetTagged(ATagged, False);
+    end);
+  FSignerInfos := TAsn1Set.GetInstance(ASeq[LPos] as TObject);
+  System.Inc(LPos);
+
+  if LPos <> LCount then
+    raise EArgumentCryptoLibException.Create(SUnexpectedElementsInSequence);
+end;
+
+constructor TSignedData.Create(const AVersion: IDerInteger;
+  const ADigestAlgorithms: IAsn1Set; const AContentInfo: IContentInfo;
+  const ACertificates: IAsn1Set; const ACrls: IAsn1Set;
+  const ASignerInfos: IAsn1Set);
+begin
+  Inherited Create();
+  if AVersion = nil then
+    raise EArgumentNilCryptoLibException.Create(SVersionNil);
+  if ADigestAlgorithms = nil then
+    raise EArgumentNilCryptoLibException.Create('digestAlgorithms');
+  if AContentInfo = nil then
+    raise EArgumentNilCryptoLibException.Create('contentInfo');
+  if ASignerInfos = nil then
+    raise EArgumentNilCryptoLibException.Create('signerInfos');
+
+  FVersion := AVersion;
+  FDigestAlgorithms := ADigestAlgorithms;
+  FContentInfo := AContentInfo;
+  FCertificates := ACertificates;
+  FCrls := ACrls;
+  FSignerInfos := ASignerInfos;
+end;
+
+function TSignedData.GetVersion: IDerInteger;
+begin
+  Result := FVersion;
+end;
+
+function TSignedData.GetDigestAlgorithms: IAsn1Set;
+begin
+  Result := FDigestAlgorithms;
+end;
+
+function TSignedData.GetContentInfo: IContentInfo;
+begin
+  Result := FContentInfo;
+end;
+
+function TSignedData.GetCertificates: IAsn1Set;
+begin
+  Result := FCertificates;
+end;
+
+function TSignedData.GetCrls: IAsn1Set;
+begin
+  Result := FCrls;
+end;
+
+function TSignedData.GetSignerInfos: IAsn1Set;
+begin
+  Result := FSignerInfos;
+end;
+
+function TSignedData.ToAsn1Object: IAsn1Object;
+var
+  LV: IAsn1EncodableVector;
+begin
+  LV := TAsn1EncodableVector.Create(6);
+  LV.Add([FVersion, FDigestAlgorithms, FContentInfo]);
+  LV.AddOptionalTagged(False, 0, FCertificates);
+  LV.AddOptionalTagged(False, 1, FCrls);
+  LV.Add(FSignerInfos);
+  Result := TBerSequence.Create(LV);
+end;
+
 end.

+ 27 - 0
CryptoLib/src/Asn1/Pkcs/ClpPkcsObjectIdentifiers.pas

@@ -54,6 +54,12 @@ type
     //
     DigestAlgorithm: String = '1.2.840.113549.2';
 
+    //
+    // pkcs-7 OBJECT IDENTIFIER ::= {
+    //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 }
+    //
+    Pkcs7: String = '1.2.840.113549.1.7';
+
     //
     // pkcs-9 OBJECT IDENTIFIER ::= {
     //       iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 }
@@ -88,6 +94,9 @@ type
     // PKCS#5
     FIdPbkdf2,
 
+    // PKCS#7
+    FSignedData,
+
     // Digest algorithms
     FMD2, FMD4, FMD5,
     FIdHmacWithSha1, FIdHmacWithSha224, FIdHmacWithSha256,
@@ -125,6 +134,9 @@ type
     // PKCS#5
     class function GetIdPbkdf2: IDerObjectIdentifier; static; inline;
 
+    // PKCS#7 getters
+    class function GetSignedData: IDerObjectIdentifier; static; inline;
+
     // Digest algorithm getters
     class function GetMD2: IDerObjectIdentifier; static; inline;
     class function GetMD4: IDerObjectIdentifier; static; inline;
@@ -177,6 +189,11 @@ type
     //
     class property IdPbkdf2: IDerObjectIdentifier read GetIdPbkdf2;
 
+    //
+    // PKCS#7
+    //
+    class property SignedData: IDerObjectIdentifier read GetSignedData;
+
     //
     // Digest algorithms
     //
@@ -236,6 +253,9 @@ begin
     // PKCS#5
     FIdPbkdf2 := TDerObjectIdentifier.Create(Pkcs5 + '.12');
 
+    // PKCS#7
+    FSignedData := TDerObjectIdentifier.Create(Pkcs7 + '.2');
+
     // Digest algorithms
     FMD2 := TDerObjectIdentifier.Create(DigestAlgorithm + '.2');
     FMD4 := TDerObjectIdentifier.Create(DigestAlgorithm + '.4');
@@ -354,6 +374,13 @@ begin
   Result := FIdPbkdf2;
 end;
 
+// PKCS#7
+
+class function TPkcsObjectIdentifiers.GetSignedData: IDerObjectIdentifier;
+begin
+  Result := FSignedData;
+end;
+
 // Digest algorithms
 
 class function TPkcsObjectIdentifiers.GetMD2: IDerObjectIdentifier;

+ 2 - 2
CryptoLib/src/Asn1/X509/ClpX509Asn1Objects.pas

@@ -287,8 +287,8 @@ type
       EncipherOnly = (1 shl 0);
       DecipherOnly = (1 shl 15);
 
-    class function GetInstance(AObj: TObject): IKeyUsage; overload; static;
-    class function GetInstance(const AEncoded: TCryptoLibByteArray): IKeyUsage; overload; static;
+    class function GetInstance(AObj: TObject): IKeyUsage; reintroduce; overload; static;
+    class function GetInstance(const AEncoded: TCryptoLibByteArray): IKeyUsage; reintroduce; overload; static;
 
     constructor Create(AUsage: Int32); overload;
     constructor Create(const AUsage: IDerBitString); overload;

+ 331 - 0
CryptoLib/src/Asn1/X509/ClpX509CertificateParser.pas

@@ -0,0 +1,331 @@
+{ *********************************************************************************** }
+{ *                              CryptoLib Library                                  * }
+{ *                Copyright (c) 2018 - 20XX Ugochukwu Mmaduekwe                    * }
+{ *                 Github Repository <https://github.com/Xor-el>                   * }
+
+{ *  Distributed under the MIT software license, see the accompanying file LICENSE  * }
+{ *          or visit http://www.opensource.org/licenses/mit-license.php.           * }
+
+{ *                              Acknowledgements:                                  * }
+{ *                                                                                 * }
+{ *      Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring     * }
+{ *                           development of this library                           * }
+
+{ * ******************************************************************************* * }
+
+(* &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& *)
+
+unit ClpX509CertificateParser;
+
+{$I ..\..\Include\CryptoLib.inc}
+
+interface
+
+uses
+  SysUtils,
+  Classes,
+  Generics.Collections,
+  ClpIX509CertificateParser,
+  ClpIX509Certificate,
+  ClpX509Certificate,
+  ClpIX509Asn1Objects,
+  ClpX509Asn1Objects,
+  ClpAsn1Objects,
+  ClpIAsn1Objects,
+  ClpPemObjects,
+  ClpIPemObjects,
+  ClpAsn1Streams,
+  ClpAsn1Utilities,
+  ClpCryptoLibTypes,
+  ClpStreams,
+  ClpPkcsAsn1Objects,
+  ClpIPkcsAsn1Objects,
+  ClpPkcsObjectIdentifiers;
+
+type
+  /// <summary>
+  /// Class for dealing with X509 certificates.
+  /// At the moment this will deal with "-----BEGIN CERTIFICATE-----" to "-----END CERTIFICATE-----"
+  /// base 64 encoded certs, as well as the BER binaries of certificates and some classes of PKCS#7
+  /// objects.
+  /// </summary>
+  TX509CertificateParser = class sealed(TInterfacedObject, IX509CertificateParser)
+  strict private
+  class var
+    FPemCertParser: IPemParser;
+
+  var
+    FSData: IAsn1Set;
+    FSDataObjectCount: Int32;
+    FCurrentStream: TStream;
+
+    class constructor Boot();
+    class procedure InitializePemCertParser();
+
+    function ReadDerCertificate(const ADIn: TAsn1InputStream): IX509Certificate;
+    function ReadPemCertificate(const AInStream: TStream): IX509Certificate;
+    function GetCertificate(): IX509Certificate;
+
+  public
+    constructor Create();
+    destructor Destroy(); override;
+
+    function ReadCertificate(const AInput: TCryptoLibByteArray): IX509Certificate; overload;
+    function ReadCertificates(const AInput: TCryptoLibByteArray): TCryptoLibGenericArray<IX509Certificate>; overload;
+    function ReadCertificate(const AInStream: TStream): IX509Certificate; overload;
+    function ReadCertificates(const AInStream: TStream): TCryptoLibGenericArray<IX509Certificate>; overload;
+    function ParseCertificates(const AInStream: TStream): TCryptoLibGenericArray<IX509Certificate>;
+  end;
+
+implementation
+
+{ TX509CertificateParser }
+
+class constructor TX509CertificateParser.Boot();
+begin
+  InitializePemCertParser();
+end;
+
+class procedure TX509CertificateParser.InitializePemCertParser();
+begin
+  FPemCertParser := TPemParser.Create('CERTIFICATE');
+end;
+
+constructor TX509CertificateParser.Create();
+begin
+  Inherited Create();
+  FSData := nil;
+  FSDataObjectCount := 0;
+  FCurrentStream := nil;
+end;
+
+destructor TX509CertificateParser.Destroy();
+begin
+  Inherited Destroy();
+end;
+
+function TX509CertificateParser.ReadDerCertificate(const ADIn: TAsn1InputStream): IX509Certificate;
+var
+  LSeq: IAsn1Sequence;
+  LContentType: IDerObjectIdentifier;
+  LSignedData: ISignedData;
+begin
+  LSeq := ADIn.ReadObject() as IAsn1Sequence;
+
+  if (LSeq.Count > 1) and Supports(LSeq[0], IDerObjectIdentifier, LContentType) then
+  begin
+    if LContentType.Equals(TPkcsObjectIdentifiers.SignedData) then
+    begin
+      if TAsn1Utilities.TryGetOptionalContextTagged<Boolean, ISignedData>(
+        LSeq[1], 0, True, LSignedData,
+        function(ATagged: IAsn1TaggedObject; AState: Boolean): ISignedData
+        begin
+          Result := TSignedData.GetTagged(ATagged, AState);
+        end) then
+      begin
+        FSData := LSignedData.Certificates;
+        if FSData <> nil then
+        begin
+          FSDataObjectCount := 0;
+          Result := GetCertificate();
+          Exit;
+        end;
+      end;
+    end;
+  end;
+
+  Result := TX509Certificate.Create(TX509CertificateStructure.GetInstance(LSeq as TObject));
+end;
+
+function TX509CertificateParser.ReadPemCertificate(const AInStream: TStream): IX509Certificate;
+var
+  LSeq: IAsn1Sequence;
+begin
+  LSeq := FPemCertParser.ReadPemObject(AInStream);
+
+  if LSeq = nil then
+    Result := nil
+  else
+    Result := TX509Certificate.Create(TX509CertificateStructure.GetInstance(LSeq as TObject));
+end;
+
+function TX509CertificateParser.GetCertificate(): IX509Certificate;
+var
+  LCertificate: IX509CertificateStructure;
+begin
+  if FSData <> nil then
+  begin
+    while FSDataObjectCount < FSData.Count do
+    begin
+      LCertificate := TX509CertificateStructure.GetOptional(FSData[FSDataObjectCount]);
+      System.Inc(FSDataObjectCount);
+      if LCertificate <> nil then
+      begin
+        Result := TX509Certificate.Create(LCertificate);
+        Exit;
+      end;
+    end;
+  end;
+
+  Result := nil;
+end;
+
+function TX509CertificateParser.ReadCertificate(const AInput: TCryptoLibByteArray): IX509Certificate;
+var
+  LInStream: TMemoryStream;
+begin
+  LInStream := TMemoryStream.Create();
+  try
+    if System.Length(AInput) > 0 then
+      LInStream.Write(AInput[0], System.Length(AInput));
+    LInStream.Position := 0;
+    Result := ReadCertificate(LInStream);
+  finally
+    LInStream.Free();
+  end;
+end;
+
+function TX509CertificateParser.ReadCertificates(const AInput: TCryptoLibByteArray): TCryptoLibGenericArray<IX509Certificate>;
+var
+  LInStream: TMemoryStream;
+begin
+  LInStream := TMemoryStream.Create();
+  try
+    if System.Length(AInput) > 0 then
+      LInStream.Write(AInput[0], System.Length(AInput));
+    LInStream.Position := 0;
+    Result := ReadCertificates(LInStream);
+  finally
+    LInStream.Free();
+  end;
+end;
+
+function TX509CertificateParser.ReadCertificate(const AInStream: TStream): IX509Certificate;
+
+  function ReadDerCertificateFromStream(const AStream: TStream): IX509Certificate;
+  var
+    LAsn1In: TAsn1InputStream;
+  begin
+    LAsn1In := TAsn1InputStream.Create(AStream, Int32.MaxValue, True);
+    try
+      Result := ReadDerCertificate(LAsn1In);
+    finally
+      LAsn1In.Free();
+    end;
+  end;
+
+var
+  LTag: Int32;
+  LByte: Byte;
+  LPushbackStream: TPushbackStream;
+  LStreamToUse: TStream;
+begin
+  LPushbackStream := nil;
+  if AInStream = nil then
+    raise EArgumentNilCryptoLibException.Create('inStream');
+
+  if not AInStream.CanRead then
+    raise EArgumentCryptoLibException.Create('Stream must be read-able');
+
+  if FCurrentStream = nil then
+  begin
+    FCurrentStream := AInStream;
+    FSData := nil;
+    FSDataObjectCount := 0;
+  end
+  else if FCurrentStream <> AInStream then // reset if input stream has changed
+  begin
+    FCurrentStream := AInStream;
+    FSData := nil;
+    FSDataObjectCount := 0;
+  end;
+
+  try
+    if FSData <> nil then
+    begin
+      if FSDataObjectCount <> FSData.Count then
+      begin
+        Result := GetCertificate();
+        Exit;
+      end;
+
+      FSData := nil;
+      FSDataObjectCount := 0;
+      // TODO[api] Consider removing this and continuing directly
+      Result := nil;
+      Exit;
+    end;
+
+    LTag := AInStream.ReadByte();
+    if LTag < 0 then
+    begin
+      Result := nil;
+      Exit;
+    end;
+
+    if AInStream.CanSeek then
+    begin
+      AInStream.Seek(-1, TSeekOrigin.soCurrent);
+      LStreamToUse := AInStream;
+    end
+    else
+    begin
+      LPushbackStream := TPushbackStream.Create(AInStream);
+      try
+        LByte := Byte(LTag);
+        LPushbackStream.UnRead(Int32(LByte));
+        LStreamToUse := LPushbackStream;
+
+        if LTag <> $30 then // assume ascii PEM encoded.
+        begin
+          Result := ReadPemCertificate(LStreamToUse);
+          Exit;
+        end;
+
+        Result := ReadDerCertificateFromStream(LStreamToUse);
+      finally
+        LPushbackStream.Free();
+      end;
+      Exit;
+    end;
+
+    if LTag <> $30 then // assume ascii PEM encoded.
+    begin
+      Result := ReadPemCertificate(LStreamToUse);
+      Exit;
+    end;
+
+    Result := ReadDerCertificateFromStream(LStreamToUse);
+  except
+    on E: ECertificateCryptoLibException do
+      raise;
+    on E: Exception do
+      raise ECertificateCryptoLibException.Create('Failed to read certificate: ' + E.Message);
+  end;
+end;
+
+function TX509CertificateParser.ReadCertificates(const AInStream: TStream): TCryptoLibGenericArray<IX509Certificate>;
+var
+  LCerts: TList<IX509Certificate>;
+  LCert: IX509Certificate;
+begin
+  LCerts := TList<IX509Certificate>.Create();
+  try
+    LCert := ReadCertificate(AInStream);
+    while LCert <> nil do
+    begin
+      LCerts.Add(LCert);
+      LCert := ReadCertificate(AInStream);
+    end;
+    Result := LCerts.ToArray();
+  finally
+    LCerts.Free();
+  end;
+end;
+
+function TX509CertificateParser.ParseCertificates(const AInStream: TStream): TCryptoLibGenericArray<IX509Certificate>;
+begin
+  Result := ReadCertificates(AInStream);
+end;
+
+end.

+ 16 - 1
CryptoLib/src/Interfaces/ClpIPemObjects.pas

@@ -23,7 +23,8 @@ interface
 
 uses
   Classes,
-  ClpCryptoLibTypes;
+  ClpCryptoLibTypes,
+  ClpIAsn1Objects;
 
 type
   IPemHeader = interface;
@@ -146,6 +147,20 @@ type
     procedure WriteObject(const AObjGen: IPemObjectGenerator);
   end;
 
+  /// <summary>
+  /// Interface for PEM parser.
+  /// </summary>
+  IPemParser = interface(IInterface)
+    ['{F6A7B8C9-D0E1-2345-F012-3456789ABCDE}']
+
+    /// <summary>
+    /// Read a PEM object from the stream and return it as an ASN.1 sequence.
+    /// </summary>
+    /// <param name="AInStream">The input stream to read from</param>
+    /// <returns>An ASN.1 sequence, or nil if no PEM object found</returns>
+    function ReadPemObject(const AInStream: TStream): IAsn1Sequence;
+  end;
+
 implementation
 
 end.

+ 36 - 0
CryptoLib/src/Interfaces/ClpIPkcsAsn1Objects.pas

@@ -32,8 +32,10 @@ type
   IAttributePkcs = interface;
   ICertificationRequest = interface;
   ICertificationRequestInfo = interface;
+  IContentInfo = interface;
   IPrivateKeyInfo = interface;
   IRsassaPssParameters = interface;
+  ISignedData = interface;
 
   /// <summary>
   /// Interface for AttributePkcs.
@@ -127,6 +129,19 @@ type
     property Coefficient: TBigInteger read GetCoefficient;
   end;
 
+  /// <summary>
+  /// Interface for ContentInfo.
+  /// </summary>
+  IContentInfo = interface(IAsn1Encodable)
+    ['{B9C0D1E2-F3A4-5678-9012-3456789ABCDE}']
+
+    function GetContentType: IDerObjectIdentifier;
+    function GetContent: IAsn1Encodable;
+
+    property ContentType: IDerObjectIdentifier read GetContentType;
+    property Content: IAsn1Encodable read GetContent;
+  end;
+
   /// <summary>
   /// Interface for RsassaPssParameters.
   /// </summary>
@@ -144,6 +159,27 @@ type
     property TrailerField: IDerInteger read GetTrailerField;
   end;
 
+  /// <summary>
+  /// Interface for SignedData (PKCS#7).
+  /// </summary>
+  ISignedData = interface(IAsn1Encodable)
+    ['{C0D1E2F3-A4B5-6789-0123-456789ABCDEF}']
+
+    function GetVersion: IDerInteger;
+    function GetDigestAlgorithms: IAsn1Set;
+    function GetContentInfo: IContentInfo;
+    function GetCertificates: IAsn1Set;
+    function GetCrls: IAsn1Set;
+    function GetSignerInfos: IAsn1Set;
+
+    property Version: IDerInteger read GetVersion;
+    property DigestAlgorithms: IAsn1Set read GetDigestAlgorithms;
+    property ContentInfo: IContentInfo read GetContentInfo;
+    property Certificates: IAsn1Set read GetCertificates;
+    property Crls: IAsn1Set read GetCrls;
+    property SignerInfos: IAsn1Set read GetSignerInfos;
+  end;
+
 implementation
 
 end.

+ 1 - 0
CryptoLib/src/Interfaces/ClpIX509Asn1Objects.pas

@@ -363,6 +363,7 @@ type
     function ToAsn1ObjectTrimmed: IAsn1Sequence;
 
     property Count: Int32 read GetCount;
+    property ExtensionOids: TCryptoLibGenericArray<IDerObjectIdentifier> read GetExtensionOids;
   end;
 
   /// <summary>

+ 74 - 0
CryptoLib/src/Interfaces/ClpIX509CertificateParser.pas

@@ -0,0 +1,74 @@
+{ *********************************************************************************** }
+{ *                              CryptoLib Library                                  * }
+{ *                Copyright (c) 2018 - 20XX Ugochukwu Mmaduekwe                    * }
+{ *                 Github Repository <https://github.com/Xor-el>                   * }
+
+{ *  Distributed under the MIT software license, see the accompanying file LICENSE  * }
+{ *          or visit http://www.opensource.org/licenses/mit-license.php.           * }
+
+{ *                              Acknowledgements:                                  * }
+{ *                                                                                 * }
+{ *      Thanks to Sphere 10 Software (http://www.sphere10.com/) for sponsoring     * }
+{ *                           development of this library                           * }
+
+{ * ******************************************************************************* * }
+
+(* &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& *)
+
+unit ClpIX509CertificateParser;
+
+{$I ..\Include\CryptoLib.inc}
+
+interface
+
+uses
+  Classes,
+  ClpCryptoLibTypes,
+  ClpIX509Certificate;
+
+type
+  /// <summary>
+  /// Interface for X.509 certificate parser.
+  /// </summary>
+  IX509CertificateParser = interface(IInterface)
+    ['{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}']
+
+    /// <summary>
+    /// Read a certificate from a byte array.
+    /// </summary>
+    /// <param name="AInput">The byte array containing the certificate</param>
+    /// <returns>An X.509 certificate, or nil if no certificate found</returns>
+    function ReadCertificate(const AInput: TCryptoLibByteArray): IX509Certificate; overload;
+
+    /// <summary>
+    /// Read certificates from a byte array.
+    /// </summary>
+    /// <param name="AInput">The byte array containing the certificates</param>
+    /// <returns>A list of X.509 certificates</returns>
+    function ReadCertificates(const AInput: TCryptoLibByteArray): TCryptoLibGenericArray<IX509Certificate>; overload;
+
+    /// <summary>
+    /// Read a certificate from a stream.
+    /// </summary>
+    /// <param name="AInStream">The input stream to read from</param>
+    /// <returns>An X.509 certificate, or nil if no certificate found</returns>
+    function ReadCertificate(const AInStream: TStream): IX509Certificate; overload;
+
+    /// <summary>
+    /// Read certificates from a stream.
+    /// </summary>
+    /// <param name="AInStream">The input stream to read from</param>
+    /// <returns>A list of X.509 certificates</returns>
+    function ReadCertificates(const AInStream: TStream): TCryptoLibGenericArray<IX509Certificate>; overload;
+
+    /// <summary>
+    /// Parse certificates from a stream (enumerable style).
+    /// </summary>
+    /// <param name="AInStream">The input stream to read from</param>
+    /// <returns>An array of X.509 certificates</returns>
+    function ParseCertificates(const AInStream: TStream): TCryptoLibGenericArray<IX509Certificate>;
+  end;
+
+implementation
+
+end.

+ 1 - 0
CryptoLib/src/Utils/ClpCryptoLibTypes.pas

@@ -75,6 +75,7 @@ type
   EOutputLengthCryptoLibException = class(ECryptoLibException);
   EBadBlockCryptoLibException = class(ECryptoLibException);
   EPemGenerationCryptoLibException = class(ECryptoLibException);
+  ECertificateCryptoLibException = class(ECryptoLibException);
 
   /// <summary>
   /// Represents a dynamic array of Byte.

+ 124 - 1
CryptoLib/src/Utils/Pem/ClpPemObjects.pas

@@ -31,7 +31,9 @@ uses
   ClpPlatform,
   ClpStreams,
   ClpStringUtils,
-  ClpConverters;
+  ClpConverters,
+  ClpAsn1Objects,
+  ClpIAsn1Objects;
 
 type
   /// <summary>
@@ -144,6 +146,24 @@ type
     property Writer: TStream read GetWriter;
   end;
 
+  /// <summary>
+  /// PEM parser implementation.
+  /// </summary>
+  TPemParser = class sealed(TInterfacedObject, IPemParser)
+  strict private
+    FHeader1: String;
+    FHeader2: String;
+    FFooter1: String;
+    FFooter2: String;
+
+    function ReadLine(const AInStream: TStream): String;
+
+  public
+    constructor Create(const AType: String);
+
+    function ReadPemObject(const AInStream: TStream): IAsn1Sequence;
+  end;
+
 implementation
 
 { TPemHeader }
@@ -677,4 +697,107 @@ begin
   WritePostEncapsulationBoundary(LObj.&Type);
 end;
 
+{ TPemParser }
+
+constructor TPemParser.Create(const AType: String);
+begin
+  Inherited Create();
+  FHeader1 := '-----BEGIN ' + AType + '-----';
+  FHeader2 := '-----BEGIN X509 ' + AType + '-----';
+  FFooter1 := '-----END ' + AType + '-----';
+  FFooter2 := '-----END X509 ' + AType + '-----';
+end;
+
+function TPemParser.ReadLine(const AInStream: TStream): String;
+var
+  LC: Int32;
+  LByte: Byte;
+  LBytesRead: Int32;
+  LBuilder: TStringBuilder;
+begin
+  LBuilder := TStringBuilder.Create();
+  try
+    repeat
+      while True do
+      begin
+        LBytesRead := AInStream.Read(LByte, 1);
+        if LBytesRead = 0 then
+        begin
+          LC := -1;
+          Break;
+        end;
+        LC := Int32(LByte);
+
+        if (LC = Ord(#13)) or (LC = Ord(#10)) then
+        begin
+          if LC = Ord(#13) then
+            Continue;
+          Break;
+        end;
+
+        if LC >= 0 then
+          LBuilder.Append(Char(LC));
+      end;
+    until (LC < 0) or (LBuilder.Length > 0);
+
+    if LC < 0 then
+      Result := ''
+    else
+      Result := LBuilder.ToString();
+  finally
+    LBuilder.Free();
+  end;
+end;
+
+function TPemParser.ReadPemObject(const AInStream: TStream): IAsn1Sequence;
+var
+  LLine: String;
+  LPemBuf: TStringBuilder;
+  LDecoded: TCryptoLibByteArray;
+  LAsn1Obj: IAsn1Object;
+begin
+  Result := nil;
+  LPemBuf := TStringBuilder.Create();
+  try
+    // Skip until we find the header
+    while True do
+    begin
+      LLine := ReadLine(AInStream);
+      if LLine = '' then
+      begin
+        Exit;
+      end;
+
+      if TPlatform.StartsWith(LLine, FHeader1) or TPlatform.StartsWith(LLine, FHeader2) then
+        Break;
+    end;
+
+    // Read until we find the footer
+    while True do
+    begin
+      LLine := ReadLine(AInStream);
+      if LLine = '' then
+      begin
+        Exit;
+      end;
+
+      if TPlatform.StartsWith(LLine, FFooter1) or TPlatform.StartsWith(LLine, FFooter2) then
+        Break;
+
+      LPemBuf.Append(LLine);
+    end;
+
+    if LPemBuf.Length > 0 then
+    begin
+      LDecoded := TBase64.Decode(LPemBuf.ToString());
+      LAsn1Obj := TAsn1Object.FromByteArray(LDecoded);
+
+      if not Supports(LAsn1Obj, IAsn1Sequence, Result) then
+        raise EIOCryptoLibException.Create('malformed PEM data encountered');
+    end;
+  finally
+    LPemBuf.Free();
+  end;
+end;
+
 end.