Beispiel #1
0
        /// <summary>
        /// Validates that the document has a valid, supported KeePass version.
        /// </summary>
        /// <param name="reader">A DataReader over the document file.</param>
        /// <returns>A Task representing the result of the validation.</returns>
        private async Task <ReaderResult> ValidateVersion(DataReader reader)
        {
            await reader.LoadAsync(4);

            uint version            = reader.ReadUInt32();
            uint maskedVersion      = version & FileVersionMask;
            uint maskedLegacyFormat = FileVersion32_3 & FileVersionMask;
            uint maskedModernFormat = FileVersion32_4 & FileVersionMask;

            if (maskedVersion <= maskedLegacyFormat)
            {
                this.parameters = new KdbxSerializationParameters(KdbxVersion.Three);
            }
            else if (maskedVersion == maskedModernFormat)
            {
                this.parameters = new KdbxSerializationParameters(KdbxVersion.Four);
            }
            else
            {
                DebugHelper.Assert(maskedVersion > maskedModernFormat);
                return(new ReaderResult(KdbxParserCode.Version));
            }

            // Based on the version, initialize a map of required headers
            foreach (OuterHeaderField value in Enum.GetValues(typeof(OuterHeaderField))
                     .Cast <OuterHeaderField>())
            {
                MemberInfo enumMember = typeof(OuterHeaderField).GetMember(value.ToString())
                                        .FirstOrDefault();

                // Skip optional headers regardless of version
                if (enumMember.GetCustomAttribute <OptionalAttribute>() != null)
                {
                    continue;
                }

                // Get the headers that support this version
                KdbxVersionSupportAttribute versionAttr = enumMember.GetCustomAttribute <KdbxVersionSupportAttribute>();
                if (versionAttr == null || versionAttr.Supports(this.parameters.Version))
                {
                    this.headerInitializationMap[value] = false;
                }
            }

            return(ReaderResult.Success);
        }
Beispiel #2
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);
        }