Skip to content

Commit

Permalink
Merge branch 'release/0.77.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jericho committed Apr 4, 2021
2 parents 49fe8db + d23bac9 commit 42ae078
Show file tree
Hide file tree
Showing 11 changed files with 153 additions and 325 deletions.
12 changes: 12 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"cake.tool": {
"version": "1.1.0",
"commands": [
"dotnet-cake"
]
}
}
}
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mono_crash.*
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
Expand Down Expand Up @@ -65,6 +66,9 @@ project.lock.json
project.fragment.lock.json
artifacts/

# ASP.NET Scaffolding
ScaffoldingReadMe.txt

# StyleCop
StyleCopReport.xml

Expand Down Expand Up @@ -315,7 +319,7 @@ __pycache__/

# Cake
tools/**
!tools/packages.config
BuildArtifacts/

# Tabs Studio
*.tss
Expand Down Expand Up @@ -356,6 +360,9 @@ MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/

# Fody - auto-generated XML schema
FodyWeavers.xsd

# WinMerge
*.bak

Expand Down
7 changes: 7 additions & 0 deletions GitVersion.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
mode: ContinuousDelivery
branches:
master:
regex: (master|main)
mode: ContinuousDelivery
tag:
increment: Patch
prevent-increment-of-merged-branch-version: true
track-merge-target: false
feature:
regex: feature(s)?[/-]
mode: ContinuousDeployment
Expand Down
14 changes: 7 additions & 7 deletions Source/StrongGrid.UnitTests/WebhookParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ public void InboundEmail()
inboundEmail.SpamScore.ShouldBeNull();
inboundEmail.Spf.ShouldBe("softfail");
inboundEmail.Subject.ShouldBe("Test #1");
inboundEmail.Text.ShouldBe("Test #1\r\n");
inboundEmail.Text.Replace("\r\n", "\n").ShouldBe("Test #1\n");
inboundEmail.To.ShouldNotBeNull();
inboundEmail.To.Length.ShouldBe(1);
inboundEmail.To[0].Email.ShouldBe("[email protected]");
Expand Down Expand Up @@ -455,7 +455,7 @@ public async Task InboundEmailAsync()
inboundEmail.SpamScore.ShouldBeNull();
inboundEmail.Spf.ShouldBe("softfail");
inboundEmail.Subject.ShouldBe("Test #1");
inboundEmail.Text.ShouldBe("Test #1\r\n");
inboundEmail.Text.Replace("\r\n", "\n").ShouldBe("Test #1\n");
inboundEmail.To.ShouldNotBeNull();
inboundEmail.To.Length.ShouldBe(1);
inboundEmail.To[0].Email.ShouldBe("[email protected]");
Expand All @@ -482,8 +482,8 @@ public void RawPayloadWithAttachments()

inboundEmail.Dkim.ShouldBe("{@sendgrid.com : pass}");

var rawEmailTestData = File.ReadAllText("InboudEmailTestData/raw_email.txt");
inboundEmail.RawEmail.Trim().ShouldBe(rawEmailTestData);
var rawEmailTestData = File.ReadAllText("InboudEmailTestData/raw_email.txt").Replace("\r\n", "\n");
inboundEmail.RawEmail.Trim().Replace("\r\n", "\n").ShouldBe(rawEmailTestData);

inboundEmail.To[0].Email.ShouldBe("[email protected]");
inboundEmail.To[0].Name.ShouldBe(string.Empty);
Expand Down Expand Up @@ -535,8 +535,8 @@ public async Task RawPayloadWithAttachmentsAsync()

inboundEmail.Dkim.ShouldBe("{@sendgrid.com : pass}");

var rawEmailTestData = File.ReadAllText("InboudEmailTestData/raw_email.txt");
inboundEmail.RawEmail.Trim().ShouldBe(rawEmailTestData);
var rawEmailTestData = File.ReadAllText("InboudEmailTestData/raw_email.txt").Replace("\r\n", "\n");
inboundEmail.RawEmail.Trim().Replace("\r\n", "\n").ShouldBe(rawEmailTestData);

inboundEmail.To[0].Email.ShouldBe("[email protected]");
inboundEmail.To[0].Name.ShouldBe(string.Empty);
Expand Down Expand Up @@ -589,7 +589,7 @@ public void InboundEmail_with_unusual_encoding()
new KeyValuePair<string, Encoding>("html", Encoding.ASCII),
new KeyValuePair<string, Encoding>("text", Encoding.UTF8) // The original encoding is iso-8859-10 but we fallback on UTF-8
}).Count().ShouldBe(0);
inboundEmail.Text.ShouldBe("Hello SendGrid!\r\n");
inboundEmail.Text.Replace("\r\n", "\n").ShouldBe("Hello SendGrid!\n");
}
}

Expand Down
42 changes: 30 additions & 12 deletions Source/StrongGrid/Utilities/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@ internal static class Utils
public static RecyclableMemoryStreamManager MemoryStreamManager { get; } = new RecyclableMemoryStreamManager();

/// <summary>
/// Converts a base64 encoded secp256r1/NIST P-256 public key.
/// Converts a secp256r1/NIST P-256 public key.
/// </summary>
/// <param name="base64EncodedPublicKey">The base64 encoded public key.</param>
/// <param name="subjectPublicKeyInfo">The public key.</param>
/// <returns>The converted public key.</returns>
/// <remarks>
/// From https://stackoverflow.com/questions/44502331/c-sharp-get-cngkey-object-from-public-key-in-text-file/44527439#44527439 .
/// </remarks>
public static byte[] ConvertSecp256R1PublicKeyToEccPublicBlob(string base64EncodedPublicKey)
public static byte[] ConvertSecp256R1PublicKeyToEccPublicBlob(byte[] subjectPublicKeyInfo)
{
var subjectPublicKeyInfo = Convert.FromBase64String(base64EncodedPublicKey);

if (subjectPublicKeyInfo.Length != 91)
throw new InvalidOperationException();

Expand All @@ -38,15 +36,35 @@ public static byte[] ConvertSecp256R1PublicKeyToEccPublicBlob(string base64Encod

var cngBlob = new byte[CngBlobPrefix.Length + 64];
Buffer.BlockCopy(CngBlobPrefix, 0, cngBlob, 0, CngBlobPrefix.Length);

Buffer.BlockCopy(
subjectPublicKeyInfo,
Secp256R1Prefix.Length,
cngBlob,
CngBlobPrefix.Length,
64);
Buffer.BlockCopy(subjectPublicKeyInfo, Secp256R1Prefix.Length, cngBlob, CngBlobPrefix.Length, 64);

return cngBlob;
}

/// <summary>
/// Get the 'x' and 'y' values from a secp256r1/NIST P-256 public key.
/// </summary>
/// <param name="subjectPublicKeyInfo">The public key.</param>
/// <returns>The X and Y values.</returns>
/// <remarks>
/// From https://stackoverflow.com/a/66938822/153084.
/// </remarks>
public static (byte[] X, byte[] Y) GetXYFromSecp256r1PublicKey(byte[] subjectPublicKeyInfo)
{
if (subjectPublicKeyInfo.Length != 91)
throw new InvalidOperationException();

var prefix = Secp256R1Prefix;

if (!subjectPublicKeyInfo.Take(prefix.Length).SequenceEqual(prefix))
throw new InvalidOperationException();

var x = new byte[32];
var y = new byte[32];
Buffer.BlockCopy(subjectPublicKeyInfo, prefix.Length, x, 0, x.Length);
Buffer.BlockCopy(subjectPublicKeyInfo, prefix.Length + x.Length, y, 0, y.Length);

return (x, y);
}
}
}
46 changes: 43 additions & 3 deletions Source/StrongGrid/WebhookParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,57 @@ public Event[] ParseSignedEventsWebhook(string requestBody, string publicKey, st
if (string.IsNullOrEmpty(signature)) throw new ArgumentNullException(nameof(signature));
if (string.IsNullOrEmpty(timestamp)) throw new ArgumentNullException(nameof(timestamp));

// Convert the signature and public key provided by SendGrid into formats usable by the .net crypto classes
var sig = ConvertECDSASignature.LightweightConvertSignatureFromX9_62ToISO7816_8(256, Convert.FromBase64String(signature));
var cngBlob = Utils.ConvertSecp256R1PublicKeyToEccPublicBlob(publicKey);
// Decode the base64 encoded values
var signatureBytes = Convert.FromBase64String(signature);
var publicKeyBytes = Convert.FromBase64String(publicKey);

// Must combine the timestamp and the payload
var data = Encoding.UTF8.GetBytes(timestamp + requestBody);

/*
The 'ECDsa.ImportSubjectPublicKeyInfo' method was introduced in .NET core 3.0
and the DSASignatureFormat enum was introduced in .NET 5.0.
We can get rid of the 'ConvertECDSASignature' class and the Utils methods that
convert public keys when we stop suporting .NET framework and .NET standard
Note:
ECDsa is cross-platform and can be used on Windows and Linux/Ubuntu.
ECDsaCng is Windows only.
*/

