-
Notifications
You must be signed in to change notification settings - Fork 5k
ML-DSA+COSE #115158
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
base: main
Are you sure you want to change the base?
ML-DSA+COSE #115158
Conversation
Note regarding the
|
1 similar comment
Note regarding the
|
Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones |
...ies/System.Security.Cryptography.Cose/src/System/Security/Cryptography/Cose/CoseSignature.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography.Cose/tests/SignVerify.AlgorithmSupport.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography.Cose/tests/SignVerify.AlgorithmSupport.cs
Outdated
Show resolved
Hide resolved
|
||
public static IEnumerable<CoseTestSign1> GetImplementations() | ||
{ | ||
yield return new(true, "Sign/VerifyEmbedded(byte[])", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note for reviewers: this and equivalent in CoseTestMultiSign is a core of testing all overloads
|
||
if (msg.ProtectedHeaders.TryGetValue(CoseHeaderLabel.KeyIdentifier, out var keyIdentifier)) | ||
{ | ||
Assert.Equal(32 /* can't correlate that with anything on the draft example, it doesn't seem to match kid */, keyIdentifier.GetValueAsBytes().Length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still confused where is this value coming from - it supposedly should be somewhere in the draft
src/libraries/System.Security.Cryptography.Cose/tests/SignVerify.AlgorithmSupport.cs
Show resolved
Hide resolved
src/libraries/System.Security.Cryptography.Cose/tests/SignVerify.AlgorithmSupport.cs
Show resolved
Hide resolved
...y.Cryptography.Cose/src/System/Security/Cryptography/Cose/MLDsaAsymmetricAlgorithmWrapper.cs
Outdated
Show resolved
Hide resolved
Dispose(true); | ||
_disposed = true; | ||
GC.SuppressFinalize(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the type hierarchy is internal
and we know we won't use finalizers, we can use DisposeCore instead of Dispose(bool), if preferred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left as is, I like using single pattern everywhere
if (PlatformDetection.IsAndroid) | ||
{ | ||
// Android supports PSS at the algorithms layer, but does not support it | ||
// being used in cert chains. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the support check is about cert chains, then the class or method should say that. Since the class seems like the wrong answer here, the method (and property) should.
@@ -10,16 +10,17 @@ | |||
<EnableDefaultPackageReadmeFile>false</EnableDefaultPackageReadmeFile> | |||
</PropertyGroup> | |||
|
|||
<PropertyGroup> | |||
<!-- PQC is new in .NET 10, so we need to build some pieces of it for .NET 9, .NET Standard, and .NET Framework --> | |||
<BuildPqc Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net10.0'))">true</BuildPqc> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Microsoft.Bcl.Cryptography exposes the types to netstandard 2.0... why wouldn't we expose the new members here to netstandard 2.0? (Unlike SignedCms, COSE doesn't have to worry about netfx compat/limitations)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can see BuildPqc
is only used to decide if Microsoft.Bcl.Cryptography
needs to be a referenced.
Since it is used just in this case I would get rid of BuildPqc
and put the condition directly on the ItemGroup.
<Compile Include="$(CommonPath)System\Experimentals.cs" Link="Common\Experimentals.cs" /> | ||
<Compile Include="$(CommonPath)System\HashCodeRandomization.cs" Link="Common\System\HashCodeRandomization.cs" /> | ||
<Compile Include="$(CommonPath)System\Memory\PointerMemoryManager.cs" Link="Common\System\Memory\PointerMemoryManager.cs" /> | ||
<Compile Include="$(CommonPath)System\Security\Cryptography\IncrementalHash.netfx.cs" Link="Common\System\Security\Cryptography\IncrementalHash.cs" Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like we usually put conditions on ItemGroup, not individual items..
<ItemGroup> | ||
<Compile Include="$(CommonPath)System\HashCodeRandomization.cs" | ||
Link="Common\System\HashCodeRandomization.cs" /> | ||
<Compile Include="$(CommonPath)System\Memory\PointerMemoryManager.cs" | ||
Link="Common\System\Memory\PointerMemoryManager.cs" /> | ||
<Compile Include="$(CommonPath)System\Security\Cryptography\IncrementalHash.netfx.cs" | ||
Link="Common\System\Security\Cryptography\IncrementalHash.cs" | ||
Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'" /> | ||
<Compile Include="$(LibrariesProjectRoot)System.Formats.Cbor\src\System\Formats\Cbor\CborInitialByte.cs" | ||
Link="System\Formats\Cbor\CborInitialByte.cs" /> | ||
<Compile Include="$(CommonPath)System\Experimentals.cs" Link="Common\Experimentals.cs" /> | ||
<Compile Include="$(CommonPath)System\HashCodeRandomization.cs" Link="Common\System\HashCodeRandomization.cs" /> | ||
<Compile Include="$(CommonPath)System\Memory\PointerMemoryManager.cs" Link="Common\System\Memory\PointerMemoryManager.cs" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are you restyling this? Is it making it consistent with something else? (AFAIK, the newline style is more prevalent)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't recall explicitly changing it so presumably VS did that on my behalf and I didn't notice - will fix
|
||
action("RSA-PKCS1", CoseTestKeyType.RSAPkcs1, HashAlgorithmName.SHA256); | ||
|
||
if (PlatformSupport.IsRsaPssSupported) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The helper described that it was answering if PSS works in certs, but this is using it as whether PSS works at all. So it'll be wrong on Android.
// we use 3 suffix because if PSS is not supported then ML-DSA will not be as well | ||
action("RSA-PKCS1-3", CoseTestKeyType.RSAPkcs1, HashAlgorithmName.SHA256); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If PSS doesn't work then why do anything at all? I'd expect "if PSS works, try PSS. If it doesn't, either check we get the right kind of exception, or ignore it"
// we currently need 5 keys for the tests | ||
action("ECDsa-2", CoseTestKeyType.ECDsa, HashAlgorithmName.SHA256); | ||
action("RSA-PKCS1-2", CoseTestKeyType.RSAPkcs1, HashAlgorithmName.SHA256); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If ML-DSA is supported, we have 6. Else we have 5. Seems like just supporting unbalanced is the way to go.
private CoseTestMultiSign(bool isEmbedded, string label, | ||
Func<CoseTestKey, byte[], byte[]> signFirstImpl, | ||
Action<CoseMultiSignMessage, CoseTestKey, byte[]> addSignatureImpl, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chop consistently
private CoseTestMultiSign(bool isEmbedded, string label, | |
Func<CoseTestKey, byte[], byte[]> signFirstImpl, | |
Action<CoseMultiSignMessage, CoseTestKey, byte[]> addSignatureImpl, | |
private CoseTestMultiSign( | |
bool isEmbedded, | |
string label, | |
Func<CoseTestKey, byte[], byte[]> signFirstImpl, | |
Action<CoseMultiSignMessage, CoseTestKey, byte[]> addSignatureImpl, |
}; | ||
MLDsa mldsaKey = MLDsa.GenerateKey(algorithm); | ||
CoseSigner mldsaSigner = new(mldsaKey, protectedHeaders: new CoseHeaderMap { [CoseHeaderLabel.KeyIdentifier] = CoseHeaderValue.FromBytes(Encoding.UTF8.GetBytes(keyId)) }); | ||
return (mldsaKey, mldsaSigner.Key, mldsaSigner); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this feels dirty.
I think we want to just say "we added a new feature and that if you use it, .Key
will be null" (so change the nullability of CoseSigner.Key)... that's the approach that is currently being tried out for CmsSigner. And then we just set a field/property for the ML-DSA (et al) key that we can carry through to signing.
That means that we then want something like CoseVerifier. Or maybe we should make a CoseKey class, if we can serialize it to COSE_Key (https://github.com/dotnet/runtime/blob/26c9fb8e747ec936ff93b27e8c4de7e6bc43ec70/src/libraries/System.Formats.Cbor/tests/CoseKeyHelpers.cs) that sounds like goodness. Then we overload Sign and Verify once (per current method) with CoseKey, and just need to make sure CoseKey has ctors/factories for the supported algorithms.
public sealed class CoseKey
{
private CoseKey();
public static CoseKey FromKey(RSA key, RSASignaturePadding, HashAlgorithmName);
public static CoseKey FromKey(ECDsa key, HashAlgorithmName);
public static CoseKey FromKey(MLDsa key);
public int AlgorithmId { get; }
public int KeyType { get; }
public byte[] ExportCoseKey();
public static CoseKey ImportCoseKey(ReadOnlySpan<byte> source);
}
(for example)
And that CoseSigner should really have been CoseKey plus protected and unprotected headers.
Verify only needs the CoseKey, not the full signer. And, voila, we've broken up with AsymmetricAlgorithm.
--
Import/export to COSE_Key format can, of course, be put off until someone asks for it. But keeping it in mind helps name the type. Similarly, we could make the KTY and Alg values internal unless we have a reason to expose them. I'm just thinking forward.
@@ -129,5 +159,6 @@ private static bool CheckIfVbsAvailable() | |||
|
|||
private static bool? s_isVbsAvailable; | |||
internal static bool IsVbsAvailable => s_isVbsAvailable ??= CheckIfVbsAvailable(); | |||
internal static bool IsRsaPssSupported { get; } = CheckIfRsaPssSupported(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to eagerly check if RSA-PSS is supported in the static constructor which affects test discovery times. We should do this lazily like we do for IsVbsAvailable
.
|
||
internal PureDataToBeSignedBuilder() | ||
{ | ||
_stream = new(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't use new()
here.
There are some things left here: