Пример #1
0
        /// <summary>
        /// Attempts to read and validate the next header field in the data stream.
        /// </summary>
        /// <param name="reader">A reader of the document file.</param>
        /// <param name="headerData">The header data that has been extracted so far.</param>
        /// <returns>A Task representing the field that was read.</returns>
        private async Task <OuterHeaderField> ReadOuterHeaderField(DataReader reader, KdbxHeaderData headerData)
        {
            // A header is guaranteed to have 3 (or 5) bytes at the beginning.
            // The first byte represents the type or ID of the header.
            // The next two (or four) bytes represent a 16-bit unsigned integer giving the size of the data field.
            uint sizeBytes = this.parameters.HeaderFieldSizeBytes;

            await reader.LoadAsync(1U + sizeBytes);

            // Read the header ID from the first byte
            var fieldId = (OuterHeaderField)reader.ReadByte();

            // Read the header data field size from the next two bytes
            uint size;

            if (sizeBytes == 2)
            {
                size = reader.ReadUInt16();
            }
            else
            {
                DebugHelper.Assert(sizeBytes == 4);
                size = reader.ReadUInt32();
            }

            LoggingFields headerTraceFields = new LoggingFields();

            headerTraceFields.AddString("FieldId", fieldId.ToString());
            headerTraceFields.AddUInt32("Bytes", size);
            this.logger.LogEvent("KdbxReader.OuterHeaderField", headerTraceFields, EventVerbosity.Info);
            await reader.LoadAsync(size);

            // The cast above may have succeeded but still resulted in an unknown value (outside of the enum).
            // If so, we need to bail.
            if (!Enum.IsDefined(typeof(OuterHeaderField), fieldId))
            {
                throw new KdbxParseException(ReaderResult.FromHeaderFieldUnknown((byte)fieldId));
            }

            MemberInfo memberInfo = typeof(OuterHeaderField).GetMember(fieldId.ToString())
                                    .FirstOrDefault();

            DebugHelper.Assert(memberInfo != null);
            KdbxVersionSupportAttribute versionAttr = memberInfo.GetCustomAttribute <KdbxVersionSupportAttribute>();

            if (versionAttr != null)
            {
                DebugHelper.Trace($"Found version attribute for header: {versionAttr}");
                DebugHelper.Assert(versionAttr.Supports(this.parameters.Version));
            }

            byte[] data = new byte[size];
            reader.ReadBytes(data);

            // Based on the header field in question, the data is validated differently.
            // The size of the data field needs to be validated, and the data itself may need to be parsed.
            switch (fieldId)
            {
            case OuterHeaderField.EndOfHeader:
                break;

            case OuterHeaderField.CipherID:
                RequireFieldDataSizeEq(fieldId, 16, size);

                Guid cipherGuid = new Guid(data);
                if (cipherGuid.Equals(AesCipher.Uuid))
                {
                    headerData.Cipher    = EncryptionAlgorithm.Aes;
                    this.expectedIvBytes = AesCipher.IvBytes;
                }
                else if (cipherGuid.Equals(ChaCha20Cipher.Uuid))
                {
                    DebugHelper.Assert(this.parameters.Version == KdbxVersion.Four);
                    headerData.Cipher    = EncryptionAlgorithm.ChaCha20;
                    this.expectedIvBytes = ChaCha20Cipher.IvBytes;
                }
                else
                {
                    // If we get here, the cipher provided is not supported.
                    throw new KdbxParseException(ReaderResult.FromHeaderDataUnknown(fieldId, cipherGuid.ToString()));
                }

                break;

            case OuterHeaderField.CompressionFlags:
                RequireFieldDataSizeEq(fieldId, 4, size);
                headerData.Compression      = (CompressionAlgorithm)BitConverter.ToUInt32(data, 0);
                this.parameters.Compression = headerData.Compression;
                RequireEnumDefined(fieldId, headerData.Compression);
                break;

            case OuterHeaderField.MasterSeed:
                RequireFieldDataSizeEq(fieldId, 32, size);
                headerData.MasterSeed = CryptographicBuffer.CreateFromByteArray(data);
                break;

            case OuterHeaderField.TransformSeed:
                RequireFieldDataSizeEq(fieldId, 32, size);
                headerData.TransformSeed = CryptographicBuffer.CreateFromByteArray(data);
                break;

            case OuterHeaderField.TransformRounds:
                RequireFieldDataSizeEq(fieldId, 8, size);
                headerData.TransformRounds = BitConverter.ToUInt64(data, 0);
                break;

            case OuterHeaderField.EncryptionIV:
                RequireFieldDataSizeEq(fieldId, this.expectedIvBytes, size);
                headerData.EncryptionIV = CryptographicBuffer.CreateFromByteArray(data);
                break;

            case OuterHeaderField.ProtectedStreamKey:
                RequireFieldDataSize(fieldId, size, (n) => n > 0, "must be nonzero");
                headerData.InnerRandomStreamKey = data;
                break;

            case OuterHeaderField.StreamStartBytes:
                headerData.StreamStartBytes = CryptographicBuffer.CreateFromByteArray(data);
                break;

            case OuterHeaderField.InnerRandomStreamID:
                RequireFieldDataSizeEq(fieldId, 4, size);
                headerData.InnerRandomStream = (RngAlgorithm)BitConverter.ToUInt32(data, 0);
                RequireEnumDefined(fieldId, headerData.InnerRandomStream);
                break;

            case OuterHeaderField.KdfParameters:
                try
                {
                    using (IInputStream memStream = new MemoryStream(data).AsInputStream())
                    {
                        using (DataReader vdReader = GetReaderForStream(memStream))
                        {
                            VariantDictionary kdfParamDict = await VariantDictionary.ReadDictionaryAsync(vdReader);

                            KdfParameters kdfParams = new KdfParameters(kdfParamDict);

                            if (kdfParams.Uuid == AesParameters.AesUuid)
                            {
                                headerData.KdfParameters = new AesParameters(kdfParamDict);
                            }
                            else if (kdfParams.Uuid == Argon2Parameters.Argon2Uuid)
                            {
                                headerData.KdfParameters = new Argon2Parameters(kdfParamDict);
                            }
                            else
                            {
                                throw new FormatException($"Unknown KDF UUID: {kdfParams.Uuid}");
                            }
                        }
                    }
                }
                catch (FormatException ex)
                {
                    throw new KdbxParseException(ReaderResult.FromBadVariantDictionary(ex.Message));
                }
                break;

            case OuterHeaderField.PublicCustomData:
                try
                {
                    VariantDictionary customParams = await VariantDictionary.ReadDictionaryAsync(reader);
                }
                catch (FormatException ex)
                {
                    throw new KdbxParseException(ReaderResult.FromBadVariantDictionary(ex.Message));
                }
                break;
            }

            return(fieldId);
        }
