Skip to content

Commit 8dcef95

Browse files
committed
Future-proof (wip)
Integrated the possibility that future dkim signatures are completely different
1 parent ab9ee5a commit 8dcef95

9 files changed

+160
-70
lines changed

src/Nager.EmailAuthentication.UnitTest/DkimSignatureParserTests/BasicTest.cs

+18-12
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,29 @@ public void TryParse_ValidDkimSignature_ReturnsTrueAndDkimSignature()
2525
var isSuccessful = DkimSignatureParser.TryParse(dkimSignatureRaw, out var dkimSignature, out var parsingResults);
2626

2727
Assert.IsTrue(isSuccessful);
28+
Assert.IsNotNull(dkimSignature);
2829

2930
Assert.IsNotNull(parsingResults);
3031
Assert.IsTrue(parsingResults.Length > 0);
32+
33+
if (dkimSignature is not DkimSignatureV1 dkimSignatureV1)
34+
{
35+
Assert.Fail("Wrong DkimSignature class");
36+
return;
37+
}
3138

32-
Assert.IsNotNull(dkimSignature);
3339
Assert.AreEqual("1", dkimSignature.Version);
34-
Assert.AreEqual(SignatureAlgorithm.RsaSha256, dkimSignature.SignatureAlgorithm);
35-
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignature.MessageCanonicalizationHeader);
36-
Assert.AreEqual(CanonicalizationType.Simple, dkimSignature.MessageCanonicalizationBody);
37-
Assert.AreEqual("dns/txt", dkimSignature.QueryMethods);
38-
Assert.AreEqual("domain.com", dkimSignature.SigningDomainIdentifier);
39-
Assert.AreEqual("[email protected]", dkimSignature.AgentOrUserIdentifier);
40-
Assert.AreEqual("mailjet", dkimSignature.Selector);
41-
Assert.AreEqual(new DateTimeOffset(2025, 1, 16, 8, 57, 4, TimeSpan.Zero), dkimSignature.SignatureExpiration);
42-
Assert.AreEqual(18, dkimSignature.SignedHeaderFields.Length);
43-
Assert.AreEqual("TyN/x6t3AOfI298rgJAgZHgdWcq/XLISGen5nN3NLAc=", dkimSignature.BodyHash);
44-
Assert.AreEqual("HLCLiikV92Ku/k9mGlZM0bmqPjKggGnMI0igqhXmPRzPJUC+5SUWRS6/FLUpxbX6AUGJRDYQnKKMtp6uZkYVuKG8SPZ01cUkvIiiAkczb4bK6IVvPbZOnsWqHkD6EvK3TrpIhgFfGLlcG+zIwgdDZ3O++uhpJkIX1WJlkXZYqxQ=", dkimSignature.SignatureData);
40+
Assert.AreEqual(SignatureAlgorithm.RsaSha256, dkimSignatureV1.SignatureAlgorithm);
41+
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignatureV1.MessageCanonicalizationHeader);
42+
Assert.AreEqual(CanonicalizationType.Simple, dkimSignatureV1.MessageCanonicalizationBody);
43+
Assert.AreEqual("dns/txt", dkimSignatureV1.QueryMethods);
44+
Assert.AreEqual("domain.com", dkimSignatureV1.SigningDomainIdentifier);
45+
Assert.AreEqual("[email protected]", dkimSignatureV1.AgentOrUserIdentifier);
46+
Assert.AreEqual("mailjet", dkimSignatureV1.Selector);
47+
Assert.AreEqual(new DateTimeOffset(2025, 1, 16, 8, 57, 4, TimeSpan.Zero), dkimSignatureV1.SignatureExpiration);
48+
Assert.AreEqual(18, dkimSignatureV1.SignedHeaderFields.Length);
49+
Assert.AreEqual("TyN/x6t3AOfI298rgJAgZHgdWcq/XLISGen5nN3NLAc=", dkimSignatureV1.BodyHash);
50+
Assert.AreEqual("HLCLiikV92Ku/k9mGlZM0bmqPjKggGnMI0igqhXmPRzPJUC+5SUWRS6/FLUpxbX6AUGJRDYQnKKMtp6uZkYVuKG8SPZ01cUkvIiiAkczb4bK6IVvPbZOnsWqHkD6EvK3TrpIhgFfGLlcG+zIwgdDZ3O++uhpJkIX1WJlkXZYqxQ=", dkimSignatureV1.SignatureData);
4551
}
4652
}
4753
}

