Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions src/Core/Vault/Entities/Cipher.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ο»Ώ// FIXME: Update this file to be null safe and then delete the line below
#nullable disable

using System.Text;
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Utilities;
Expand Down Expand Up @@ -39,8 +40,34 @@ public bool IsDataBlobEncrypted()
return false;
}

var span = Data.AsSpan().TrimStart();
return span.Length > 0 && span[0] != '{';
try
{
var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(Data));

// Blob-encrypted data is a JSON object carrying a top-level "format_version"
// key; legacy field-level CipherData JSON never contains it. Probe only the
// top-level properties rather than materializing the whole document.
if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
{
return false;
}

while (reader.Read() && reader.TokenType == JsonTokenType.PropertyName)
{
if (reader.ValueTextEquals("format_version"))
{
return true;
}

reader.Skip();
}

return false;
}
catch (JsonException)
{
return false;
}
}

public Dictionary<string, CipherAttachment.MetaData> GetAttachments()
Expand Down
14 changes: 9 additions & 5 deletions test/Core.Test/Vault/Models/CipherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,21 @@ public void Clone_OrganizationCipher_CreatesExactCopy(Cipher cipher)
[InlineData(" ")]
[InlineData("{\"Name\":\"x\"}")]
[InlineData(" { \"Name\": \"x\" }")]
public void IsDataBlobEncrypted_LegacyOrEmpty_ReturnsFalse(string? data)
[InlineData("2.iv|ct|mac")]
[InlineData("plain string")]
[InlineData(" 2.iv|ct|mac")]
[InlineData("{\"other_key\":1}")]
[InlineData("\"format_version\"")]
public void IsDataBlobEncrypted_LegacyEmptyOrInvalid_ReturnsFalse(string? data)
{
var cipher = new Cipher { Data = data! };
Assert.False(cipher.IsDataBlobEncrypted());
}

[Theory]
[InlineData("2.iv|ct|mac")]
[InlineData("plain string")]
[InlineData(" 2.iv|ct|mac")]
public void IsDataBlobEncrypted_OpaqueData_ReturnsTrue(string data)
[InlineData("{\"format_version\":1,\"wrapped_cek\":\"abc\",\"envelope\":\"def\"}")]
[InlineData(" { \"format_version\": 1, \"wrapped_cek\": \"abc\", \"envelope\": \"def\" }")]
public void IsDataBlobEncrypted_BlobData_ReturnsTrue(string data)
{
var cipher = new Cipher { Data = data };
Assert.True(cipher.IsDataBlobEncrypted());
Expand Down
Loading