Add crypto *Formatter classes and extend HashAlgorithm#11959
Conversation
|
@bartonjs please review |
| [Fact] | ||
| public static void FormatterArguments() | ||
| { | ||
| AsymmetricSignatureFormatterTests.FormatterArguments(new DSASignatureFormatter()); |
There was a problem hiding this comment.
The tests which don't involve instantiating an object should move to the Algorithms library specifically (instead of being in the factory/conformance include files)
There was a problem hiding this comment.
Done. Moved those two tests and from RSA side to new files in Algorithms
| { | ||
| using (DSA dsa = DSAFactory.Create()) | ||
| { | ||
|
|
| return DSAFactory.SupportsFips186_3; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
RSA has a known value test for PKCS#1, why doesn't DSA have a known value test?
There was a problem hiding this comment.
Added a test for a known signature although not sure how valuable that is w.r.t. RSA PKCS tests because those validate a given hash will create the same signature each time which isn't true in DSA (not deterministic)
| [Fact] | ||
| public static void VerifyDecryptKeyExchangeOaep() | ||
| { | ||
| using (RSA rsa = RSAFactory.Create(1024)) |
There was a problem hiding this comment.
is the 1024-ness necessary? If so, can we write that down?
There was a problem hiding this comment.
No. Removed it and using default
|
|
||
| namespace System.Security.Cryptography.Tests | ||
| { | ||
| public class AsymmetricKeyExchangeFormatterTests |
There was a problem hiding this comment.
This can entirely move to Algorithms by itself.
|
|
||
| public override void SetHashAlgorithm(string strName) | ||
| { | ||
| // The implemenation is symmetric with RSAPKCS1SignatureFormatter even though _algName |
|
|
||
| public override string Parameters | ||
| { | ||
| get |
There was a problem hiding this comment.
can we lift these up to one-liners?
public override string Parameters
{
get { return null; }
set { }
}There was a problem hiding this comment.
You thumbs-up'd this, but didn't make the corresponding change. I'm confused...
There was a problem hiding this comment.
Looks like I lifted the other formatter but not deformatter. Done
| { | ||
| public class DSASignatureFormatter : AsymmetricSignatureFormatter | ||
| { | ||
| DSA _dsaKey; |
There was a problem hiding this comment.
Style: use explicit modifiers (pervasive)
There was a problem hiding this comment.
Sure. Forgot that. Thanks
| // Verify the name | ||
| Oid.FromFriendlyName(strName, OidGroup.HashAlgorithm); | ||
|
|
||
| // Keep the raw strName as Oid may change the case ("SHA1" to "sha1") making it incompatible. |
There was a problem hiding this comment.
Whatever places we care that it's cased right are probably porting bugs we didn't see because of the predefined HashAlgorithmName values; but they'd be good to identify.
There was a problem hiding this comment.
The Oid code round-tripping from friendlyname->value->friendlyname causes "SHA1" to become "sha1" which doesn't work later on...
https://github.com/dotnet/corefx/blob/master/src/System.Security.Cryptography.Encoding/src/Internal/Cryptography/OidLookup.cs#L189
There was a problem hiding this comment.
Right... the "doesn't work later on..." is (probably) a bug due to having a switch on non-normalized inputs. One concern I have with this is if someone passes "sha1" here we'll validate it, then let it fail later.
There was a problem hiding this comment.
The "sha1" in this case is passed all the way to ncrypt which fails with NTE_NOT_SUPPORTED
https://github.com/dotnet/corefx/blob/master/src/Common/src/Internal/Cryptography/CngCommon.SignVerify.cs#L25 (*pPaddingInfo contains a struct that has "sha1"). And ncrypt doesn't like "1.3.14.3.2.26" either.
There was a problem hiding this comment.
CryptoConfig in desktop uses "SHA1"
ht.Add("SHA1", Constants.OID_OIWSEC_SHA1);
There was a problem hiding this comment.
In latest code, I perform a check against known hash algorithms and do an string.ToUpper
| } | ||
| else | ||
| { | ||
| outputBytes = new byte[0]; |
|
A new build is required to fix the refs. See 5c2c440#diff-bd3e14409afbfd7c5cb1880ed53c7ae0 |
a2e4f8c to
444962d
Compare
|
test Innerloop OSX Debug Build and Test |
| internal static byte[] GetDSA1024_186_2_Data() | ||
| { | ||
| return Encoding.ASCII.GetBytes("abc"); | ||
| } |
There was a problem hiding this comment.
These three methods feel like they should be one method returning three values. It's pretty clunky this way.
There was a problem hiding this comment.
The original intent was that the three parts could be used independently (i.e. not always all 3 would be used in a given test), but since that is not the case I can combine
| VerifySignatureWithHashBytes(formatter, deformatter, hash); | ||
|
|
||
| // Check that the hash is preserved from the ComputeHash above | ||
| VerifySignatureWithHashAlgorithm(formatter, deformatter, hashAlgorithm); |
There was a problem hiding this comment.
That comment makes it feel more like a HashAlgorithm test than an AsymmetricSignatureFormatter test. But I think it's the comment that needs adjusting, instead of the test (since it is testing the other overload).
There was a problem hiding this comment.
I removed the comment
| public byte[] P; | ||
| public byte[] Q; | ||
| } | ||
| [System.Runtime.InteropServices.ComVisibleAttribute(true)] |
There was a problem hiding this comment.
In NetFx both the Deformatter and Formatter are ComVisible. I don't know that we're preserving ComVisible attributes at all, but I'm definitely confused as to why it isn't being done consistently.
There was a problem hiding this comment.
I removed it. Apparently I ran the genapi tool before I had removed all of the attributes from the corresponding source file.
| { | ||
| if (ALL_NAMES.IndexOf(hashAlgorithName, StringComparison.OrdinalIgnoreCase) >= 0) | ||
| { | ||
| return hashAlgorithName.ToUpperInvariant(); |
There was a problem hiding this comment.
This seems pretty weird. A HashSet seems much more suited to this than strstr. Especially because you're not checking a word boundary. So "m", "d", "ha", "a2", etc are all treated as "known" by this algorithm.
There was a problem hiding this comment.
The Oid is already verified at this point to be a valid hash algorithm... HashSet seems like overkill but if you agree that we should only check against known names (and not do a simple ToUpper on any potentially future\other hash alg) I will change it
There was a problem hiding this comment.
In current usage it is; that's not anything asserted by or guaranteed in this routine. If we added support for a new hash algorithm we'd have to add it to the ALL_NAMES string unless it was conveniently a substring of one of the existing names. So HashSet or just an array, or whatever; but it needs to be a hard list, not a soft one.
There was a problem hiding this comment.
Done. Added HashSet
| try | ||
| { | ||
| // Verify the name is a valid HashAlgorithm even though it is not currently used | ||
| Oid.FromFriendlyName(strName, OidGroup.HashAlgorithm); |
There was a problem hiding this comment.
Please make this just check that the input is a legal identifier for SHA1 to match Desktop.
Desktop currently only supports SHA1, and we should be thoughtful when that behavior is changed. Do we allow only FIPS 186-3-acceptable algorithms (SHA-1 and SHA-2-x)? Do we widen it up to all resolvable HashAlgorithms? Do we just get rid of the check altogether?
We should keep porting differences to a minimum; particularly when it's not due to not being able to easily replicate a behavior on Unix.
There was a problem hiding this comment.
Let's take this offline for a bit
There was a problem hiding this comment.
Per discussion, hard-code to SHA1 for now (to match desktop) and then log an issue to allow additional
| _rsaKey = (RSA)key; | ||
| } | ||
|
|
||
| /// <internalonly/> |
There was a problem hiding this comment.
We can probably get rid of these comments. Especially since I have no idea what they mean.
There was a problem hiding this comment.
Sure will remove. I noticed that there's a bunch of others in our source so I left them originally.
| { | ||
| VerifyICryptoTransformStream(stream, output); | ||
| } | ||
| #endif |
There was a problem hiding this comment.
There are some quirky behaviors in NetFx HashAlgorithm that we should have tests to ensure we don't change unless we mean to.
- TransformBlock(a) followed by ComputeHash(b) is the same as ComputeHash(a.Concat(b))
- TransformBlock(a), TransformFinalBlock(b), TransformBlock(a), TransformFinalBlock(b) is (I think) ComputeHash(a.Concat(b).Concat(a).Concat(b))
- TransformBlock(a), TransformFinalBlock(b) => x, ComputeHash(Array.Empty() => same x => ComputeHash(Array.Empty()) => correct empty hash answer
- TransformBlock(a), TransformFinalBlock(b) => x, ComputeHash(Array.Empty() => same x => TransformBlock(a), TransformFinalBlock(b) => same x (because ComputeHash reset the state).
Also, a whole bunch of exception tests for TransformBlock and TransformFinalBlock.
There was a problem hiding this comment.
Done. Note after a call to TransformFinalBlock an exception is raised if TransformBlock or TransformFinalBlock is immediately called again unless transforming 0 bytes..
There was a problem hiding this comment.
There are differences between netfx and corefx here. See the ActiveIssue and todo in the tests
| public override long Position { get { return default(long); } set { } } | ||
| protected override void Dispose(bool disposing) { } | ||
| public override void Flush() { } | ||
| public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { return default(System.Threading.Tasks.Task); } |
There was a problem hiding this comment.
I didn't remove it :) It looks like it was never overidden to begin with; it exists in the base class
There was a problem hiding this comment.
The netfx code does override FlushAsync, but the implementation is the same as the base class (Stream) so I suppose it wasn't carried over. It was a no-op to begin with; Flush() is also no-op
There was a problem hiding this comment.
@weshaggard I assume we need (in the end) all the overrides to align on non-sealed types for base. invocations; is that being well-flagged by the current netstandard20 deviations process?
There was a problem hiding this comment.
In general we don't enforce that a member is overridden but we do try to keep the overridden member in the ref if we know of any implementations that have the override.
| if (_disposed) | ||
| throw new ObjectDisposedException(null); | ||
| if (State != 0) | ||
| throw new CryptographicUnexpectedOperationException(SR.Cryptography_HashNotYetFinalized); |
There was a problem hiding this comment.
Let's see some tests for these exceptions, too, please.
| public CryptographicUnexpectedOperationException() | ||
| : base(SR.Arg_CryptographyException) | ||
| { | ||
| } |
There was a problem hiding this comment.
Possibly , but then we should also change CryptographicException as well as that doesn't set CORSEC_E_CRYPTO
internal const int CORSEC_E_CRYPTO = unchecked((int)0x80131430);
internal const int CORSEC_E_CRYPTO_UNEX_OPER = unchecked((int)0x80131431);
There was a problem hiding this comment.
That's fair. @stephentoub any thoughts on if we care about making the HResult values of the default ctors of these two exceptions match desktop? The HResult value of CryptographicException is pretty hard to depend on since we don't try to normalize the codes between Windows and Unix (or across versions of Windows).
| } | ||
|
|
||
| [Fact] | ||
| [ActiveIssue(0)] // Todo: netfx treats HashFinal and Initialize separately, corefx doesn't and Initialize is a no-op |
There was a problem hiding this comment.
There was a problem hiding this comment.
If there is an issue that cannot be resolved then an actual issue needs to be created and it tracked here. If it's a behavior deviation that we're okay with (I don't know if we have a way of really deciding/codifying what we're okay with as a deviation or not) then we need to make the test do the thing we decide on.
But [ActiveIssue(0)] isn't acceptable.
There was a problem hiding this comment.
The ActiveIssue is temporary and for discussion; I do not intend to submit that way. For now I will emulate netfx behavior unless I hear objections otherwise.
| hash.TransformBlock(block, 0, block.Length, null, 0); | ||
| hash.TransformFinalBlock(block, 0, block.Length); | ||
|
|
||
| // Todo: these do not throw on corefx, but do on netfx |
There was a problem hiding this comment.
Doesn't seem like it would be hard to make this also throw on corefx; just requires tracking being in this state.
|
|
||
| // Todo: this will also fail due to the ActiveIssue below in VerifyUseAfterTransformFinalBlock | ||
| // but commented out so the other tests can run | ||
| // actual = hash.ComputeHash(Array.Empty<byte>(), 0, 0); |
There was a problem hiding this comment.
I'm not sure what the comment means here. Do we give the empty hash answer on corefx, but the tainted hash answer on netfx? I'd expect the call to ComputeHash to be necessary on netfx to set up for the next test...
There was a problem hiding this comment.
yes corefx=empty, netfx=tainted\same as last. On whether ComputeHash is necessary on netfx for the next call - yes it is (that's what the ActiveIssue \ VerifyUseAfterTransformFinalBlock method checks for)
| public override long Position { get { return default(long); } set { } } | ||
| protected override void Dispose(bool disposing) { } | ||
| public override void Flush() { } | ||
| public override System.Threading.Tasks.Task FlushAsync(System.Threading.CancellationToken cancellationToken) { return default(System.Threading.Tasks.Task); } |
There was a problem hiding this comment.
@weshaggard I assume we need (in the end) all the overrides to align on non-sealed types for base. invocations; is that being well-flagged by the current netstandard20 deviations process?
| public CryptographicUnexpectedOperationException() | ||
| : base(SR.Arg_CryptographyException) | ||
| { | ||
| } |
There was a problem hiding this comment.
That's fair. @stephentoub any thoughts on if we care about making the HResult values of the default ctors of these two exceptions match desktop? The HResult value of CryptographicException is pretty hard to depend on since we don't try to normalize the codes between Windows and Unix (or across versions of Windows).
| { | ||
| var formatter = new DSASignatureFormatter(dsa); | ||
| var deformatter = new DSASignatureDeformatter(dsa); | ||
| VerifySignature(formatter, deformatter, SHA1.Create(), "SHA1"); |
There was a problem hiding this comment.
I'd like to see the hash algorithm object creations put in using statements. (Either I never made that comment or it got lost, because I can't find it now)
| } | ||
|
|
||
| [Fact] | ||
| [ActiveIssue(0)] // Todo: netfx treats HashFinal and Initialize separately, corefx doesn't and Initialize is a no-op |
There was a problem hiding this comment.
If there is an issue that cannot be resolved then an actual issue needs to be created and it tracked here. If it's a behavior deviation that we're okay with (I don't know if we have a way of really deciding/codifying what we're okay with as a deviation or not) then we need to make the test do the thing we decide on.
But [ActiveIssue(0)] isn't acceptable.
| } | ||
|
|
||
| private byte[] CaptureHashCodeAndReinitialize() | ||
| private byte[] CaptureHashCodeAndReinitialize(int inputCount) |
There was a problem hiding this comment.
Why do we process the input count after doing all the work? I'm a bit confused...
There was a problem hiding this comment.
This is for netfx compat to throw when appropriate. In netfx the only way to "clear" after a TransformFinalBlock so it can hash again is to call ComputeHash(empty) which calls Initialize(). It's messy but due to the differences in Initialize + HashFinal between corefx and netfx -- corefx doesn't do anything in Initialize since it does everything in HashFinal thus it wouldn't normally throw when netfx does. Hope that helps, otherwise we can take it offline
|
test Innerloop CentOS7.1 Release Build and Test |
|
New information leads to new conclusions. I found out this morning that I was aware only of one of the (at least) THREE different behaviors we have regarding TransformFinalBlock / ComputeHash interaction. SHA1Managed: Never throws, has the concatenation effect I described in my comment previously. Given that there's no one behavior to match for compatibility; I'm now happiest with the 4th behavior of "always give the correct answer", and will pursue change for future NetFx. |
|
(Having said that, for the "match SHA1CryptoServiceProvider" approach I like the revisions of this latest commit) |
0a5bb9b to
b4f6fc4
Compare
bartonjs
left a comment
There was a problem hiding this comment.
Aside from not understanding why some csproj conditions got rewritten, LGTM.
| <DefineConstants Condition="'$(TargetGroup)'=='netstandard1.7'">$(DefineConstants);netstandard17</DefineConstants> | ||
| <NugetTargetMoniker Condition="'$(NugetTargetMoniker)'==''">.NETStandard,Version=v1.6</NugetTargetMoniker> | ||
| <DefineConstants Condition="'$(TargetGroup)'==''">$(DefineConstants);netstandard17</DefineConstants> | ||
| <NugetTargetMoniker Condition="'$(TargetGroup)'==''">.NETStandard,Version=v1.7</NugetTargetMoniker> |
There was a problem hiding this comment.
Why is this condition changing? (I understand the value changing, but not the condition)
| <Compile Include="TripleDesTests.cs" /> | ||
| </ItemGroup> | ||
| <ItemGroup Condition="'$(TargetGroup)'=='netstandard1.7'"> | ||
| <ItemGroup Condition="'$(TargetGroup)'==''"> |
There was a problem hiding this comment.
Why is this condition changing?
There was a problem hiding this comment.
Tests were changed globally to run on 1.7 instead of 1.6 by default. I didn't change it in the rebase so it appeared here.
|
Okay, I guess I'm happy after a squash. |
Three sets of changes:
(primary) #9990 Port System.Security.Cryptography.*Formatter
(dependency) #9983 Port System.Security.Cryptography.HashAlgorithm
(trivial) #9991 Port KeyedHashAlgorithm.KeyValue
The abstract formatter types mentioned in #9990 were placed into the Algorithms assembly instead of Primitives to make the code cleaner when converting from a HashAlgorithm type to the string-based algorithm version of that type (see HashAlgorithmNames.ToAlgorithmName())
The formatter types have some questionable semantics but kept it this way for desktop compat. This includes Parameters property, 'dead' code in DSA's SetHashAlgorithm, and some missed validation on null parameters.