src/Nager.EmailAuthentication.UnitTest/DkimSignatureParserTests/FoldingTest.cs

+14-8
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,21 @@ public void TryParse_ValidSelector_ReturnsTrueAndPopulatesDataFragment()
1919
Assert.IsTrue(isSuccessful);
2020
Assert.IsNotNull(dkimSignature);
2121

22-
Assert.AreEqual("1", dkimSignature.Version);
23-
Assert.AreEqual(SignatureAlgorithm.RsaSha256, dkimSignature.SignatureAlgorithm);
24-
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignature.MessageCanonicalizationHeader);
25-
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignature.MessageCanonicalizationBody);
26-
Assert.AreEqual("dmarc.com", dkimSignature.SigningDomainIdentifier);
27-
Assert.AreEqual("selector1", dkimSignature.Selector);
22+
if (dkimSignature is not DkimSignatureV1 dkimSignatureV1)
23+
{
24+
Assert.Fail("Wrong DkimSignature class");
25+
return;
26+
}
27+
28+
Assert.AreEqual("1", dkimSignatureV1.Version);
29+
Assert.AreEqual(SignatureAlgorithm.RsaSha256, dkimSignatureV1.SignatureAlgorithm);
30+
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignatureV1.MessageCanonicalizationHeader);
31+
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignatureV1.MessageCanonicalizationBody);
32+
Assert.AreEqual("dmarc.com", dkimSignatureV1.SigningDomainIdentifier);
33+
Assert.AreEqual("selector1", dkimSignatureV1.Selector);
2834
//Assert.AreEqual(["cc"], dkimSignature.SignedHeaderFields);
29-
Assert.AreEqual(new DateTimeOffset(2023, 8, 3, 3, 22, 14, TimeSpan.Zero), dkimSignature.Timestamp);
30-
Assert.AreEqual(new DateTimeOffset(2023, 8, 4, 3, 22, 14, TimeSpan.Zero), dkimSignature.SignatureExpiration);
35+
Assert.AreEqual(new DateTimeOffset(2023, 8, 3, 3, 22, 14, TimeSpan.Zero), dkimSignatureV1.Timestamp);
36+
Assert.AreEqual(new DateTimeOffset(2023, 8, 4, 3, 22, 14, TimeSpan.Zero), dkimSignatureV1.SignatureExpiration);
3137

3238

3339
//Assert.IsNull(parsingResults, "ParsingResults is not null");

src/Nager.EmailAuthentication.UnitTest/DkimSignatureParserTests/MessageCanonicalizationTest.cs

+45-10
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,15 @@ public void TryParse_SingleMessageCanonicalization1_ReturnsTrueAndPopulatesDkimS
2727
Assert.IsTrue(isSuccessful);
2828
Assert.IsNotNull(dkimSignature);
2929
Assert.IsNull(parsingResults, "ParsingResults is not null");
30-
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignature.MessageCanonicalizationHeader);
31-
Assert.AreEqual(CanonicalizationType.Simple, dkimSignature.MessageCanonicalizationBody);
30+
31+
if (dkimSignature is not DkimSignatureV1 dkimSignatureV1)
32+
{
33+
Assert.Fail("Wrong DkimSignature class");
34+
return;
35+
}
36+
37+
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignatureV1.MessageCanonicalizationHeader);
38+
Assert.AreEqual(CanonicalizationType.Simple, dkimSignatureV1.MessageCanonicalizationBody);
3239
}
3340

3441

@@ -42,8 +49,15 @@ public void TryParse_SingleMessageCanonicalization2_ReturnsTrueAndPopulatesDkimS
4249
Assert.IsTrue(isSuccessful);
4350
Assert.IsNotNull(dkimSignature);
4451
Assert.IsNull(parsingResults, "ParsingResults is not null");
45-
Assert.AreEqual(CanonicalizationType.Simple, dkimSignature.MessageCanonicalizationHeader);
46-
Assert.AreEqual(CanonicalizationType.Simple, dkimSignature.MessageCanonicalizationBody);
52+
53+
if (dkimSignature is not DkimSignatureV1 dkimSignatureV1)
54+
{
55+
Assert.Fail("Wrong DkimSignature class");
56+
return;
57+
}
58+
59+
Assert.AreEqual(CanonicalizationType.Simple, dkimSignatureV1.MessageCanonicalizationHeader);
60+
Assert.AreEqual(CanonicalizationType.Simple, dkimSignatureV1.MessageCanonicalizationBody);
4761
}
4862