Пример #2
0
        /// <summary>
        /// Attempts to read and validate the next header field in the data stream.
        /// </summary>
        /// <param name="reader">A reader over the document file.</param>
        /// <param name="headerData">The header data that has been extracted so far.</param>
        /// <returns>A Task representing the field that was read.</returns>
        private async Task <InnerHeaderField> ReadInnerHeaderField(DataReader reader, KdbxHeaderData headerData)
        {
            DebugHelper.Assert(this.parameters.UseInnerHeader);

            // A header is guaranteed to have 5 bytes at the beginning.
            // The first byte represents the type or ID of the header.
            // The next four bytes represent a 32-bit signed integer giving the size of the data field.
            await reader.LoadAsync(5U);

            // Read the header ID from the first byte
            InnerHeaderField fieldId = (InnerHeaderField)reader.ReadByte();

            // The cast above may have succeeded but still resulted in an unknown value (outside of the enum).
            // If so, we need to bail.
            if (!Enum.IsDefined(typeof(InnerHeaderField), fieldId))
            {
                throw new KdbxParseException(ReaderResult.FromHeaderFieldUnknown((byte)fieldId));
            }

            // Read the header data field size from the next two bytes
            uint size = reader.ReadUInt32();

            DebugHelper.Assert(size <= Int32.MaxValue, "Size should be an Int32");

            LoggingFields headerTraceFields = new LoggingFields();

            headerTraceFields.AddString("FieldId", fieldId.ToString());
            headerTraceFields.AddUInt32("Bytes", size);
            this.logger.LogEvent("KdbxReader.InnerHeaderField", headerTraceFields, EventVerbosity.Info);
            await reader.LoadAsync(size);

            byte[] data = new byte[size];
            reader.ReadBytes(data);

            // Based on the header field in question, the data is validated differently.
            // The size of the data field needs to be validated, and the data itself may need to be parsed.
            switch (fieldId)
            {
            case InnerHeaderField.EndOfHeader:
                break;

            case InnerHeaderField.InnerRandomStreamKey:
                RequireFieldDataSize(fieldId, size, (n) => n > 0, "must be nonzero");
                headerData.InnerRandomStreamKey = data;
                break;

            case InnerHeaderField.InnerRandomStreamID:
                RequireFieldDataSizeEq(fieldId, 4, size);
                headerData.InnerRandomStream = (RngAlgorithm)BitConverter.ToUInt32(data, 0);
                RequireEnumDefined(fieldId, headerData.InnerRandomStream);
                break;

            case InnerHeaderField.Binary:
                // Data := F || M where F is one byte and M is the binary content
                KdbxBinaryFlags flags = (KdbxBinaryFlags)data[0];
                ProtectedBinary bin   = new ProtectedBinary(
                    data,
                    1,
                    data.Length - 1,
                    flags.HasFlag(KdbxBinaryFlags.MemoryProtected)
                    );

                headerData.ProtectedBinaries.Add(bin);

                break;
            }

            return(fieldId);
        }