Beispiel #1
0
 /// <summary>
 /// Represents a parse error due to header data that could not be interpreted.
 /// </summary>
 /// <param name="field">Identifier for the problematic header.</param>
 /// <param name="value">The unsupported header value.</param>
 /// <returns>A ReaderResult representing a parse error due to header data that could not be parsed.</returns>
 public static ReaderResult FromHeaderDataUnknown(OuterHeaderField field, string value)
 {
     return(new ReaderResult(
                KdbxParserCode.HeaderDataUnknown,
                $"field: {field}, value: {value}"
                ));
 }
Beispiel #2
0
 /// <summary>
 /// Represents a parse error due to a header data field being the wrong size.
 /// </summary>
 /// <param name="field">Identifier for the problematic header.</param>
 /// <param name="bytesReceived">The number of bytes actually in the header data.</param>
 /// <param name="requirement">The size requirement in English.</param>
 /// <returns>A ReaderResult representing a parse error due to a bad header data size.</returns>
 public static ReaderResult FromHeaderDataSize(OuterHeaderField field, uint bytesReceived, string requirement)
 {
     return(new ReaderResult(
                KdbxParserCode.HeaderDataSize,
                $"field: {field}, sizeReq: {requirement}, got: {bytesReceived}"
                ));
 }
Beispiel #3
0
 /// <summary>
 /// Validates that the specified enum value is defined in the provided enum.
 /// </summary>
 /// <typeparam name="T">An enum type to validate against.</typeparam>
 /// <param name="field">The header field to validated.</param>
 /// <param name="value">The enum value to validate.</param>
 private void RequireEnumDefined <T>(OuterHeaderField field, T value)
     where T : struct
 {
     if (!Enum.IsDefined(typeof(T), value))
     {
         throw new KdbxParseException(ReaderResult.FromHeaderDataUnknown(field, value.ToString()));
     }
 }
Beispiel #4
0
        /// <summary>
        /// Validates that the specified header field has a size matching a provided requirement. Throws on failure.
        /// </summary>
        /// <param name="field">The header field to validate.</param>
        /// <param name="size">The size of the data field.</param>
        /// <param name="requirement">An evaluator function that returns whether the size is valid.</param>
        /// <param name="explanation">A String explanation of the requirement.</param>
        private void RequireFieldDataSize(OuterHeaderField field, uint size, Predicate <uint> requirement, string explanation)
        {
            bool result = requirement(size);

            if (result)
            {
                return;
            }

            throw new KdbxParseException(ReaderResult.FromHeaderDataSize(field, size, explanation));
        }
Beispiel #5
0
        /// <summary>
        /// Attempts to load KDBX header data from the provided data stream.
        /// </summary>
        /// <remarks>
        /// On success, the HeaderData property of this KdbxReader will be populated.
        /// On failure, it will be null.
        /// </remarks>
        /// <param name="stream">A stream representing a KDBX document (or header).</param>
        /// <param name="token">A token allowing the operation to be cancelled.</param>
        /// <returns>A Task representing the result of the read operation.</returns>
        public async Task <ReaderResult> ReadHeaderAsync(IRandomAccessStream stream, CancellationToken token)
        {
            HeaderData = null;

            using (DataReader reader = GetReaderForStream(stream))
            {
                ReaderResult result = await ValidateSignature(reader);

                if (result != ReaderResult.Success)
                {
                    reader.DetachStream();
                    return(result);
                }

                result = await ValidateVersion(reader);

                if (result != ReaderResult.Success)
                {
                    reader.DetachStream();
                    return(result);
                }

                // If we get this far, we've passed basic sanity checks.
                // Construct a HeaderData entity and start reading fields.
                KdbxHeaderData headerData = new KdbxHeaderData(KdbxHeaderData.Mode.Read);

                bool gotEndOfHeader = false;
                while (!gotEndOfHeader)
                {
                    if (token.IsCancellationRequested)
                    {
                        return(new ReaderResult(KdbxParserCode.OperationCancelled));
                    }

                    try
                    {
                        OuterHeaderField field = await ReadOuterHeaderField(reader, headerData);

                        if (field == OuterHeaderField.EndOfHeader)
                        {
                            gotEndOfHeader = true;
                        }

                        this.headerInitializationMap[field] = true;
                    }
                    catch (KdbxParseException e)
                    {
                        reader.DetachStream();
                        return(e.Error);
                    }
                }

                // Ensure all headers are initialized
                bool gotAllHeaders = this.headerInitializationMap.All(
                    kvp => this.headerInitializationMap[kvp.Key]
                    );

                if (!gotAllHeaders)
                {
                    reader.DetachStream();
                    return(new ReaderResult(KdbxParserCode.HeaderMissing));
                }

                // Hash entire header
                var sha256                  = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
                CryptographicHash hash      = sha256.CreateHash();
                ulong             streamPos = stream.Position;
                stream.Seek(0);
                await reader.LoadAsync((uint)streamPos);

                headerData.FullHeader = reader.ReadBuffer((uint)streamPos);

                hash.Append(headerData.FullHeader);
                IBuffer plainHeaderHash = hash.GetValueAndReset();

                if (this.parameters.UseXmlHeaderAuthentication)
                {
                    // The header was parsed successfully - finish creating the object and return success
                    headerData.HeaderHash = CryptographicBuffer.EncodeToBase64String(plainHeaderHash);
                    headerData.Size       = streamPos;
                }

                if (this.parameters.UseInlineHeaderAuthentication)
                {
                    // In KDBX4, the header hash is written directly after the header fields.
                    // After the unencrypted hash, an HMAC-SHA-256 hash of the header is written.
                    await reader.LoadAsync(32);

                    IBuffer existingPlainHash = reader.ReadBuffer(32);

                    // Validate plaintext hash
                    if (plainHeaderHash.Length != existingPlainHash.Length)
                    {
                        return(new ReaderResult(KdbxParserCode.BadHeaderHash));
                    }

                    for (uint i = 0; i < plainHeaderHash.Length; i++)
                    {
                        if (plainHeaderHash.GetByte(i) != existingPlainHash.GetByte(i))
                        {
                            return(new ReaderResult(KdbxParserCode.BadHeaderHash));
                        }
                    }

                    DebugHelper.Trace("Validated plaintext KDBX4 header hash");
                }

                if (this.parameters.Version <= KdbxVersion.Three && headerData.KdfParameters == null)
                {
                    // Need to manually populate KDF parameters for older versions
                    headerData.KdfParameters = new AesParameters(
                        headerData.TransformRounds,
                        headerData.TransformSeed
                        );
                }

                HeaderData = headerData;
                reader.DetachStream();
                return(ReaderResult.Success);
            }
        }
Beispiel #6
0
 /// <summary>
 /// Validates that the specified header field has a size matching a provided value. Throws on failure.
 /// </summary>
 /// <param name="field">The header field to validate.</param>
 /// <param name="expectedSize">The expected size of the data field.</param>
 /// <param name="actualSize">The actual size of the data field.</param>
 private void RequireFieldDataSizeEq(OuterHeaderField field, uint expectedSize, uint actualSize)
 {
     RequireFieldDataSize(field, expectedSize, (size) => (size == expectedSize), String.Format("expected: {0}", expectedSize));
 }
Beispiel #7
0
 private void WriteFieldId(DataWriter writer, OuterHeaderField field)
 {
     writer.WriteByte((byte)field);
 }