4963
[TestMethod]
@@ -56,8 +70,15 @@ public void TryParse_DefaultMessageCanonicalization1_ReturnsTrueAndPopulatesDkim
5670
Assert.IsTrue(isSuccessful);
5771
Assert.IsNotNull(dkimSignature);
5872
Assert.IsNull(parsingResults, "ParsingResults is not null");
59-
Assert.AreEqual(CanonicalizationType.Simple, dkimSignature.MessageCanonicalizationHeader);
60-
Assert.AreEqual(CanonicalizationType.Simple, dkimSignature.MessageCanonicalizationBody);
73+
74+
if (dkimSignature is not DkimSignatureV1 dkimSignatureV1)
75+
{
76+
Assert.Fail("Wrong DkimSignature class");
77+
return;
78+
}
79+
80+
Assert.AreEqual(CanonicalizationType.Simple, dkimSignatureV1.MessageCanonicalizationHeader);
81+
Assert.AreEqual(CanonicalizationType.Simple, dkimSignatureV1.MessageCanonicalizationBody);
6182
}
6283

6384
[TestMethod]
@@ -70,8 +91,15 @@ public void TryParse_DefaultMessageCanonicalization2_ReturnsTrueAndPopulatesDkim
7091
Assert.IsTrue(isSuccessful);
7192
Assert.IsNotNull(dkimSignature);
7293
Assert.IsNull(parsingResults, "ParsingResults is not null");
73-
Assert.AreEqual(CanonicalizationType.Simple, dkimSignature.MessageCanonicalizationHeader);
74-
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignature.MessageCanonicalizationBody);
94+
95+
if (dkimSignature is not DkimSignatureV1 dkimSignatureV1)
96+
{
97+
Assert.Fail("Wrong DkimSignature class");
98+
return;
99+
}
100+
101+
Assert.AreEqual(CanonicalizationType.Simple, dkimSignatureV1.MessageCanonicalizationHeader);
102+
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignatureV1.MessageCanonicalizationBody);
75103
}
76104

77105
[TestMethod]
@@ -84,8 +112,15 @@ public void TryParse_DefaultMessageCanonicalization3_ReturnsTrueAndPopulatesDkim
84112
Assert.IsTrue(isSuccessful);
85113
Assert.IsNotNull(dkimSignature);
86114
Assert.IsNull(parsingResults, "ParsingResults is not null");
87-
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignature.MessageCanonicalizationHeader);
88-
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignature.MessageCanonicalizationBody);
115+
116+
if (dkimSignature is not DkimSignatureV1 dkimSignatureV1)
117+
{
118+
Assert.Fail("Wrong DkimSignature class");
119+
return;
120+
}
121+
122+
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignatureV1.MessageCanonicalizationHeader);
123+
Assert.AreEqual(CanonicalizationType.Relaxed, dkimSignatureV1.MessageCanonicalizationBody);
89124
}
90125

91126
[TestMethod]

src/Nager.EmailAuthentication/DkimSignatureDataFragmentParser.cs

+25-18
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public static class DkimSignatureDataFragmentParser
1818
/// <returns></returns>
1919
public static bool TryParse(
2020
string? dkimSignature,
21-
[NotNullWhen(true)] out DkimSignatureDataFragment? dkimSignatureDataFragment)
21+
[NotNullWhen(true)] out DkimSignatureDataFragmentBase? dkimSignatureDataFragment)
2222
{
2323
return TryParse(dkimSignature, out dkimSignatureDataFragment, out _);
2424
}
@@ -32,7 +32,7 @@ public static bool TryParse(
3232
/// <returns></returns>
3333
public static bool TryParse(
3434
string? dkimSignature,
35-
[NotNullWhen(true)] out DkimSignatureDataFragment? dkimSignatureDataFragment,
35+
[NotNullWhen(true)] out DkimSignatureDataFragmentBase? dkimSignatureDataFragment,
3636
out ParsingResult[]? parsingResults)
3737
{
3838
if (string.IsNullOrWhiteSpace(dkimSignature))
@@ -43,98 +43,105 @@ public static bool TryParse(
4343
return false;
4444
}
4545

46-
var handlers = new Dictionary<string, MappingHandler<DkimSignatureDataFragment>>
46+
var handlers = new Dictionary<string, MappingHandler<DkimSignatureDataFragmentV1>>
4747
{
4848
{
49-
"v", new MappingHandler<DkimSignatureDataFragment>
49+
"v", new MappingHandler<DkimSignatureDataFragmentV1>
5050
{
5151
Map = (dataFragment, value) => dataFragment.Version = value,
5252
Validate = ValidateVersion
5353
}
5454
},
5555
{
56-
"a", new MappingHandler<DkimSignatureDataFragment>
56+
"a", new MappingHandler<DkimSignatureDataFragmentV1>
5757
{
5858
Map = (dataFragment, value) => dataFragment.SignatureAlgorithm = value,
5959
Validate = ValidateSignatureAlgorithm
6060
}
6161
},
6262
{
63-
"b", new MappingHandler<DkimSignatureDataFragment>
63+
"b", new MappingHandler<DkimSignatureDataFragmentV1>
6464
{
6565
Map = (dataFragment, value) => dataFragment.SignatureData = value
6666
}
6767
},
6868
{
69-
"bh", new MappingHandler<DkimSignatureDataFragment>
69+
"bh", new MappingHandler<DkimSignatureDataFragmentV1>
7070
{
7171
Map = (dataFragment, value) => dataFragment.BodyHash = value
7272
}
7373
},
7474
{
75-
"c", new MappingHandler<DkimSignatureDataFragment>
75+
"c", new MappingHandler<DkimSignatureDataFragmentV1>
7676
{
7777
Map = (dataFragment, value) => dataFragment.MessageCanonicalization = value
7878
//TODO: Add validate logic
7979
}
8080
},
8181
{
82-
"d", new MappingHandler<DkimSignatureDataFragment>
82+
"d", new MappingHandler<DkimSignatureDataFragmentV1>
8383
{
8484
Map = (dataFragment, value) => dataFragment.SigningDomainIdentifier = value,
8585
Validate = ValidateDomain
8686
}
8787
},
8888
{
89-
"l", new MappingHandler<DkimSignatureDataFragment>
89+
"l", new MappingHandler<DkimSignatureDataFragmentV1>
9090
{
9191
Map = (dataFragment, value) => dataFragment.BodyLengthCount = value,
9292
Validate = ValidateBodyLengthCount
9393
}
9494
},
9595
{
96-
"s", new MappingHandler<DkimSignatureDataFragment>
96+
"s", new MappingHandler<DkimSignatureDataFragmentV1>
9797
{
9898
Map = (dataFragment, value) => dataFragment.Selector = value,
9999
Validate = ValidateSelector
100100
}
101101
},
102102
{
103-
"t", new MappingHandler<DkimSignatureDataFragment>
103+
"t", new MappingHandler<DkimSignatureDataFragmentV1>
104104
{
105105
Map = (dataFragment, value) => dataFragment.Timestamp = value,
106106
Validate = ValidateTimestamp
107107
}
108108
},
109109
{
110-
"x", new MappingHandler<DkimSignatureDataFragment>
110+
"x", new MappingHandler<DkimSignatureDataFragmentV1>
111111
{
112112
Map = (dataFragment, value) => dataFragment.SignatureExpiration = value
113113
}
114114
},
115115
{
116-
"h", new MappingHandler<DkimSignatureDataFragment>
116+
"h", new MappingHandler<DkimSignatureDataFragmentV1>
117117
{
118118
Map = (dataFragment, value) => dataFragment.SignedHeaderFields = value,
119119
Validate = ValidateSignedHeaderFields
120120
}
121121
},
122122
{
123-
"q", new MappingHandler<DkimSignatureDataFragment>
123+
"q", new MappingHandler<DkimSignatureDataFragmentV1>
124124
{
125125
Map = (dataFragment, value) => dataFragment.QueryMethods = value
126126
}
127127
},
128128
{
129-
"i", new MappingHandler<DkimSignatureDataFragment>
129+
"i", new MappingHandler<DkimSignatureDataFragmentV1>
130130
{
131131
Map = (dataFragment, value) => dataFragment.AgentOrUserIdentifier = value
132132
}
133133
}
134134
};
135135

136-
var parserBase = new KeyValueParserBase<DkimSignatureDataFragment>(handlers);
137-
return parserBase.TryParse(dkimSignature, out dkimSignatureDataFragment, out parsingResults);
136+
var parserBase = new KeyValueParserBase<DkimSignatureDataFragmentV1>(handlers);
137+
if (parserBase.TryParse(dkimSignature, out var dkimSignatureDataFragmentV1, out parsingResults))
138+
{
139+
dkimSignatureDataFragment = dkimSignatureDataFragmentV1!;
140+
return true;
141+
}
142+
143+
dkimSignatureDataFragment = null;
144+
return false;
138145
}
139146

140147
private static ParsingResult[] ValidatePositiveNumber(

src/Nager.EmailAuthentication/DkimSignatureParser.cs

+28-8
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,25 @@ public static class DkimSignatureParser
1616
/// <returns></returns>
1717
public static bool TryParse(
1818
string? dkimSignatureRaw,
19-
[NotNullWhen(true)] out DkimSignature? dkimSignature)
19+
[NotNullWhen(true)] out DkimSignatureBase? dkimSignature)
2020
{
2121
if (!DkimSignatureDataFragmentParser.TryParse(dkimSignatureRaw, out var dataFragment, out _))
2222
{
2323
dkimSignature = null;
2424
return false;
2525
}
2626

27-
return TryParse(dataFragment, out dkimSignature);
27+
if (dataFragment is DkimSignatureDataFragmentV1 dataFragmentV1)
28+
{
29+
if (TryParseV1(dataFragmentV1, out var dkimSignatureV1))
30+
{
31+
dkimSignature = dkimSignatureV1;
32+
return true;
33+
}
34+
}
35+
36+
dkimSignature = null;
37+
return false;
2838
}
2939

3040
/// <summary>
@@ -36,7 +46,7 @@ public static bool TryParse(
3646
/// <returns></returns>
3747
public static bool TryParse(
3848
string? dkimSignatureRaw,
39-
[NotNullWhen(true)] out DkimSignature? dkimSignature,
49+
[NotNullWhen(true)] out DkimSignatureBase? dkimSignature,
4050
out ParsingResult[]? parsingResults)
4151
{
4252
if (!DkimSignatureDataFragmentParser.TryParse(dkimSignatureRaw, out var dataFragment, out parsingResults))
@@ -45,7 +55,17 @@ public static bool TryParse(
4555
return false;
4656
}
4757

48-
return TryParse(dataFragment, out dkimSignature);
58+
if (dataFragment is DkimSignatureDataFragmentV1 dataFragmentV1)
59+
{
60+
if (TryParseV1(dataFragmentV1, out var dkimSignatureV1))
61+
{
62+
dkimSignature = dkimSignatureV1;
63+
return true;
64+
}
65+
}
66+
67+
dkimSignature = null;
68+
return false;
4969
}
5070

5171
/// <summary>
@@ -54,9 +74,9 @@ public static bool TryParse(
5474
/// <param name="dkimSignatureDataFragment"></param>
5575
/// <param name="dkimSignature"></param>
5676
/// <returns></returns>
57-
public static bool TryParse(
58-
DkimSignatureDataFragment? dkimSignatureDataFragment,
59-
[NotNullWhen(true)] out DkimSignature? dkimSignature)
77+
public static bool TryParseV1(
78+
DkimSignatureDataFragmentV1? dkimSignatureDataFragment,
79+
[NotNullWhen(true)] out DkimSignatureV1? dkimSignature)
6080
{
6181
dkimSignature = null;
6282

@@ -153,7 +173,7 @@ public static bool TryParse(
153173

154174
var signedHeaders = dkimSignatureDataFragment.SignedHeaderFields.Split(':', StringSplitOptions.TrimEntries);
155175

156-
dkimSignature = new DkimSignature
176+
dkimSignature = new DkimSignatureV1
157177
{
158178
Version = dkimSignatureDataFragment.Version,
159179
SigningDomainIdentifier = dkimSignatureDataFragment.SigningDomainIdentifier.Trim(' ', '\t'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Nager.EmailAuthentication.Models
2+
{
3+
/// <summary>
4+
/// Dkim Signature Base
5+
/// </summary>
6+
public class DkimSignatureBase
7+
{
8+
/// <summary>
9+
/// Dkim Version <strong>(v=)</strong>
10+
/// </summary>
11+
public required string Version { get; set; }
12+
}
13+
}

0 commit comments

Comments
 (0)