Skip to content

Commit 2f10765

Browse files
committed
API reorg. Breaking changes.
Confined support to Rsa/Aes256/BlockSIze128
1 parent 43bf8f4 commit 2f10765

File tree

9 files changed

+250
-744
lines changed

9 files changed

+250
-744
lines changed

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

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

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public static class X509CertificateCache
3838
/// Disposing the certificate, for example, will leave a stale and useless X509Certificate2 instance in the cache.
3939
/// Throws an exception if requested certificate is NOT found.
4040
/// </summary>
41-
public static X509Certificate2 GetCertificate(string x509Thumbprint, StoreName storeName = StoreName.My, StoreLocation storeLocation = StoreLocation.CurrentUser, string cacheKeyPrefix = "/")
41+
public static X509Certificate2 GetCertificate(string x509Thumbprint, StoreName storeName = StoreName.My, StoreLocation storeLocation = StoreLocation.CurrentUser, string cacheKeyPrefix = null)
4242
{
4343
if (string.IsNullOrWhiteSpace(x509Thumbprint)) throw new ArgumentException("x509Thumbprint was NULL or EMPTY.");
4444

@@ -51,7 +51,7 @@ public static X509Certificate2 GetCertificate(string x509Thumbprint, StoreName s
5151
/// Disposing the certificate, for example, will leave a stale and useless X509Certificate2 instance in the cache.
5252
/// Returns the X509Certificate2 if found, else NULL.
5353
/// </summary>
54-
public static X509Certificate2 TryGetCertificate(string x509Thumbprint, StoreName storeName = StoreName.My, StoreLocation storeLocation = StoreLocation.CurrentUser, string cacheKeyPrefix = "/")
54+
public static X509Certificate2 TryGetCertificate(string x509Thumbprint, StoreName storeName = StoreName.My, StoreLocation storeLocation = StoreLocation.CurrentUser, string cacheKeyPrefix = null)
5555
{
5656
if (string.IsNullOrWhiteSpace(x509Thumbprint)) throw new ArgumentException("x509Thumbprint was NULL or EMPTY.");
5757

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+

2+
using System;
3+
using System.IO;
4+
using System.Security.Cryptography;
5+
using System.Security.Cryptography.X509Certificates;
6+
7+
namespace Org.Security.Cryptography.X509RsaAes
8+
{
9+
/// <summary>
10+
/// Extensions to encrypt/decrypt Streams using X509 Certificates.
11+
/// Expects X509 RSA Certificate.
12+
/// Uses AES-256 with 128bit blockSize for Data encryption.
13+
/// </summary>
14+
public static class X509RsaAesStreamEncryptionExtensions
15+
{
16+
// Not confgurable, to avoid provider/consumer mis-configuration
17+
const int AesKeySize = 256;
18+
const int AesBlockSize = 128;
19+
20+
// Random prefix, for a private slice of X509 Cache.
21+
static readonly string X509CachePrefix = Guid.NewGuid().ToString();
22+
23+
/// <summary>
24+
/// X509Certificate public key serves as KeyEncryptionKey (KEK).
25+
/// Data is encrypted using a randomly generated DataEncryptionKey and IV.
26+
/// Writes encrypted DataEncryptionKey (using KEK), encrypted IV (using KEK) and encrypted data (using DEK) to the output stream.
27+
/// </summary>
28+
public static void Encrypt(this Stream inputStream, Stream outputStream, string thumbPrint, StoreName storeName, StoreLocation storeLocation)
29+
{
30+
if (null == inputStream) throw new ArgumentNullException(nameof(inputStream));
31+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
32+
if (null == thumbPrint) throw new ArgumentNullException(nameof(thumbPrint));
33+
34+
var cert = X509CertificateCache.GetCertificate(thumbPrint, storeName, storeLocation, X509CachePrefix);
35+
36+
inputStream.Encrypt(outputStream, cert);
37+
}
38+
39+
/// <summary>
40+
/// X509Certificate private key serves as KeyEncryptionKey (KEK).
41+
/// Reads and decrypts the Encrypted DataEncryptionKey and Encrypted IV using the KEK.
42+
/// Decrypts the data using the DataEncryptionKey and IV.
43+
/// </summary>
44+
public static void Decrypt(this Stream inputStream, Stream outputStream, string thumbPrint, StoreName storeName, StoreLocation storeLocation)
45+
{
46+
if (null == inputStream) throw new ArgumentNullException(nameof(inputStream));
47+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
48+
if (null == thumbPrint) throw new ArgumentNullException(nameof(thumbPrint));
49+
50+
var cert = X509CertificateCache.GetCertificate(thumbPrint, storeName, storeLocation, X509CachePrefix);
51+
52+
inputStream.Decrypt(outputStream, cert);
53+
}
54+
55+
/// <summary>
56+
/// X509Certificate public key serves as KeyEncryptionKey (KEK).
57+
/// Data is encrypted using a randomly generated DataEncryptionKey and IV.
58+
/// Writes encrypted DataEncryptionKey (using KEK), encrypted IV (using KEK) and encrypted data (using DEK) to the output stream.
59+
/// </summary>
60+
public static void Encrypt(this Stream inputStream, Stream outputStream, X509Certificate2 cert)
61+
{
62+
if (null == inputStream) throw new ArgumentNullException(nameof(inputStream));
63+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
64+
if (null == cert) throw new ArgumentNullException(nameof(cert));
65+
66+
// Ensure PublicKey exists and supports RSA
67+
if (null == cert.PublicKey) throw new ArgumentNullException($"X509Certificate2.PublicKey was NULL: {cert.Thumbprint}");
68+
if (null == cert.PublicKey.Key) throw new ArgumentNullException($"X509Certificate2.PublicKey.Key was NULL: {cert.Thumbprint}");
69+
if (null == cert.PublicKey.Key as RSA) throw new ArgumentNullException($"X509Certificate2 is NOT a RSA Certificate: {cert.Thumbprint}");
70+
71+
// DO NOT Dispose this.
72+
RSA keyEncryption = (RSA)cert.PublicKey.Key;
73+
74+
using (Aes dataEncryption = Aes.Create())
75+
{
76+
dataEncryption.KeySize = AesKeySize;
77+
dataEncryption.BlockSize = AesBlockSize;
78+
79+
Encrypt(keyEncryption, dataEncryption, inputStream, outputStream);
80+
}
81+
}
82+
83+
/// <summary>
84+
/// X509Certificate private key serves as KeyEncryptionKey (KEK).
85+
/// Reads and decrypts the Encrypted DataEncryptionKey and Encrypted IV using the KEK.
86+
/// Decrypts the data using the DataEncryptionKey and IV.
87+
/// </summary>
88+
public static void Decrypt(this Stream inputStream, Stream outputStream, X509Certificate2 cert)
89+
{
90+
if (null == inputStream) throw new ArgumentNullException(nameof(inputStream));
91+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
92+
if (null == cert) throw new ArgumentNullException(nameof(cert));
93+
94+
// Ensure PrivateKey exists and supports RSA
95+
if (null == cert.PrivateKey) throw new ArgumentNullException($"X509Certificate2.PrivateKey was NULL: {cert.Thumbprint}");
96+
if (null == cert.PrivateKey as RSA) throw new ArgumentNullException($"X509Certificate2 is NOT a RSA Certificate: {cert.Thumbprint}");
97+
98+
// DO NOT Dispose this.
99+
RSA keyEncryption = (RSA)cert.PrivateKey;
100+
101+
using (Aes dataEncryption = Aes.Create())
102+
{
103+
Decrypt(keyEncryption, dataEncryption, inputStream, outputStream);
104+
}
105+
}
106+
107+
static void Encrypt(AsymmetricAlgorithm keyEncryption, SymmetricAlgorithm dataEncryption, Stream inputStream, Stream outputStream)
108+
{
109+
if (null == keyEncryption) throw new ArgumentNullException(nameof(keyEncryption));
110+
if (null == dataEncryption) throw new ArgumentNullException(nameof(dataEncryption));
111+
if (null == inputStream) throw new ArgumentNullException(nameof(inputStream));
112+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
113+
114+
// The DataEncryptionKey and the IV.
115+
var DEK = dataEncryption.Key ?? throw new Exception("SymmetricAlgorithm.Key was NULL");
116+
var IV = dataEncryption.IV ?? throw new Exception("SymmetricAlgorithm.IV was NULL");
117+
118+
// Encrypt the DataEncryptionKey and the IV
119+
var keyFormatter = new RSAPKCS1KeyExchangeFormatter(keyEncryption);
120+
var encryptedDEK = keyFormatter.CreateKeyExchange(DEK);
121+
var encryptedIV = keyFormatter.CreateKeyExchange(IV);
122+
123+
// Write the Encrypted DEK and IV
124+
outputStream.WriteLengthAndBytes(encryptedDEK);
125+
outputStream.WriteLengthAndBytes(encryptedIV);
126+
127+
// Write the encrypted data.
128+
using (var transform = dataEncryption.CreateEncryptor())
129+
using (var cryptoStream = new CryptoStream(outputStream, transform, CryptoStreamMode.Write))
130+
{
131+
inputStream.CopyTo(cryptoStream, dataEncryption.BlockSize);
132+
}
133+
}
134+
135+
static void Decrypt(AsymmetricAlgorithm keyEncryption, SymmetricAlgorithm dataEncryption, Stream inputStream, Stream outputStream)
136+
{
137+
if (null == keyEncryption) throw new ArgumentNullException(nameof(keyEncryption));
138+
if (null == dataEncryption) throw new ArgumentNullException(nameof(dataEncryption));
139+
if (null == inputStream) throw new ArgumentNullException(nameof(inputStream));
140+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
141+
142+
var encryptedDEK = inputStream.ReadLengthAndBytes();
143+
var encryptedIV = inputStream.ReadLengthAndBytes();
144+
145+
var keyDeformatter = new RSAPKCS1KeyExchangeDeformatter(keyEncryption);
146+
dataEncryption.Key = keyDeformatter.DecryptKeyExchange(encryptedDEK);
147+
dataEncryption.IV = keyDeformatter.DecryptKeyExchange(encryptedIV);
148+
149+
using (var transform = dataEncryption.CreateDecryptor())
150+
using (var cryptoStream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read))
151+
{
152+
cryptoStream.CopyTo(outputStream, dataEncryption.BlockSize);
153+
}
154+
}
155+
156+
static void WriteLengthAndBytes(this Stream outputStream, byte[] bytes)
157+
{
158+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
159+
if (null == bytes) throw new ArgumentNullException(nameof(bytes));
160+
161+
var length = BitConverter.GetBytes((Int32)bytes.Length);
162+
163+
outputStream.Write(length, 0, length.Length);
164+
outputStream.Write(bytes, 0, bytes.Length);
165+
}
166+
167+
static byte[] ReadLengthAndBytes(this Stream outputStream)
168+
{
169+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
170+
171+
// Read an Int32, exactly four bytes.
172+
var arrLength = new byte[4];
173+
var bytesRead = outputStream.Read(arrLength, 0, 4);
174+
if (bytesRead != 4) throw new Exception("Unexpected end of InputStream. Expecting 4 bytes.");
175+
176+
// Read suggested no of bytes...
177+
var length = BitConverter.ToInt32(arrLength, 0);
178+
var bytes = new byte[length];
179+
bytesRead = outputStream.Read(bytes, 0, bytes.Length);
180+
if (bytesRead != bytes.Length) throw new Exception($"Unexpected end of input stream. Expecting {bytes.Length:#,0} bytes.");
181+
182+
return bytes;
183+
}
184+
185+
static void CopyTo(this Stream inputStream, Stream outputStream, int bufferSize)
186+
{
187+
if (null == inputStream) throw new ArgumentNullException(nameof(inputStream));
188+
if (null == outputStream) throw new ArgumentNullException(nameof(outputStream));
189+
if (bufferSize <= 0) throw new ArgumentException("Invalid buffer size. Must be > 0");
190+
191+
byte[] buffer = new byte[bufferSize];
192+
int bytesRead = 0;
193+
194+
do {
195+
bytesRead = inputStream.Read(buffer, 0, buffer.Length);
196+
if (bytesRead > 0) outputStream.Write(buffer, 0, bytesRead);
197+
}
198+
while (bytesRead > 0);
199+
}
200+
201+
}
202+
}

0 commit comments

Comments
 (0)