/// <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}" )); }
/// <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}" )); }
/// <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())); } }
/// <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)); }
/// <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); } }
/// <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)); }
private void WriteFieldId(DataWriter writer, OuterHeaderField field) { writer.WriteByte((byte)field); }