// DER -> Hash private static KeyValuePair <Common.HashAlgorithmName, byte[]> GetESSCertIDv2Entry(DerSequenceReader reader) { var hashAlgorithm = CryptoHashUtility.OidToHashAlgorithmName(reader.ReadOidAsString()); var attributeHashValue = reader.ReadOctetString(); return(new KeyValuePair <Common.HashAlgorithmName, byte[]>(hashAlgorithm, attributeHashValue)); }
public static void InvalidLengthSpecified(string hexInput) { byte[] bytes = hexInput.HexToByteArray(); DerSequenceReader reader = DerSequenceReader.CreateForPayload(bytes); // Doesn't throw. reader.PeekTag(); // Since EatTag will have succeeded the reader needs to be reconstructed after each test. Assert.Throws <CryptographicException>(() => reader.SkipValue()); reader = DerSequenceReader.CreateForPayload(bytes); Assert.Throws <CryptographicException>(() => reader.ReadOctetString()); reader = DerSequenceReader.CreateForPayload(bytes); Assert.Throws <CryptographicException>(() => reader.ReadNextEncodedValue()); }
public static void InteriorLengthTooLong_Nested() { byte[] bytes = { // CONSTRUCTED SEQUENCE (9 bytes) 0x30, 0x09, // CONSTRUCTED SEQUENCE (2 bytes) 0x30, 0x02, // OCTET STRING (1 byte, but 0 remain for the inner sequence) 0x04, 0x01, // OCTET STRING (in the outer sequence, after the inner sequence, 3 bytes) 0x04, 0x03, 0x01, 0x02, 0x03 }; DerSequenceReader reader = new DerSequenceReader(bytes); DerSequenceReader nested = reader.ReadSequence(); Assert.Throws <CryptographicException>(() => nested.ReadOctetString()); }
public static void InteriorLengthTooLong() { byte[] bytes = { // CONSTRUCTED SEQUENCE (8 bytes) 0x30, 0x08, // CONSTRUCTED SEQUENCE (2 bytes) 0x30, 0x02, // OCTET STRING (0 bytes) 0x04, 0x00, // OCTET STRING (after the inner sequence, 3 bytes, but that exceeds the sequence bounds) 0x04, 0x03, 0x01, 0x02, 0x03 }; DerSequenceReader reader = new DerSequenceReader(bytes); DerSequenceReader nested = reader.ReadSequence(); Assert.Equal(0, nested.ReadOctetString().Length); Assert.False(nested.HasData); Assert.Throws <CryptographicException>(() => reader.ReadOctetString()); }
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); } }
private static DerSequenceReader ReadEncryptedPkcs8Blob(string passphrase, DerSequenceReader reader) { // EncryptedPrivateKeyInfo::= SEQUENCE { // encryptionAlgorithm EncryptionAlgorithmIdentifier, // encryptedData EncryptedData } // // EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier // // EncryptedData ::= OCTET STRING DerSequenceReader algorithmIdentifier = reader.ReadSequence(); string algorithmOid = algorithmIdentifier.ReadOidAsString(); // PBES2 (Password-Based Encryption Scheme 2) if (algorithmOid != OidPbes2) { Debug.Fail($"Expected PBES2 ({OidPbes2}), got {algorithmOid}"); throw new CryptographicException(); } // PBES2-params ::= SEQUENCE { // keyDerivationFunc AlgorithmIdentifier { { PBES2 - KDFs} }, // encryptionScheme AlgorithmIdentifier { { PBES2 - Encs} } // } DerSequenceReader pbes2Params = algorithmIdentifier.ReadSequence(); algorithmIdentifier = pbes2Params.ReadSequence(); string kdfOid = algorithmIdentifier.ReadOidAsString(); // PBKDF2 (Password-Based Key Derivation Function 2) if (kdfOid != OidPbkdf2) { Debug.Fail($"Expected PBKDF2 ({OidPbkdf2}), got {kdfOid}"); throw new CryptographicException(); } // PBKDF2-params ::= SEQUENCE { // salt CHOICE { // specified OCTET STRING, // otherSource AlgorithmIdentifier { { PBKDF2 - SaltSources} } // }, // iterationCount INTEGER (1..MAX), // keyLength INTEGER(1..MAX) OPTIONAL, // prf AlgorithmIdentifier { { PBKDF2 - PRFs} } DEFAULT algid - hmacWithSHA1 // } DerSequenceReader pbkdf2Params = algorithmIdentifier.ReadSequence(); byte[] salt = pbkdf2Params.ReadOctetString(); int iterCount = pbkdf2Params.ReadInteger(); int keySize = -1; if (pbkdf2Params.HasData && pbkdf2Params.PeekTag() == (byte)DerSequenceReader.DerTag.Integer) { keySize = pbkdf2Params.ReadInteger(); } if (pbkdf2Params.HasData) { string prfOid = pbkdf2Params.ReadOidAsString(); // SHA-1 is the only hash algorithm our PBKDF2 supports. if (prfOid != OidSha1) { Debug.Fail($"Expected SHA1 ({OidSha1}), got {prfOid}"); throw new CryptographicException(); } } DerSequenceReader encryptionScheme = pbes2Params.ReadSequence(); string cipherOid = encryptionScheme.ReadOidAsString(); // DES-EDE3-CBC (TripleDES in CBC mode) if (cipherOid != OidTripleDesCbc) { Debug.Fail($"Expected DES-EDE3-CBC ({OidTripleDesCbc}), got {cipherOid}"); throw new CryptographicException(); } byte[] decrypted; using (TripleDES des3 = TripleDES.Create()) { if (keySize == -1) { foreach (KeySizes keySizes in des3.LegalKeySizes) { keySize = Math.Max(keySize, keySizes.MaxSize); } } byte[] iv = encryptionScheme.ReadOctetString(); using (Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(passphrase, salt, iterCount)) using (ICryptoTransform decryptor = des3.CreateDecryptor(pbkdf2.GetBytes(keySize / 8), iv)) { byte[] encrypted = reader.ReadOctetString(); decrypted = decryptor.TransformFinalBlock(encrypted, 0, encrypted.Length); } } DerSequenceReader pkcs8Reader = new DerSequenceReader(decrypted); return(pkcs8Reader); }