From e6df9f5e0f7cece387414e5cbf2f2aea41fc58cc Mon Sep 17 00:00:00 2001 From: Kurt Brendon Date: Mon, 6 Dec 2021 09:14:32 -0800 Subject: [PATCH 1/2] Initial test case project --- .../Models/RoleAuthorizer.cs | 12 +++++------ .../EmailAuthorizerTests.cs | 19 ++++++++++++++++++ .../customerkeystoretest/KeyManagerTests.cs | 14 +++++++++++++ .../RoleAuthorizerTests.cs | 14 +++++++++++++ .../customerkeystoretest.csproj | 20 +++++++++++++++++++ 5 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 src/test/customerkeystoretest/EmailAuthorizerTests.cs create mode 100644 src/test/customerkeystoretest/KeyManagerTests.cs create mode 100644 src/test/customerkeystoretest/RoleAuthorizerTests.cs create mode 100644 src/test/customerkeystoretest/customerkeystoretest.csproj diff --git a/src/customer-key-store/Models/RoleAuthorizer.cs b/src/customer-key-store/Models/RoleAuthorizer.cs index 914ec17..757ab28 100644 --- a/src/customer-key-store/Models/RoleAuthorizer.cs +++ b/src/customer-key-store/Models/RoleAuthorizer.cs @@ -22,6 +22,12 @@ public RoleAuthorizer(IConfiguration configuration) ldapPath = configuration["RoleAuthorizer:LDAPPath"]; } + public static string GetRole(string memberOf) + { + memberOf.ThrowIfNull(nameof(memberOf)); + return ParseCN(memberOf); + } + public void AddRole(string role) { roles.Add(role); @@ -133,11 +139,5 @@ private static string ParseCN(string distinguishedName) return role.ToString(); } - - public static string GetRole(string memberOf) - { - memberOf.ThrowIfNull(nameof(memberOf)); - return ParseCN(memberOf); - } } } \ No newline at end of file diff --git a/src/test/customerkeystoretest/EmailAuthorizerTests.cs b/src/test/customerkeystoretest/EmailAuthorizerTests.cs new file mode 100644 index 0000000..ed650ce --- /dev/null +++ b/src/test/customerkeystoretest/EmailAuthorizerTests.cs @@ -0,0 +1,19 @@ +namespace customerkeystoretest +{ + using System; + using System.Security.Claims; + using Xunit; + + using Microsoft.InformationProtection.Web.Models; + public class EmailAuthorizerTests + { + [Fact] + public void Test1() + { + EmailAuthorizer auth = new EmailAuthorizer(); + ClaimsPrincipal principal = new ClaimsPrincipal(); + auth.CanUserAccessKey(); + Assert.NotEqual(expected, actual); + } + } +} diff --git a/src/test/customerkeystoretest/KeyManagerTests.cs b/src/test/customerkeystoretest/KeyManagerTests.cs new file mode 100644 index 0000000..6e9ab0b --- /dev/null +++ b/src/test/customerkeystoretest/KeyManagerTests.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace customerkeystoretest +{ + public class KeyManagerTests + { + [Fact] + public void Test1() + { + + } + } +} diff --git a/src/test/customerkeystoretest/RoleAuthorizerTests.cs b/src/test/customerkeystoretest/RoleAuthorizerTests.cs new file mode 100644 index 0000000..47969d7 --- /dev/null +++ b/src/test/customerkeystoretest/RoleAuthorizerTests.cs @@ -0,0 +1,14 @@ +using System; +using Xunit; + +namespace customerkeystoretest +{ + public class RoleAuthorizerTests + { + [Fact] + public void Test1() + { + + } + } +} diff --git a/src/test/customerkeystoretest/customerkeystoretest.csproj b/src/test/customerkeystoretest/customerkeystoretest.csproj new file mode 100644 index 0000000..ecb7ff7 --- /dev/null +++ b/src/test/customerkeystoretest/customerkeystoretest.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + From 49e3564ceef910fb7c9f5a06123468e96013ff19 Mon Sep 17 00:00:00 2001 From: Kurt Brendon Date: Wed, 16 Mar 2022 09:54:11 -0700 Subject: [PATCH 2/2] Added unit tests --- .../Models/RoleAuthorizer.cs | 81 ++++--- .../customerkeystoretest/ConfigurationMock.cs | 29 +++ .../EmailAuthorizerTests.cs | 61 +++++- .../customerkeystoretest/KeyManagerTests.cs | 203 +++++++++++++++++- src/test/customerkeystoretest/KeyMock.cs | 28 +++ src/test/customerkeystoretest/KeyStoreMock.cs | 54 +++++ .../KeysControllerTests.cs | 141 ++++++++++++ .../RoleAuthorizerMock.cs | 31 +++ .../RoleAuthorizerTests.cs | 119 +++++++++- .../customerkeystoretest.csproj | 3 +- 10 files changed, 699 insertions(+), 51 deletions(-) create mode 100644 src/test/customerkeystoretest/ConfigurationMock.cs create mode 100644 src/test/customerkeystoretest/KeyMock.cs create mode 100644 src/test/customerkeystoretest/KeyStoreMock.cs create mode 100644 src/test/customerkeystoretest/KeysControllerTests.cs create mode 100644 src/test/customerkeystoretest/RoleAuthorizerMock.cs diff --git a/src/customer-key-store/Models/RoleAuthorizer.cs b/src/customer-key-store/Models/RoleAuthorizer.cs index 757ab28..e61abc7 100644 --- a/src/customer-key-store/Models/RoleAuthorizer.cs +++ b/src/customer-key-store/Models/RoleAuthorizer.cs @@ -20,12 +20,10 @@ public RoleAuthorizer(IConfiguration configuration) configuration.ThrowIfNull(nameof(configuration)); ldapPath = configuration["RoleAuthorizer:LDAPPath"]; - } - - public static string GetRole(string memberOf) - { - memberOf.ThrowIfNull(nameof(memberOf)); - return ParseCN(memberOf); + if(ldapPath == null) + { + throw new System.ArgumentException("LDAPPath is required"); + } } public void AddRole(string role) @@ -37,38 +35,22 @@ public void CanUserAccessKey(string sid) { sid.ThrowIfNull(nameof(sid)); - using(DirectoryEntry entry = new DirectoryEntry("LDAP://" + ldapPath)) + bool success = false; + foreach(var member in GetMembersOfFromSID(sid)) { - using(DirectorySearcher dSearch = new DirectorySearcher(entry)) + //Split out the first part of the role to the comma + var role = ParseCN(member); + if(!string.IsNullOrEmpty(role) && roles.Contains(role)) { - dSearch.Filter = "(objectSid=" + sid + ")"; - - var result = dSearch.FindOne(); - - if(result == null) - { - throw new System.ArgumentException("User not found"); - } - - var memberof = result.Properties[RoleProperty]; - bool success = false; - foreach(var member in memberof) - { - //Split out the first part of the role to the comma - var role = GetRole(member.ToString()); - if(!string.IsNullOrEmpty(role) && roles.Contains(role)) - { - success = true; - break; - } - } - - if(!success) - { - throw new CustomerKeyStore.Models.KeyAccessException("User does not have access to the key"); - } + success = true; + break; } } + + if(!success) + { + throw new CustomerKeyStore.Models.KeyAccessException("User does not have access to the key"); + } } public void CanUserAccessKey(ClaimsPrincipal user, KeyStoreData key) @@ -94,7 +76,7 @@ public void CanUserAccessKey(ClaimsPrincipal user, KeyStoreData key) CanUserAccessKey(sid); } - private static string ParseCN(string distinguishedName) + protected static string ParseCN(string distinguishedName) { distinguishedName.ThrowIfNull(nameof(distinguishedName)); @@ -139,5 +121,34 @@ private static string ParseCN(string distinguishedName) return role.ToString(); } + + protected virtual IEnumerable GetMembersOfFromSID(string sid) + { + sid.ThrowIfNull(nameof(sid)); + List membersOf = new List(); + + using(DirectoryEntry entry = new DirectoryEntry("LDAP://" + ldapPath)) + { + using(DirectorySearcher dSearch = new DirectorySearcher(entry)) + { + dSearch.Filter = "(objectSid=" + sid + ")"; + + var result = dSearch.FindOne(); + + if(result == null) + { + throw new System.ArgumentException("User not found"); + } + + var memberof = result.Properties[RoleProperty]; + foreach(var member in memberof) + { + membersOf.Add(member.ToString()); + } + } + } + + return membersOf; + } } } \ No newline at end of file diff --git a/src/test/customerkeystoretest/ConfigurationMock.cs b/src/test/customerkeystoretest/ConfigurationMock.cs new file mode 100644 index 0000000..91372ae --- /dev/null +++ b/src/test/customerkeystoretest/ConfigurationMock.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Customerkeystoretest +{ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + + public class ConfigurationMock : Microsoft.Extensions.Configuration.IConfiguration + { + private Dictionary properties = new Dictionary(); + public string this[string key] + { + get { return properties.ContainsKey(key) ? properties[key] : null; } + set { properties[key] = value; } + } + public IEnumerable GetChildren() + { + throw new System.NotImplementedException(); + } + public Microsoft.Extensions.Primitives.IChangeToken GetReloadToken() + { + throw new System.NotImplementedException(); + } + public IConfigurationSection GetSection(string key) + { + throw new System.NotImplementedException(); + } + } +} diff --git a/src/test/customerkeystoretest/EmailAuthorizerTests.cs b/src/test/customerkeystoretest/EmailAuthorizerTests.cs index ed650ce..06152c0 100644 --- a/src/test/customerkeystoretest/EmailAuthorizerTests.cs +++ b/src/test/customerkeystoretest/EmailAuthorizerTests.cs @@ -1,6 +1,8 @@ -namespace customerkeystoretest +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Customerkeystoretest { - using System; + using System.Collections.Generic; using System.Security.Claims; using Xunit; @@ -8,12 +10,61 @@ namespace customerkeystoretest public class EmailAuthorizerTests { [Fact] - public void Test1() + public void NoClaims_Fail() { EmailAuthorizer auth = new EmailAuthorizer(); ClaimsPrincipal principal = new ClaimsPrincipal(); - auth.CanUserAccessKey(); - Assert.NotEqual(expected, actual); + Assert.Throws(() => auth.CanUserAccessKey(principal, null)); + } + + [Theory] + [InlineData(ClaimTypes.Email)] + [InlineData(ClaimTypes.Upn)] + public void NoValidEmails_Fail(string claimType) + { + EmailAuthorizer auth = new EmailAuthorizer(); + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(claimType, "testuser@contoso.com")}); + principal.AddIdentity(identity); + Assert.Throws(() => auth.CanUserAccessKey(principal, null)); + } + + [Theory] + [InlineData(ClaimTypes.Email)] + [InlineData(ClaimTypes.Upn)] + public void ValidEmail_OneEmail_Success(string claimType) + { + EmailAuthorizer auth = new EmailAuthorizer(); + auth.AddEmail("testuser@contoso.com"); + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(claimType, "testuser@contoso.com")}); + principal.AddIdentity(identity); + auth.CanUserAccessKey(principal, null); + } + + [Theory] + [InlineData(ClaimTypes.Email)] + [InlineData(ClaimTypes.Upn)] + public void ValidEmail_ManyEmails_Success(string claimType) + { + EmailAuthorizer auth = new EmailAuthorizer(); + auth.AddEmail("testuser@contoso.com"); + auth.AddEmail("testuser4@contoso.com"); + auth.AddEmail("testuser2@contoso.com"); + ClaimsPrincipal principalUser1 = new ClaimsPrincipal(); + ClaimsIdentity identityUser1 = new ClaimsIdentity(new List(){new Claim(claimType, "testuser@contoso.com")}); + principalUser1.AddIdentity(identityUser1); + auth.CanUserAccessKey(principalUser1, null); + + ClaimsPrincipal principalUser2 = new ClaimsPrincipal(); + ClaimsIdentity identityUser2 = new ClaimsIdentity(new List(){new Claim(claimType, "testuser4@contoso.com")}); + principalUser2.AddIdentity(identityUser2); + auth.CanUserAccessKey(principalUser2, null); + + ClaimsPrincipal principalUser3 = new ClaimsPrincipal(); + ClaimsIdentity identityUser3 = new ClaimsIdentity(new List(){new Claim(claimType, "testuser2@contoso.com")}); + principalUser3.AddIdentity(identityUser3); + auth.CanUserAccessKey(principalUser3, null); } } } diff --git a/src/test/customerkeystoretest/KeyManagerTests.cs b/src/test/customerkeystoretest/KeyManagerTests.cs index 6e9ab0b..6ba8635 100644 --- a/src/test/customerkeystoretest/KeyManagerTests.cs +++ b/src/test/customerkeystoretest/KeyManagerTests.cs @@ -1,14 +1,207 @@ -using System; -using Xunit; - -namespace customerkeystoretest +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Customerkeystoretest { + using System; + using System.Collections.Generic; + using System.Security.Claims; + using Microsoft.InformationProtection.Web.Models; + using Xunit; public class KeyManagerTests { [Fact] - public void Test1() + public void GetPublicKey_NoKeys_Fail() + { + var keyStore = new KeyStoreMock(); + KeyManager keyManager = new KeyManager(keyStore); + Assert.Throws(() => keyManager.GetPublicKey(new Uri("https://keystore/key1"), "testkey1")); + } + + [Fact] + public void GetPublicKey_KeyNotFound_Fail() + { + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + Assert.Throws(() => keyManager.GetPublicKey(new Uri("https://keystore/key1"), "testkey1")); + } + + [Fact] + public void GetPublicKey_KeyFound_NoCache_Success() + { + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(false, "testkey1", "3w4534", new KeyMock("mod1", 54), "type1", "alg1", auth, null); + keyStore.AddKey(true, "testkey1", "2343", new KeyMock("mod2", 54), "type2", "alg2", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var publicKey = keyManager.GetPublicKey(new Uri("https://keystore/key1"), "testkey1"); + Assert.Null(publicKey.Cache); + Assert.Equal("alg2", publicKey.Key.Algorithm); + Assert.Equal(54u, publicKey.Key.Exponent); + Assert.Equal("https://keystore/key1/2343", publicKey.Key.KeyId); + Assert.Equal("type2", publicKey.Key.KeyType); + Assert.Equal("mod2", publicKey.Key.Modulus); + } + + [Fact] + public void GetPublicKey_KeyFound_WithCache_Success() + { + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(false, "testkey1", "3w4534", new KeyMock("mod1", 54), "type1", "alg1", auth, 30); + keyStore.AddKey(true, "testkey1", "2343", new KeyMock("mod2", 54), "type2", "alg2", auth, 30); + KeyManager keyManager = new KeyManager(keyStore); + var publicKey = keyManager.GetPublicKey(new Uri("https://keystore/key1"), "testkey1"); + Assert.NotNull(publicKey.Cache); + Assert.InRange((DateTime.Parse(publicKey.Cache.Expiration) - DateTime.UtcNow).TotalHours, 30 * 23, 30 * 24); //It should be 30 days but by the time it gets here it will be a little less + Assert.Equal("alg2", publicKey.Key.Algorithm); + Assert.Equal(54u, publicKey.Key.Exponent); + Assert.Equal("https://keystore/key1/2343", publicKey.Key.KeyId); + Assert.Equal("type2", publicKey.Key.KeyType); + Assert.Equal("mod2", publicKey.Key.Modulus); + } + + public void Decrypt_NoKeys_Fail() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + var keyStore = new KeyStoreMock(); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + Assert.Throws(() => keyManager.Decrypt(principal, "testkey1", "324234", encryptedData)); + } + + [Fact] + public void Decrypt_KeyNameNotFound_Fail() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + Assert.Throws(() => keyManager.Decrypt(principal, "testkey1", "324234", encryptedData)); + } + + [Fact] + public void Decrypt_KeyIdNotFound_Fail() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + Assert.Throws(() => keyManager.Decrypt(principal, "testkey2", "324234", encryptedData)); + } + + [Fact] + public void Decrypt_NoAccessToKey_Fail() { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + Assert.Throws(() => keyManager.Decrypt(principal, "testkey2", "2343", encryptedData)); + } + + [Fact] + public void Decrypt_BadAlgorithm_Fail() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + EmailAuthorizer auth = new EmailAuthorizer(); + auth.AddEmail("testuser@contoso.com"); + + var keyStore = new KeyStoreMock(); + + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-26"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + Assert.Throws(() => keyManager.Decrypt(principal, "testkey2", "2343", encryptedData)); + } + + [Fact] + public void Decrypt_SingleActiveKey_Success() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + EmailAuthorizer auth = new EmailAuthorizer(); + auth.AddEmail("testuser@contoso.com"); + + var keyStore = new KeyStoreMock(); + + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + var decryptedData = keyManager.Decrypt(principal, "testkey2", "2343", encryptedData); + Assert.Equal(encryptedData.Value, decryptedData.Value); //KeyMock returns the encrypted data as the decrypted data + } + + [Fact] + public void Decrypt_OldActiveKey_Success() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + EmailAuthorizer auth = new EmailAuthorizer(); + auth.AddEmail("testuser@contoso.com"); + + var keyStore = new KeyStoreMock(); + + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + keyStore.AddKey(false, "testkey2", "75466", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + var decryptedData = keyManager.Decrypt(principal, "testkey2", "75466", encryptedData); + Assert.Equal(encryptedData.Value, decryptedData.Value); //KeyMock returns the encrypted data as the decrypted data + } + + [Fact] + public void Decrypt_CurrentActiveKey_Success() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + EmailAuthorizer auth = new EmailAuthorizer(); + auth.AddEmail("testuser@contoso.com"); + + var keyStore = new KeyStoreMock(); + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + keyStore.AddKey(false, "testkey2", "75466", new KeyMock("mod", 0), "type", "alg", auth, null); + keyStore.AddKey(false, "testkey1", "654345", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + var decryptedData = keyManager.Decrypt(principal, "testkey2", "2343", encryptedData); + Assert.Equal(encryptedData.Value, decryptedData.Value); //KeyMock returns the encrypted data as the decrypted data } } } diff --git a/src/test/customerkeystoretest/KeyMock.cs b/src/test/customerkeystoretest/KeyMock.cs new file mode 100644 index 0000000..d5ae2fa --- /dev/null +++ b/src/test/customerkeystoretest/KeyMock.cs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Customerkeystoretest +{ + using Microsoft.InformationProtection.Web.Models; + + public class KeyMock : IKey + { + private string modulus; + private uint exponent; + + public KeyMock(string modulus, uint exponent) + { + this.modulus = modulus; + this.exponent = exponent; + } + + public PublicKey GetPublicKey() + { + return new PublicKey(modulus, exponent); + } + + public byte[] Decrypt(byte[] encryptedData) + { + return encryptedData; + } + } +} diff --git a/src/test/customerkeystoretest/KeyStoreMock.cs b/src/test/customerkeystoretest/KeyStoreMock.cs new file mode 100644 index 0000000..80394a6 --- /dev/null +++ b/src/test/customerkeystoretest/KeyStoreMock.cs @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Customerkeystoretest +{ + using System; + using System.Collections.Generic; + using Microsoft.InformationProtection.Web.Models; + + public class KeyStoreMock : IKeyStore + { + private Dictionary> keys = new Dictionary>(); + private Dictionary activeKeys = new Dictionary(); + + public void AddKey(bool active, string keyName, string keyId, IKey key, string keyType, string supportedAlgorithm, IAuthorizer keyAuth, int? expirationTimeInDays) + { + if(!keys.ContainsKey(keyName)) + { + keys[keyName] = new Dictionary(); + } + keys[keyName][keyId] = new KeyStoreData(key, keyId, keyType, supportedAlgorithm, keyAuth, expirationTimeInDays); + if(active) + { + activeKeys[keyName] = keyId; + } + } + + public KeyStoreData GetActiveKey(string keyName) + { + Dictionary keys; + string activeKey; + KeyStoreData foundKey; + if(!this.keys.TryGetValue(keyName, out keys) || !activeKeys.TryGetValue(keyName, out activeKey) || + !keys.TryGetValue(activeKey, out foundKey)) + { + throw new ArgumentException("Key " + keyName + " not found"); + } + + return foundKey; + } + + public KeyStoreData GetKey(string keyName, string keyId) + { + Dictionary keys; + KeyStoreData foundKey; + if(!this.keys.TryGetValue(keyName, out keys) || + !keys.TryGetValue(keyId, out foundKey)) + { + throw new ArgumentException("Key " + keyName + "-" + keyId + " not found"); + } + + return foundKey; + } + } +} diff --git a/src/test/customerkeystoretest/KeysControllerTests.cs b/src/test/customerkeystoretest/KeysControllerTests.cs new file mode 100644 index 0000000..7b06a3e --- /dev/null +++ b/src/test/customerkeystoretest/KeysControllerTests.cs @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Customerkeystoretest +{ + using System; + using System.Collections.Generic; + using System.Security.Claims; + using Microsoft.AspNetCore.Mvc; + using Microsoft.InformationProtection.Web.Controllers; + using Microsoft.InformationProtection.Web.Models; + using Xunit; + + public class KeyControllerTests + { + [Fact] + public void GetKey_KeyNotFound_Fail() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var controller = GetKeysController(keyManager, principal, "keystore", "/key1"); + + Assert.IsType(controller.GetKey("testkey1")); + } + + [Fact] + public void GetKey_KeyFound_Success() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(false, "testkey1", "3w4534", new KeyMock("mod1", 54), "type1", "alg1", auth, null); + keyStore.AddKey(true, "testkey1", "2343", new KeyMock("mod2", 54), "type2", "alg2", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var controller = GetKeysController(keyManager, principal, "keystore", "/key1"); + + var result = controller.GetKey("testkey1"); + Assert.IsType(result); + Assert.IsType(((OkObjectResult)result).Value); + + var publicKey = (KeyData)((OkObjectResult)result).Value; + Assert.Null(publicKey.Cache); + Assert.Equal("alg2", publicKey.Key.Algorithm); + Assert.Equal(54u, publicKey.Key.Exponent); + Assert.Equal("https://keystore/key1/2343", publicKey.Key.KeyId); + Assert.Equal("type2", publicKey.Key.KeyType); + Assert.Equal("mod2", publicKey.Key.Modulus); + } + + [Fact] + public void Decrypt_KeyNameNotFound_Fail() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + var controller = GetKeysController(keyManager, principal, "keystore", "/key1"); + Assert.IsType(controller.Decrypt("testkey1", "324234", encryptedData)); + } + + [Fact] + public void Decrypt_NoAccessToKey_Fail() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + var keyStore = new KeyStoreMock(); + EmailAuthorizer auth = new EmailAuthorizer(); + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + + var controller = GetKeysController(keyManager, principal, "keystore", "/key1"); + var result = controller.Decrypt("testkey2", "2343", encryptedData); + Assert.IsType(result); + Assert.Equal(403, ((StatusCodeResult)result).StatusCode); + } + + [Fact] + public void Decrypt_Success() + { + ClaimsPrincipal principal = new ClaimsPrincipal(); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim(ClaimTypes.Email, "testuser@contoso.com")}); + principal.AddIdentity(identity); + + EmailAuthorizer auth = new EmailAuthorizer(); + auth.AddEmail("testuser@contoso.com"); + + var keyStore = new KeyStoreMock(); + + keyStore.AddKey(true, "testkey2", "2343", new KeyMock("mod", 0), "type", "alg", auth, null); + KeyManager keyManager = new KeyManager(keyStore); + var encryptedData = new EncryptedData(); + encryptedData.Algorithm = "RSA-OAEP-256"; + encryptedData.Value = Convert.ToBase64String(new byte[] {0x1}); + + var controller = GetKeysController(keyManager, principal, "keystore", "/key1"); + var result = controller.Decrypt("testkey2", "2343", encryptedData); + Assert.IsType(result); + Assert.IsType(((OkObjectResult)result).Value); + + var decryptedData = (DecryptedData)((OkObjectResult)result).Value; + + Assert.Equal(encryptedData.Value, decryptedData.Value); //KeyMock returns the encrypted data as the decrypted data + } + + private KeysController GetKeysController(KeyManager keyManager, ClaimsPrincipal user, string host, string path) + { + var controller = new KeysController(keyManager); + + var context = new Microsoft.AspNetCore.Http.DefaultHttpContext(); + context.Request.Path = path; + context.Request.Host = new Microsoft.AspNetCore.Http.HostString(host); + context.Request.Scheme = "https"; + context.Request.IsHttps = true; + context.User = user; + controller.ControllerContext = new ControllerContext(); + controller.ControllerContext.HttpContext = context; + + return controller; + } + } +} diff --git a/src/test/customerkeystoretest/RoleAuthorizerMock.cs b/src/test/customerkeystoretest/RoleAuthorizerMock.cs new file mode 100644 index 0000000..cc3bdd9 --- /dev/null +++ b/src/test/customerkeystoretest/RoleAuthorizerMock.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Customerkeystoretest +{ + using System.Collections.Generic; + using Microsoft.Extensions.Configuration; + using Microsoft.InformationProtection.Web.Models; + + public class RoleAuthorizerMock : RoleAuthorizer + { + private Dictionary> mMembersOf = new Dictionary>(); + + public RoleAuthorizerMock(IConfiguration configuration) : base(configuration) + { + } + + public void AddMemberOf(string sid, string memberOf) + { + if(!mMembersOf.ContainsKey(sid)) + { + mMembersOf[sid] = new List(); + } + mMembersOf[sid].Add(memberOf); + } + + protected override IEnumerable GetMembersOfFromSID(string sid) + { + return mMembersOf.GetValueOrDefault(sid, new List()); + } + } +} diff --git a/src/test/customerkeystoretest/RoleAuthorizerTests.cs b/src/test/customerkeystoretest/RoleAuthorizerTests.cs index 47969d7..9147c0e 100644 --- a/src/test/customerkeystoretest/RoleAuthorizerTests.cs +++ b/src/test/customerkeystoretest/RoleAuthorizerTests.cs @@ -1,14 +1,123 @@ -using System; -using Xunit; - -namespace customerkeystoretest +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Customerkeystoretest { + using System.Collections.Generic; + using System.Security.Claims; + using Microsoft.InformationProtection.Web.Models; + using Xunit; + public class RoleAuthorizerTests { [Fact] - public void Test1() + public void NoValidLDAPPath_Fail() + { + Assert.Throws(() => new RoleAuthorizer(new ConfigurationMock())); + } + + [Fact] + public void ValidLDAPPath_Success() + { + ConfigurationMock configurationMock = new ConfigurationMock(); + configurationMock["RoleAuthorizer:LDAPPath"] = "test"; + var roleAuthorizer = new RoleAuthorizerMock(configurationMock); + } + + [Fact] + public void NoClaims_Fail() + { + ConfigurationMock configurationMock = new ConfigurationMock(); + configurationMock["RoleAuthorizer:LDAPPath"] = "test"; + var roleAuthorizer = new RoleAuthorizerMock(configurationMock); + ClaimsPrincipal principal = new ClaimsPrincipal(); + Assert.Throws(() => roleAuthorizer.CanUserAccessKey(principal, null)); + } + + [Fact] + public void NoValidRoles_Fail() + { + ConfigurationMock configurationMock = new ConfigurationMock(); + configurationMock["RoleAuthorizer:LDAPPath"] = "test"; + var roleAuthorizer = new RoleAuthorizerMock(configurationMock); + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim("onprem_sid", "345435")}); + ClaimsPrincipal principal = new ClaimsPrincipal(); + principal.AddIdentity(identity); + Assert.Throws(() => roleAuthorizer.CanUserAccessKey(principal, null)); + } + + [Fact] + public void NotMemberOfRoles_Fail() + { + ConfigurationMock configurationMock = new ConfigurationMock(); + configurationMock["RoleAuthorizer:LDAPPath"] = "test"; + var roleAuthorizer = new RoleAuthorizerMock(configurationMock); + roleAuthorizer.AddRole("testgroup"); //testgroup is allowed access to the key + roleAuthorizer.AddMemberOf("345435", "CN=othergroup"); //sid is a member of testgroup + + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim("onprem_sid", "345435")}); + ClaimsPrincipal principal = new ClaimsPrincipal(); + principal.AddIdentity(identity); + Assert.Throws(() => roleAuthorizer.CanUserAccessKey(principal, null)); + } + + [Fact] + public void ValidRoles_OneSid_Success() + { + ConfigurationMock configurationMock = new ConfigurationMock(); + configurationMock["RoleAuthorizer:LDAPPath"] = "test"; + var roleAuthorizer = new RoleAuthorizerMock(configurationMock); + roleAuthorizer.AddRole("testgroup"); //testgroup is allowed access to the key + roleAuthorizer.AddMemberOf("345435", "CN=testgroup3"); //sid is a member of testgroup3 + roleAuthorizer.AddMemberOf("345435", "CN=testgroup"); //sid is a member of testgroup + + ClaimsIdentity identity = new ClaimsIdentity(new List(){new Claim("onprem_sid", "345435")}); //User's sid is 345435 + ClaimsPrincipal principal = new ClaimsPrincipal(); + principal.AddIdentity(identity); + + roleAuthorizer.CanUserAccessKey(principal, null); + } + + [Fact] + public void ValidRoles_ManySid_Success() { + ConfigurationMock configurationMock = new ConfigurationMock(); + configurationMock["RoleAuthorizer:LDAPPath"] = "test"; + var roleAuthorizer = new RoleAuthorizerMock(configurationMock); + + //Below are valid roles + roleAuthorizer.AddRole("testgr,oup1"); + roleAuthorizer.AddRole("testgroup2"); + roleAuthorizer.AddRole("testgroup5"); + + //Each sid is a member of the following groups + roleAuthorizer.AddMemberOf("0987635457", "CN=testgr\\,oup3,"); + roleAuthorizer.AddMemberOf("0987635457", "CN=testgr\\,oup1,"); + roleAuthorizer.AddMemberOf("2547546374", "CN=testgroup2,"); + roleAuthorizer.AddMemberOf("86575765567", "CN=testgroup3,"); + roleAuthorizer.AddMemberOf("86575765567", "CN=testgroup1,"); + roleAuthorizer.AddMemberOf("86575765567", "CN=testgroup3,"); + roleAuthorizer.AddMemberOf("86575765567", "CN=testgroup5,"); + roleAuthorizer.AddMemberOf("4576588653", "CN=othergroup,"); + + ClaimsPrincipal principalUser1 = new ClaimsPrincipal(); + ClaimsIdentity identityUser1 = new ClaimsIdentity(new List(){new Claim("onprem_sid", "0987635457")}); + principalUser1.AddIdentity(identityUser1); + roleAuthorizer.CanUserAccessKey(principalUser1, null); + + ClaimsPrincipal principalUser2 = new ClaimsPrincipal(); + ClaimsIdentity identityUser2 = new ClaimsIdentity(new List(){new Claim("onprem_sid", "2547546374")}); + principalUser2.AddIdentity(identityUser2); + roleAuthorizer.CanUserAccessKey(principalUser2, null); + + ClaimsPrincipal principalUser3 = new ClaimsPrincipal(); + ClaimsIdentity identityUser3 = new ClaimsIdentity(new List(){new Claim("onprem_sid", "86575765567")}); + principalUser3.AddIdentity(identityUser3); + roleAuthorizer.CanUserAccessKey(principalUser3, null); + ClaimsPrincipal principalUser4 = new ClaimsPrincipal(); + ClaimsIdentity identityUser4 = new ClaimsIdentity(new List(){new Claim("onprem_sid", "4576588653")}); + principalUser4.AddIdentity(identityUser4); + Assert.Throws(() => roleAuthorizer.CanUserAccessKey(principalUser4, null)); } } } diff --git a/src/test/customerkeystoretest/customerkeystoretest.csproj b/src/test/customerkeystoretest/customerkeystoretest.csproj index ecb7ff7..968bb10 100644 --- a/src/test/customerkeystoretest/customerkeystoretest.csproj +++ b/src/test/customerkeystoretest/customerkeystoretest.csproj @@ -1,8 +1,9 @@ + false + false netcoreapp3.1 - false