Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added improved error reporting for ArcVerifier #593

Merged
merged 2 commits into from
Jul 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions MimeKit/Cryptography/ArcSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,18 +343,14 @@ Header GenerateArcSeal (FormatOptions options, int instance, string cv, long t,

async Task ArcSignAsync (FormatOptions options, MimeMessage message, IList<string> headers, bool doAsync, CancellationToken cancellationToken)
{
ArcVerifier.GetArcHeaderSets (message, true, out ArcHeaderSet[] sets, out int count);
ArcVerifier.GetArcHeaderSets (message, true, out ArcHeaderSet[] sets, out int count, out var errors);
AuthenticationResults authres;
int instance = count + 1;
string cv;

if (count > 0) {
var parameters = sets[count - 1].ArcSealParameters;

// do not sign if there is already a failed ARC-Seal.
if (!parameters.TryGetValue ("cv", out cv) || cv.Equals ("fail", StringComparison.OrdinalIgnoreCase))
return;
}
// do not sign if there is already a failed/invalid ARC-Seal.
if (count > 0 && (errors & ArcValidationErrors.InvalidArcSealChainValidationValue) != 0)
return;

options = options.Clone ();
options.NewLineFormat = NewLineFormat.Dos;
Expand Down
192 changes: 170 additions & 22 deletions MimeKit/Cryptography/ArcVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,86 @@ public enum ArcSignatureValidationResult
Fail
}

/// <summary>
/// An enumeration of possible ARC validation errors.
/// </summary>
/// <remarks>
/// An enumeration of possible ARC validation errors.
/// </remarks>
[Flags]
public enum ArcValidationErrors
{
/// <summary>
/// No errors.
/// </summary>
None = 0,

/// <summary>
/// One or more duplicate ARC-Authentication-Results headers exist.
/// </summary>
DuplicateArcAuthenticationResults = 1 << 0,

/// <summary>
/// One or more duplicate ARC-Message-Signature headers exist.
/// </summary>
DuplicateArcMessageSignature = 1 << 1,

/// <summary>
/// One or more duplicate ARC-Seal headers exist.
/// </summary>
DuplicateArcSeal = 1 << 2,

/// <summary>
/// One or more ARC-Authentication-Results headers are missing.
/// </summary>
MissingArcAuthenticationResults = 1 << 3,

/// <summary>
/// One or more ARC-Message-Signature headers are missing.
/// </summary>
MissingArcMessageSignature = 1 << 4,

/// <summary>
/// One or more ARC-Seal headers are missing.
/// </summary>
MissingArcSeal = 1 << 5,

/// <summary>
/// One or more ARC-Authentication-Results headers could not be parsed.
/// </summary>
InvalidArcAuthenticationResults = 1 << 6,

/// <summary>
/// One or more ARC-Message-Signature headers could not be parsed.
/// </summary>
InvalidArcMessageSignature = 1 << 7,

/// <summary>
/// One or more ARC-Seal headers could not be parsed.
/// </summary>
InvalidArcSeal = 1 << 8,

/// <summary>
/// One or more ARC-Seal headers have an invalid <c>cv</c> value.
/// </summary>
InvalidArcSealChainValidationValue = 1 << 9,

/// <summary>
/// One or more ARC-Seal headers are missing a <c>cv</c> value.
/// </summary>
MissingArcSealChainValidationValue = 1 << 10,

/// <summary>
/// Validation failed for the most recent ARC-Message-Signature header.
/// </summary>
MessageSignatureValidationFailed = 1 << 11,

/// <summary>
/// Validation failed for one or more of the ARC-Seal headers.
/// </summary>
SealValidationFailed = 1 << 12
}

/// <summary>
/// An ARC header validation result.
/// </summary>
Expand Down Expand Up @@ -205,6 +285,17 @@ public ArcHeaderValidationResult[] Seals {
public ArcSignatureValidationResult Chain {
get; internal set;
}

/// <summary>
/// Get the chain validation errors.
/// </summary>
/// <remarks>
/// Gets the chain validation errors.
/// </remarks>
/// <value>The chain validation errors.</value>
public ArcValidationErrors ChainErrors {
get; internal set;
}
}

class ArcHeaderSet
Expand Down Expand Up @@ -396,17 +487,18 @@ async Task<bool> VerifyArcSealAsync (FormatOptions options, ArcHeaderSet[] sets,
}
}

