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

Command Line Validation of Indirect Signatures #78

Merged
merged 9 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
40 changes: 26 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [v1.1.6-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.6-pre1) (2024-02-07)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.7...v1.1.6-pre1)

## [v1.1.7](https://github.com/microsoft/CoseSignTool/tree/v1.1.7) (2024-02-07)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.6...v1.1.7)

**Merged pull requests:**

- Action Permission Changes [\#76](https://github.com/microsoft/CoseSignTool/pull/76) ([elantiguamsft](https://github.com/elantiguamsft))

## [v1.1.6](https://github.com/microsoft/CoseSignTool/tree/v1.1.6) (2024-02-07)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.5...v1.1.6)
Expand Down Expand Up @@ -58,19 +70,19 @@

## [v1.1.1-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.1-pre1) (2024-01-17)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.1...v1.1.1-pre1)
[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0-pre7...v1.1.1-pre1)

**Merged pull requests:**

- Move CreateChangelog to after build in PR build [\#70](https://github.com/microsoft/CoseSignTool/pull/70) ([lemccomb](https://github.com/lemccomb))

## [v1.1.1](https://github.com/microsoft/CoseSignTool/tree/v1.1.1) (2024-01-12)
## [v1.1.0-pre7](https://github.com/microsoft/CoseSignTool/tree/v1.1.0-pre7) (2024-01-12)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0-pre7...v1.1.1)
[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.1...v1.1.0-pre7)

## [v1.1.0-pre7](https://github.com/microsoft/CoseSignTool/tree/v1.1.0-pre7) (2024-01-12)
## [v1.1.1](https://github.com/microsoft/CoseSignTool/tree/v1.1.1) (2024-01-12)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0-pre6...v1.1.0-pre7)
[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0-pre6...v1.1.1)

**Closed issues:**

Expand Down Expand Up @@ -127,7 +139,7 @@

## [v1.1.0-pre1](https://github.com/microsoft/CoseSignTool/tree/v1.1.0-pre1) (2023-11-03)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.1-pre.10...v1.1.0-pre1)
[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0...v1.1.0-pre1)

**Merged pull requests:**

Expand All @@ -137,13 +149,13 @@
- DetachedSignatureFactory accepts pre-hashed content as payload [\#53](https://github.com/microsoft/CoseSignTool/pull/53) ([elantiguamsft](https://github.com/elantiguamsft))
- Add password support for certificate files [\#52](https://github.com/microsoft/CoseSignTool/pull/52) ([lemccomb](https://github.com/lemccomb))

## [v0.3.1-pre.10](https://github.com/microsoft/CoseSignTool/tree/v0.3.1-pre.10) (2023-10-10)
## [v1.1.0](https://github.com/microsoft/CoseSignTool/tree/v1.1.0) (2023-10-10)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v1.1.0...v0.3.1-pre.10)
[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.1-pre.10...v1.1.0)

## [v1.1.0](https://github.com/microsoft/CoseSignTool/tree/v1.1.0) (2023-10-10)
## [v0.3.1-pre.10](https://github.com/microsoft/CoseSignTool/tree/v0.3.1-pre.10) (2023-10-10)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.1-pre.9...v1.1.0)
[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.2...v0.3.1-pre.10)

**Merged pull requests:**

Expand All @@ -153,13 +165,13 @@
- Port changes from ADO repo to GitHub repo [\#46](https://github.com/microsoft/CoseSignTool/pull/46) ([lemccomb](https://github.com/lemccomb))
- Re-enable CodeQL [\#45](https://github.com/microsoft/CoseSignTool/pull/45) ([lemccomb](https://github.com/lemccomb))

## [v0.3.1-pre.9](https://github.com/microsoft/CoseSignTool/tree/v0.3.1-pre.9) (2023-09-28)
## [v0.3.2](https://github.com/microsoft/CoseSignTool/tree/v0.3.2) (2023-09-28)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.2...v0.3.1-pre.9)
[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.1-pre.9...v0.3.2)

## [v0.3.2](https://github.com/microsoft/CoseSignTool/tree/v0.3.2) (2023-09-28)
## [v0.3.1-pre.9](https://github.com/microsoft/CoseSignTool/tree/v0.3.1-pre.9) (2023-09-28)

[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.1-pre.8...v0.3.2)
[Full Changelog](https://github.com/microsoft/CoseSignTool/compare/v0.3.1-pre.8...v0.3.1-pre.9)

**Merged pull requests:**

Expand Down
65 changes: 65 additions & 0 deletions CoseHandler.Tests/CoseSignValidateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

namespace CoseSignUnitTests;

using CoseDetachedSignature;
using System.Net.Mime;
using System.Runtime.ConstrainedExecution;

[TestClass]
public class CoseHandlerSignValidateTests
{
Expand Down Expand Up @@ -332,6 +336,67 @@ public void DetachedValidateModifiedPayload()
result.Success.Should().Be(false);
result.Errors.Should().Contain(e => e.ErrorCode.Equals(ValidationFailureCode.PayloadMismatch));
}

[TestMethod]
public void IndirectSignatureValidation()
{
var msgFac = new DetachedSignatureFactory();
byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
payload: Payload1Bytes,
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(Leaf1Priv)).ToArray();

// Try to validate byte[]
var result = CoseHandler.Validate(signedBytes, Payload1Bytes, ValidRootSetPriv, RevMode);
result.Success.Should().Be(true);

// Try to validate stream
var result2 = CoseHandler.Validate(signedBytes, new MemoryStream(Payload1Bytes), ValidRootSetPriv, RevMode);
Dismissed Show dismissed Hide dismissed
result2.Success.Should().Be(true);
}

[TestMethod]
public void IndirectSignatureModifiedPayload()
{
var msgFac = new DetachedSignatureFactory();
byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
payload: Payload1Bytes,
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(Leaf1Priv)).ToArray();

// Now change one character in the payload
var modifiedPayload = Encoding.ASCII.GetBytes("Payload2!");

// Try to validate byte[]
var result = CoseHandler.Validate(signedBytes, modifiedPayload, ValidRootSetPriv, RevMode);
result.Success.Should().Be(false);
result.Errors.Should().Contain(e => e.ErrorCode.Equals(ValidationFailureCode.PayloadMismatch));

// Try to validate stream
var result2 = CoseHandler.Validate(signedBytes, new MemoryStream(modifiedPayload), ValidRootSetPriv, RevMode);
Dismissed Show dismissed Hide dismissed
result2.Success.Should().Be(false);
result2.Errors.Should().Contain(e => e.ErrorCode.Equals(ValidationFailureCode.PayloadMismatch));
}

[TestMethod]
public void IndirectSignatureUntrustedSignature()
{
var msgFac = new DetachedSignatureFactory();
byte[] signedBytes = msgFac.CreateDetachedSignatureBytes(
payload: Payload1Bytes,
contentType: "application/spdx+json",
signingKeyProvider: new X509Certificate2CoseSigningKeyProvider(Leaf1Priv)).ToArray();

// Try to validate byte[]
var result = CoseHandler.Validate(signedBytes, Payload1Bytes, null, RevMode);
result.Success.Should().Be(false);
result.Errors.Should().Contain(e => e.ErrorCode.Equals(ValidationFailureCode.TrustValidationFailed));

// Try to validate stream
var result2 = CoseHandler.Validate(signedBytes, new MemoryStream(Payload1Bytes), null, RevMode);
Dismissed Show dismissed Hide dismissed
result2.Success.Should().Be(false);
result2.Errors.Should().Contain(e => e.ErrorCode.Equals(ValidationFailureCode.TrustValidationFailed));
}
#endregion

#region TryX wrappers
Expand Down
32 changes: 32 additions & 0 deletions CoseHandler/ContentValidationType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace CoseX509;

/// <summary>
/// A set of COSE content validation types. Content validation refers to the method used to validate
/// that the payload has not been modified.
/// </summary>
public enum ContentValidationType
{
/// <summary>
/// Indicates that validation on the content was not performed.
/// </summary>
ContentValidationNotPerformed = 0,

/// <summary>
/// Indicates validation using a detached payload. The payload is not included in the message.
/// </summary>
Detached = 1,

/// <summary>
/// Indicates validation using an embedded payload. The payload is included in the message.
/// </summary>
Embedded = 2,

/// <summary>
/// Indicates validation using an indirect payload. The payload is hashed using the algorithm in the COSE message
/// and then the payload hash is compared to the embedded content.
/// </summary>
IndirectSignature = 3
}
32 changes: 27 additions & 5 deletions CoseHandler/CoseHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using CoseSign1.Certificates.Local.Validators;
using CoseSign1.Extensions;
using CoseSign1.Interfaces;
using CoseDetachedSignature.Extensions;

/// <summary>
/// Contains static methods to generate and validate Cose X509 signatures.
Expand Down Expand Up @@ -596,17 +597,38 @@

// Validate that the COSE header is formatted correctly and that the payload and hash are consistent.
bool messageVerified = false;

// Checks for an indirect signature, where the content header contains the hash of the payload, and the algorithm is stored in the message.
// If this is the case, we need to make sure the external payload hash matches the hash stored in the cose message content.
bool indirectSignature = msg.IsDetachedSignature();
elantiguamsft marked this conversation as resolved.
Show resolved Hide resolved
try
{
if (!payloadBytes.IsNullOrEmpty())

if (!payloadBytes.IsNullOrEmpty())
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
{
// Detached payload received as byte array
messageVerified = msg.VerifyDetached(publicKey, new ReadOnlySpan<byte>(payloadBytes));
if (indirectSignature)
{
// Indirect signature, hash embedded in the COSE message
messageVerified = (msg.VerifyEmbedded(publicKey) && msg.SignatureMatches(payloadBytes));
}
else
{
// Detached payload received as byte array
messageVerified = msg.VerifyDetached(publicKey, new ReadOnlySpan<byte>(payloadBytes));
}
Fixed Show fixed Hide fixed
}
else if (payloadStream is not null)
{
// Detached payload received as a stream
messageVerified = Task.Run(() => msg.VerifyDetachedAsync(publicKey, payloadStream)).GetAwaiter().GetResult();
if (indirectSignature)
{
// Indirect signature, hash embedded in the COSE message
messageVerified = (msg.VerifyEmbedded(publicKey) && msg.SignatureMatches(payloadStream));
}
else
{
// Detached payload received as a stream
messageVerified = Task.Run(() => msg.VerifyDetachedAsync(publicKey, payloadStream)).GetAwaiter().GetResult();
}
Fixed Show fixed Hide fixed
}
else
{
Expand Down
1 change: 1 addition & 0 deletions CoseHandler/CoseHandler.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CoseDetachedSignature\CoseDetachedSignature.csproj" />
<ProjectReference Include="..\CoseSign1.Abstractions\CoseSign1.Abstractions.csproj" />
<ProjectReference Include="..\CoseSign1.Certificates\CoseSign1.Certificates.csproj" />
<ProjectReference Include="..\CoseSign1\CoseSign1.csproj" />
Expand Down
21 changes: 19 additions & 2 deletions CoseHandler/ValidationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace CoseX509;

using System;
using System.Collections.Generic;
using System.Diagnostics.SymbolStore;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
Expand All @@ -25,12 +26,14 @@ public struct ValidationResult
public ValidationResult(bool success,
List<ValidationFailureCode>? errors,
List<CoseSign1ValidationResult>? internalResults = null,
List<X509Certificate2>? certChain = null)
List<X509Certificate2>? certChain = null,
ContentValidationType validationType = default)
{
Success = success;
Errors = ExpandErrors(errors);
InnerResults = internalResults;
CertificateChain = certChain;
ContentValidationType = validationType;
}

/// <summary>
Expand All @@ -48,6 +51,11 @@ public ValidationResult(bool success,
/// </summary>
public List<X509Certificate2>? CertificateChain = null;

/// <summary>
/// Indicates which signature validation format was used.
/// </summary>
public ContentValidationType ContentValidationType { get; set; } = default;

/// <summary>
/// The set of specific errors passed from the internal validator, if any.
/// </summary>
Expand Down Expand Up @@ -93,11 +101,20 @@ public readonly string ToString(bool verbose = false, bool showCertDetails = fal
certDetails = certDetailsBuilder.ToString();
}

string contentValidationTypeMsg = "Payload validation type: " + ContentValidationType switch
{
ContentValidationType.Detached => "Detached",
ContentValidationType.Embedded => "Embedded",
ContentValidationType.IndirectSignature => "Indirect Signature",
ContentValidationType.ContentValidationNotPerformed => "Not Performed",
_ => "Unknown"
};

if (Success)
{
// Print success. If verbose, include any chain validation messages.
return ((verbose && InnerResults != null) ? $"Validation succeeded.{newline}{string.Join(newline, InnerResults.Select(r => r.ResultMessage))}" :
$"Validation succeeded.") + $"{newline}{certDetails}";
$"Validation succeeded.") + $"{newline}{certDetails}{newline}{contentValidationTypeMsg}";
}

// Validation failed, so build the error text.
Expand Down
Loading
Loading