Skip to content

Commit 4e9622a

Browse files
committed
code cleanup
1 parent 8085e00 commit 4e9622a

10 files changed

Lines changed: 90 additions & 50 deletions

File tree

APIMatic.Core.Test/Security/Cryptography/DigestCodecTests.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public void DigestCodec_Decode_Success(EncodingType encodingType, string input,
2020

2121
[TestCase(EncodingType.Hex, "")]
2222
[TestCase(EncodingType.Hex, null)]
23-
[TestCase(EncodingType.Hex, "ABC")]
2423
[TestCase(EncodingType.Base64, "")]
2524
[TestCase(EncodingType.Base64, null)]
2625
[TestCase(EncodingType.Base64Url, "")]
@@ -31,11 +30,18 @@ public void DigestCodecIncorrectInput_Decode_DigestCodec_Create_Exception(Encodi
3130
Assert.Throws<ArgumentException>(() => codec.Decode(input));
3231
}
3332

33+
[TestCase(EncodingType.Hex, "ABC")]
34+
public void DigestCodecIncorrectFormat_Decode_DigestCodec_Create_Exception(EncodingType encodingType, string input)
35+
{
36+
var codec = DigestCodecFactory.Create(encodingType);
37+
Assert.Throws<FormatException>(() => codec.Decode(input));
38+
}
39+
3440
[TestCase(-1)]
3541
public void DigestCodec_Create_Exception(int invalidValue)
3642
{
3743
var encodingType = (EncodingType)invalidValue;
38-
Assert.Throws<ArgumentOutOfRangeException>(() => DigestCodecFactory.Create(encodingType));
44+
Assert.Throws<NotSupportedException>(() => DigestCodecFactory.Create(encodingType));
3945
}
4046
}
4147
}

APIMatic.Core.Test/Security/SignatureVerifier/SignatureVerificationExtensionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public class SignatureVerificationExtensionsTests
1515
[TestCase(new byte[] { 1, 2, 3, 4 }, new byte[] { 1, 2, 3, 5 }, false)]
1616
public void ConstantTimeEquals_VariousInputs_ReturnsExpected(byte[] a, byte[] b, bool expected)
1717
{
18-
Assert.AreEqual(expected, a.ConstantTimeEquals(b));
18+
Assert.AreEqual(expected, SignatureVerifierExtensions.FixedTimeEquals(a, b));
1919
}
2020
}
2121
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Text;
4+
using System.Threading.Tasks;
5+
using APIMatic.Core.Test.MockTypes.Http.Request;
6+
using APIMatic.Core.Utilities;
7+
using NUnit.Framework;
8+
9+
namespace APIMatic.Core.Test.Utilities
10+
{
11+
[TestFixture]
12+
public class IHttpRequestDataExtensionsTests
13+
{
14+
15+
[TestCase(null, new byte[0])]
16+
[TestCase("", new byte[0])]
17+
[TestCase("hello", new byte[] { 104, 101, 108, 108, 111 })]
18+
public async Task ReadBodyStreamToByteArrayAsync_VariousBodies_ReturnsExpected(string body, byte[] expected)
19+
{
20+
var stream = body == null ? null : new MemoryStream(Encoding.UTF8.GetBytes(body));
21+
var request = new HttpRequestData(new Dictionary<string, string[]>(), stream);
22+
23+
var result = await request.ReadBodyStreamToByteArrayAsync();
24+
25+
Assert.AreEqual(expected, result);
26+
}
27+
}
28+
}

APIMatic.Core.Test/Utilities/Json/JsonPointerResolverTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,18 @@ public void ResolveScopedJsonValue_BooleanToken_ReturnsTokenToString()
8181

8282
Assert.AreEqual("True", result);
8383
}
84+
85+
[TestCase("#/name", "{\"name\":\"John\",\"age\":30}", "John")]
86+
[TestCase("#/invalid", "{\"name\":\"John\"}", null)]
87+
[TestCase(null, "{\"name\":\"John\"}", null)]
88+
[TestCase("", "{\"name\":\"John\"}", null)]
89+
[TestCase("/name", "{\"name\":\"John\"}", null)]
90+
[TestCase("#/age", "{\"age\":30}", "30")]
91+
[TestCase("#/name", null, null)]
92+
public void ResolveJsonValue_VariousCases_ReturnsExpected(string jsonPointer, string json, string expected)
93+
{
94+
var result = JsonPointerResolver.ResolveJsonValue(jsonPointer, json);
95+
Assert.AreEqual(expected, result);
96+
}
8497
}
8598
}

APIMatic.Core/Security/Cryptography/Base64UrlDigestCodec.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class Base64UrlDigestCodec : IDigestCodec
1212
/// </summary>
1313
/// <param name="encoded">The Base64Url string to decode.</param>
1414
/// <returns>The decoded byte array.</returns>
15+
/// <exception cref="ArgumentNullException">Thrown when the input is null.</exception>
1516
/// <exception cref="FormatException">Thrown when the input is not a valid Base64Url string.</exception>
1617
public byte[] Decode(string encoded)
1718
{

APIMatic.Core/Security/Cryptography/DigestCodecFactory.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,17 @@ public static class DigestCodecFactory
1111
/// <summary>
1212
/// Creates a digest codec for the specified encoding type.
1313
/// </summary>
14-
/// <param name="digestEncoding">The encoding type to use.</param>
14+
/// <param name="encodingType">The encoding type to use.</param>
1515
/// <returns>A digest codec instance.</returns>
1616
/// <exception cref="ArgumentOutOfRangeException">Thrown when an unsupported encoding type is specified.</exception>
17-
public static IDigestCodec Create(EncodingType digestEncoding)
17+
public static IDigestCodec Create(EncodingType encodingType)
1818
{
19-
return digestEncoding switch
19+
return encodingType switch
2020
{
2121
EncodingType.Hex => new HexDigestCodec(),
2222
EncodingType.Base64 => new Base64DigestCodec(),
2323
EncodingType.Base64Url => new Base64UrlDigestCodec(),
24-
_ => throw new ArgumentOutOfRangeException(nameof(digestEncoding),
25-
$"Unsupported encoding type: {digestEncoding}")
24+
_ => throw new NotSupportedException($"Unsupported encoding type: {encodingType}")
2625
};
2726
}
2827
}

