Skip to content

Commit 6595330

Browse files
committed
Added RestSharp encryption/decryption interceptor
1 parent 42ba0eb commit 6595330

File tree

8 files changed

+567
-2
lines changed

8 files changed

+567
-2
lines changed

Mastercard.Developer.ClientEncryption.Core/Encryption/FieldLevelEncryption.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,8 @@ private static string SanitizeJson(string json)
345345
{
346346
return json.Replace("\n", string.Empty)
347347
.Replace("\r", string.Empty)
348-
.Replace("\t", string.Empty);
348+
.Replace("\t", string.Empty)
349+
.Replace(Environment.NewLine, string.Empty);
349350
}
350351

351352
private static JToken AsPrimitiveValue(string value)

Mastercard.Developer.ClientEncryption.Core/Encryption/FieldLevelEncryptionConfig.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,13 @@ internal FieldLevelEncryptionConfig() {}
138138
public FieldValueEncoding ValueEncoding { get; internal set; }
139139

140140
/// <summary>
141-
/// If the encryption parameters must be written to/read from HTTP headers.
141+
/// If the encryption parameters must be written to/read from HTTP payloads.
142142
/// </summary>
143143
public bool UseHttpPayloads() => !string.IsNullOrEmpty(EncryptedKeyFieldName) && !string.IsNullOrEmpty(IvFieldName);
144+
145+
/// <summary>
146+
/// If the encryption parameters must be written to/read from HTTP headers.
147+
/// </summary>
148+
public bool UseHttpHeaders() => !string.IsNullOrEmpty(EncryptedKeyHeaderName) && !string.IsNullOrEmpty(IvHeaderName);
144149
}
145150
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using Mastercard.Developer.ClientEncryption.Core.Encryption;
5+
using RestSharp.Portable;
6+
7+
namespace Mastercard.Developer.ClientEncryption.RestSharp.Interceptors
8+
{
9+
/// <summary>
10+
/// A class for encrypting RestSharp requests and decrypting RestSharp responses.
11+
/// </summary>
12+
public class RestSharpFieldLevelEncryptionInterceptor
13+
{
14+
private readonly FieldLevelEncryptionConfig _config;
15+
16+
public RestSharpFieldLevelEncryptionInterceptor(FieldLevelEncryptionConfig config)
17+
{
18+
_config = config;
19+
}
20+
21+
/// <summary>
22+
/// Encrypt RestSharp request payloads.
23+
/// </summary>
24+
/// <param name="request">A RestSharp request object</param>
25+
public void InterceptRequest(IRestRequest request)
26+
{
27+
if (request == null) throw new ArgumentNullException(nameof(request));
28+
29+
try
30+
{
31+
// Check request actually has a payload
32+
var bodyParam = request.Parameters.FirstOrDefault(param => param.Type == ParameterType.RequestBody);
33+
if (bodyParam == null)
34+
{
35+
// Nothing to encrypt
36+
return;
37+
}
38+
var payload = bodyParam.Value.ToString();
39+
if (string.IsNullOrEmpty(payload))
40+
{
41+
// Nothing to encrypt
42+
return;
43+
}
44+
45+
// Encrypt fields & update headers
46+
string encryptedPayload;
47+
if (_config.UseHttpHeaders())
48+
{
49+
// Generate encryption params and add them as HTTP headers
50+
var parameters = FieldLevelEncryptionParams.Generate(_config);
51+
UpdateRequestHeader(request, _config.IvHeaderName, parameters.IvValue);
52+
UpdateRequestHeader(request, _config.EncryptedKeyHeaderName, parameters.EncryptedKeyValue);
53+
UpdateRequestHeader(request, _config.EncryptionCertificateFingerprintHeaderName, parameters.EncryptionCertificateFingerprintValue);
54+
UpdateRequestHeader(request, _config.EncryptionKeyFingerprintHeaderName, parameters.EncryptionKeyFingerprintValue);
55+
UpdateRequestHeader(request, _config.OaepPaddingDigestAlgorithmHeaderName, parameters.OaepPaddingDigestAlgorithmValue);
56+
encryptedPayload = FieldLevelEncryption.EncryptPayload(payload, _config, parameters);
57+
}
58+
else
59+
{
60+
// Encryption params will be stored in the payload
61+
encryptedPayload = FieldLevelEncryption.EncryptPayload(payload, _config);
62+
}
63+
64+
// Update body and content length
65+
bodyParam.Value = encryptedPayload;
66+
UpdateRequestHeader(request, "Content-Length", encryptedPayload.Length);
67+
}
68+
catch (EncryptionException)
69+
{
70+
throw;
71+
}
72+
catch (Exception e)
73+
{
74+
throw new EncryptionException("Failed to intercept and encrypt request!", e);
75+
}
76+
}
77+
78+
/// <summary>
79+
/// Decrypt RestSharp response payloads.
80+
/// </summary>
81+
/// <param name="response">A RestSharp response object</param>
82+
public void InterceptResponse(IRestResponse response)
83+
{
84+
if (response == null) throw new ArgumentNullException(nameof(response));
85+
86+
try
87+
{
88+
// Read response payload
89+
var encryptedPayload = response.Content;
90+
if (string.IsNullOrEmpty(encryptedPayload))
91+
{
92+
// Nothing to decrypt
93+
return;
94+
}
95+
96+
// Decrypt fields & update headers
97+
string decryptedPayload;
98+
if (_config.UseHttpHeaders())
99+
{
100+
// Read encryption params from HTTP headers and delete headers
101+
var ivValue = ReadAndRemoveHeader(response, _config.IvHeaderName);
102+
var encryptedKeyValue = ReadAndRemoveHeader(response, _config.EncryptedKeyHeaderName);
103+
var oaepPaddingDigestAlgorithmValue = ReadAndRemoveHeader(response, _config.OaepPaddingDigestAlgorithmHeaderName);
104+
ReadAndRemoveHeader(response, _config.EncryptionCertificateFingerprintHeaderName);
105+
ReadAndRemoveHeader(response, _config.EncryptionKeyFingerprintHeaderName);
106+
var parameters = new FieldLevelEncryptionParams(_config, ivValue, encryptedKeyValue, oaepPaddingDigestAlgorithmValue);
107+
decryptedPayload = FieldLevelEncryption.DecryptPayload(encryptedPayload, _config, parameters);
108+
}
109+
else
110+
{
111+
// Encryption params are stored in the payload
112+
decryptedPayload = FieldLevelEncryption.DecryptPayload(encryptedPayload, _config);
113+
}
114+
115+
// Update body and content length
116+
var contentTypeInfo = response.GetType().GetTypeInfo().GetDeclaredField("_content");
117+
contentTypeInfo.SetValue(response, new Lazy<string>(() => decryptedPayload));
118+
UpdateResponseHeader(response, "Content-Length", decryptedPayload.Length.ToString());
119+
}
120+
catch (EncryptionException)
121+
{
122+
throw;
123+
}
124+
catch (Exception e)
125+
{
126+
throw new EncryptionException("Failed to intercept and decrypt response!", e);
127+
}
128+
}
129+
130+
private static void UpdateRequestHeader(IRestRequest request, string name, object value)
131+
{
132+
if (string.IsNullOrEmpty(name))
133+
{
134+
// Do nothing
135+
return;
136+
}
137+
138+
request.AddOrUpdateHeader(name, value);
139+
}
140+
141+
private static void UpdateResponseHeader(IRestResponse response, string name, string value)
142+
{
143+
if (string.IsNullOrEmpty(name))
144+
{
145+
// Do nothing
146+
return;
147+
}
148+
149+
response.Headers.Remove(name);
150+
response.Headers.Add(name, value);
151+
}
152+
153+
private static string ReadAndRemoveHeader(IRestResponse response, string name)
154+
{
155+
if (string.IsNullOrEmpty(name) || !response.Headers.Contains(name))
156+
{
157+
// Do nothing
158+
return null;
159+
}
160+
161+
var value = response.Headers.GetValue(name);
162+
response.Headers.Remove(name);
163+
return value;
164+
}
165+
}
166+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard1.3</TargetFramework>
5+
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
6+
<Version>1.0.0</Version>
7+
<Product>Mastercard.Developer.ClientEncryption.RestSharp</Product>
8+
<Authors>Mastercard</Authors>
9+
<Company>Mastercard</Company>
10+
<PackageLicenseFile>LICENSE</PackageLicenseFile>
11+
<PackageProjectUrl>https://github.com/Mastercard/client-encryption-csharp</PackageProjectUrl>
12+
<PackageReleaseNotes>See: https://github.com/Mastercard/client-encryption-csharp/releases</PackageReleaseNotes>
13+
<RepositoryUrl>https://github.com/Mastercard/client-encryption-csharp</RepositoryUrl>
14+
<Description>Library for Mastercard API compliant payload encryption/decryption</Description>
15+
<!-- See: https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/strong-naming -->
16+
<SignAssembly>true</SignAssembly>
17+
<AssemblyOriginatorKeyFile>../Identity.snk</AssemblyOriginatorKeyFile>
18+
<AssemblyVersion>1.0.0.0</AssemblyVersion> <!-- Frozen -->
19+
<FileVersion>1.0.0.0</FileVersion> <!-- Same version as the package version -->
20+
</PropertyGroup>
21+
22+
<ItemGroup>
23+
<None Include="../LICENSE" Pack="true" PackagePath="" />
24+
<None Include="../README.md" Pack="true" PackagePath="" />
25+
</ItemGroup>
26+
27+
<ItemGroup>
28+
<PackageReference Include="FubarCoder.RestSharp.Portable.Core" Version="4.0.6" /> <!-- Minimum version, inclusive -->
29+
</ItemGroup>
30+
31+
<ItemGroup>
32+
<ProjectReference Include="..\Mastercard.Developer.ClientEncryption.Core\Mastercard.Developer.ClientEncryption.Core.csproj" />
33+
</ItemGroup>
34+
35+
</Project>

0 commit comments

Comments
 (0)