Skip to content

Commit 860ef46

Browse files
authored
feat: Handle authenticate certificate policy in emulator (#61)
1 parent a43c630 commit 860ef46

File tree

6 files changed

+165
-3
lines changed

6 files changed

+165
-3
lines changed

src/Testing/Document/MockAuthenticationCertificateProvider.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Security.Cryptography.X509Certificates;
5+
46
using Azure.ApiManagement.PolicyToolkit.Authoring;
57
using Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Policies;
68

@@ -34,5 +36,8 @@ internal Setup(
3436

3537
public void WithCallback(Action<GatewayContext, CertificateAuthenticationConfig> callback) =>
3638
_handler.CallbackSetup.Add((_predicate, callback).ToTuple());
39+
40+
public void WithCertificate(X509Certificate2 certificate) =>
41+
_handler.CertificateSetup.Add((_predicate, certificate).ToTuple());
3742
}
3843
}

src/Testing/Document/TestDocumentExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using Azure.ApiManagement.PolicyToolkit.Authoring;
5+
using Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Data;
56

67
namespace Azure.ApiManagement.PolicyToolkit.Testing.Document;
78

@@ -18,4 +19,7 @@ public static MockPoliciesProvider<IOutboundContext> SetupOutbound(this TestDocu
1819

1920
public static MockPoliciesProvider<IOnErrorContext> SetupOnError(this TestDocument document) =>
2021
new(document.Context.OnErrorProxy);
22+
23+
public static CertificateStore SetupCertificateStore(this TestDocument document) =>
24+
document.Context.CertificateStore;
2125
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Security.Cryptography.X509Certificates;
2+
3+
namespace Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Data;
4+
5+
public class CertificateStore
6+
{
7+
internal readonly Dictionary<string, X509Certificate2> ById = new();
8+
internal readonly Dictionary<string, X509Certificate2> ByThumbprint = new();
9+
10+
public CertificateStore WithCertificateById(string id, X509Certificate2 certificate)
11+
{
12+
ById.Add(id, certificate);
13+
return this;
14+
}
15+
16+
public CertificateStore WithCertificateByThumbprint(string thumbprint, X509Certificate2 certificate)
17+
{
18+
ByThumbprint.Add(thumbprint, certificate);
19+
return this;
20+
}
21+
}
Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,49 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Security.Cryptography.X509Certificates;
5+
46
using Azure.ApiManagement.PolicyToolkit.Authoring;
57

68
namespace Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Policies;
79

810
[Section(nameof(IInboundContext))]
911
internal class AuthenticationCertificateHandler : PolicyHandler<CertificateAuthenticationConfig>
1012
{
13+
public List<Tuple<
14+
Func<GatewayContext, CertificateAuthenticationConfig, bool>,
15+
X509Certificate2
16+
>> CertificateSetup { get; } = new();
17+
1118
public override string PolicyName => nameof(IInboundContext.AuthenticationCertificate);
1219

1320
protected override void Handle(GatewayContext context, CertificateAuthenticationConfig config)
1421
{
15-
throw new NotImplementedException();
22+
var certificateFromCallback = CertificateSetup.Find(tuple => tuple.Item1(context, config))?.Item2;
23+
if (certificateFromCallback is not null)
24+
{
25+
context.Request.Certificate = certificateFromCallback;
26+
return;
27+
}
28+
29+
var certificateStore = context.CertificateStore;
30+
31+
if (!string.IsNullOrWhiteSpace(config.Thumbprint))
32+
{
33+
context.Request.Certificate = certificateStore.ByThumbprint.GetValueOrDefault(config.Thumbprint);
34+
}
35+
else if (!string.IsNullOrWhiteSpace(config.CertificateId))
36+
{
37+
context.Request.Certificate = certificateStore.ById.GetValueOrDefault(config.CertificateId);
38+
}
39+
else if (config.Body is not null)
40+
{
41+
context.Request.Certificate = new X509Certificate2(config.Body, config.Password);
42+
}
43+
else
44+
{
45+
throw new InvalidOperationException(
46+
"AuthenticationCertificatePolicy doesn't have certificate source defined");
47+
}
1648
}
1749
}

src/Testing/GatewayContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using Azure.ApiManagement.PolicyToolkit.Authoring;
55
using Azure.ApiManagement.PolicyToolkit.Testing.Emulator;
6+
using Azure.ApiManagement.PolicyToolkit.Testing.Emulator.Data;
67
using Azure.ApiManagement.PolicyToolkit.Testing.Expressions;
78

89
namespace Azure.ApiManagement.PolicyToolkit.Testing;
@@ -13,6 +14,7 @@ public class GatewayContext : MockExpressionContext
1314
internal readonly SectionContextProxy<IBackendContext> BackendProxy;
1415
internal readonly SectionContextProxy<IOutboundContext> OutboundProxy;
1516
internal readonly SectionContextProxy<IOnErrorContext> OnErrorProxy;
17+
internal readonly CertificateStore CertificateStore = new();
1618

1719
public GatewayContext()
1820
{

test/Test.Testing/Emulator/Policies/AuthenticationCertificateTests.cs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System.Security.Cryptography;
5+
using System.Security.Cryptography.X509Certificates;
6+
47
using Azure.ApiManagement.PolicyToolkit.Authoring;
8+
using Azure.ApiManagement.PolicyToolkit.Authoring.Expressions;
59
using Azure.ApiManagement.PolicyToolkit.Testing;
610
using Azure.ApiManagement.PolicyToolkit.Testing.Document;
711

@@ -10,18 +14,41 @@ namespace Test.Emulator.Emulator.Policies;
1014
[TestClass]
1115
public class AuthenticationCertificateTests
1216
{
13-
class SimpleAuthenticationCertificate : IDocument
17+
class ByIdCertificate : IDocument
1418
{
1519
public void Inbound(IInboundContext context)
1620
{
1721
context.AuthenticationCertificate(new CertificateAuthenticationConfig { CertificateId = "abcdefgh" });
1822
}
1923
}
2024

25+
class ByThumbprintCertificate : IDocument
26+
{
27+
public void Inbound(IInboundContext context)
28+
{
29+
context.AuthenticationCertificate(new CertificateAuthenticationConfig { Thumbprint = "abcdefgh" });
30+
}
31+
}
32+
33+
class ByBodyCertificate : IDocument
34+
{
35+
public void Inbound(IInboundContext context)
36+
{
37+
context.AuthenticationCertificate(
38+
new CertificateAuthenticationConfig
39+
{
40+
Body = GetCertBody(context.ExpressionContext), Password = "testPass"
41+
});
42+
}
43+
44+
public byte[] GetCertBody(IExpressionContext context) =>
45+
context.Deployment.Certificates["someKey"].Export(X509ContentType.Pfx, "testPass");
46+
}
47+
2148
[TestMethod]
2249
public void AuthenticationCertificate_Callback()
2350
{
24-
var test = new SimpleAuthenticationCertificate().AsTestDocument();
51+
var test = new ByIdCertificate().AsTestDocument();
2552
var executedCallback = false;
2653
test.SetupInbound().AuthenticationCertificate().WithCallback((_, _) =>
2754
{
@@ -32,4 +59,75 @@ public void AuthenticationCertificate_Callback()
3259

3360
executedCallback.Should().BeTrue();
3461
}
62+
63+
[TestMethod]
64+
public void AuthenticationCertificate_ReturnCertificate()
65+
{
66+
var certificate = CreateTestCertificate();
67+
var test = new ByIdCertificate().AsTestDocument();
68+
test.SetupInbound().AuthenticationCertificate().WithCertificate(certificate);
69+
70+
test.RunInbound();
71+
72+
test.Context.Request.Certificate.Should().Be(certificate);
73+
}
74+
75+
[TestMethod]
76+
public void AuthenticationCertificate_SetupCertificateStore_WithCertificateByThumbprint()
77+
{
78+
var certificate = CreateTestCertificate();
79+
var test = new ByThumbprintCertificate().AsTestDocument();
80+
test.SetupCertificateStore().WithCertificateByThumbprint("abcdefgh", certificate);
81+
82+
test.RunInbound();
83+
84+
test.Context.Request.Certificate.Should().Be(certificate);
85+
}
86+
87+
[TestMethod]
88+
public void AuthenticationCertificate_SetupCertificateStore_WithCertificateById()
89+
{
90+
var certificate = CreateTestCertificate();
91+
var test = new ByIdCertificate().AsTestDocument();
92+
test.SetupCertificateStore().WithCertificateById("abcdefgh", certificate);
93+
94+
test.RunInbound();
95+
96+
test.Context.Request.Certificate.Should().Be(certificate);
97+
}
98+
99+
[TestMethod]
100+
public void AuthenticationCertificate_Body()
101+
{
102+
var certificate = CreateTestCertificate();
103+
var test = new ByBodyCertificate().AsTestDocument();
104+
test.Context.Deployment.Certificates.Add("someKey", certificate);
105+
106+
test.RunInbound();
107+
108+
test.Context.Request.Certificate.Should().Be(certificate);
109+
}
110+
111+
public X509Certificate2 CreateTestCertificate()
112+
{
113+
using RSA rsa = RSA.Create(2048);
114+
var request = new CertificateRequest(
115+
"CN=MyCertificate",
116+
rsa,
117+
HashAlgorithmName.SHA256,
118+
RSASignaturePadding.Pkcs1);
119+
120+
// Add extensions
121+
request.CertificateExtensions.Add(
122+
new X509BasicConstraintsExtension(false, false, 0, false));
123+
request.CertificateExtensions.Add(
124+
new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));
125+
request.CertificateExtensions.Add(
126+
new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
127+
128+
var certificate = request.CreateSelfSigned(
129+
DateTimeOffset.Now,
130+
DateTimeOffset.Now.AddYears(1));
131+
return certificate;
132+
}
35133
}

0 commit comments

Comments
 (0)