public static string Name(this GeneralNameType type) { switch (type) { default: throw new NotSupportedException(); case GeneralNameType.OtherName: return(GeneralNameOtherName); case GeneralNameType.RFC822Name: return(RFC822Name); case GeneralNameType.DNSName: return(DNSName); case GeneralNameType.X400Address: return(X400Address); case GeneralNameType.DirectoryName: return(DirectoryName); case GeneralNameType.EdiPartyName: return(EdiPartyName); case GeneralNameType.UniformResourceIdentifier: return(UniformResourceIdentifier); case GeneralNameType.IPAddress: return(IPAddress); case GeneralNameType.RegisteredId: return(RegisteredId); } }
private static string FindAltNameMatch(byte[] extensionBytes, GeneralNameType matchType, string otherOid) { // If Other, have OID, else, no OID. Debug.Assert( (otherOid == null) == (matchType != GeneralNameType.OtherName), $"otherOid has incorrect nullarity for matchType {matchType}"); Debug.Assert( matchType == GeneralNameType.UniformResourceIdentifier || matchType == GeneralNameType.DnsName || matchType == GeneralNameType.Email || matchType == GeneralNameType.OtherName, $"matchType ({matchType}) is not currently supported"); Debug.Assert( otherOid == null || otherOid == Oids.UserPrincipalName, $"otherOid ({otherOid}) is not supported"); AsnReader reader = new AsnReader(extensionBytes, AsnEncodingRules.DER); AsnReader sequenceReader = reader.ReadSequence(); reader.ThrowIfNotEmpty(); while (sequenceReader.HasData) { GeneralNameAsn.Decode(sequenceReader, out GeneralNameAsn generalName); switch (matchType) { case GeneralNameType.OtherName: // If the OtherName OID didn't match, move to the next entry. if (generalName.OtherName.HasValue && generalName.OtherName.Value.TypeId == otherOid) { // Currently only UPN is supported, which is a UTF8 string per // https://msdn.microsoft.com/en-us/library/ff842518.aspx AsnReader nameReader = new AsnReader(generalName.OtherName.Value.Value, AsnEncodingRules.DER); string udnName = nameReader.ReadCharacterString(UniversalTagNumber.UTF8String); nameReader.ThrowIfNotEmpty(); return(udnName); } break; case GeneralNameType.Rfc822Name: if (generalName.Rfc822Name != null) { return(generalName.Rfc822Name); } break; case GeneralNameType.DnsName: if (generalName.DnsName != null) { return(generalName.DnsName); } break; case GeneralNameType.UniformResourceIdentifier: if (generalName.Uri != null) { return(generalName.Uri); } break; } } return(null); }
private static string FindAltNameMatch(byte[] extensionBytes, GeneralNameType matchType, string otherOid) { // If Other, have OID, else, no OID. Debug.Assert( (otherOid == null) == (matchType != GeneralNameType.OtherName), $"otherOid has incorrect nullarity for matchType {matchType}"); Debug.Assert( matchType == GeneralNameType.UniformResourceIdentifier || matchType == GeneralNameType.DnsName || matchType == GeneralNameType.Email || matchType == GeneralNameType.OtherName, $"matchType ({matchType}) is not currently supported"); Debug.Assert( otherOid == null || otherOid == Oids.UserPrincipalName, $"otherOid ({otherOid}) is not supported"); // SubjectAltName ::= GeneralNames // // IssuerAltName ::= GeneralNames // // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName // // GeneralName ::= CHOICE { // otherName [0] OtherName, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER } // // OtherName::= SEQUENCE { // type - id OBJECT IDENTIFIER, // value[0] EXPLICIT ANY DEFINED BY type - id } byte expectedTag = (byte)(DerSequenceReader.ContextSpecificTagFlag | (byte)matchType); if (matchType == GeneralNameType.OtherName) { expectedTag |= DerSequenceReader.ConstructedFlag; } DerSequenceReader altNameReader = new DerSequenceReader(extensionBytes); while (altNameReader.HasData) { if (altNameReader.PeekTag() != expectedTag) { altNameReader.SkipValue(); continue; } switch (matchType) { case GeneralNameType.OtherName: { DerSequenceReader otherNameReader = altNameReader.ReadSequence(); string oid = otherNameReader.ReadOidAsString(); if (oid == otherOid) { // Payload is value[0] EXPLICIT, meaning // a) it'll be tagged as ContextSpecific0 // b) that's interpretable as a Sequence (EXPLICIT) // c) the payload will then be retagged as the correct type (EXPLICIT) if (otherNameReader.PeekTag() != DerSequenceReader.ContextSpecificConstructedTag0) { throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding); } otherNameReader = otherNameReader.ReadSequence(); // Currently only UPN is supported, which is a UTF8 string per // https://msdn.microsoft.com/en-us/library/ff842518.aspx return(otherNameReader.ReadUtf8String()); } // If the OtherName OID didn't match, move to the next entry. continue; } case GeneralNameType.Rfc822Name: case GeneralNameType.DnsName: case GeneralNameType.UniformResourceIdentifier: return(altNameReader.ReadIA5String()); default: altNameReader.SkipValue(); continue; } } return(null); }
private string FormatSubjectAlternativeName(byte[] rawData) { // Because SubjectAlternativeName is a commonly parsed structure, we'll // specifically format this one. And we'll match the OpenSSL format, which // includes not localizing any of the values (or respecting the multiLine boolean) // // The intent here is to be functionally equivalent to OpenSSL GENERAL_NAME_print. // The end size of this string is hard to predict. // * dNSName values have a tag that takes four characters to represent ("DNS:") // and then their payload is ASCII encoded (so one byte -> one char), so they // work out to be about equal (in chars) to their DER encoded length (in bytes). // * iPAddress values have a tag that takes 11 characters ("IP Address:") and then // grow from 4 bytes to up to 15 characters for IPv4, or 16 bytes to 47 characters // for IPv6 // // So use a List<string> and just Concat them all when we're done, and we reduce the // number of times we copy the header values (vs pointers to the header values). List <string> segments = new List <string>(); try { // SubjectAltName ::= GeneralNames // // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName // // GeneralName ::= CHOICE { // otherName [0] OtherName, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER } // // OtherName::= SEQUENCE { // type - id OBJECT IDENTIFIER, // value[0] EXPLICIT ANY DEFINED BY type - id } DerSequenceReader altNameReader = new DerSequenceReader(rawData); while (altNameReader.HasData) { if (segments.Count != 0) { segments.Add(CommaSpace); } byte tag = altNameReader.PeekTag(); if ((tag & DerSequenceReader.ContextSpecificTagFlag) == 0) { // All GeneralName values need the ContextSpecific flag. return(null); } GeneralNameType nameType = (GeneralNameType)(tag & DerSequenceReader.TagNumberMask); bool needsConstructedFlag = false; switch (nameType) { case GeneralNameType.OtherName: case GeneralNameType.X400Address: case GeneralNameType.DirectoryName: case GeneralNameType.EdiPartyName: needsConstructedFlag = true; break; } if (needsConstructedFlag && (tag & DerSequenceReader.ConstructedFlag) == 0) { // All of the SEQUENCE types require the constructed bit, // or OpenSSL will have refused to print it. return(null); } switch (nameType) { case GeneralNameType.OtherName: segments.Add("othername:<unsupported>"); altNameReader.SkipValue(); break; case GeneralNameType.Rfc822Name: segments.Add("email:"); segments.Add(altNameReader.ReadIA5String()); break; case GeneralNameType.DnsName: segments.Add("DNS:"); segments.Add(altNameReader.ReadIA5String()); break; case GeneralNameType.X400Address: segments.Add("X400Name:<unsupported>"); altNameReader.SkipValue(); break; case GeneralNameType.DirectoryName: // OpenSSL supports printing one of these, but the logic lives in X509Certificates, // and it isn't very common. So we'll skip this one until someone asks for it. segments.Add("DirName:<unsupported>"); altNameReader.SkipValue(); break; case GeneralNameType.EdiPartyName: segments.Add("EdiPartyName:<unsupported>"); altNameReader.SkipValue(); break; case GeneralNameType.UniformResourceIdentifier: segments.Add("URI:"); segments.Add(altNameReader.ReadIA5String()); break; case GeneralNameType.IPAddress: segments.Add("IP Address"); byte[] ipAddressBytes = altNameReader.ReadOctetString(); if (ipAddressBytes.Length == 4) { // Add the colon and dotted-decimal representation of IPv4. segments.Add( $":{ipAddressBytes[0]}.{ipAddressBytes[1]}.{ipAddressBytes[2]}.{ipAddressBytes[3]}"); } else if (ipAddressBytes.Length == 16) { // Print the IP Address value as colon separated UInt16 hex values without leading zeroes. // 20 01 0D B8 AC 10 FE 01 00 00 00 00 00 00 00 00 // // IP Address:2001:DB8:AC10:FE01:0:0:0:0 for (int i = 0; i < ipAddressBytes.Length; i += 2) { segments.Add($":{ipAddressBytes[i] << 8 | ipAddressBytes[i + 1]:X}"); } } else { segments.Add(":<invalid>"); } break; case GeneralNameType.RegisteredId: segments.Add("Registered ID:"); segments.Add(altNameReader.ReadOidAsString()); break; default: // A new extension to GeneralName could legitimately hit this, // but it's correct to say that until we know what that is that // the pretty-print has failed, and we should fall back to hex. // // But it could also simply be poorly encoded user data. return(null); } } return(string.Concat(segments)); } catch (CryptographicException) { return(null); } }