Files
desktop-edge-win/ZitiUpdateService/checkers/PeFile/SignedFileValidator.cs
T

608 lines
33 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using NLog;
using VerifyingFiles.PInvoke;
namespace ZitiUpdateService.Checkers.PeFile {
public class SignedFileValidator {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public const int LengthOfChecksum = 4; //the checksum is 4 bytes
public const int LengthCertificateTable = 8; //the Certificate Table is 8 bytes
public HashPositions ImportantHashPositions { get; private set; }
public string FilePath { get; private set; }
public PeType Type { get; private set; }
public IMAGE_FILE_HEADER CoffHeader { get; private set; }
public IMAGE_OPTIONAL_HEADER32 StandardFieldsPe32 { get; private set; }
public IMAGE_OPTIONAL_HEADER64 StandardFieldsPe32Plus { get; private set; }
private readonly X509Certificate2 expectedRootCa = null;
private readonly X509Store empty = new X509Store();
private X509Store SystemCAs = new X509Store();
private const string oldKnownThumbprint = "39636E9F5E80308DE370C914CE8112876ECF4E0C";
public SignedFileValidator(string pathToFile) {
X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
SystemCAs.Open(OpenFlags.ReadWrite);
SystemCAs.AddRange(store.Certificates);
expectedRootCa = certFromResource("ZitiUpdateService.checkers.PeFile.openziti.rootCA.rsa.pem.txt");
FilePath = pathToFile;
ImportantHashPositions = new HashPositions();
parse();
if (ImportantHashPositions.reorderNeeded) {
ImportantHashPositions.SectionTableHeaders.Sort((x, y) => x.PointerToRawData.CompareTo(y.PointerToRawData));
}
}
private X509Certificate2 certFromResource(string resourceName) {
var assembly = this.GetType().Assembly;
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
using (BinaryReader reader = new BinaryReader(stream)) {
byte[] bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
return new X509Certificate2(bytes);
}
}
// ReSharper disable once InconsistentNaming
private void parse() {
using (FileStream stream = new FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
using (BinaryReader reader = new BinaryReader(stream)) {
//seek to 0x3c to get right to the PE header location...
stream.Seek(0x3c, SeekOrigin.Begin);
uint startCoffHeader = BitConverter.ToUInt32(reader.ReadBytes(4), 0);
ImportantHashPositions.SetStartCoffHeader(startCoffHeader);
stream.Seek(startCoffHeader, SeekOrigin.Begin);
//parse COFF Header (28 or 24 bytes): https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#coff-file-header-object-and-image
byte[] coffHeaderBytes = reader.ReadBytes(24);
CoffHeader = StructHelper.FromBytes<IMAGE_FILE_HEADER>(coffHeaderBytes);
byte[] magicNumberBytes = reader.ReadBytes(2);
int magicNumber = BitConverter.ToInt16(magicNumberBytes, 0);
Type = magicNumber == 0x10b ? PeType.Pe32 : PeType.Pe32Plus;
stream.Seek(-2, SeekOrigin.Current); //skip back to before the magic number so the struct below is created properly
//parse Optional Header Standard and Windows-Specific fields (66 or 88 bytes)
//see: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-standard-fields-image-only
if (Type == PeType.Pe32) {
byte[] headerBytes = reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_OPTIONAL_HEADER32)));
StandardFieldsPe32 = StructHelper.FromBytes<IMAGE_OPTIONAL_HEADER32>(headerBytes);
} else {
byte[] headerBytes = reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_OPTIONAL_HEADER64)));
StandardFieldsPe32Plus = StructHelper.FromBytes<IMAGE_OPTIONAL_HEADER64>(headerBytes);
}
ImageOptionalHeaderWrapper wrapper = new ImageOptionalHeaderWrapper() {
h32 = StandardFieldsPe32,
h64 = StandardFieldsPe32Plus,
IsPe = Type == PeType.Pe32
};
ImportantHashPositions.SetCertificateDetails(Type, wrapper.SizeOfHeaders, wrapper.CertificateTableVirtualAddress, wrapper.CertificateTableSize);
for (int i = 0; i < CoffHeader.NumberOfSections; i++) {
byte[] nextSection = reader.ReadBytes(Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER))); //should be 40 bytes...
IMAGE_SECTION_HEADER sectionTableHeader = StructHelper.FromBytes<IMAGE_SECTION_HEADER>(nextSection);
ImportantHashPositions.AddSectionTableHeader(sectionTableHeader);
}
}
}
private void addCertToList(SignerInfo si, List<X509Certificate2> list) {
X509Certificate2 cert = si.Certificate;
if (list.Find(x => x.Thumbprint == cert.Thumbprint) == null) {
list.Add(cert);
} else {
Logger.Debug("Certificate with Thumbprint {0} already in list. skipping.", cert.Thumbprint);
}
}
public List<X509Certificate2> ExtractVerifiedSignatureCertificates() {
List<X509Certificate2> list = new List<X509Certificate2>();
using (FileStream stream = new FileStream(FilePath, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(stream)) {
//verify all the embedded signatures
stream.Seek(this.ImportantHashPositions.CertificateTableStart, SeekOrigin.Begin);
int readCertLen = reader.ReadInt32();
int readRevision = reader.ReadInt16();
int readCertType = reader.ReadInt16();
byte[] pkcs7 = reader.ReadBytes(readCertLen);
SignedCms cms = new SignedCms();
cms.Decode(pkcs7);
cms.CheckHash();
//shout out to Scott MG: https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/655f5c27-b049-4275-a8b3-cc1c0be2b4f2/retrieve-certificate-info-for-dualsigned-sha1sha256
//for indicating that the unsigned attributes contain the other SignerInfos
foreach (var cmsSi in cms.SignerInfos) {
cmsSi.CheckSignature(true);
if (VerifyTrust(SystemCAs, expectedRootCa, cmsSi.Certificate).Result) {
VerifyFileHash(cms, cmsSi);
addCertToList(cmsSi, list);
}
if (cmsSi.UnsignedAttributes.Count > 0) {
foreach (var unsignedAttr in cmsSi.UnsignedAttributes) {
foreach (AsnEncodedData asn in unsignedAttr.Values) {
SignedCms innerCms = new SignedCms();
innerCms.Decode(asn.RawData);
innerCms.CheckHash();
if (innerCms.SignerInfos.Count > 0) {
SignerInfo innerSignerInfo = innerCms.SignerInfos[0];
try {
innerSignerInfo.CheckSignature(false);
}
catch (CryptographicException) {
if (innerSignerInfo.Certificate.Thumbprint == oldKnownThumbprint) {
//special handling for the known 'old' NetFoundry signing certificate, now expired...
//TODO: remove this code after 2021
//just allow this one error
Logger.Warn("Ignoring timestamp validity issue for the existing code signing certificate. Subject: {0}, Thumbprint: {1}", innerSignerInfo.Certificate.Subject, innerSignerInfo.Certificate.Thumbprint);
} else {
if (IsCertificateOpenZitiVerifies(innerSignerInfo.Certificate)) {
Logger.Debug("Signature could not be verified. This is expected for the certificates issued by openziti/netfoundry.");
} else {
throw;
}
}
}
if (VerifyTrust(SystemCAs, expectedRootCa, innerSignerInfo.Certificate).Result) {
VerifyFileHash(innerCms, innerSignerInfo);
addCertToList(innerSignerInfo, list);
}
}
}
}
}
}
}
return list;
}
public bool IsCertificateOpenZitiVerifies(X509Certificate2 cert) {
bool yesOrNo = cert.Subject.ToLower().Contains("netfoundry") || cert.Subject.ToLower().Contains("openziti");
if (yesOrNo) {
Logger.Debug("Certificate does need verification: {0}", cert.Subject);
} else {
Logger.Debug("Certificate does not need verification: {0}", cert.Subject);
}
return yesOrNo;
}
public void Verify() {
Logger.Debug("extracting certificates from file: {0}", FilePath);
List<X509Certificate2> list = ExtractVerifiedSignatureCertificates();
Logger.Info("Certificates extracted: {0}", list.Count);
foreach (X509Certificate2 cert in list) {
Logger.Info("Checking certificate: [{0}] {1}", cert.Thumbprint, cert);
if (IsCertificateOpenZitiVerifies(cert)) {
//verify this certificate was issued from the known CA
try {
Logger.Info("Verifying trust of certificate: [{0}] {1}", cert, cert.Thumbprint);
VerifyTrust(empty, expectedRootCa, cert, true).Wait();
Logger.Info("Certificate [{0}] {1} was verified signed by [{2}] {3}", cert.Thumbprint, cert.Subject, expectedRootCa.Thumbprint, expectedRootCa.Subject);
return; //yes!
}
catch {
//empty on purpose. keep checking...
}
}
}
throw new CryptographicException("Executable not signed by an appropriate certificate");
}
public void VerifyFileHash(SignedCms cms, SignerInfo signerInfo) {
if (!IsCertificateOpenZitiVerifies(signerInfo.Certificate)) {
return;
}
//calculate the hash using the first signed info
var hashAlg = IncrementalHash.CreateHash(new HashAlgorithmName(signerInfo.DigestAlgorithm.FriendlyName.ToUpper()));
byte[] calculatedHash = CalculatePeHashStreaming(hashAlg);
string hash = BitConverter.ToString(calculatedHash).Replace("-", "");
string content = BitConverter.ToString(cms.ContentInfo.Content).Replace("-", "");
if (!content.Contains(hash)) {
throw new CryptographicException("The expected hash and the actual hash did not match!");
}
}
public async static Task<bool> VerifyTrust(X509Store cas, X509Certificate2 trustedRootCertificateAuthority, X509Certificate2 certificate) {
return await VerifyTrust(cas, trustedRootCertificateAuthority, certificate, false);
}
public async static Task<bool> VerifyTrust(X509Store cas, X509Certificate2 trustedRootCertificateAuthority, X509Certificate2 certificate, bool verifyTrustedRoot) {
X509Chain chain = new X509Chain();
chain.ChainPolicy.ExtraStore.Add(trustedRootCertificateAuthority);
chain.ChainPolicy.ExtraStore.AddRange(cas.Certificates);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.Build(new X509Certificate2(certificate));
List<Exception> exceptions = new List<Exception>();
foreach (X509ChainStatus status in chain.ChainStatus) {
if (status.Status == X509ChainStatusFlags.NoError || status.Status == X509ChainStatusFlags.UntrustedRoot) {
//X509ChainStatusFlags.UntrustedRoot simply means it was not found in the computer/user's trust store.... that's fine...
} else {
if (status.Status == X509ChainStatusFlags.NotTimeValid && certificate.Thumbprint == oldKnownThumbprint) {
//X509ChainStatusFlags.NotTimeValid means the certificate has expired - we're allowing this for now (summer 2021) to allow older clients to update
Logger.Warn("Executable is signed using the old signing certificate. Allowing this certificate to report as expired");
} else {
exceptions.Add(new CryptographicException("Could not verify trust: " + status.StatusInformation));
}
}
}
//final check - make sure the last certificate matches the expected thumbprint
if (verifyTrustedRoot && chain.ChainElements[chain.ChainElements.Count - 1].Certificate.Thumbprint != trustedRootCertificateAuthority.Thumbprint) {
exceptions.Add(new Exception("Could not verify trust. The expected thumbprint was not found!"));
}
foreach (var e in certificate.Extensions) {
if (e.Oid.Value == "2.5.29.31") { //2.5.29.31 == CRL Distribution Points
try {
CrlDistributionPointParser t = new CrlDistributionPointParser(e.RawData);
if (t != null) {
foreach (string url in t.urls) {
try {
//good - things are going the way we want... keep going
//fetch the CRL from the url provided and parse it...
byte[] crlBytes = await new HttpClient().GetByteArrayAsync(url);
if (crlBytes != null && crlBytes.Length > 0) {
//fetch the crl
Win32Crypto.CrlInfo info = Win32Crypto.FromBlob(crlBytes);
if (info.RevokedSerialNumbers.Contains(certificate.SerialNumber)) {
exceptions.Add(new CryptographicException("Serial number " + certificate.SerialNumber + " has been revoked."));
}
} else {
exceptions.Add(new CryptographicException("Could not retrieve revocation list from " + url + "- cannot verify trust"));
}
}
catch (Exception innerException) {
exceptions.Add(new CryptographicException("crl at " + url + " could not be used", innerException));
}
}
}
}
catch (Exception ex) {
exceptions.Add(ex);
}
}
}
if (exceptions.Count > 0) {
throw new AggregateException(exceptions);
}
return true;
}
public byte[] CalculatePeHashStreaming(IncrementalHash alg) {
using (FileStream stream = new FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read))
using (BinaryReader reader = new BinaryReader(stream)) {
stream.Seek(ImportantHashPositions.Chunk1.Start, SeekOrigin.Begin);
alg.AppendData(reader.ReadBytes(ImportantHashPositions.Chunk1.Length));
stream.Seek(ImportantHashPositions.Chunk2.Start, SeekOrigin.Begin);
alg.AppendData(reader.ReadBytes(ImportantHashPositions.Chunk2.Length));
stream.Seek(ImportantHashPositions.Chunk3.Start, SeekOrigin.Begin);
alg.AppendData(reader.ReadBytes(ImportantHashPositions.Chunk3.Length));
int blockSize = 8192;
int read = 0;
byte[] buf = new byte[blockSize];
foreach (IMAGE_SECTION_HEADER h in ImportantHashPositions.SectionTableHeaders) {
stream.Seek(h.PointerToRawData, SeekOrigin.Begin);
if (h.SizeOfRawData <= blockSize) {
//just read the full block as one
alg.AppendData(reader.ReadBytes((int)h.SizeOfRawData));
} else {
long pos = stream.Position;
long end = h.PointerToRawData + h.SizeOfRawData - blockSize;
//process the data into blocks
while (pos < end) {
read = stream.Read(buf, 0, blockSize);
alg.AppendData(buf, 0, read);
pos += blockSize;
}
int remaining = (int)(end - pos + blockSize);
if (remaining > 0) {
read = stream.Read(buf, 0, remaining);
alg.AppendData(buf, 0, read);
}
}
}
/*
14. Create a value called FILE_SIZE, which is not part of the signature. Set this value to the
image’s file size, acquired from the underlying file system. If FILE_SIZE is greater than
SUM_OF_BYTES_HASHED, the file contains extra data that must be added to the hash. This data
begins at the SUM_OF_BYTES_HASHED file offset, and its length is:
(File Size) – ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED)
Note: The size of Attribute Certificate Table is specified in the second ULONG value in the
Certificate Table entry (32 bit: offset 132, 64 bit: offset 148) in Optional Header Data
Directories.
*/
long FILE_SIZE = new FileInfo(FilePath).Length;
long leftoverBytesToHash = FILE_SIZE - ImportantHashPositions.SumOfBytesHashed - ImportantHashPositions.CertificateTableSize;
if (leftoverBytesToHash > 0) {
//HashBlock(alg, reader, SUM_OF_BYTES_HASHED + certTableSize, FILE_SIZE);
stream.Seek(ImportantHashPositions.SumOfBytesHashed, SeekOrigin.Begin);
byte[] leftoverBytes = reader.ReadBytes((int)leftoverBytesToHash);
alg.AppendData(leftoverBytes);
} else {
//no more bytes to hash...
}
//alg.TransformFinalBlock(new byte[0], 0, 0);
byte[] result = alg.GetHashAndReset();
return result;
}
}
public class HashPositions {
internal HashPositions() {
Chunk1 = new Range();
Chunk2 = new Range();
Chunk3 = new Range();
SectionTableHeaders = new List<IMAGE_SECTION_HEADER>();
}
/*
* These functions are all based from the document linked to from the bottom of:
* https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#optional-header-standard-fields-image-only
*
* Link to the document:
* https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/Authenticode_PE.docx
*
* Relevant section copied from that document:
* To calculate the hash value:
1. Load the image header into memory.
2. Initialize a hash algorithm context.
3. Hash the image header from its base to immediately before the start of the
checksum address, as specified in Optional Header Windows-Specific Fields.
4. Skip over the checksum, which is a 4-byte field.
5. Hash everything from the end of the checksum field to immediately before the start of
the Certificate Table entry, as specified in Optional Header Data Directories.
6. Get the Attribute Certificate Table address and size from the Certificate Table entry.
For details, see section 5.7 of the PE/COFF specification.
7. Exclude the Certificate Table entry from the calculation and hash everything from the
end of the Certificate Table entry to the end of image header, including Section Table
(headers).The Certificate Table entry is 8 bytes long, as specified in Optional Header
Data Directories.
8. Create a counter called SUM_OF_BYTES_HASHED, which is not part of the signature. Set
this counter to the SizeOfHeaders field, as specified in Optional Header Windows-Specific
Field.
9. Build a temporary table of pointers to all of the section headers in the image. The
NumberOfSections field of COFF File Header indicates how big the table should be. Do not
include any section headers in the table whose SizeOfRawData field is zero.
10. Using the PointerToRawData field (offset 20) in the referenced SectionHeader structure as
a key, arrange the table's elements in ascending order. In other words, sort the section
headers in ascending order according to the disk-file offset of the sections.
11. Walk through the sorted table, load the corresponding section into memory, and hash the entire
section. Use the SizeOfRawData field in the SectionHeader structure to determine the amount
of data to hash.
12. Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED.
13. Repeat steps 11 and 12 for all of the sections in the sorted table.
14. Create a value called FILE_SIZE, which is not part of the signature. Set this value to the
image’s file size, acquired from the underlying file system. If FILE_SIZE is greater than
SUM_OF_BYTES_HASHED, the file contains extra data that must be added to the hash. This data
begins at the SUM_OF_BYTES_HASHED file offset, and its length is:
(File Size) – ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED)
Note: The size of Attribute Certificate Table is specified in the second ULONG value in the
Certificate Table entry (32 bit: offset 132, 64 bit: offset 148) in Optional Header Data
Directories.
15. Finalize the hash algorithm context.
Note: This procedure uses offset values from the PE/COFF specification, version 8.1 .
For authoritative offset values, refer to the most recent version of the PE/COFF specification.
*/
public List<IMAGE_SECTION_HEADER> SectionTableHeaders { get; private set; }
/// <summary>
/// Chunk1 Start will set the position in the file which the hashing of the file should begin from
/// This is always the start of the file and thus is hardcoded to 0 and takes no parameters
///
/// Chunk1 End is defined to be: "immediately before the start of the checksum address"
///
/// Relevant step:
/// 3. Hash the image header from its base to immediately before the start of the
/// checksum address, as specified in Optional Header Windows-Specific Fields.
/// </summary>
public Range Chunk1 { get; private set; }
/// <summary>
/// Chunk2 Start is defined to be: "end of the checksum field"
/// Chunk2 End is defined to be: "immediately before the start of the Certificate Table entry"
///
/// Relevant step:
/// 5. Hash everything from the end of the checksum field to immediately before the start of
/// the Certificate Table entry, as specified in Optional Header Data Directories.
/// </summary>
public Range Chunk2 { get; private set; }
/// <summary>
/// Chunk3 Start is defined to be: "the end of the Certificate Table entry"
/// Chunk3 End is defined to be: "the end of image header"
/// When testing/developing this solution - this was found to be the position specified by
/// the SizeOfHeaders field
///
/// Relevant step:
/// 7. Exclude the Certificate Table entry from the calculation and hash everything from the
/// end of the Certificate Table entry to the end of image header, including Section Table
/// (headers).The Certificate Table entry is 8 bytes long, as specified in Optional Header
/// Data Directories.
/// </summary>
public Range Chunk3 { get; private set; }
public uint SizeOfHeaders { get; private set; }
public uint SumOfBytesHashed { get; private set; }
public long CertificateTableStart { get; private set; }
public long CertificateTableSize { get; private set; }
internal bool reorderNeeded = false;
private uint lastAddress = 0;
public void AddSectionTableHeader(IMAGE_SECTION_HEADER sectionTableHeader) {
if (sectionTableHeader.SizeOfRawData > 0) {
SectionTableHeaders.Add(sectionTableHeader);
}
if (lastAddress < sectionTableHeader.PointerToRawData) {
//good - it's in order...
} else {
reorderNeeded = true;
}
lastAddress = sectionTableHeader.PointerToRawData;
SumOfBytesHashed += sectionTableHeader.SizeOfRawData;
}
public void SetStartCoffHeader(uint peHeaderStart) {
Chunk1.Start = 0;
uint checksumStart = peHeaderStart + 88; //88 is the size of the COFF Header (24) + Standard Headers + Windows-Specific Fields up to checksum (offset 64)
Chunk1.End = checksumStart;
Chunk2.Start = checksumStart + LengthOfChecksum;
//and the four tables before the Certificate Table
//(ExportTable, ImportTable, ResourceTable, ExceptionTable)
uint certHeaderTableOffset = checksumStart + 64;
Chunk2.End = certHeaderTableOffset;
Chunk3.Start = certHeaderTableOffset + LengthCertificateTable;
}
public void SetCertificateDetails(PeType type, uint sizeOfHeaders, uint certificateTableStart,
uint certificateTableSize) {
if (type == PeType.Pe32Plus) {
uint winSpecificExtraBytes = 16; //need to account for the extra bytes in the Windows-Specific Fields (each field has 4 extra bytes)
Chunk2.End += winSpecificExtraBytes;
Chunk3.Start += winSpecificExtraBytes;
}
Chunk3.End = sizeOfHeaders;
SizeOfHeaders = sizeOfHeaders;
SumOfBytesHashed = sizeOfHeaders;
CertificateTableStart = certificateTableStart;
CertificateTableSize = certificateTableSize;
}
}
public class Range {
public long Start { get; internal set; }
public long End { get; internal set; }
public int Length => (int)(End - Start);
}
}
public enum PeType {
Pe32,
Pe32Plus
}
internal class CrlDistributionPointParser {
internal int offset;
internal int length;
internal List<CrlDistributionPointParser> tags = new List<CrlDistributionPointParser>();
private CrlDistributionPointParser parent = null;
private AsnTagType type = AsnTagType.NONE;
internal List<string> urls = new List<string>();
private int consumedBytes = 0;
internal CrlDistributionPointParser(byte[] asn1Data) : this(new MemoryStream(asn1Data)) {
}
internal CrlDistributionPointParser(MemoryStream asn1Data) : this(null, asn1Data/*new BinaryReader(asn1Data)*/, 0) {
}
internal CrlDistributionPointParser(CrlDistributionPointParser parent, MemoryStream memoryStream, /*BinaryReader asn1Reader,*/ int offset) {
BinaryReader asn1Reader = new BinaryReader(memoryStream);
this.offset = offset;
type = readTag(asn1Reader);
length = readTagLen(asn1Reader);
this.parent = parent;
int headerBytes = consumedBytes;
while (this.consumedBytes < length + headerBytes) {
switch (type) {
case AsnTagType.SEQUENCE:
case AsnTagType.ARRAY_ELEMENT:
var t = new CrlDistributionPointParser(this, memoryStream, (int) memoryStream.Position);
consumedBytes += t.consumedBytes;
tags.Add(t);
this.urls.AddRange(t.urls);
break;
case AsnTagType.STRING:
consumedBytes += length;
urls.Add(System.Text.Encoding.UTF8.GetString(asn1Reader.ReadBytes(length)));
break;
}
}
}
internal AsnTagType readTag(BinaryReader asn1Data) {
byte tagByte = asn1Data.ReadByte();
if (tagByte != 0x30 && tagByte != 0xA0 && tagByte != 0x86) {
throw new CryptographicException("Could not parse CRL Distribution Points. First byte not asn SEQUENCE|ARRAY|STRING marker");
}
consumedBytes++;
if (tagByte == 0x30) return AsnTagType.SEQUENCE;
if (tagByte == 0xA0) return AsnTagType.ARRAY_ELEMENT;
if (tagByte == 0x86) return AsnTagType.STRING;
return AsnTagType.NONE;
}
internal int readTagLen(BinaryReader asn1Data) {
int tagLen = asn1Data.ReadByte();
consumedBytes++;
if (tagLen > 128) {
//means there's more bytes to read that control the overall length of the next section
int moreBytes = tagLen - 128;
byte[] lenBytes = new byte[4];
byte[] d = asn1Data.ReadBytes(moreBytes);
int pos = 0;
for (int i = d.Length - 1; i >= 0; i--) {
lenBytes[pos++] = d[i];
}
tagLen = BitConverter.ToInt32(lenBytes, 0);
consumedBytes += moreBytes;
}
return tagLen;
}
}
internal enum AsnTagType {
SEQUENCE = 0x30,
ARRAY_ELEMENT = 0xA0,
STRING = 0x86,
NONE = 0,
}
}