Skip to content

Commit 81e9f56

Browse files
committed
Code cleanup
1 parent 98db2b7 commit 81e9f56

File tree

7 files changed

+169
-72
lines changed

7 files changed

+169
-72
lines changed

src/Org.Security.Cryptography.X509Extensions/X509CertificateCache.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11

22
//...................................................................................
3-
#region Readme: X509CertificateCache
3+
#region About X509CertificateCache
44
//...................................................................................
55
//
6-
// It takes approx 5 miiliSec to lookup and obtain the certificate from local certificate store,
6+
// It takes approx 5 milliSec to lookup and obtain the certificate from local certificate store,
77
// unless the Store itself is handled as singleton and never closed during process lifetime.
88
// X509CertificateCache can be used to cache and re-use the certs.
99
//

src/Org.Security.Cryptography.X509Extensions/X509StreamEncryptionExtensions.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,16 @@ public static void Encrypt(this Stream inputStream, Stream outputStream, X509Cer
7070
if (null == cert) throw new ArgumentNullException(nameof(cert));
7171
if (null == algName) throw new ArgumentNullException(nameof(algName));
7272

73-
// DO NOT Dispose this; Doing so will render the X509Certificate in cache use-less.
73+
// Encrypt using Public key.
74+
// DO NOT Dispose this; Doing so will render the X509Certificate in the cache use-less.
75+
// Did endurance test of 1 mil cycles, found NO HANDLE leak.
7476
var keyEncryption = cert.GetRsaPublicKeyAsymmetricAlgorithm();
7577

7678
using (var dataEncryption = SymmetricAlgorithm.Create(algName))
7779
{
7880
if (null == dataEncryption) throw new Exception($"SymmetricAlgorithm.Create() returned null. Check algName: '{algName}'");
7981

82+
// Select suggested keySize/blockSize.
8083
dataEncryption.KeySize = keySize;
8184
dataEncryption.BlockSize = blockSize;
8285
Encrypt(inputStream, outputStream, keyEncryption, dataEncryption);
@@ -95,17 +98,24 @@ public static void Decrypt(this Stream inputStream, Stream outputStream, X509Cer
9598
if (null == cert) throw new ArgumentNullException(nameof(cert));
9699
if (null == algName) throw new ArgumentNullException(nameof(algName));
97100

101+
// Decrypt using Private key.
98102
// DO NOT Dispose this; Doing so will render the X509Certificate in cache use-less.
103+
// Did endurance test of 1 mil cycles, found NO HANDLE leak.
99104
var keyEncryption = cert.GetRsaPrivateKeyAsymmetricAlgorithm();
100105

101106
using (var dataEncryption = SymmetricAlgorithm.Create(algName))
102107
{
103108
if (null == dataEncryption) throw new Exception($"SymmetricAlgorithm.Create() returned null. Check algName: '{algName}'");
104109

110+
// KeySize/blockSize will be selected when we assign key/IV later.
105111
Decrypt(inputStream, outputStream, keyEncryption, dataEncryption);
106112
}
107113
}
108114

115+
//...............................................................................
116+
#region Encrypt/Decrypt the Key (Asymmetric) and the Data (Symmetric)
117+
//...............................................................................
118+
109119
static void Encrypt(Stream inputStream, Stream outputStream, AsymmetricAlgorithm keyEncryption, SymmetricAlgorithm dataEncryption)
110120
{
111121
if (null == inputStream) throw new ArgumentNullException(nameof(inputStream));
@@ -131,6 +141,7 @@ static void Encrypt(Stream inputStream, Stream outputStream, AsymmetricAlgorithm
131141
outputStream.WriteLengthAndBytes(encryptedIV);
132142

133143
// Write the encrypted data.
144+
// Note: Disposing the CryptoStream also disposes the outputStream. There is no keepOpen option.
134145
using (var transform = dataEncryption.CreateEncryptor())
135146
using (var cryptoStream = new CryptoStream(outputStream, transform, CryptoStreamMode.Write))
136147
{
@@ -156,13 +167,17 @@ static void Decrypt(Stream inputStream, Stream outputStream, AsymmetricAlgorithm
156167
// Trace.WriteLine($"Decrypting. KEK: {keyEncryption.GetType().Name} / {keyEncryption.KeySize} bits");
157168
// Trace.WriteLine($"Decrypting. DEK: {dataEncryption.GetType().Name} / {dataEncryption.KeySize} bits / BlockSize: {dataEncryption.BlockSize} bits");
158169

170+
// Read the encrypted data.
171+
// Note: Disposing the CryptoStream also disposes the inputStream. There is no keepOpen option.
159172
using (var transform = dataEncryption.CreateDecryptor())
160173
using (var cryptoStream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read))
161174
{
162175
cryptoStream.CopyTo(outputStream, bufferSize: dataEncryption.BlockSize * 4);
163176
}
164177
}
165178

179+
#endregion
180+
166181
//...............................................................................
167182
#region Utils: WriteLengthAndBytes(), ReadLengthAndBytes()
168183
//...............................................................................
@@ -171,8 +186,10 @@ static void WriteLengthAndBytes(this Stream outputStream, byte[] bytes)
171186
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
172187
if (null == bytes) throw new ArgumentNullException(nameof(bytes));
173188

189+
// Int32 length to exactly-four-bytes array.
174190
var length = BitConverter.GetBytes((Int32)bytes.Length);
175191

192+
// Write the four-byte-length followed by the data.
176193
outputStream.Write(length, 0, length.Length);
177194
outputStream.Write(bytes, 0, bytes.Length);
178195
}
@@ -188,7 +205,7 @@ static byte[] ReadLengthAndBytes(this Stream inputStream, int maxBytes)
188205

189206
// Length of data to read.
190207
var length = BitConverter.ToInt32(arrLength, 0);
191-
if (length > maxBytes) throw new Exception($"Unexpected data size {length:#,0} bytes. Expecting not more than {maxBytes:#,0} bytes.");
208+
if (length > maxBytes) throw new Exception($"Unexpected data size {length:#,0} bytes. Expecting NOT more than {maxBytes:#,0} bytes.");
192209

193210
// Read suggested no of bytes...
194211
var bytes = new byte[length];
@@ -201,8 +218,9 @@ static byte[] ReadLengthAndBytes(this Stream inputStream, int maxBytes)
201218
#endregion
202219

203220
//...............................................................................
204-
#region Obtain private/public key AsymmetricAlgorithm
221+
#region Obtain public/private key AsymmetricAlgorithm
205222
//...............................................................................
223+
206224
static AsymmetricAlgorithm GetRsaPublicKeyAsymmetricAlgorithm(this X509Certificate2 cert)
207225
{
208226
if (null == cert) throw new ArgumentNullException(nameof(cert));

src/UnitTests/Algorithms.txt

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/UnitTests/App.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<configuration>
33

44
<appSettings>
5-
<add key="X509.ThumbPrint" value="F73299C92B327DB836170E9CF1C6AA948BFF5124" />
5+
<add key="X509.ThumbPrint" value="7A89FF9299CDE8C690DB93CE1F128EA3BAD642AB" />
66
</appSettings>
77

88
</configuration>

src/UnitTests/POCs/SignatureSamples.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ namespace UnitTests.POCs
1111
{
1212
// https://docs.microsoft.com/en-us/dotnet/standard/security/cryptographic-signatures
1313

14+
// WIP WIP WIP
15+
1416
[TestClass]
1517
public class SignatureSamples
1618
{

src/UnitTests/ReadMe.txt

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,68 @@
11

2+
//.............................................................................
3+
// IMP: CryptographicException "Invalid provider type specified”
4+
//.............................................................................
5+
// Cert.PrivateKey throws CryptographicException in .Net Framework 4.7.1 if KeySpec is not specified.
6+
// Alternative is to use cert.GetRSAPrivateKey()
7+
// Or use -KeySpec KeyExchange when creating self-signed-cert
8+
// Noticed AsymmetricAlgorithm using cert.GetRSAPrivateKey() is 4x slower compared to Cert.PrivateKey
9+
210

311
// REF: Useful info on X509 certs, cert stores and their locations.
412
// http://paulstovell.com/blog/x509certificate2
513

614
// REF: Comparison of Encryption algorithms
715
// https://symbiosisonlinepublishing.com/computer-science-technology/computerscience-information-technology32.php
816

9-
// REF: https://docs.microsoft.com/en-us/dotnet/standard/security/how-to-store-asymmetric-keys-in-a-key-container
10-
// REF: https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/system-cryptography-use-fips-compliant-algorithms-for-encryption-hashing-and-signing
11-
// REF: https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rsacryptoserviceprovider?view=netstandard-2.0
12-
13-
//...............................................................................
14-
// Sample Cryptographic Scheme
15-
//...............................................................................
16-
// REF: https://docs.microsoft.com/en-us/dotnet/standard/security/creating-a-cryptographic-scheme
17-
// A simple cryptographic scheme for encrypting and decrypting data might specify the following steps:
18-
// 1) Each party generates a public/private key pair.
19-
// 2) The parties exchange their public keys.
20-
// 3) Each party generates a secret key for TripleDES encryption, for example, and encrypts the newly created key using the other's public key.
21-
// 4) Each party sends the data to the other and combines the other's secret key with its own, in a particular order, to create a new secret key.
22-
// 5) The parties then initiate a conversation using symmetric encryption.
23-
//...............................................................................
17+
// System.Security.Cryptography
18+
// https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography?view=netstandard-2.0
19+
20+
//.............................................................................
21+
// AsymmetricAlgorithm
22+
//.............................................................................
23+
RSA
24+
RSACng
25+
RSACryptoServiceProvider
26+
RSAOpenSsl
27+
DSA
28+
DSACng
29+
DSACryptoServiceProvider
30+
DSAOpenSsl
31+
ECDiffieHellman
32+
ECDiffieHellmanCng
33+
ECDiffieHellmanOpenSsl
34+
ECDsa
35+
ECDsaCng
36+
ECDsaOpenSsl
37+
38+
notes:
39+
* X509Certificate2.PublicKey.Key and X509Certificate2.PrivateKey returns RSACng
40+
* The RSACng class is an implementation of the RSA algorithm using the Windows CNG libraries and isn't available on operating systems other than Windows
41+
42+
//.............................................................................
43+
// SymmetricAlgorithm
44+
//.............................................................................
45+
Aes
46+
AesCng
47+
AesCryptoServiceProvider
48+
AesManaged
49+
TripleDES
50+
TripleDESCng
51+
TripleDESCryptoServiceProvider
52+
Rijndael
53+
RijndaelManaged
54+
DES
55+
DESCryptoServiceProvider
56+
RC2
57+
RC2CryptoServiceProvider
58+
59+
Notes:
60+
* AES algorithm can support any combination of data (128 bits) and key length of 128, 192, and 256 bits.
61+
* The algorithm is referred to as AES-128, AES-192, or AES-256, depending on the key length.
62+
* The Rijndael class is the predecessor of the Aes algorithm. You should use the Aes algorithm instead of Rijndael
63+
64+
65+
66+
67+
2468

src/UnitTests/X509Tests.cs

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,96 @@
1111

1212
namespace UnitTests
1313
{
14-
1514
[TestClass]
1615
public class X509Tests
1716
{
1817
static string TestCertThumbPrint =>
1918
System.Configuration.ConfigurationManager.AppSettings["X509.ThumbPrint"] ??
20-
throw new Exception($"AppSetting 'Test.X509.ThumbPrint' not defined.");
19+
throw new Exception($"AppSetting 'X509.ThumbPrint' not defined.");
2120

2221
[TestMethod]
23-
public void FindMyCertificate()
22+
public void X509_LookupSpeed()
23+
{
24+
//......................................................................
25+
// OpenStore-Lookup-CloseStore: apprx 5 milliSec per lookup
26+
// KeepStoreOpen-Lookup: apprx 14 microSec per lookup
27+
//......................................................................
28+
29+
const int loopCount = 10000;
30+
const StoreName storeName = StoreName.My;
31+
const StoreLocation storeLocation = StoreLocation.CurrentUser;
32+
33+
var thumb = TestCertThumbPrint;
34+
Console.WriteLine(thumb);
35+
36+
// Dry run
37+
OpenStoreAndLookupCert();
38+
39+
// Scenario #1: OpenStore/Lookup/CloseStore
40+
Stopwatch timer = Stopwatch.StartNew();
41+
for (int i=0; i<loopCount; i++)
42+
{
43+
OpenStoreAndLookupCert();
44+
}
45+
timer.Stop();
46+
PrintStats("OPEN-CLOSE-STORE", timer.Elapsed, loopCount);
47+
48+
// Scenario #2: OpenStore ONCE. LookupCert.
49+
timer = Stopwatch.StartNew();
50+
using (X509Store store = new X509Store(storeName, storeLocation))
51+
{
52+
// Open an existing store.
53+
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
54+
55+
for (int i = 0; i < loopCount; i++)
56+
{
57+
UseMyStoreLookupCert(store);
58+
}
59+
}
60+
timer.Stop();
61+
PrintStats("KEEP-STORE-OPEN", timer.Elapsed, loopCount);
62+
63+
void PrintStats(string scenario, TimeSpan elapsed, int iterations)
64+
{
65+
var ratePerSec = (long)iterations / elapsed.TotalSeconds;
66+
var avgMs = elapsed.TotalMilliseconds / iterations;
67+
Console.WriteLine($"{scenario}: {iterations:#,0} iterations @ {ratePerSec:#,0.00} per-Sec. Avg: {avgMs:#,0.000} millSec");
68+
}
69+
70+
void OpenStoreAndLookupCert()
71+
{
72+
using (X509Store store = new X509Store(storeName, storeLocation))
73+
{
74+
// Open an existing store.
75+
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
76+
77+
// Look for the certificate by thumbPrint.
78+
var certs = store
79+
.Certificates
80+
.Cast<X509Certificate2>()
81+
.Where(x => null != x?.Thumbprint)
82+
.Where(x => x.Thumbprint.Equals(thumb, StringComparison.OrdinalIgnoreCase))
83+
.ToArray();
84+
85+
if (1 != certs.Length) throw new Exception("Cert not found.");
86+
}
87+
}
88+
89+
void UseMyStoreLookupCert(X509Store storeThatIsAlreadyOpen)
90+
{
91+
var certs = storeThatIsAlreadyOpen
92+
.Certificates
93+
.Cast<X509Certificate2>()
94+
.Where(x => null != x?.Thumbprint)
95+
.Where(x => x.Thumbprint.Equals(thumb, StringComparison.OrdinalIgnoreCase))
96+
.ToArray();
97+
98+
if (1 != certs.Length) throw new Exception("Cert not found.");
99+
}
100+
}
101+
102+
[TestMethod]
103+
public void X509_CacheLookup()
24104
{
25105
var cert = X509CertificateCache.GetCertificate(TestCertThumbPrint, StoreName.My, StoreLocation.CurrentUser);
26106

@@ -144,6 +224,6 @@ static byte[] GenerateJunk(int kiloBytes)
144224
return buffer.ToArray();
145225
}
146226
}
147-
148227
}
149228
}
229+

0 commit comments

Comments
 (0)