public static Rfc3161TimestampRequest CreateFromSignerInfo( SignerInfo signerInfo, HashAlgorithmName hashAlgorithm, Oid?requestedPolicyId = null, ReadOnlyMemory <byte>?nonce = null, bool requestSignerCertificates = false, X509ExtensionCollection?extensions = null) { if (signerInfo == null) { throw new ArgumentNullException(nameof(signerInfo)); } // https://tools.ietf.org/html/rfc3161, Appendix A. // // The value of messageImprint field within TimeStampToken shall be a // hash of the value of signature field within SignerInfo for the // signedData being time-stamped. return(CreateFromData( signerInfo.GetSignature(), hashAlgorithm, requestedPolicyId, nonce, requestSignerCertificates, extensions)); }
internal Pkcs12SecretBag(Oid secretTypeOid, ReadOnlyMemory <byte> secretValue) : this(EncodeBagValue(secretTypeOid, secretValue)) { _secretTypeOid = new Oid(secretTypeOid); _decoded = SecretBagAsn.Decode(EncodedBagValue, AsnEncodingRules.BER); }
internal ECKey(ISystemClock clock, ICKBinaryReader r) : base(clock, r) { r.ReadByte(); Oid?curveName = HelperAndExtensions.ReadNullableOid(r); Debug.Assert(curveName != null); _parameters = new ECParameters() { Curve = ECCurve.CreateFromOid(curveName), D = ReadBytes(r), Q = new ECPoint() { X = ReadBytes(r), Y = ReadBytes(r) } }; if (r.ReadBoolean()) { _ecdh = ECDiffieHellman.Create(_parameters); } else { _ec = ECDsa.Create(_parameters); } JWKCurveName = GetJWKCurveName(_parameters.Curve.Oid);
public static Oid?CopyOid(this Oid?oid) { #if NETCOREAPP return(oid); #else return(oid is null ? null : new Oid(oid)); #endif }
public Oid GetSecretType() { if (_secretTypeOid == null) { _secretTypeOid = new Oid(_decoded.SecretTypeId); } return(new Oid(_secretTypeOid)); }
public Oid GetBagId() { if (_bagOid == null) { _bagOid = new Oid(_bagIdValue); } return(new Oid(_bagOid)); }
public Oid GetCertificateType() { if (_certTypeOid == null) { _certTypeOid = new Oid(_decoded.CertId); } return(_certTypeOid.CopyOid()); }
public static Oid?CopyOid(this Oid?oid) { if (s_oidIsInitOnceOnly) { return(oid); } else { return(oid is null ? null : new Oid(oid)); } }
/// <summary> /// Create a CertBag for a specified certificate type and encoding. /// </summary> /// <param name="certificateType">The identifier for the certificate type</param> /// <param name="encodedCertificate">The encoded value</param> /// <remarks> /// No validation is done to ensure that the <paramref name="encodedCertificate"/> value is /// correct for the indicated <paramref name="certificateType"/>. Note that for X.509 /// public-key certificates the correct encoding for a CertBag value is to wrap the /// DER-encoded certificate in an OCTET STRING. /// </remarks> public Pkcs12CertBag(Oid certificateType, ReadOnlyMemory <byte> encodedCertificate) : base( Oids.Pkcs12CertBag, EncodeBagValue(certificateType, encodedCertificate), skipCopy: true) { _certTypeOid = certificateType.CopyOid(); _decoded = CertBagAsn.Decode(EncodedBagValue, AsnEncodingRules.BER); IsX509Certificate = _decoded.CertId == Oids.Pkcs12X509CertBagType; }
internal AsnEncodedData(Oid?oid, byte[] rawData, bool skipCopy) { if (skipCopy) { ArgumentNullException.ThrowIfNull(rawData); Oid = oid; _rawData = rawData; } else { Reset(oid, rawData); } }
public override void Reset() { _lazyRawData = null; _lazySignatureAlgorithm = null; _lazyVersion = 0; _lazySubjectName = null; _lazyIssuerName = null; _lazyPublicKey = null; _lazyPrivateKey = null; _lazyExtensions = null; base.Reset(); }
internal static Oid GetSharedOrNewOid(ref AsnValueReader asnValueReader) { Oid?ret = GetSharedOrNullOid(ref asnValueReader); if (ret is not null) { return(ret); } string oidValue = asnValueReader.ReadObjectIdentifier(); return(new Oid(oidValue, null)); }
protected override string?FormatNative(Oid?oid, byte[] rawData, bool multiLine) { if (oid == null || string.IsNullOrEmpty(oid.Value)) { return(EncodeSpaceSeparatedHexString(rawData)); } switch (oid.Value) { case "2.5.29.17": return(FormatSubjectAlternativeName(rawData)); } return(null); }
protected override string?FormatNative(Oid?oid, byte[] rawData, bool multiLine) { // If OID is not present, then we can force CryptFormatObject // to use hex formatting by providing an empty OID string. string oidValue = string.Empty; if (oid != null && oid.Value != null) { oidValue = oid.Value; } int dwFormatStrType = multiLine ? Interop.Crypt32.CRYPT_FORMAT_STR_MULTI_LINE : Interop.Crypt32.CRYPT_FORMAT_STR_NONE; int cbFormat = 0; const int X509_ASN_ENCODING = 0x00000001; unsafe { IntPtr oidValuePtr = Marshal.StringToHGlobalAnsi(oidValue); char[]? pooledarray = null; try { if (Interop.Crypt32.CryptFormatObject(X509_ASN_ENCODING, 0, dwFormatStrType, IntPtr.Zero, (byte *)oidValuePtr, rawData, rawData.Length, null, ref cbFormat)) { int charLength = (cbFormat + 1) / 2; Span <char> buffer = charLength <= 256 ? stackalloc char[256] : (pooledarray = ArrayPool <char> .Shared.Rent(charLength)); fixed(char *bufferPtr = buffer) { if (Interop.Crypt32.CryptFormatObject(X509_ASN_ENCODING, 0, dwFormatStrType, IntPtr.Zero, (byte *)oidValuePtr, rawData, rawData.Length, bufferPtr, ref cbFormat)) { return(new string(bufferPtr)); } } } } finally { Marshal.FreeHGlobal(oidValuePtr); if (pooledarray != null) { ArrayPool <char> .Shared.Return(pooledarray); } } } return(null); }
public static Rfc3161TimestampRequest CreateFromHash( ReadOnlyMemory <byte> hash, HashAlgorithmName hashAlgorithm, Oid?requestedPolicyId = null, ReadOnlyMemory <byte>?nonce = null, bool requestSignerCertificates = false, X509ExtensionCollection?extensions = null) { string oidStr = PkcsHelpers.GetOidFromHashAlgorithm(hashAlgorithm); return(CreateFromHash( hash, new Oid(oidStr, oidStr), requestedPolicyId, nonce, requestSignerCertificates, extensions)); }
private static ECCurve CreateFromValueAndName(string?oidValue, string?oidFriendlyName) { Oid?oid = null; if (oidValue == null && oidFriendlyName != null) { try { oid = Oid.FromFriendlyName(oidFriendlyName, OidGroup.PublicKeyAlgorithm); } catch (CryptographicException) { } } oid ??= new Oid(oidValue, oidFriendlyName); return(ECCurve.Create(oid)); }
internal X500RelativeDistinguishedName(ReadOnlyMemory <byte> rawData) { RawData = rawData; ReadOnlySpan <byte> rawDataSpan = rawData.Span; AsnValueReader outer = new AsnValueReader(rawDataSpan, AsnEncodingRules.DER); // Windows does not enforce the sort order on multi-value RDNs. AsnValueReader rdn = outer.ReadSetOf(skipSortOrderValidation: true); AsnValueReader typeAndValue = rdn.ReadSequence(); Oid firstType = Oids.GetSharedOrNewOid(ref typeAndValue); ReadOnlySpan <byte> firstValue = typeAndValue.ReadEncodedValue(); typeAndValue.ThrowIfNotEmpty(); if (rdn.HasData) { do { typeAndValue = rdn.ReadSequence(); // Check that the attribute type is a valid OID, // if it's from the cache, even better (faster, lower alloc). if (Oids.GetSharedOrNullOid(ref typeAndValue) is null) { typeAndValue.ReadObjectIdentifier(); } typeAndValue.ReadEncodedValue(); typeAndValue.ThrowIfNotEmpty(); }while (rdn.HasData); } else { _singleElementType = firstType; bool overlaps = rawDataSpan.Overlaps(firstValue, out int offset); Debug.Assert(overlaps, "AsnValueReader.ReadEncodedValue returns a slice of the source"); Debug.Assert(offset > 0); _singleElementValue = rawData.Slice(offset, firstValue.Length); } }
public static Rfc3161TimestampRequest CreateFromData( ReadOnlySpan <byte> data, HashAlgorithmName hashAlgorithm, Oid?requestedPolicyId = null, ReadOnlyMemory <byte>?nonce = null, bool requestSignerCertificates = false, X509ExtensionCollection?extensions = null) { using (IncrementalHash hasher = IncrementalHash.CreateHash(hashAlgorithm)) { hasher.AppendData(data); byte[] digest = hasher.GetHashAndReset(); return(CreateFromHash( digest, hashAlgorithm, requestedPolicyId, nonce, requestSignerCertificates, extensions)); } }
/// <summary> /// Initializes a new instance of the <see cref="AsnEncodedData"/> class from an object identifier /// (OID) and existing encoded data. /// </summary> /// <param name="oid"> /// The object identifier for this data. /// </param> /// <param name="rawData"> /// The Abstract Syntax Notation One (ASN.1)-encoded data. /// </param> public AsnEncodedData(Oid?oid, ReadOnlySpan <byte> rawData) { Reset(oid, rawData); }
public AsnEncodedData(Oid?oid, byte[] rawData) { Reset(oid, rawData); }
private void Reset(Oid?oid, byte[] rawData) { this.Oid = oid; this.RawData = rawData; }
protected override string? FormatNative(Oid? oid, byte[] rawData, bool multiLine) { if (oid == null || string.IsNullOrEmpty(oid.Value)) { return EncodeHexString(rawData, true); } // The established behavior for this method is to return the native answer, if possible, // or to return null and let rawData get hex-encoded. CryptographicException should not // be raised. bool clearErrors = true; try { using (SafeAsn1ObjectHandle asnOid = Interop.Crypto.ObjTxt2Obj(oid.Value)) using (SafeAsn1OctetStringHandle octetString = Interop.Crypto.Asn1OctetStringNew()) { if (asnOid.IsInvalid || octetString.IsInvalid) { return null; } if (!Interop.Crypto.Asn1OctetStringSet(octetString, rawData, rawData.Length)) { return null; } using (SafeBioHandle bio = Interop.Crypto.CreateMemoryBio()) using (SafeX509ExtensionHandle x509Ext = Interop.Crypto.X509ExtensionCreateByObj(asnOid, false, octetString)) { if (bio.IsInvalid || x509Ext.IsInvalid) { return null; } if (!Interop.Crypto.X509V3ExtPrint(bio, x509Ext)) { return null; } // X509V3ExtPrint might contaminate the error queue on success, always clear now. Interop.Crypto.ErrClearError(); // Errors past here are handled by throws, don't need to double-lock // the success path. clearErrors = false; int printLen = Interop.Crypto.GetMemoryBioSize(bio); // Account for the null terminator that it'll want to write. var buf = new byte[printLen + 1]; int read = Interop.Crypto.BioGets(bio, buf, buf.Length); if (read < 0) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } return Encoding.UTF8.GetString(buf, 0, read); } } } finally { // All of the return null paths might have errors that we are ignoring. if (clearErrors) { Interop.Crypto.ErrClearError(); } } }
/// <summary> /// Decodes the specified Certificate Revocation List (CRL) and produces /// a <see cref="CertificateRevocationListBuilder" /> with all of the revocation /// entries from the decoded CRL. /// </summary> /// <param name="currentCrl"> /// The DER-encoded CRL to decode. /// </param> /// <param name="currentCrlNumber"> /// When this method returns, contains the CRL sequence number from the decoded CRL. /// This parameter is treated as uninitialized. /// </param> /// <param name="bytesConsumed"> /// When this method returns, contains the number of bytes that were read from /// <paramref name="currentCrl"/> while decoding. /// </param> /// <returns> /// A new builder that has the same revocation entries as the decoded CRL. /// </returns> /// <exception cref="CryptographicException"> /// <paramref name="currentCrl" /> could not be decoded. /// </exception> public static CertificateRevocationListBuilder Load( ReadOnlySpan <byte> currentCrl, out BigInteger currentCrlNumber, out int bytesConsumed) { List <RevokedCertificate> list = new(); BigInteger crlNumber = 0; int payloadLength; try { AsnValueReader reader = new AsnValueReader(currentCrl, AsnEncodingRules.DER); payloadLength = reader.PeekEncodedValue().Length; AsnValueReader certificateList = reader.ReadSequence(); AsnValueReader tbsCertList = certificateList.ReadSequence(); AlgorithmIdentifierAsn.Decode(ref certificateList, ReadOnlyMemory <byte> .Empty, out _); if (!certificateList.TryReadPrimitiveBitString(out _, out _)) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } certificateList.ThrowIfNotEmpty(); int version = 0; if (tbsCertList.PeekTag().HasSameClassAndValue(Asn1Tag.Integer)) { // https://datatracker.ietf.org/doc/html/rfc5280#section-5.1 says the only // version values are v1 (0) and v2 (1). // // Since v1 (0) is supposed to not write down the version value, v2 (1) is the // only legal value to read. if (!tbsCertList.TryReadInt32(out version) || version != 1) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } } AlgorithmIdentifierAsn.Decode(ref tbsCertList, ReadOnlyMemory <byte> .Empty, out _); // X500DN tbsCertList.ReadSequence(); // thisUpdate ReadX509Time(ref tbsCertList); // nextUpdate ReadX509TimeOpt(ref tbsCertList); AsnValueReader revokedCertificates = default; if (tbsCertList.HasData && tbsCertList.PeekTag().HasSameClassAndValue(Asn1Tag.Sequence)) { revokedCertificates = tbsCertList.ReadSequence(); } if (version > 0 && tbsCertList.HasData) { AsnValueReader crlExtensionsExplicit = tbsCertList.ReadSequence(new Asn1Tag(TagClass.ContextSpecific, 0)); AsnValueReader crlExtensions = crlExtensionsExplicit.ReadSequence(); crlExtensionsExplicit.ThrowIfNotEmpty(); while (crlExtensions.HasData) { AsnValueReader extension = crlExtensions.ReadSequence(); Oid? extnOid = Oids.GetSharedOrNullOid(ref extension); if (extnOid is null) { extension.ReadObjectIdentifier(); } if (extension.PeekTag().HasSameClassAndValue(Asn1Tag.Boolean)) { extension.ReadBoolean(); } if (!extension.TryReadPrimitiveOctetString(out ReadOnlySpan <byte> extnValue)) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } // Since we're only matching against OIDs that come from GetSharedOrNullOid // we can use ReferenceEquals and skip the Value string equality check in // the Oid.ValueEquals extension method (as it will always be preempted by // the ReferenceEquals or will evaulate to false). if (ReferenceEquals(extnOid, Oids.CrlNumberOid)) { AsnValueReader crlNumberReader = new AsnValueReader( extnValue, AsnEncodingRules.DER); crlNumber = crlNumberReader.ReadInteger(); crlNumberReader.ThrowIfNotEmpty(); } } } tbsCertList.ThrowIfNotEmpty(); while (revokedCertificates.HasData) { RevokedCertificate revokedCertificate = new RevokedCertificate(ref revokedCertificates, version); list.Add(revokedCertificate); } } catch (AsnContentException e) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e); } bytesConsumed = payloadLength; currentCrlNumber = crlNumber; return(new CertificateRevocationListBuilder(list)); }
public AsnEncodedData(Oid?oid, byte[] rawData) : this(oid, rawData, skipCopy : false) { }
private static List <byte[]> ParseDistinguishedName( string stringForm, List <char> dnSeparators, bool noQuotes) { // 16 is way more RDNs than we should ever need. A fairly standard set of values is // { E, CN, O, OU, L, S, C } = 7; // The EV values add in // { // STREET, PostalCode, SERIALNUMBER, 2.5.4.15, // 1.3.6.1.4.1.311.60.2.1.2, 1.3.6.1.4.1.311.60.2.1.3 // } = 6 // // 7 + 6 = 13, round up to the nearest power-of-two. const int InitalRdnSize = 16; List <byte[]> encodedSets = new List <byte[]>(InitalRdnSize); ReadOnlySpan <char> chars = stringForm; int pos; int end = chars.Length; int tagStart = -1; int tagEnd = -1; Oid? tagOid = null; int valueStart = -1; int valueEnd = -1; bool hadEscapedQuote = false; const char KeyValueSeparator = '='; const char QuotedValueChar = '"'; ParseState state = ParseState.SeekTag; for (pos = 0; pos < end; pos++) { char c = chars[pos]; switch (state) { case ParseState.SeekTag: if (char.IsWhiteSpace(c)) { continue; } if (char.IsControl(c)) { state = ParseState.Invalid; break; } // The first character in the tag start. // We know that there's at least one valid // character, so make end be start+1. // // Single letter values with no whitespace padding them // (e.g. E=) would otherwise be ambiguous with length. // (SeekEquals can't set the tagEnd value because it // doesn't know if it was preceded by whitespace) // Note that we make no check here for the dnSeparator(s). // Two separators in a row is invalid (except for UseNewlines, // and they are only allowed because they are whitespace). // // But the throw for an invalid value will come from when the // OID fails to encode. tagStart = pos; tagEnd = pos + 1; state = ParseState.SeekTagEnd; break; case ParseState.SeekTagEnd: if (c == KeyValueSeparator) { goto case ParseState.SeekEquals; } if (char.IsWhiteSpace(c)) { // Tag values aren't permitted whitespace, but there // can be whitespace between the tag and the separator. state = ParseState.SeekEquals; break; } if (char.IsControl(c)) { state = ParseState.Invalid; break; } // We found another character in the tag, so move the // end (non-inclusive) to the next character. tagEnd = pos + 1; break; case ParseState.SeekEquals: if (c == KeyValueSeparator) { Debug.Assert(tagStart >= 0); tagOid = ParseOid(stringForm, tagStart, tagEnd); tagStart = -1; state = ParseState.SeekValueStart; break; } if (!char.IsWhiteSpace(c)) { state = ParseState.Invalid; break; } break; case ParseState.SeekValueStart: if (char.IsWhiteSpace(c)) { continue; } // If the first non-whitespace character is a quote, // this is a quoted string. Unless the flags say to // not interpret quoted strings. if (c == QuotedValueChar && !noQuotes) { state = ParseState.SeekEndQuote; valueStart = pos + 1; break; } // It's possible to just write "CN=,O=". So we might // run into the RDN separator here. if (dnSeparators.Contains(c)) { valueStart = pos; valueEnd = pos; goto case ParseState.SeekComma; } state = ParseState.SeekValueEnd; valueStart = pos; valueEnd = pos + 1; break; case ParseState.SeekEndQuote: // The only escape sequence in DN parsing is that a quoted // value can embed quotes via "", the same as a C# verbatim // string. So, if we see a quote while looking for a close // quote we need to remember that this might have been the // end, but be open to the possibility that there's another // quote coming. if (c == QuotedValueChar) { state = ParseState.MaybeEndQuote; valueEnd = pos; break; } // Everything else is okay. break; case ParseState.MaybeEndQuote: if (c == QuotedValueChar) { state = ParseState.SeekEndQuote; hadEscapedQuote = true; valueEnd = -1; break; } // If the character wasn't another quote: // dnSeparator: process value, state transition to SeekTag // whitespace: state transition to SeekComma // anything else: invalid. // since that's the same table as SeekComma, just change state // and go there. state = ParseState.SeekComma; goto case ParseState.SeekComma; case ParseState.SeekValueEnd: // Every time we see a non-whitespace character we need to mark it if (dnSeparators.Contains(c)) { goto case ParseState.SeekComma; } if (char.IsWhiteSpace(c)) { continue; } // Including control characters. valueEnd = pos + 1; break; case ParseState.SeekComma: if (dnSeparators.Contains(c)) { Debug.Assert(tagOid != null); Debug.Assert(valueEnd != -1); Debug.Assert(valueStart != -1); encodedSets.Add(ParseRdn(tagOid, chars[valueStart..valueEnd], hadEscapedQuote));
public string Format(Oid?oid, byte[] rawData, bool multiLine) { return(FormatNative(oid, rawData, multiLine) ?? Convert.ToHexString(rawData)); }
public string Format(Oid?oid, byte[] rawData, bool multiLine) { return(FormatNative(oid, rawData, multiLine) ?? HexConverter.ToString(rawData.AsSpan(), HexConverter.Casing.Upper)); }
/// <summary> /// Create a timestamp request using a pre-computed hash value. /// </summary> /// <param name="hash">The pre-computed hash value to be timestamped.</param> /// <param name="hashAlgorithmId"> /// The Object Identifier (OID) for the hash algorithm which produced <paramref name="hash"/>. /// </param> /// <param name="requestedPolicyId"> /// The Object Identifier (OID) for a timestamp policy the Timestamp Authority (TSA) should use, /// or <c>null</c> to express no preference. /// </param> /// <param name="nonce"> /// An optional nonce (number used once) to uniquely identify this request to pair it with the response. /// The value is interpreted as an unsigned big-endian integer and may be normalized to the encoding format. /// </param> /// <param name="requestSignerCertificates"> /// Indicates whether the Timestamp Authority (TSA) must (<c>true</c>) or must not (<c>false</c>) include /// the signing certificate in the issued timestamp token. /// </param> /// <param name="extensions">RFC3161 extensions to present with the request.</param> /// <returns> /// An <see cref="Rfc3161TimestampRequest"/> representing the chosen values. /// </returns> /// <seealso cref="Encode"/> /// <seealso cref="TryEncode"/> public static Rfc3161TimestampRequest CreateFromHash( ReadOnlyMemory <byte> hash, Oid hashAlgorithmId, Oid?requestedPolicyId = null, ReadOnlyMemory <byte>?nonce = null, bool requestSignerCertificates = false, X509ExtensionCollection?extensions = null) { // Normalize the nonce: if (nonce.HasValue) { ReadOnlyMemory <byte> nonceMemory = nonce.Value; ReadOnlySpan <byte> nonceSpan = nonceMemory.Span; // If it's empty, or it would be negative, insert the requisite byte. if (nonceSpan.Length == 0 || nonceSpan[0] >= 0x80) { byte[] temp = new byte[nonceSpan.Length + 1]; nonceSpan.CopyTo(temp.AsSpan(1)); nonce = temp; } else { int slice = 0; // Find all extra leading 0x00 values and trim them off. while (slice < nonceSpan.Length && nonceSpan[slice] == 0) { slice++; } // Back up one if it was all zero, or we turned the number negative. if (slice == nonceSpan.Length || nonceSpan[slice] >= 0x80) { slice--; } nonce = nonceMemory.Slice(slice); } } var req = new Rfc3161TimeStampReq { Version = 1, MessageImprint = new MessageImprint { HashAlgorithm = { Algorithm = hashAlgorithmId, Parameters = AlgorithmIdentifierAsn.ExplicitDerNull, }, HashedMessage = hash, }, ReqPolicy = requestedPolicyId, CertReq = requestSignerCertificates, Nonce = nonce, }; if (extensions != null) { req.Extensions = extensions.OfType <X509Extension>().Select(e => new X509ExtensionAsn(e)).ToArray(); } // The RFC implies DER (see TryParse), and DER is the most widely understood given that // CER isn't specified. const AsnEncodingRules ruleSet = AsnEncodingRules.DER; using (AsnWriter writer = new AsnWriter(ruleSet)) { req.Encode(writer); byte[] encodedBytes = writer.Encode(); // Make sure everything normalizes req = Rfc3161TimeStampReq.Decode(encodedBytes, ruleSet); return(new Rfc3161TimestampRequest { _encodedBytes = writer.Encode(), _parsedData = req, }); } }
protected abstract string?FormatNative(Oid?oid, byte[] rawData, bool multiLine);
public static PublicKey ParsePublicKeyFromX509Format(byte[] x509SubjectPublicKeyInfoBlob) { Oid? Oid = null; AsnEncodedData?Parameters = null; AsnEncodedData?KeyValue = null; using MemoryStream Stream = new MemoryStream(x509SubjectPublicKeyInfoBlob); using BinaryReader Reader = new BinaryReader(Stream); try { while (Stream.Position < Stream.Length) { byte Token = Reader.ReadByte(); switch (Token) { case 0x30: // SEQUENCE case 0x03: // BIT STRING case 0x06: // OID byte PayloadLength = Reader.ReadByte(); if (PayloadLength > 0x7F) { throw new InvalidOperationException($"Payload lengths > 0x74 are not supported. Found at position [{Stream.Position - 1}]"); } if (Token == 0x30) { continue; } byte UnusedBits = 0; if (Token == 0x03) { UnusedBits = Reader.ReadByte(); if (UnusedBits > 0) { throw new InvalidOperationException($"Bit strings with unused bits are not supported. Found at position [{Stream.Position - 1}]"); } PayloadLength--; } byte[] Payload = new byte[PayloadLength]; int BytesRead = Reader.Read(Payload, 0, Payload.Length); if (BytesRead != PayloadLength) { throw new InvalidOperationException($"Payload length did not match. Found at position [{Stream.Position - 1}]"); } if (Token == 0x06) { if (Oid == null) { Oid = new Oid(ConvertOidByteArrayToStringValue(Payload)); } else { Parameters = new AsnEncodedData(Payload); } } if (Token == 0x03) { KeyValue = new AsnEncodedData(Payload); } break; default: throw new InvalidOperationException($"Unknown token byte [{Token:X2}] found at position [{Stream.Position - 1}]."); } } if (Oid == null || Parameters == null || KeyValue == null) { throw new InvalidOperationException("Required information could not be found in blob."); } return(new PublicKey(Oid, Parameters, KeyValue)); } catch (Exception ParseException) { throw new InvalidOperationException("Public key could not be parsed from X.509 format blob.", ParseException); } }