-
Notifications
You must be signed in to change notification settings - Fork 5k
Port DES and RC2 in System.Security.Cryptography.Algorithms and .Csp #12744
Conversation
80c788f
to
ea9004d
Compare
cee8077
to
b0bd20d
Compare
@@ -229,6 +242,13 @@ private static class Interop | |||
[DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)] | |||
public static extern unsafe NTSTATUS BCryptSetProperty(SafeAlgorithmHandle hObject, String pszProperty, String pbInput, int cbInput, int dwFlags); | |||
|
|||
[DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode, EntryPoint = "BCryptSetProperty")] | |||
private static extern unsafe NTSTATUS BCryptSetIntPropertyPrivate(SafeBCryptHandle hObject, string pszProperty, ref int pdwInput, int cbInput, int dwFlags); | |||
public static unsafe NTSTATUS BCryptSetIntProperty(SafeBCryptHandle hObject, string pszProperty, ref int pdwInput, int dwFlags) |
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.
Style: Don't glue these together
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.
Added line
|
||
public override ICryptoTransform CreateEncryptor() | ||
{ | ||
return CreateTransform(this.Key, this.IV, encrypting: true); |
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.
Style: There are a lot of this.
s in this file that don't seem required.
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.
Sure. Removed them from 4 providers (including TripleDes)
@@ -5,8 +5,7 @@ | |||
</PropertyGroup> | |||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" /> | |||
<PropertyGroup> | |||
<Configuration Condition=" '$(Configuration)' == '' ">Windows_Debug</Configuration> | |||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> | |||
<Platform Condition="'$(Platform)' == '' ">AnyCPU</Platform> | |||
<ProjectGuid>{81A05E2E-E3AE-4246-B4E6-DD5F31FB71F9}</ProjectGuid> |
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 did the default configuration go away? And why did the padding space around the platform condition go away?
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.
Reverted those. There is a duplicate config entry above before the dir.prop import; not sure if both are necessary
throw new CryptographicException(SR.Cryptography_InvalidKeySize); | ||
} | ||
} | ||
} |
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.
There are a lot of branches in this setter, and I don't know that they're all tested since the implementations require that KeySizeValue be EffectiveKeySizeValue (so the backwards-looking first if never gets hit... I think).
I'm also not sure that it can actually be reset to 0.
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 essentially the existing implementation in netfx; didn't change the semantics assuming perhaps someone may have derived from this class and is expecting this behavior.
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, but we should still have tests for it.
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.
Done
{ | ||
SafeKeyHandle safeKeyHandle; | ||
|
||
SafeProvHandle safeProvHandleTemp = AcquireSafeProviderHandle(); |
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 looks like it gets handed off to the finalizer. Can we add a using here?
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.
Good catch. I created a field for the provhandle (as not sure of lifetime semantics between provider and keys) and clean up in Dispose()
Debug.Assert((outputCount % 8) == 0); | ||
|
||
// Figure out how big the encrypted data will be | ||
// REVIEW NOTE: we could just bump up the buffer by 8 bytes always instead of doing the CryptEncrypt check for buffer size |
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.
Well, not 8 bytes... blocksize. But we don't have blocksize available.
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.
(And even then only when isFinal
is true. And even then only in CBC mode. But it's definitely safest to just ask the crypto library how big it is)
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.
FWIW the netfx native code that does this bumps up the buffer by 16 bytes (doesn't ask for the size)
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.
16 being the biggest block size that it knows about, yeah.
Debug.Assert(encryptedDataLength == cbEncryptedData); | ||
|
||
// If isFinal, padding was added so ignore it by using outputCount as size | ||
Buffer.BlockCopy(encryptedData, 0, output, outputOffset, outputCount); |
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 get this. Why are we eliminating data? How'd the caller know the desired outputCount?
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 might just be because we never set isFinal to true. And so maybe some of this code can be ripped out?
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.
isFinal does get set to true in this implementation; the netfx c++ also sets true and similarly removes the added padding
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.
Looking at that again, it seems as though it pads the data twice. Once manually (which for this implementation we're doing in UniversalTransform, IIRC) then again because they opened the key in the desired mode.
For CTS (which isn't supported) that would produce the wrong answer. For ECB, CBC, CFB, and OFB it's fine.
But a much simpler result would be to just always use the native object in "no padding" mode, and then the isFinal flag should just be resetting the key object to the start state.
Since this is an entirely new implementation we should be verifying that using the same ICryptoTransform object twice works the same way as it does in Desktop (which can validate that simplified code proposal).
Or, we can just drop all the CAPI code and just make this type wrap an instance of RC2.Create(). (@morganB do you have a feeling between "simple" (wrap CNG) and "highly compatible" (port all the symmetric CAPI code) for this portion of the change?)
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 this is an entirely new implementation we should be verifying that using the same ICryptoTransform object twice works the same way as it does in Desktop (which can validate that simplified code proposal).
Sure will add test to use same encryptors\decryptor instances over the same data
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.
Do you mean once transformfinal is called we can do another transform (the tests already chain multiple transforms)
Yeah. Transform, TransformFinal, Transform, TransformFinal. If that's already part of the test suite I must have missed it. (I know we have it somewhere in the stacks, but didn't see it explicitly)
{ | ||
[Fact] | ||
public static void RC2KeySize() | ||
{ |
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 test doesn't seem specific to the CSP instance... why isn't it in the shared tests?
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.
It is specific. I just used the factory pattern and not the ctor. The key sizes are more constrained for csp
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.
Please switch it to the ctor if it's supposed to be specific to that one type.
CTS = 5, | ||
ECB = 2, | ||
[System.ComponentModel.EditorBrowsableAttribute((System.ComponentModel.EditorBrowsableState)(1))] | ||
OFB = 3, |
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.
While this is part of netstandard2.0 it's not really related to the subject of this change...
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.
At one point the code checked against the new values; now it doesn't. Included the issue in the PR description
@@ -125,6 +125,7 @@ public virtual CipherMode Mode | |||
|
|||
set | |||
{ | |||
// Review note: should we throw PNSE instead for OFB and CFB? |
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.
We could, I suppose.
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.
Though note that CTS seems to have already resulted in this exception in Desktop.
// done to maintain backward desktop compatibility with the behavior shipped in V1.x. | ||
// The call to set the IV in CryptoAPI will ignore any bytes after the first 8 | ||
// bytes. We'll still reject IV's that are shorter than the block size though. | ||
if (rgbIV.Length < 8) |
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.
Can't the IV be null in ECB mode?
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.
There will always have an IV at this point (the SymmetricAlgorithm base class enforces that), and is stripped off after this code in the BasicSymmetricCipherCsp ctor if ECB
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.
DES des = new DESCryptoServiceProvider();
des.Mode = CipherMode.ECB;
ICryptoTransform encryptor = des.CreateEncryptor(key, null);
Since the CloneByteArray extension method returns null for a null input, that'll nullref here.
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.
Thanks. Fixed + added tests that use explicit CreateEncryptor(key, null)
Debug.Assert((outputCount % 8) == 0); | ||
|
||
// Figure out how big the encrypted data will be | ||
// REVIEW NOTE: we could just bump up the buffer by 8 bytes always instead of doing the CryptEncrypt check for buffer size |
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.
16 being the biggest block size that it knows about, yeah.
Debug.Assert(encryptedDataLength == cbEncryptedData); | ||
|
||
// If isFinal, padding was added so ignore it by using outputCount as size | ||
Buffer.BlockCopy(encryptedData, 0, output, outputOffset, outputCount); |
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.
Looking at that again, it seems as though it pads the data twice. Once manually (which for this implementation we're doing in UniversalTransform, IIRC) then again because they opened the key in the desired mode.
For CTS (which isn't supported) that would produce the wrong answer. For ECB, CBC, CFB, and OFB it's fine.
But a much simpler result would be to just always use the native object in "no padding" mode, and then the isFinal flag should just be resetting the key object to the start state.
Since this is an entirely new implementation we should be verifying that using the same ICryptoTransform object twice works the same way as it does in Desktop (which can validate that simplified code proposal).
Or, we can just drop all the CAPI code and just make this type wrap an instance of RC2.Create(). (@morganB do you have a feeling between "simple" (wrap CNG) and "highly compatible" (port all the symmetric CAPI code) for this portion of the change?)
throw new CryptographicException(SR.Cryptography_InvalidKeySize); | ||
} | ||
} | ||
} |
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, but we should still have tests for it.
::mutter:: I meant @morganbr (tagged a wrong Morgan). MorganBr: do you have a feeling between "simple" (wrap CNG) and "highly compatible" (port all the symmetric CAPI code) for the RC2CryptoServiceProvider portion of the change? |
byte[] expectedDecrypted1 = s_multiBlockString.HexToByteArray(); | ||
Assert.Equal<byte>(expectedDecrypted1, decrypted1); | ||
|
||
byte[] plainText2 = s_multiBlockString_8.HexToByteArray(); |
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 think it would help clarity if it encrypted the same plaintext a second time, to produce the same stable output.
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 pattern was copied from 3des. It does iterate. Note that there was an issue with second decrypt (fixed now) so added a blank call to encrypt to ensure final=true clears the handle and resets the IV
@bartonjs The only factor about using CNG vs CAPI for RC2 and DES that comes to mind for me is UWP apps where CAPI might not be available. I'm not too worried about compat as long as we have a reasonable set of tests. |
75adc83
to
b8248c4
Compare
{ | ||
public static class DesCipherTests | ||
{ | ||
// These are the expected output of many decryptions. Changing these values requires re-generating test input. |
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.
Are there public test vectors we can use to confirm compatibility?
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.
Yes the current tests use RFC2268 for RC2+ECB and FIPS81 for DES (ECB and CBC) in addition to comparing against values from .net desktop. I could not find public test vectors for RC2+CBC.
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.
Are ALL the DES test cases from FIPS81?
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.
No, just the two flagged (one for ECB and one for CBC). The rest are based on desktop output.
|
||
public static class RC2CipherTests | ||
{ | ||
// These are the expected output of many decryptions. Changing these values requires re-generating test input. |
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.
Same question on public test vectors
ECB = 2, | ||
[EditorBrowsable(EditorBrowsableState.Never)]OFB = 3, | ||
[EditorBrowsable(EditorBrowsableState.Never)]CFB = 4, |
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.
It looks like TripleDes is supposed to have some logic around CFB and OFB (see multiple references in https://referencesource.microsoft.com/#mscorlib/system/security/cryptography/tripledescryptoserviceprovider.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.
It looks like AESManaged rejects these as well. Not sure if we'd want to try to port that.
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 that SymmeticAlgorithm currently rejects everything other than ECB and CBC https://github.com/dotnet/corefx/blob/master/src/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs#L128-L129 so if that changes we would need to fix up other classes as appropriate
internal sealed partial class RC2Implementation : RC2 | ||
{ | ||
private const int BitsPerByte = 8; | ||
private static readonly RandomNumberGenerator s_rng = RandomNumberGenerator.Create(); |
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 noticed a few of these types have such an s_rng field initialized to its own RandomNumberGenerator.Create() instance. Could we instead put a single instance on some shared class such that all of them can use the same instance?
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 think just putting an internal static method\field on RandomNumberGenerator would work although we may need to be concerned about thread safety if used incorrectly. I think that could\should be addressed by a new issue however and not here. Thanks
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.
although we may need to be concerned about thread safety if used incorrectly
What kind of concerns?
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 a method somewhere is thread-safe and calls the static method which uses the static field (and assuming RandomNumberGenerator is not thead-safe). Does not apply to the crypto classes that use s_rng, but could be misused elsewhere
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.
RandomNumberGenerator is thread-safe. (If it weren't, this existing code using s_rng would be broken.)
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.
You're right - it has to be. When I replied originally I was thinking it was not static here (but the s_ gives it away)
LegalBlockSizesValue = s_legalBlockSizes; | ||
LegalKeySizesValue = s_legalKeySizes; | ||
KeySize = 128; | ||
BlockSize = 64; |
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 currently fixing the existing SymmetricAlgorithm classes to assign the fields instead of the properties. Please switch these new ones to set KeySizeValue and BlockSizeValue.
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.
Can point me to you PR when ready to make sure we're the same. The cloning semantics are inconsistent also. Thanks
new KeySizes(minSize: 64, maxSize: 64, skipSize: 0) | ||
}; | ||
|
||
private static KeySizes[] s_legalKeySizes = |
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.
These can be readonly
protected RC2() | ||
{ | ||
LegalBlockSizesValue = s_legalBlockSizes; | ||
LegalKeySizesValue = s_legalKeySizes; |
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 this assignment came up in the AES/3DES review you'll probably want to match the fixed behavior, and merge in the tamper tests.
068a3d5
to
7913633
Compare
|
||
break; | ||
default: | ||
Debug.Assert(false); |
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 believe we use Debug.Fail()
instead of Debug.Assert(false)
|
||
byte[] dataTobeDecrypted = new byte[inputCount]; | ||
Buffer.BlockCopy(input, inputOffset, dataTobeDecrypted, 0, inputCount); | ||
//Array.Reverse(dataTobeDecrypted); |
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.
Please delete commented out code.
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.
A couple tiny nits from this pass, but otherwise LGTM
Enabled for .Csp via CAPI, and .Algorithms for Windows (via Cng), Linux (via OpenSsl) and Apple.
a03275d
to
ec92082
Compare
Add support for RC2 and DES
Issues addressed:
#11117 Port T:System.Security.Cryptography.RC2xx
#11116 Port T:System.Security.Cryptography.DESxx
#12313 Port System.Security.Cryptography.CipherMode.CFB and .OFB
RC2\DES support is enabled for S.S.C.Csp via CAPI, and S.S.C.Algorithms for Windows (via Cng), Linux (via OpenSsl) and Apple.
In .Csp, support for ICryptoTransform was achieved by extending the UniversalCryptoTransform pattern which is much cleaner than porting the netfx CryptoAPITransform.
Note that there is no RC2\DES support in S.S.C.Cng as there is no netfx support for Cng RC2\DES.
For RC2, there was a lot of code that needed to be touched to set the effective key length property.
@bartonjs please review