internal static ArcSignatureValidationResult GetArcHeaderSets (MimeMessage message, bool throwOnError, out ArcHeaderSet[] sets, out int count)
internal static ArcSignatureValidationResult GetArcHeaderSets (MimeMessage message, bool throwOnError, out ArcHeaderSet[] sets, out int count, out ArcValidationErrors errors)
{
ArcHeaderSet set;

errors = ArcValidationErrors.None;
sets = new ArcHeaderSet[50];
count = 0;

for (int i = 0; i < message.Headers.Count; i++) {
Dictionary<string, string> parameters = null;
var header = message.Headers[i];
int instance;
int instance = 0;
string value;

switch (header.Id) {
Expand All @@ -415,14 +507,16 @@ internal static ArcSignatureValidationResult GetArcHeaderSets (MimeMessage messa
if (throwOnError)
throw new FormatException ("Invalid ARC-Authentication-Results header.");

return ArcSignatureValidationResult.Fail;
errors |= ArcValidationErrors.InvalidArcAuthenticationResults;
break;
}

if (!authres.Instance.HasValue) {
if (throwOnError)
throw new FormatException ("Missing instance tag in ARC-Authentication-Results header.");

return ArcSignatureValidationResult.Fail;
errors |= ArcValidationErrors.InvalidArcAuthenticationResults;
break;
}

instance = authres.Instance.Value;
Expand All @@ -431,7 +525,9 @@ internal static ArcSignatureValidationResult GetArcHeaderSets (MimeMessage messa
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Invalid instance tag in ARC-Authentication-Results header: i={0}", instance));

return ArcSignatureValidationResult.Fail;
errors |= ArcValidationErrors.InvalidArcAuthenticationResults;
instance = 0;
break;
}
break;
case HeaderId.ArcMessageSignature:
Expand All @@ -442,26 +538,39 @@ internal static ArcSignatureValidationResult GetArcHeaderSets (MimeMessage messa
if (throwOnError)
throw;

return ArcSignatureValidationResult.Fail;
if (header.Id == HeaderId.ArcMessageSignature)
errors |= ArcValidationErrors.InvalidArcMessageSignature;
else
errors |= ArcValidationErrors.InvalidArcSeal;

break;
}

if (!parameters.TryGetValue ("i", out value)) {
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Missing instance tag in {0} header.", header.Id.ToHeaderName ()));

return ArcSignatureValidationResult.Fail;
if (header.Id == HeaderId.ArcMessageSignature)
errors |= ArcValidationErrors.InvalidArcMessageSignature;
else
errors |= ArcValidationErrors.InvalidArcSeal;

break;
}

if (!int.TryParse (value, NumberStyles.Integer, CultureInfo.InvariantCulture, out instance) || instance < 1 || instance > 50) {
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Invalid instance tag in {0} header: i={1}", header.Id.ToHeaderName (), value));

return ArcSignatureValidationResult.Fail;
if (header.Id == HeaderId.ArcMessageSignature)
errors |= ArcValidationErrors.InvalidArcMessageSignature;
else
errors |= ArcValidationErrors.InvalidArcSeal;

instance = 0;
break;
}
break;
default:
instance = 0;
break;
}

if (instance == 0)
Expand All @@ -471,8 +580,22 @@ internal static ArcSignatureValidationResult GetArcHeaderSets (MimeMessage messa
if (set == null)
sets[instance - 1] = set = new ArcHeaderSet ();

if (!set.Add (header, parameters))
return ArcSignatureValidationResult.Fail;
if (!set.Add (header, parameters)) {
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Duplicate {0} header for i={1}", header.Id.ToHeaderName (), instance));

switch (header.Id) {
case HeaderId.ArcAuthenticationResults:
errors |= ArcValidationErrors.DuplicateArcAuthenticationResults;
break;
case HeaderId.ArcMessageSignature:
errors |= ArcValidationErrors.DuplicateArcMessageSignature;
break;
case HeaderId.ArcSeal:
errors |= ArcValidationErrors.DuplicateArcSeal;
break;
}
}

if (instance > count)
count = instance;
Expand All @@ -491,50 +614,63 @@ internal static ArcSignatureValidationResult GetArcHeaderSets (MimeMessage messa
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Missing ARC headers for i={0}", i + 1));

return ArcSignatureValidationResult.Fail;
if ((errors & ArcValidationErrors.InvalidArcAuthenticationResults) == 0)
errors |= ArcValidationErrors.MissingArcAuthenticationResults;
if ((errors & ArcValidationErrors.InvalidArcMessageSignature) == 0)
errors |= ArcValidationErrors.MissingArcMessageSignature;
if ((errors & ArcValidationErrors.InvalidArcSeal) == 0)
errors |= ArcValidationErrors.MissingArcSeal;
continue;
}

if (set.ArcAuthenticationResult == null) {
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Missing ARC-Authentication-Results header for i={0}", i + 1));

return ArcSignatureValidationResult.Fail;
if ((errors & ArcValidationErrors.InvalidArcAuthenticationResults) == 0)
errors |= ArcValidationErrors.MissingArcAuthenticationResults;
}

if (set.ArcMessageSignature == null) {
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Missing ARC-Message-Signature header for i={0}", i + 1));

return ArcSignatureValidationResult.Fail;
if ((errors & ArcValidationErrors.InvalidArcMessageSignature) == 0)
errors |= ArcValidationErrors.MissingArcMessageSignature;
}

if (set.ArcSeal == null) {
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Missing ARC-Seal header for i={0}", i + 1));

return ArcSignatureValidationResult.Fail;
if ((errors & ArcValidationErrors.InvalidArcSeal) == 0)
errors |= ArcValidationErrors.MissingArcSeal;
continue;
}

if (!set.ArcSealParameters.TryGetValue ("cv", out string cv)) {
if (throwOnError)
throw new FormatException (string.Format (CultureInfo.InvariantCulture, "Missing chain validation tag in ARC-Seal header for i={0}.", i + 1));

return ArcSignatureValidationResult.Fail;
errors |= ArcValidationErrors.MissingArcSealChainValidationValue;
continue;
}

// The "cv" value for all ARC-Seal header fields MUST NOT be
// "fail". For ARC Sets with instance values > 1, the values
// MUST be "pass". For the ARC Set with instance value = 1, the
// value MUST be "none".
if (!cv.Equals (i == 0 ? "none" : "pass", StringComparison.Ordinal))
return ArcSignatureValidationResult.Fail;
errors |= ArcValidationErrors.InvalidArcSealChainValidationValue;
}

return ArcSignatureValidationResult.Pass;
return errors == ArcValidationErrors.None ? ArcSignatureValidationResult.Pass : ArcSignatureValidationResult.Fail;
}

async Task<ArcValidationResult> VerifyAsync (FormatOptions options, MimeMessage message, bool doAsync, CancellationToken cancellationToken)
{
const ArcValidationErrors ArcSealCvParamErrors = ArcValidationErrors.InvalidArcSealChainValidationValue | ArcValidationErrors.MissingArcSealChainValidationValue;

if (options == null)
throw new ArgumentNullException (nameof (options));

Expand All @@ -543,17 +679,25 @@ async Task<ArcValidationResult> VerifyAsync (FormatOptions options, MimeMessage

var result = new ArcValidationResult ();

switch (GetArcHeaderSets (message, false, out ArcHeaderSet[] sets, out int count)) {
switch (GetArcHeaderSets (message, false, out ArcHeaderSet[] sets, out int count, out var errors)) {
case ArcSignatureValidationResult.None: return result;
case ArcSignatureValidationResult.Fail:
result.Chain = ArcSignatureValidationResult.Fail;
result.ChainErrors = errors;

// If the only error(s) are invalid or missing 'cv' values, ignore the errors for now.
if ((errors & ~ArcSealCvParamErrors) == 0)
break;

return result;
default:
result.Chain = ArcSignatureValidationResult.Pass;
break;
}

int newest = count - 1;

result.Seals = new ArcHeaderValidationResult[count];
result.Chain = ArcSignatureValidationResult.Pass;

// validate the most recent Arc-Message-Signature
try {
Expand All @@ -566,10 +710,12 @@ async Task<ArcValidationResult> VerifyAsync (FormatOptions options, MimeMessage
result.MessageSignature.Signature = ArcSignatureValidationResult.Pass;
} else {
result.MessageSignature.Signature = ArcSignatureValidationResult.Fail;
result.ChainErrors |= ArcValidationErrors.MessageSignatureValidationFailed;
result.Chain = ArcSignatureValidationResult.Fail;
}
} catch {
result.MessageSignature.Signature = ArcSignatureValidationResult.Fail;
result.ChainErrors |= ArcValidationErrors.MessageSignatureValidationFailed;
result.Chain = ArcSignatureValidationResult.Fail;
}

Expand All @@ -582,10 +728,12 @@ async Task<ArcValidationResult> VerifyAsync (FormatOptions options, MimeMessage
result.Seals[i].Signature = ArcSignatureValidationResult.Pass;
} else {
result.Seals[i].Signature = ArcSignatureValidationResult.Fail;
result.ChainErrors |= ArcValidationErrors.SealValidationFailed;
result.Chain = ArcSignatureValidationResult.Fail;
}
} catch {
result.Seals[i].Signature = ArcSignatureValidationResult.Fail;
result.ChainErrors |= ArcValidationErrors.SealValidationFailed;
result.Chain = ArcSignatureValidationResult.Fail;
}
}
Expand Down
Loading