Exemple #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);
        }
Exemple #2
0
        /// <summary>
        /// Writes out the KDBX header.
        /// </summary>
        /// <param name="writer">The DataWriter to write to.</param>
        /// <returns>A Task representing the asynchronous operation.</returns>
        private async Task WriteOuterHeaderAsync(DataWriter writer)
        {
            WriteFieldId(writer, OuterHeaderField.CipherID);
            WriteFieldSize(writer, 16);
            writer.WriteBytes(GetCipherUuid(HeaderData.Cipher).ToByteArray());
            await writer.StoreAsync();

            WriteFieldId(writer, OuterHeaderField.CompressionFlags);
            WriteFieldSize(writer, 4);
            writer.WriteUInt32((uint)HeaderData.Compression);
            await writer.StoreAsync();

            WriteFieldId(writer, OuterHeaderField.MasterSeed);
            WriteFieldSize(writer, 32);
            writer.WriteBuffer(HeaderData.MasterSeed);
            await writer.StoreAsync();

            if (!this.parameters.UseExtensibleKdf)
            {
                AesParameters kdfParams = HeaderData.KdfParameters as AesParameters;
                DebugHelper.Assert(kdfParams != null);

                WriteFieldId(writer, OuterHeaderField.TransformSeed);
                WriteFieldSize(writer, 32);
                writer.WriteBuffer(kdfParams.Seed);
                await writer.StoreAsync();

                WriteFieldId(writer, OuterHeaderField.TransformRounds);
                WriteFieldSize(writer, 8);
                writer.WriteUInt64(kdfParams.Rounds);
                await writer.StoreAsync();
            }
            else
            {
                WriteFieldId(writer, OuterHeaderField.KdfParameters);

                VariantDictionary kdfDict = HeaderData.KdfParameters.ToVariantDictionary();
                WriteFieldSize(writer, (uint)kdfDict.GetSerializedSize());
                await kdfDict.WriteToAsync(writer);
            }

            WriteFieldId(writer, OuterHeaderField.EncryptionIV);
            WriteFieldSize(writer, HeaderData.EncryptionIV.Length);
            writer.WriteBuffer(HeaderData.EncryptionIV);
            await writer.StoreAsync();

            if (!this.parameters.UseHmacBlocks)
            {
                WriteFieldId(writer, OuterHeaderField.StreamStartBytes);
                WriteFieldSize(writer, HeaderData.StreamStartBytes.Length);
                writer.WriteBuffer(HeaderData.StreamStartBytes);
                await writer.StoreAsync();
            }

            if (!this.parameters.UseInnerHeader)
            {
                WriteFieldId(writer, OuterHeaderField.ProtectedStreamKey);
                WriteFieldSize(writer, 32);
                writer.WriteBytes(HeaderData.InnerRandomStreamKey);
                await writer.StoreAsync();

                WriteFieldId(writer, OuterHeaderField.InnerRandomStreamID);
                WriteFieldSize(writer, 4);
                writer.WriteUInt32((uint)HeaderData.InnerRandomStream);
                await writer.StoreAsync();
            }

            WriteFieldId(writer, OuterHeaderField.EndOfHeader);
            WriteFieldSize(writer, 4);
            writer.WriteByte(0x0D);
            writer.WriteByte(0x0A);
            writer.WriteByte(0x0D);
            writer.WriteByte(0x0A);
            await writer.StoreAsync();
        }