You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
494 lines
19 KiB
494 lines
19 KiB
using System.Text.Json; |
|
using System.Text.Json.Serialization; |
|
using Bit.Core.Vault.Enums; |
|
using Bit.Core.Vault.Models.Data; |
|
using Bit.RustSDK; |
|
using Bit.Seeder.Attributes; |
|
using Bit.Seeder.Factories; |
|
using Bit.Seeder.Models; |
|
using Xunit; |
|
|
|
namespace Bit.SeederApi.IntegrationTest; |
|
|
|
public sealed class RustSdkCipherTests |
|
{ |
|
private static readonly JsonSerializerOptions _sdkJsonOptions = new() |
|
{ |
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, |
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull |
|
}; |
|
|
|
[Fact] |
|
public void EncryptString_DecryptString_Roundtrip() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
var encrypted = RustSdkService.EncryptString("SuperSecretP@ssw0rd!", orgKeys.Key); |
|
|
|
Assert.StartsWith("2.", encrypted); |
|
Assert.Equal("SuperSecretP@ssw0rd!", RustSdkService.DecryptString(encrypted, orgKeys.Key)); |
|
} |
|
|
|
[Fact] |
|
public void EncryptFields_DecryptString_Roundtrip() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
|
|
var cipher = new CipherViewDto |
|
{ |
|
Name = "Test Login", |
|
Notes = "Secret notes about this login", |
|
Type = CipherTypes.Login, |
|
Login = new LoginViewDto |
|
{ |
|
Username = "testuser@example.com", |
|
Password = "SuperSecretP@ssw0rd!", |
|
Uris = [new LoginUriViewDto { Uri = "https://example.com" }] |
|
} |
|
}; |
|
|
|
var json = JsonSerializer.Serialize(cipher, _sdkJsonOptions); |
|
var fieldPathsJson = JsonSerializer.Serialize(EncryptPropertyAttribute.GetFieldPaths<CipherViewDto>()); |
|
var encryptedJson = RustSdkService.EncryptFields(json, fieldPathsJson, orgKeys.Key); |
|
|
|
using var doc = JsonDocument.Parse(encryptedJson); |
|
var root = doc.RootElement; |
|
|
|
var encryptedName = root.GetProperty("name").GetString()!; |
|
Assert.StartsWith("2.", encryptedName); |
|
|
|
var decryptedName = RustSdkService.DecryptString(encryptedName, orgKeys.Key); |
|
Assert.Equal("Test Login", decryptedName); |
|
|
|
var encryptedUsername = root.GetProperty("login").GetProperty("username").GetString()!; |
|
var decryptedUsername = RustSdkService.DecryptString(encryptedUsername, orgKeys.Key); |
|
Assert.Equal("testuser@example.com", decryptedUsername); |
|
|
|
var encryptedUri = root.GetProperty("login").GetProperty("uris")[0].GetProperty("uri").GetString()!; |
|
var decryptedUri = RustSdkService.DecryptString(encryptedUri, orgKeys.Key); |
|
Assert.Equal("https://example.com", decryptedUri); |
|
} |
|
|
|
[Fact] |
|
public void EncryptFields_NoPlaintextLeakage() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
|
|
var cipher = new CipherViewDto |
|
{ |
|
Name = "Amazon Shopping", |
|
Notes = "Prime member since 2020", |
|
Type = CipherTypes.Login, |
|
Login = new LoginViewDto |
|
{ |
|
Username = "shopper@example.com", |
|
Password = "MySecretPassword123!", |
|
Uris = |
|
[ |
|
new LoginUriViewDto { Uri = "https://amazon.com/login" }, |
|
new LoginUriViewDto { Uri = "https://www.amazon.com" } |
|
] |
|
} |
|
}; |
|
|
|
var json = JsonSerializer.Serialize(cipher, _sdkJsonOptions); |
|
var fieldPathsJson = JsonSerializer.Serialize(EncryptPropertyAttribute.GetFieldPaths<CipherViewDto>()); |
|
var encryptedJson = RustSdkService.EncryptFields(json, fieldPathsJson, orgKeys.Key); |
|
|
|
Assert.DoesNotContain("Amazon Shopping", encryptedJson); |
|
Assert.DoesNotContain("shopper@example.com", encryptedJson); |
|
Assert.DoesNotContain("MySecretPassword123!", encryptedJson); |
|
Assert.DoesNotContain("Prime member since 2020", encryptedJson); |
|
Assert.Contains("\"name\":\"2.", encryptedJson); |
|
} |
|
|
|
[Fact] |
|
public void DecryptString_WithWrongKey_Throws() |
|
{ |
|
var encryptionKey = RustSdkService.GenerateOrganizationKeys(); |
|
var differentKey = RustSdkService.GenerateOrganizationKeys(); |
|
|
|
var encrypted = RustSdkService.EncryptString("secret value", encryptionKey.Key); |
|
|
|
Assert.Throws<RustSdkException>(() => |
|
RustSdkService.DecryptString(encrypted, differentKey.Key)); |
|
} |
|
|
|
[Fact] |
|
public void EncryptFields_WithCustomFields_EncryptsFieldNameAndValue() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
|
|
var cipher = new CipherViewDto |
|
{ |
|
Name = "Service Account", |
|
Type = CipherTypes.Login, |
|
Login = new LoginViewDto |
|
{ |
|
Username = "service-account", |
|
Password = "svc-password" |
|
}, |
|
Fields = |
|
[ |
|
new FieldViewDto { Name = "API Key", Value = "sk_test_FAKE_api_key_12345", Type = 1 }, |
|
new FieldViewDto { Name = "Client ID", Value = "client-id-xyz", Type = 0 } |
|
] |
|
}; |
|
|
|
var json = JsonSerializer.Serialize(cipher, _sdkJsonOptions); |
|
var fieldPathsJson = JsonSerializer.Serialize(EncryptPropertyAttribute.GetFieldPaths<CipherViewDto>()); |
|
var encryptedJson = RustSdkService.EncryptFields(json, fieldPathsJson, orgKeys.Key); |
|
|
|
Assert.DoesNotContain("sk_test_FAKE_api_key_12345", encryptedJson); |
|
Assert.DoesNotContain("client-id-xyz", encryptedJson); |
|
|
|
using var doc = JsonDocument.Parse(encryptedJson); |
|
var fields = doc.RootElement.GetProperty("fields"); |
|
|
|
var field0Name = fields[0].GetProperty("name").GetString()!; |
|
var field0Value = fields[0].GetProperty("value").GetString()!; |
|
Assert.StartsWith("2.", field0Name); |
|
Assert.StartsWith("2.", field0Value); |
|
|
|
Assert.Equal("API Key", RustSdkService.DecryptString(field0Name, orgKeys.Key)); |
|
Assert.Equal("sk_test_FAKE_api_key_12345", RustSdkService.DecryptString(field0Value, orgKeys.Key)); |
|
} |
|
|
|
[Fact] |
|
public void CipherSeeder_ProducesServerCompatibleFormat() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
var orgId = Guid.NewGuid(); |
|
|
|
var cipher = LoginCipherSeeder.Create(new CipherSeed |
|
{ |
|
Type = CipherType.Login, |
|
Name = "GitHub Account", |
|
EncryptionKey = orgKeys.Key, |
|
OrganizationId = orgId, |
|
Notes = "My development account", |
|
Login = new LoginViewDto |
|
{ |
|
Username = "developer@example.com", |
|
Password = "SecureP@ss123!", |
|
Uris = [new LoginUriViewDto { Uri = "https://github.com" }] |
|
} |
|
}); |
|
|
|
Assert.Equal(orgId, cipher.OrganizationId); |
|
Assert.Null(cipher.UserId); |
|
Assert.Equal(Core.Vault.Enums.CipherType.Login, cipher.Type); |
|
Assert.NotNull(cipher.Data); |
|
|
|
var loginData = JsonSerializer.Deserialize<CipherLoginData>(cipher.Data); |
|
Assert.NotNull(loginData); |
|
|
|
const string encStringPrefix = "2."; |
|
Assert.StartsWith(encStringPrefix, loginData.Name); |
|
Assert.StartsWith(encStringPrefix, loginData.Username); |
|
Assert.StartsWith(encStringPrefix, loginData.Password); |
|
Assert.StartsWith(encStringPrefix, loginData.Notes); |
|
|
|
Assert.NotNull(loginData.Uris); |
|
var uriData = loginData.Uris.First(); |
|
Assert.StartsWith(encStringPrefix, uriData.Uri); |
|
|
|
Assert.DoesNotContain("GitHub Account", cipher.Data); |
|
Assert.DoesNotContain("developer@example.com", cipher.Data); |
|
Assert.DoesNotContain("SecureP@ss123!", cipher.Data); |
|
} |
|
|
|
[Fact] |
|
public void CipherSeeder_WithFields_ProducesCorrectServerFormat() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
|
|
var cipher = LoginCipherSeeder.Create(new CipherSeed |
|
{ |
|
Type = CipherType.Login, |
|
Name = "API Service", |
|
EncryptionKey = orgKeys.Key, |
|
OrganizationId = Guid.NewGuid(), |
|
Login = new LoginViewDto |
|
{ |
|
Username = "service@example.com", |
|
Password = "SvcP@ss!", |
|
Uris = [new LoginUriViewDto { Uri = "https://api.example.com" }] |
|
}, |
|
Fields = |
|
[ |
|
new FieldViewDto { Name = "API Key", Value = "sk_test_FAKE_abc123", Type = 1 }, |
|
new FieldViewDto { Name = "Environment", Value = "production", Type = 0 } |
|
] |
|
}); |
|
|
|
var loginData = JsonSerializer.Deserialize<CipherLoginData>(cipher.Data); |
|
Assert.NotNull(loginData); |
|
Assert.NotNull(loginData.Fields); |
|
|
|
var fields = loginData.Fields.ToList(); |
|
Assert.Equal(2, fields.Count); |
|
|
|
const string encStringPrefix = "2."; |
|
Assert.StartsWith(encStringPrefix, fields[0].Name); |
|
Assert.StartsWith(encStringPrefix, fields[0].Value); |
|
Assert.StartsWith(encStringPrefix, fields[1].Name); |
|
Assert.StartsWith(encStringPrefix, fields[1].Value); |
|
|
|
Assert.Equal(Core.Vault.Enums.FieldType.Hidden, fields[0].Type); |
|
Assert.Equal(Core.Vault.Enums.FieldType.Text, fields[1].Type); |
|
|
|
Assert.DoesNotContain("API Key", cipher.Data); |
|
Assert.DoesNotContain("sk_test_FAKE_abc123", cipher.Data); |
|
} |
|
|
|
[Fact] |
|
public void EncryptFields_CardCipher_RoundtripDecrypt() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
|
|
var cipher = new CipherViewDto |
|
{ |
|
Name = "My Visa Card", |
|
Type = CipherTypes.Card, |
|
Card = new CardViewDto |
|
{ |
|
CardholderName = "John Doe", |
|
Number = "4111111111111111", |
|
ExpMonth = "12", |
|
ExpYear = "2028", |
|
Code = "123" |
|
} |
|
}; |
|
|
|
var json = JsonSerializer.Serialize(cipher, _sdkJsonOptions); |
|
var fieldPathsJson = JsonSerializer.Serialize(EncryptPropertyAttribute.GetFieldPaths<CipherViewDto>()); |
|
var encryptedJson = RustSdkService.EncryptFields(json, fieldPathsJson, orgKeys.Key); |
|
|
|
using var doc = JsonDocument.Parse(encryptedJson); |
|
var card = doc.RootElement.GetProperty("card"); |
|
|
|
Assert.Equal("John Doe", RustSdkService.DecryptString(card.GetProperty("cardholderName").GetString()!, orgKeys.Key)); |
|
Assert.Equal("4111111111111111", RustSdkService.DecryptString(card.GetProperty("number").GetString()!, orgKeys.Key)); |
|
Assert.Equal("12", RustSdkService.DecryptString(card.GetProperty("expMonth").GetString()!, orgKeys.Key)); |
|
Assert.Equal("2028", RustSdkService.DecryptString(card.GetProperty("expYear").GetString()!, orgKeys.Key)); |
|
Assert.Equal("123", RustSdkService.DecryptString(card.GetProperty("code").GetString()!, orgKeys.Key)); |
|
} |
|
|
|
[Fact] |
|
public void EncryptFields_IdentityCipher_RoundtripDecrypt() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
|
|
var cipher = new CipherViewDto |
|
{ |
|
Name = "Personal Identity", |
|
Type = CipherTypes.Identity, |
|
Identity = new IdentityViewDto |
|
{ |
|
FirstName = "John", |
|
LastName = "Doe", |
|
Email = "john.doe@example.com", |
|
SSN = "123-45-6789", |
|
Address1 = "123 Main Street", |
|
City = "Anytown", |
|
State = "CA", |
|
PostalCode = "90210", |
|
Country = "US" |
|
} |
|
}; |
|
|
|
var json = JsonSerializer.Serialize(cipher, _sdkJsonOptions); |
|
var fieldPathsJson = JsonSerializer.Serialize(EncryptPropertyAttribute.GetFieldPaths<CipherViewDto>()); |
|
var encryptedJson = RustSdkService.EncryptFields(json, fieldPathsJson, orgKeys.Key); |
|
|
|
using var doc = JsonDocument.Parse(encryptedJson); |
|
var identity = doc.RootElement.GetProperty("identity"); |
|
|
|
Assert.Equal("John", RustSdkService.DecryptString(identity.GetProperty("firstName").GetString()!, orgKeys.Key)); |
|
Assert.Equal("123-45-6789", RustSdkService.DecryptString(identity.GetProperty("ssn").GetString()!, orgKeys.Key)); |
|
Assert.Equal("john.doe@example.com", RustSdkService.DecryptString(identity.GetProperty("email").GetString()!, orgKeys.Key)); |
|
Assert.Equal("123 Main Street", RustSdkService.DecryptString(identity.GetProperty("address1").GetString()!, orgKeys.Key)); |
|
Assert.Equal("90210", RustSdkService.DecryptString(identity.GetProperty("postalCode").GetString()!, orgKeys.Key)); |
|
} |
|
|
|
[Fact] |
|
public void EncryptFields_SshKeyCipher_RoundtripDecrypt() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
|
|
var cipher = new CipherViewDto |
|
{ |
|
Name = "Dev Key", |
|
Type = CipherTypes.SshKey, |
|
SshKey = new SshKeyViewDto |
|
{ |
|
PrivateKey = "-----BEGIN FAKE KEY-----\nMIIE...\n-----END FAKE KEY-----", |
|
PublicKey = "ssh-rsa AAAAB3... user@host", |
|
Fingerprint = "SHA256:abc123" |
|
} |
|
}; |
|
|
|
var json = JsonSerializer.Serialize(cipher, _sdkJsonOptions); |
|
var fieldPathsJson = JsonSerializer.Serialize(EncryptPropertyAttribute.GetFieldPaths<CipherViewDto>()); |
|
var encryptedJson = RustSdkService.EncryptFields(json, fieldPathsJson, orgKeys.Key); |
|
|
|
using var doc = JsonDocument.Parse(encryptedJson); |
|
var sshKey = doc.RootElement.GetProperty("sshKey"); |
|
|
|
Assert.Equal("-----BEGIN FAKE KEY-----\nMIIE...\n-----END FAKE KEY-----", |
|
RustSdkService.DecryptString(sshKey.GetProperty("privateKey").GetString()!, orgKeys.Key)); |
|
Assert.Equal("ssh-rsa AAAAB3... user@host", |
|
RustSdkService.DecryptString(sshKey.GetProperty("publicKey").GetString()!, orgKeys.Key)); |
|
Assert.Equal("SHA256:abc123", |
|
RustSdkService.DecryptString(sshKey.GetProperty("fingerprint").GetString()!, orgKeys.Key)); |
|
} |
|
|
|
[Fact] |
|
public void CipherSeeder_CardCipher_ProducesServerCompatibleFormat() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
var orgId = Guid.NewGuid(); |
|
|
|
var card = new CardViewDto |
|
{ |
|
CardholderName = "Jane Smith", |
|
Brand = "Mastercard", |
|
Number = "5500000000000004", |
|
ExpMonth = "06", |
|
ExpYear = "2027", |
|
Code = "456" |
|
}; |
|
|
|
var cipher = CardCipherSeeder.Create(new CipherSeed |
|
{ |
|
Type = CipherType.Card, |
|
Name = "Business Card", |
|
Notes = "Company expenses", |
|
EncryptionKey = orgKeys.Key, |
|
OrganizationId = orgId, |
|
Card = card |
|
}); |
|
|
|
Assert.Equal(orgId, cipher.OrganizationId); |
|
Assert.Equal(Core.Vault.Enums.CipherType.Card, cipher.Type); |
|
|
|
var cardData = JsonSerializer.Deserialize<CipherCardData>(cipher.Data); |
|
Assert.NotNull(cardData); |
|
|
|
var encStringPrefix = "2."; |
|
Assert.StartsWith(encStringPrefix, cardData.Name); |
|
Assert.StartsWith(encStringPrefix, cardData.CardholderName); |
|
Assert.StartsWith(encStringPrefix, cardData.Number); |
|
Assert.StartsWith(encStringPrefix, cardData.Code); |
|
|
|
Assert.DoesNotContain("5500000000000004", cipher.Data); |
|
Assert.DoesNotContain("Jane Smith", cipher.Data); |
|
} |
|
|
|
[Fact] |
|
public void CipherSeeder_IdentityCipher_ProducesServerCompatibleFormat() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
var orgId = Guid.NewGuid(); |
|
|
|
var identity = new IdentityViewDto |
|
{ |
|
Title = "Dr", |
|
FirstName = "Alice", |
|
LastName = "Johnson", |
|
Email = "alice@company.com", |
|
SSN = "987-65-4321", |
|
PassportNumber = "X12345678" |
|
}; |
|
|
|
var cipher = IdentityCipherSeeder.Create(new CipherSeed |
|
{ |
|
Type = CipherType.Identity, |
|
Name = "Dr. Alice Johnson", |
|
EncryptionKey = orgKeys.Key, |
|
OrganizationId = orgId, |
|
Identity = identity |
|
}); |
|
|
|
Assert.Equal(orgId, cipher.OrganizationId); |
|
Assert.Equal(Core.Vault.Enums.CipherType.Identity, cipher.Type); |
|
|
|
var identityData = JsonSerializer.Deserialize<CipherIdentityData>(cipher.Data); |
|
Assert.NotNull(identityData); |
|
|
|
var encStringPrefix = "2."; |
|
Assert.StartsWith(encStringPrefix, identityData.Name); |
|
Assert.StartsWith(encStringPrefix, identityData.FirstName); |
|
Assert.StartsWith(encStringPrefix, identityData.SSN); |
|
|
|
Assert.DoesNotContain("987-65-4321", cipher.Data); |
|
Assert.DoesNotContain("Alice", cipher.Data); |
|
} |
|
|
|
[Fact] |
|
public void CipherSeeder_SecureNoteCipher_ProducesServerCompatibleFormat() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
var orgId = Guid.NewGuid(); |
|
|
|
var cipher = SecureNoteCipherSeeder.Create(new CipherSeed |
|
{ |
|
Type = CipherType.SecureNote, |
|
Name = "Production Secrets", |
|
Notes = "DATABASE_URL=postgres://user:FAKE_secret@db.example.com/prod", |
|
EncryptionKey = orgKeys.Key, |
|
OrganizationId = orgId |
|
}); |
|
|
|
Assert.Equal(orgId, cipher.OrganizationId); |
|
Assert.Equal(Core.Vault.Enums.CipherType.SecureNote, cipher.Type); |
|
|
|
var noteData = JsonSerializer.Deserialize<CipherSecureNoteData>(cipher.Data); |
|
Assert.NotNull(noteData); |
|
Assert.Equal(Core.Vault.Enums.SecureNoteType.Generic, noteData.Type); |
|
|
|
Assert.StartsWith("2.", noteData.Name); |
|
Assert.StartsWith("2.", noteData.Notes); |
|
|
|
Assert.DoesNotContain("postgres://", cipher.Data); |
|
Assert.DoesNotContain("secret", cipher.Data); |
|
} |
|
|
|
[Fact] |
|
public void CipherSeeder_SshKeyCipher_ProducesServerCompatibleFormat() |
|
{ |
|
var orgKeys = RustSdkService.GenerateOrganizationKeys(); |
|
var orgId = Guid.NewGuid(); |
|
|
|
var sshKey = new SshKeyViewDto |
|
{ |
|
PrivateKey = "-----BEGIN FAKE OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAA...\n-----END FAKE OPENSSH PRIVATE KEY-----", |
|
PublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample test@machine", |
|
Fingerprint = "SHA256:examplefingerprint123" |
|
}; |
|
|
|
var cipher = SshKeyCipherSeeder.Create(new CipherSeed |
|
{ |
|
Type = CipherType.SSHKey, |
|
Name = "Production Deploy Key", |
|
EncryptionKey = orgKeys.Key, |
|
OrganizationId = orgId, |
|
SshKey = sshKey |
|
}); |
|
|
|
Assert.Equal(orgId, cipher.OrganizationId); |
|
Assert.Equal(Core.Vault.Enums.CipherType.SSHKey, cipher.Type); |
|
|
|
var sshData = JsonSerializer.Deserialize<CipherSSHKeyData>(cipher.Data); |
|
Assert.NotNull(sshData); |
|
|
|
const string encStringPrefix = "2."; |
|
Assert.StartsWith(encStringPrefix, sshData.Name); |
|
Assert.StartsWith(encStringPrefix, sshData.PrivateKey); |
|
Assert.StartsWith(encStringPrefix, sshData.PublicKey); |
|
Assert.StartsWith(encStringPrefix, sshData.KeyFingerprint); |
|
|
|
Assert.DoesNotContain("BEGIN FAKE OPENSSH PRIVATE KEY", cipher.Data); |
|
Assert.DoesNotContain("ssh-ed25519", cipher.Data); |
|
} |
|
}
|
|
|