Skip to content

Commit

Permalink
Add Dkim Public Key parser (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
tinohager committed Jan 27, 2025
1 parent 0d06d17 commit 8d77a31
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 62 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Nager.EmailAuthentication.UnitTest.DkimPublicKeyRecordParserTests
{
[TestClass]
public sealed class BasicTest
{
[TestMethod]
public void TryParse_ValidDkimHeaderString1_ReturnsTrueAndPopulatesDataFragment()
{
var dkimPublicKeyRecord = "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuu8v3TAGlg2cKEVtHbqh5QfebUSdVp2qwH4NFaIG/rEsBshHI97fdP6TqiJCmpLhK8mHSQMit2HiHAEUApa0xAw7SI68XiBr6epKpTkHaUx27C/kjyuxYmGFOJy6mrDzeC2E5+Lp1u9QifjuBtUk78ORSA+EXeEMxssHy51NdHT0BlZGk1M+wXTxniQ2d198gDVjjqRGM433Q0AP6uSJac9LQj80tHkWrnr/bjct7EOdtF+6mDl4qjAaJTruk03Xt3Alaj+DIOPmnwP1mbPLQmK4blnzM7jwQc2kZz9gSJocc0nhs8KfuR6Xj23iSOJV+WEt6WoLoJzSl8/Dx5CKJwIDAQAB";

var isSuccessful = DkimPublicKeyRecordParser.TryParse(dkimPublicKeyRecord, out var dkimPublicKeyRecordDataFragment, out var parsingResults);

Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dkimPublicKeyRecordDataFragment);
//Assert.IsNull(parsingResults, "ParsingResults is not null");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Nager.EmailAuthentication.UnitTest.DkimSignatureParserTests
{
[TestClass]
public sealed class BasicTest
{
[TestMethod]
public void TryParse_ValidDkimHeaderString1_ReturnsTrueAndPopulatesDataFragment()
{
var dkimSignature = "v=1; a=rsa-sha256; c=relaxed/simple; q=dns/txt; d=domain.com; [email protected]; s=mailjet; x=1737017824; h=message-id:from:from:reply-to:to:to:subject:subject:date:date:list-unsubscribe-post:list-unsubscribe:feedback-id:x-csa-complaints:x-mj-mid:x-report-abuse-to:mime-version:content-type; bh=TyN/x6t3AOfI298rgJAgZHgdWcq/XLISGen5nN3NLAc=; b=HLCLiikV92Ku/k9mGlZM0bmqPjKggGnMI0igqhXmPRzPJUC+5SUWRS6/FLUpxbX6AUGJRDYQnKKMtp6uZkYVuKG8SPZ01cUkvIiiAkczb4bK6IVvPbZOnsWqHkD6EvK3TrpIhgFfGLlcG+zIwgdDZ3O++uhpJkIX1WJlkXZYqxQ=";

var isSuccessful = DkimSignatureParser.TryParse(dkimSignature, out var dkimSignatureDataFragment, out var parsingResults);

Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dkimSignatureDataFragment);
//Assert.IsNull(parsingResults, "ParsingResults is not null");
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Nager.EmailAuthentication.UnitTest.DkimHeaderParserTests
namespace Nager.EmailAuthentication.UnitTest.DkimSignatureParserTests
{
[TestClass]
public sealed class SelectorTest
Expand All @@ -9,9 +9,9 @@ public sealed class SelectorTest
[DataTestMethod]
public void TryParse_ValidSelector_ReturnsTrueAndPopulatesDataFragment(string selector)
{
var dkimHeader = $"v=1; a=rsa-sha256; d=domain.com; s={selector}; h=message-id:from; bh=testbodyhash=; b=signaturedata";
var dkimSignature = $"v=1; a=rsa-sha256; d=domain.com; s={selector}; h=message-id:from; bh=testbodyhash=; b=signaturedata";

var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults);
var isSuccessful = DkimSignatureParser.TryParse(dkimSignature, out var dkimHeaderDataFragment, out var parsingResults);

Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dkimHeaderDataFragment);
Expand All @@ -22,9 +22,9 @@ public void TryParse_ValidSelector_ReturnsTrueAndPopulatesDataFragment(string se
[DataTestMethod]
public void TryParse_InvalidSelector_ReturnsTrueAndPopulatesDataFragment(string selector)
{
var dkimHeader = $"v=1; a=rsa-sha256; d=domain.com; s={selector}; h=message-id:from; bh=testbodyhash=; b=signaturedata";
var dkimSignature = $"v=1; a=rsa-sha256; d=domain.com; s={selector}; h=message-id:from; bh=testbodyhash=; b=signaturedata";

var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults);
var isSuccessful = DkimSignatureParser.TryParse(dkimSignature, out var dkimHeaderDataFragment, out var parsingResults);

Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dkimHeaderDataFragment);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Nager.EmailAuthentication.UnitTest.DkimHeaderParserTests
namespace Nager.EmailAuthentication.UnitTest.DkimSignatureParserTests
{
[TestClass]
public sealed class VersionTest
Expand All @@ -9,9 +9,9 @@ public sealed class VersionTest
[DataTestMethod]
public void TryParse_ValidVersion_ReturnsTrueAndPopulatesDataFragment(string version)
{
var dkimHeader = $"v={version}; a=rsa-sha256; d=domain.com; s=myselector; h=message-id:from; bh=testbodyhash=; b=signaturedata";
var dkimSignature = $"v={version}; a=rsa-sha256; d=domain.com; s=myselector; h=message-id:from; bh=testbodyhash=; b=signaturedata";

var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults);
var isSuccessful = DkimSignatureParser.TryParse(dkimSignature, out var dkimHeaderDataFragment, out var parsingResults);

Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dkimHeaderDataFragment);
Expand All @@ -23,9 +23,9 @@ public void TryParse_ValidVersion_ReturnsTrueAndPopulatesDataFragment(string ver
[DataTestMethod]
public void TryParse_InvalidVersion_ReturnsTrueAndPopulatesDataFragment(string version)
{
var dkimHeader = $"v={version}; a=rsa-sha256; d=domain.com; s=myselector; h=message-id:from; bh=testbodyhash=; b=signaturedata";
var dkimSignature = $"v={version}; a=rsa-sha256; d=domain.com; s=myselector; h=message-id:from; bh=testbodyhash=; b=signaturedata";

var isSuccessful = DkimHeaderParser.TryParse(dkimHeader, out var dkimHeaderDataFragment, out var parsingResults);
var isSuccessful = DkimSignatureParser.TryParse(dkimSignature, out var dkimHeaderDataFragment, out var parsingResults);

Assert.IsTrue(isSuccessful);
Assert.IsNotNull(dkimHeaderDataFragment);
Expand Down
80 changes: 80 additions & 0 deletions src/Nager.EmailAuthentication/DkimPublicKeyRecordParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Nager.EmailAuthentication.Handlers;
using Nager.EmailAuthentication.Models;

namespace Nager.EmailAuthentication
{
/// <summary>
/// Dkim Public Key Record Parser
/// </summary>
public static class DkimPublicKeyRecordParser
{
/// <summary>
/// TryParse
/// </summary>
/// <param name="dkimPublicKeyRecord"></param>
/// <param name="dkimPublicKeyRecordDataFragment"></param>
/// <returns></returns>
public static bool TryParse(
string dkimPublicKeyRecord,
out DkimPublicKeyRecordDataFragment? dkimPublicKeyRecordDataFragment)
{
return TryParse(dkimPublicKeyRecord, out dkimPublicKeyRecordDataFragment, out _);
}

/// <summary>
/// TryParse
/// </summary>
/// <param name="dkimPublicKeyRecord"></param>
/// <param name="dkimPublicKeyRecordDataFragment"></param>
/// <param name="parsingResults"></param>
/// <returns></returns>
public static bool TryParse(
string dkimPublicKeyRecord,
out DkimPublicKeyRecordDataFragment? dkimPublicKeyRecordDataFragment,
out ParsingResult[]? parsingResults)
{
var handlers = new Dictionary<string, MappingHandler<DkimPublicKeyRecordDataFragment>>
{
{
"v", new MappingHandler<DkimPublicKeyRecordDataFragment>
{
Map = (dataFragment, value) => dataFragment.Version = value,
}
},
{
"p", new MappingHandler<DkimPublicKeyRecordDataFragment>
{
Map = (dataFragment, value) => dataFragment.PublicKeyData = value,
}
},
{
"n", new MappingHandler<DkimPublicKeyRecordDataFragment>
{
Map = (dataFragment, value) => dataFragment.Notes = value
}
},
{
"k", new MappingHandler<DkimPublicKeyRecordDataFragment>
{
Map = (dataFragment, value) => dataFragment.KeyType = value
}
},
{
"g", new MappingHandler<DkimPublicKeyRecordDataFragment>
{
Map = (dataFragment, value) => dataFragment.Granularity = value
}
},
{
"t", new MappingHandler<DkimPublicKeyRecordDataFragment>
{
Map = (dataFragment, value) => dataFragment.Flags = value
}
},
};

var parserBase = new KeyValueParserBase<DkimPublicKeyRecordDataFragment>(handlers);
return parserBase.TryParse(dkimPublicKeyRecord, out dkimPublicKeyRecordDataFragment, out parsingResults);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,104 +1,121 @@
using Nager.EmailAuthentication.Models;
using Nager.EmailAuthentication.Handlers;
using Nager.EmailAuthentication.Models;

namespace Nager.EmailAuthentication
{
public static class DkimHeaderParser
/// <summary>
/// Dkim Signature Parser
/// </summary>
public static class DkimSignatureParser
{
/// <summary>
/// TryParse
/// </summary>
/// <param name="dkimSignature"></param>
/// <param name="dkimSignatureDataFragment"></param>
/// <returns></returns>
public static bool TryParse(
string dkimHeader,
out DkimHeaderDataFragment? dkimHeaderDataFragment)
string dkimSignature,
out DkimSignatureDataFragment? dkimSignatureDataFragment)
{
return TryParse(dkimHeader, out dkimHeaderDataFragment, out _);
return TryParse(dkimSignature, out dkimSignatureDataFragment, out _);
}

/// <summary>
/// TryParse
/// </summary>
/// <param name="dkimSignature"></param>
/// <param name="dkimSignatureDataFragment"></param>
/// <param name="parsingResults"></param>
/// <returns></returns>
public static bool TryParse(
string dkimHeader,
out DkimHeaderDataFragment? dkimHeaderDataFragment,
string dkimSignature,
out DkimSignatureDataFragment? dkimSignatureDataFragment,
out ParsingResult[]? parsingResults)
{
var handlers = new Dictionary<string, MappingHandler<DkimHeaderDataFragment>>
var handlers = new Dictionary<string, MappingHandler<DkimSignatureDataFragment>>
{
{
"v", new MappingHandler<DkimHeaderDataFragment>
"v", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.Version = value,
Validate = ValidatePositiveNumber
}
},
{
"a", new MappingHandler<DkimHeaderDataFragment>
"a", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.SignatureAlgorithm = value,
Validate = ValidateSignatureAlgorithm
}
},
{
"b", new MappingHandler<DkimHeaderDataFragment>
"b", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.SignatureData = value
}
},
{
"bh", new MappingHandler<DkimHeaderDataFragment>
"bh", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.BodyHash = value
}
},
{
"c", new MappingHandler<DkimHeaderDataFragment>
"c", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.MessageCanonicalization = value
}
},
{
"d", new MappingHandler<DkimHeaderDataFragment>
"d", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.Domain = value
}
},
{
"s", new MappingHandler<DkimHeaderDataFragment>
"s", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.Selector = value,
Validate = ValidateSelector
}
},
{
"t", new MappingHandler<DkimHeaderDataFragment>
"t", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.Timestamp = value,
Validate = ValidatePositiveNumber
}
},
{
"x", new MappingHandler<DkimHeaderDataFragment>
"x", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.SignatureExpiration = value
}
},
{
"h", new MappingHandler<DkimHeaderDataFragment>
"h", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.SignedHeaderFields = value,
Validate = ValidateSignedHeaderFields
}
},
{
"q", new MappingHandler<DkimHeaderDataFragment>
"q", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.QueryMethods = value
}
},
{
"i", new MappingHandler<DkimHeaderDataFragment>
"i", new MappingHandler<DkimSignatureDataFragment>
{
Map = (dataFragment, value) => dataFragment.AgentOrUserIdentifier = value
}
}
};

var parserBase = new KeyValueParserBase<DkimHeaderDataFragment>(handlers);
return parserBase.TryParse(dkimHeader, out dkimHeaderDataFragment, out parsingResults);
var parserBase = new KeyValueParserBase<DkimSignatureDataFragment>(handlers);
return parserBase.TryParse(dkimSignature, out dkimSignatureDataFragment, out parsingResults);
}

private static ParsingResult[] ValidatePositiveNumber(ValidateRequest validateRequest)
Expand Down
3 changes: 2 additions & 1 deletion src/Nager.EmailAuthentication/DmarcRecordParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Nager.EmailAuthentication.Models;
using Nager.EmailAuthentication.Handlers;
using Nager.EmailAuthentication.Models;

namespace Nager.EmailAuthentication
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Nager.EmailAuthentication.Models;

namespace Nager.EmailAuthentication
namespace Nager.EmailAuthentication.Handlers
{
/// <summary>
/// Represents a handler for mapping and validating input data.
Expand Down
3 changes: 2 additions & 1 deletion src/Nager.EmailAuthentication/KeyValueParserBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Nager.EmailAuthentication.Models;
using Nager.EmailAuthentication.Handlers;
using Nager.EmailAuthentication.Models;
using Nager.KeyValueParser;

namespace Nager.EmailAuthentication
Expand Down
Loading

0 comments on commit 8d77a31

Please sign in to comment.