#if NET5_0_OR_GREATER
// Verify the signature
var eCDsa = ECDsa.Create();
eCDsa.ImportSubjectPublicKeyInfo(publicKeyBytes, out _);
var verified = eCDsa.VerifyData(data, signatureBytes, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
#elif NET472_OR_GREATER || NETSTANDARD2_0
// Convert the signature and public key provided by SendGrid into formats usable by the ECDsa .net crypto class
var sig = ConvertECDSASignature.LightweightConvertSignatureFromX9_62ToISO7816_8(256, signatureBytes);
var (x, y) = Utils.GetXYFromSecp256r1PublicKey(publicKeyBytes);

// Verify the signature
var eCDsa = ECDsa.Create();
eCDsa.ImportParameters(new ECParameters
{
Curve = ECCurve.NamedCurves.nistP256, // aka secp256r1 aka prime256v1
Q = new ECPoint
{
X = x,
Y = y
}
});
var verified = eCDsa.VerifyData(data, sig, HashAlgorithmName.SHA256);
#else
// Convert the signature and public key provided by SendGrid into formats usable by the ECDsaCng .net crypto class
var sig = ConvertECDSASignature.LightweightConvertSignatureFromX9_62ToISO7816_8(256, signatureBytes);
var cngBlob = Utils.ConvertSecp256R1PublicKeyToEccPublicBlob(publicKeyBytes);

// Verify the signature
var cngKey = CngKey.Import(cngBlob, CngKeyBlobFormat.EccPublicBlob);
var eCDsaCng = new ECDsaCng(cngKey);
var verified = eCDsaCng.VerifyData(data, sig);
#endif

if (!verified)
{
Expand Down
10 changes: 6 additions & 4 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ init:

# Build script
build_script:
- ps: .\build.ps1 -bootstrap
- ps: .\build.ps1 -Target AppVeyor
- dotnet --info
- ps: .\build.ps1 --target=AppVeyor

# Tests
test: off
Expand All @@ -21,10 +21,12 @@ branches:

# Build cache
cache:
- tools -> build.cake, tools\packages.config
- tools -> build.cake

# Environment configuration
image: Visual Studio 2019
image:
- Visual Studio 2019
- Ubuntu1804

#---------------------------------#
# Skip builds for doc changes #
Expand Down
49 changes: 25 additions & 24 deletions build.cake
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Install tools.
#tool nuget:?package=GitVersion.CommandLine&version=5.6.0
#tool dotnet:?package=GitVersion.Tool&version=5.6.6
#tool nuget:?package=GitReleaseManager&version=0.11.0
#tool nuget:?package=OpenCover&version=4.7.922
#tool nuget:?package=ReportGenerator&version=4.8.4
#tool nuget:?package=ReportGenerator&version=4.8.7
#tool nuget:?package=coveralls.io&version=1.4.2
#tool nuget:?package=xunit.runner.console&version=2.4.1

// Install addins.
#addin nuget:?package=Cake.Coveralls&version=0.10.2
#addin nuget:?package=Cake.Coveralls&version=1.0.0


///////////////////////////////////////////////////////////////////////////////
Expand All @@ -17,6 +17,10 @@
var target = Argument<string>("target", "Default");
var configuration = Argument<string>("configuration", "Release");

if (target == "AppVeyor" && IsRunningOnUnix())
{
target = "AppVeyor-Ubuntu";
}

///////////////////////////////////////////////////////////////////////////////
// GLOBAL VARIABLES
Expand All @@ -25,7 +29,7 @@ var configuration = Argument<string>("configuration", "Release");
var libraryName = "StrongGrid";
var gitHubRepo = "StrongGrid";

var testCoverageFilter = "+[StrongGrid]* -[StrongGrid]StrongGrid.Properties.* -[StrongGrid]StrongGrid.Models.* -[StrongGrid]StrongGrid.Logging.*";
var testCoverageFilter = "+[StrongGrid]* -[StrongGrid]StrongGrid.Properties.* -[StrongGrid]StrongGrid.Models.*";
var testCoverageExcludeByAttribute = "*.ExcludeFromCodeCoverage*";
var testCoverageExcludeByFile = "*/*Designer.cs;*/*AssemblyInfo.cs";

Expand All @@ -52,7 +56,7 @@ var versionInfo = GitVersion(new GitVersionSettings() { OutputType = GitVersionO
var milestone = versionInfo.MajorMinorPatch;
var cakeVersion = typeof(ICakeContext).Assembly.GetName().Version.ToString();
var isLocalBuild = BuildSystem.IsLocalBuild;
var isMainBranch = StringComparer.OrdinalIgnoreCase.Equals("master", BuildSystem.AppVeyor.Environment.Repository.Branch);
var isMainBranch = StringComparer.OrdinalIgnoreCase.Equals("main", BuildSystem.AppVeyor.Environment.Repository.Branch);
var isMainRepo = StringComparer.OrdinalIgnoreCase.Equals($"{gitHubRepoOwner}/{gitHubRepo}", BuildSystem.AppVeyor.Environment.Repository.Name);
var isPullRequest = BuildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
var isTagged = (
Expand Down Expand Up @@ -190,7 +194,8 @@ Task("Run-Unit-Tests")
{
NoBuild = true,
NoRestore = true,
Configuration = configuration
Configuration = configuration,
Framework = IsRunningOnWindows() ? null : "netcoreapp3.1"
});
});

Expand Down Expand Up @@ -220,6 +225,7 @@ Task("Run-Code-Coverage")
});

Task("Upload-Coverage-Result")
.IsDependentOn("Run-Code-Coverage")
.Does(() =>
{
CoverallsIo($"{codeCoverageDir}coverage.xml");
Expand All @@ -230,7 +236,7 @@ Task("Generate-Code-Coverage-Report")
.Does(() =>
{
ReportGenerator(
$"{codeCoverageDir}coverage.xml",
new FilePath($"{codeCoverageDir}coverage.xml"),
codeCoverageDir,
new ReportGeneratorSettings() {
ClassFilters = new[] { "*.UnitTests*" }
Expand Down Expand Up @@ -332,20 +338,15 @@ Task("Create-Release-Notes")
Name = milestone,
Milestone = milestone,
Prerelease = false,
TargetCommitish = "master"
TargetCommitish = "main"
};

if (!string.IsNullOrEmpty(gitHubToken))
if (string.IsNullOrEmpty(gitHubToken))
{
GitReleaseManagerCreate(gitHubToken, gitHubRepoOwner, gitHubRepo, settings);
throw new InvalidOperationException("GitHub token was not provided.");
}
else
{
if(string.IsNullOrEmpty(gitHubUserName)) throw new InvalidOperationException("Could not resolve GitHub user name.");
if(string.IsNullOrEmpty(gitHubPassword)) throw new InvalidOperationException("Could not resolve GitHub password.");

GitReleaseManagerCreate(gitHubUserName, gitHubPassword, gitHubRepoOwner, gitHubRepo, settings);
}
GitReleaseManagerCreate(gitHubToken, gitHubRepoOwner, gitHubRepo, settings);
});

Task("Publish-GitHub-Release")
Expand All @@ -356,17 +357,12 @@ Task("Publish-GitHub-Release")
.WithCriteria(() => isTagged)
.Does(() =>
{
if (!string.IsNullOrEmpty(gitHubToken))
if (string.IsNullOrEmpty(gitHubToken))
{
GitReleaseManagerClose(gitHubToken, gitHubRepoOwner, gitHubRepo, milestone);
throw new InvalidOperationException("GitHub token was not provided.");
}
else
{
if(string.IsNullOrEmpty(gitHubUserName)) throw new InvalidOperationException("Could not resolve GitHub user name.");
if(string.IsNullOrEmpty(gitHubPassword)) throw new InvalidOperationException("Could not resolve GitHub password.");

GitReleaseManagerClose(gitHubUserName, gitHubPassword, gitHubRepoOwner, gitHubRepo, milestone);
}
GitReleaseManagerClose(gitHubToken, gitHubRepoOwner, gitHubRepo, milestone);
});

Task("Generate-Benchmark-Report")
Expand Down Expand Up @@ -435,6 +431,11 @@ Task("AppVeyor")
.IsDependentOn("Publish-NuGet")
.IsDependentOn("Publish-GitHub-Release");

Task("AppVeyor-Ubuntu")
.IsDependentOn("Run-Unit-Tests")
.IsDependentOn("Create-NuGet-Package")
.IsDependentOn("Upload-AppVeyor-Artifacts");

Task("Default")
.IsDependentOn("Run-Unit-Tests")
.IsDependentOn("Create-NuGet-Package");
Expand Down
Loading

0 comments on commit 42ae078

Please sign in to comment.