Example #1
0
        // 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));
        }
Example #2
0
        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());
        }
Example #3
0
        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());
        }
Example #4
0
        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());
        }
Example #5
0
        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);
        }