APIMatic.Core/Security/Cryptography/HexDigestCodec.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace APIMatic.Core.Security.Cryptography
55
{
66
/// <summary>
7-
/// encodedadecimal digest codec implementation.
7+
/// HexDigestCodec digest codec implementation.
88
/// </summary>
99
public class HexDigestCodec : IDigestCodec
1010
{
@@ -25,7 +25,7 @@ public byte[] Decode(string encoded)
2525

2626
// Hex string must have even length
2727
if (encoded.Length % 2 != 0)
28-
throw new ArgumentException("Hexadecimal string must have even length", nameof(encoded));
28+
throw new FormatException("Hexadecimal string must have even length");
2929

3030
byte[] bytes = new byte[encoded.Length / 2];
3131

APIMatic.Core/Security/SignatureVerifier/HmacSignatureVerifier.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ public class HmacSignatureVerifier : ISignatureVerifier
4747
/// Codec used for encoding and decoding digests based on the specified encoding type.
4848
/// </summary>
4949
private readonly IDigestCodec _digestCodec;
50+
51+
private const string DigestPlaceHolder = "{digest}";
5052

5153
/// <summary>
5254
/// Initializes a new instance of the HmacSignatureVerifier class.
@@ -81,7 +83,7 @@ public HmacSignatureVerifier(
8183
await request.ReadBodyStreamToByteArrayAsync(cancellationToken)
8284
.ConfigureAwait(false));
8385
}
84-
86+
8587
/// <summary>
8688
/// Verifies the HMAC signature of the specified HTTP request.
8789
/// </summary>
@@ -114,7 +116,7 @@ public async Task<VerificationResult> VerifyAsync(IHttpRequestData request,
114116
{
115117
var computedHash = hmac.ComputeHash(resolvedTemplateBytes);
116118

117-
return providedSignature.ConstantTimeEquals(computedHash)
119+
return SignatureVerifierExtensions.FixedTimeEquals(computedHash, providedSignature)
118120
? VerificationResult.Success()
119121
: VerificationResult.Failure(new[] { "Signature verification failed." });
120122
}
@@ -132,16 +134,16 @@ private static string ExtractDigestFromTemplate(string signatureValue, string si
132134
return string.Empty;
133135

134136
// If template is just "{digest}", return the signature as-is
135-
if (signatureValueTemplate == "{digest}")
137+
if (signatureValueTemplate == DigestPlaceHolder)
136138
return signatureValue;
137139

138140
// Extract digest from template
139-
var digestIndex = signatureValueTemplate.IndexOf("{digest}", StringComparison.Ordinal);
141+
var digestIndex = signatureValueTemplate.IndexOf(DigestPlaceHolder, StringComparison.Ordinal);
140142
if (digestIndex == -1)
141143
return string.Empty;
142144

143145
var prefix = signatureValueTemplate[..digestIndex];
144-
var suffix = signatureValueTemplate[(digestIndex + 8)..];
146+
var suffix = signatureValueTemplate[(digestIndex + DigestPlaceHolder.Length)..];
145147

146148
if (!signatureValue.StartsWith(prefix) || !signatureValue.EndsWith(suffix))
147149
return string.Empty;
Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,41 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
14
namespace APIMatic.Core.Security.SignatureVerifier
25
{
36
internal static class SignatureVerifierExtensions
47
{
58
/// <summary>
69
/// Performs a secure comparison of two byte arrays to prevent timing attacks.
710
/// </summary>
8-
/// <param name="a">First byte array.</param>
9-
/// <param name="b">Second byte array.</param>
11+
/// <param name="left">First byte array.</param>
12+
/// <param name="right">Second byte array.</param>
1013
/// <returns>True if arrays are equal, false otherwise.</returns>
11-
public static bool ConstantTimeEquals(this byte[] a, byte[] b)
14+
/// <remarks>
15+
/// This implementation is copied from CryptographicOperations in System.Security.Cryptography
16+
/// </remarks>
17+
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
18+
public static bool FixedTimeEquals(ReadOnlySpan<byte> left, ReadOnlySpan<byte> right)
1219
{
13-
if (a == null && b == null)
14-
return true;
15-
16-
if (a == null || b == null)
17-
return false;
18-
19-
if (a.Length != b.Length)
20+
// NoOptimization because we want this method to be exactly as non-short-circuiting
21+
// as written.
22+
//
23+
// NoInlining because the NoOptimization would get lost if the method got inlined.
24+
25+
if (left.Length != right.Length)
26+
{
2027
return false;
21-
22-
var result = 0;
23-
for (int i = 0; i < a.Length; i++)
28+
}
29+
30+
int length = left.Length;
31+
int accum = 0;
32+
33+
for (int i = 0; i < length; i++)
2434
{
25-
result |= a[i] ^ b[i];
35+
accum |= left[i] - right[i];
2636
}
27-
28-
return result == 0;
37+
38+
return accum == 0;
2939
}
3040
}
3141
}

APIMatic.Core/Types/Sdk/Exceptions/SignatureVerificationException.cs

Lines changed: 0 additions & 19 deletions
This file was deleted.

0 commit comments

Comments
 (0)