Skip to content

CoseKey overloads of VerifyAsync do not always agree with (synchronous) Verify #130922

Description

@bartonjs

Description

The sync and async versions of the CoseSign1Message and CoseSignature Verify method groups accepting CoseKey do not apply the same validation to the alg header.

Specifically, the synchronous version validates that the presented key matches the alg header, and the asynchronous version skips that check.

This means that a COSE_Sign1 which incorrectly writes a PS512 header when the signature was applied with Pkcs1 padding with SHA-2-256 will fail to verify with the synchronous method, but succeed with the asynchronous one.

Reproduction Steps

byte[] content = Encoding.UTF8.GetBytes($"Hello, World");
byte[] aad = Encoding.UTF8.GetBytes($"aadftw");

CoseSign1Message sign1 = CoseMessage.DecodeSign1(Convert.FromBase64String(@"
0oREoQE4JqD2WQEAN7/Emyuco7bWAjI3ChfWlsUrC3t2eRHB7P1UAQeqo2NRlv8p314VRgf43
xplcBYJKl/d/cFqacuVwrATbbv2v/wGal8DGShvj5uGkwFwIs+FQZaBKSk3hTjn4ct6ckw+H/
4IRVL0fyCENVzEqPmmPzs0FywoyoYleXEy8OacBxd0Y2iGaynMqGNfH8ZjuqSKh4Gwel0VKEv
xiXofFXYcTZu8jQaYkOasCe4EawkJzAcJ9NC0xv7OEC9N6xnQN7gmbEY27ubbyu2ydpF+ej8V
ftPvhgmd8GARcys68DZRDEbTOkplMz0Rco5iOX2YvnZePi7EbjrDJGFYaDRGWqIlQg=="));

using (RSA key = RSA.Create())
{
    key.ImportSubjectPublicKeyInfo(
        Convert.FromBase64String(@"
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm20nIKpbIBlB9wcqcwrPFm/9zaoyL
zKNqNGY82EIax4Wv2cIoy83RA4NK5Eb/VBYchWchMJ2xutOQpJxcZzPXhvmrwjFBLXxJTStrQ
YWBUdPEJHZqIWwlX8CpaHSIqhJ4TOaBhj9tWSCBEVos7224JMtVmzMQHe/E0GYUKAnu2C7Nq5
QJVdqlbafCkoBzUJArYNq8FxL9dwo3vEe4rhGPNAwgB1aHt1X9ph4op4TJ8fauogj9O1e355q
HxangP2segJD1hrViiM/V9Ty+JsJoLD+Ljut1zOw8Jahk6gWKG3bKp780lFpS7Tp/JSW79s3p
UF3PKbtZhoPH6ORprZMuQIDAQAB"),
        out _);

    CoseKey coseKey = new CoseKey(key, RSASignaturePadding.Pkcs1, HashAlgorithmName.SHA256);

    bool? syncAA = null;
    bool? syncCK = null;
    bool? asyncAA = null;
    bool? asyncCK = null;

    try { syncAA = sign1.VerifyDetached(key, content, aad); } catch {}
    try { syncCK = sign1.VerifyDetached(coseKey, content, aad); } catch {}
    try { asyncAA = sign1.VerifyDetachedAsync(key, new MemoryStream(content), aad).GetAwaiter().GetResult(); } catch {}
    try { asyncCK = sign1.VerifyDetachedAsync(coseKey, new MemoryStream(content), aad).GetAwaiter().GetResult(); } catch {}

    Console.WriteLine($"syncAA: {syncAA}, syncCK: {syncCK}, asyncAA: {asyncAA}, asyncCK: {asyncCK}");
}

Expected behavior

syncAA: false, syncCK: [false or null], asyncAA: false, asyncCK: [false or null]

Actual behavior

syncAA: False, syncCK: , asyncAA: False, asyncCK: True

(syncCK threw, and null prints as the empty string)

Configuration

.NET 10.0.10, System.Security.Cryptography.Cose 10.0.10

Activity

  1. added theissue type on Jul 16, 2026
  2. dotnet-policy-service commented on Jul 16, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @bartonjs, @vcsjones, @dotnet/area-system-security
    See info in area-owners.md if you want to be subscribed.

  3. bartonjs commented on Jul 16, 2026

    @bartonjs
    MemberAuthor

    The async overloads taking CoseKey don't look at the alg header at all, accepting documents that don't even provide it.

    I'd say the RFC strongly implies that the alg header should be written down, but it doesn't actually explicitly say it... but what's most important is that the sync and async versions should agree.

  4. locked and limited conversation to collaborators on Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions