[bcl] Add CommonCrypto to corlib, Mono.Security and System.Core.
[mono.git] / mcs / class / corlib / CommonCrypto / FastCryptorTransform.cs
1 // Fast, multi-block, ICryptoTransform implementation on top of CommonCrypto
2 //
3 // Authors:
4 //      Sebastien Pouliot  <sebastien@xamarin.com>
5 //
6 // Copyright 2012 Xamarin Inc.
7
8 using System;
9 using System.Security.Cryptography;
10
11 using Mono.Security.Cryptography;
12
13 namespace Crimson.CommonCrypto {
14
15         unsafe class FastCryptorTransform : ICryptoTransform {
16                 
17                 IntPtr handle;
18                 bool encrypt;
19                 int BlockSizeByte;
20                 byte[] workBuff;
21                 bool lastBlock;
22                 PaddingMode padding;
23                 
24                 public FastCryptorTransform (IntPtr cryptor, SymmetricAlgorithm algo, bool encryption, byte[] iv)
25                 {
26                         BlockSizeByte = (algo.BlockSize >> 3);
27                         
28                         if (iv == null) {
29                                 iv = KeyBuilder.IV (BlockSizeByte);
30                         } else if (iv.Length < BlockSizeByte) {
31                                 string msg = String.Format ("IV is too small ({0} bytes), it should be {1} bytes long.",
32                                         iv.Length, BlockSizeByte);
33                                 throw new CryptographicException (msg);
34                         }
35                         
36                         handle = cryptor;
37                         encrypt = encryption;
38                         padding = algo.Padding;
39                         // transform buffer
40                         workBuff = new byte [BlockSizeByte];
41                 }
42                 
43                 ~FastCryptorTransform ()
44                 {
45                         Dispose (false);
46                 }
47                 
48                 public void Dispose ()
49                 {
50                         Dispose (true);
51                 }
52                 
53                 protected virtual void Dispose (bool disposing)
54                 {
55                         if (handle != IntPtr.Zero) {
56                                 Cryptor.CCCryptorRelease (handle);
57                                 handle = IntPtr.Zero;
58                         }
59                         GC.SuppressFinalize (this);
60                 }
61
62                 public virtual bool CanTransformMultipleBlocks {
63                         get { return true; }
64                 }
65
66                 public virtual bool CanReuseTransform {
67                         get { return false; }
68                 }
69
70                 public virtual int InputBlockSize {
71                         get { return BlockSizeByte; }
72                 }
73
74                 public virtual int OutputBlockSize {
75                         get { return BlockSizeByte; }
76                 }
77
78                 int Transform (byte[] input, int inputOffset, byte[] output, int outputOffset, int length)
79                 {
80                         IntPtr len = IntPtr.Zero;
81                         IntPtr in_len = (IntPtr) length;
82                         IntPtr out_len = (IntPtr) (output.Length - outputOffset);
83                         fixed (byte* inputBuffer = &input [0])
84                         fixed (byte* outputBuffer = &output [0]) {
85                                 CCCryptorStatus s = Cryptor.CCCryptorUpdate (handle, (IntPtr) (inputBuffer + inputOffset), in_len, (IntPtr) (outputBuffer + outputOffset), out_len, ref len);
86                                 if (s != CCCryptorStatus.Success)
87                                         throw new CryptographicException (s.ToString ());
88                         }
89                         return (int) len;
90                 }
91
92                 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
93                 {
94                         if (inputBuffer == null)
95                                 throw new ArgumentNullException ("inputBuffer");
96                         if (inputOffset < 0)
97                                 throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
98                         if (inputCount < 0)
99                                 throw new ArgumentOutOfRangeException ("inputCount", "< 0");
100                         // ordered to avoid possible integer overflow
101                         if (inputOffset > inputBuffer.Length - inputCount)
102                                 throw new ArgumentException ("inputBuffer", "Overflow");
103                 }
104
105                 // this method may get called MANY times so this is the one to optimize
106                 public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
107                 {
108                         CheckInput (inputBuffer, inputOffset, inputCount);
109                         // check output parameters
110                         if (outputBuffer == null)
111                                 throw new ArgumentNullException ("outputBuffer");
112                         if (outputOffset < 0)
113                                 throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
114
115                         // ordered to avoid possible integer overflow
116                         int len = outputBuffer.Length - inputCount - outputOffset;
117                         if (!encrypt && (0 > len) && ((padding == PaddingMode.None) || (padding == PaddingMode.Zeros))) {
118                                 throw new CryptographicException ("outputBuffer", "Overflow");
119                         } else if (KeepLastBlock) {
120                                 if (0 > len + BlockSizeByte) {
121                                         throw new CryptographicException ("outputBuffer", "Overflow");
122                                 }
123                         } else {
124                                 if (0 > len) {
125                                         // there's a special case if this is the end of the decryption process
126                                         if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
127                                                 inputCount = outputBuffer.Length - outputOffset;
128                                         else
129                                                 throw new CryptographicException ("outputBuffer", "Overflow");
130                                 }
131                         }
132                         return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
133                 }
134
135                 private bool KeepLastBlock {
136                         get {
137                                 return ((!encrypt) && (padding != PaddingMode.None) && (padding != PaddingMode.Zeros));
138                         }
139                 }
140
141                 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
142                 {
143                         int offs = inputOffset;
144                         int full;
145
146                         // this way we don't do a modulo every time we're called
147                         // and we may save a division
148                         if (inputCount != BlockSizeByte) {
149                                 if ((inputCount % BlockSizeByte) != 0)
150                                         throw new CryptographicException ("Invalid input block size.");
151
152                                 full = inputCount / BlockSizeByte;
153                         } else
154                                 full = 1;
155
156                         if (KeepLastBlock)
157                                 full--;
158
159                         int total = 0;
160
161                         if (lastBlock) {
162                                 Transform (workBuff, 0, outputBuffer, outputOffset, BlockSizeByte);
163                                 outputOffset += BlockSizeByte;
164                                 total += BlockSizeByte;
165                                 lastBlock = false;
166                         }
167
168                         if (full > 0) {
169                                 int length = full * BlockSizeByte;
170                                 Transform (inputBuffer, offs, outputBuffer, outputOffset, length);
171                                 offs += length;
172                                 outputOffset += length;
173                                 total += length;
174                         }
175
176                         if (KeepLastBlock) {
177                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
178                                 lastBlock = true;
179                         }
180
181                         return total;
182                 }
183
184                 private void Random (byte[] buffer, int start, int length)
185                 {
186                         byte[] random = new byte [length];
187                         Cryptor.GetRandom (random);
188                         Buffer.BlockCopy (random, 0, buffer, start, length);
189                 }
190
191                 private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
192                 {
193                         string msg = String.Format ("Bad {0} padding.", padding);
194                         if (length >= 0)
195                                 msg += String.Format (" Invalid length {0}.", length);
196                         if (position >= 0)
197                                 msg += String.Format (" Error found at position {0}.", position);
198                         throw new CryptographicException (msg);
199                 }
200
201                 private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
202                 {
203                         // are there still full block to process ?
204                         int full = (inputCount / BlockSizeByte) * BlockSizeByte;
205                         int rem = inputCount - full;
206                         int total = full;
207
208                         switch (padding) {
209                         case PaddingMode.ANSIX923:
210                         case PaddingMode.ISO10126:
211                         case PaddingMode.PKCS7:
212                                 // we need to add an extra block for padding
213                                 total += BlockSizeByte;
214                                 break;
215                         default:
216                                 if (inputCount == 0)
217                                         return new byte [0];
218                                 if (rem != 0) {
219                                         if (padding == PaddingMode.None)
220                                                 throw new CryptographicException ("invalid block length");
221                                         // zero padding the input (by adding a block for the partial data)
222                                         byte[] paddedInput = new byte [full + BlockSizeByte];
223                                         Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
224                                         inputBuffer = paddedInput;
225                                         inputOffset = 0;
226                                         inputCount = paddedInput.Length;
227                                         total = inputCount;
228                                 }
229                                 break;
230                         }
231
232                         byte[] res = new byte [total];
233                         int outputOffset = 0;
234
235                         // process all blocks except the last (final) block
236                         if (total > BlockSizeByte) {
237                                 outputOffset = InternalTransformBlock (inputBuffer, inputOffset, total - BlockSizeByte, res, 0);
238                                 inputOffset += outputOffset;
239                         }
240
241                         // now we only have a single last block to encrypt
242                         byte pad = (byte) (BlockSizeByte - rem);
243                         switch (padding) {
244                         case PaddingMode.ANSIX923:
245                                 // XX 00 00 00 00 00 00 07 (zero + padding length)
246                                 res [res.Length - 1] = pad;
247                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
248                                 // the last padded block will be transformed in-place
249                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
250                                 break;
251                         case PaddingMode.ISO10126:
252                                 // XX 3F 52 2A 81 AB F7 07 (random + padding length)
253                                 Random (res, res.Length - pad, pad - 1);
254                                 res [res.Length - 1] = pad;
255                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
256                                 // the last padded block will be transformed in-place
257                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
258                                 break;
259                         case PaddingMode.PKCS7:
260                                 // XX 07 07 07 07 07 07 07 (padding length)
261                                 for (int i = res.Length; --i >= (res.Length - pad);) 
262                                         res [i] = pad;
263                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
264                                 // the last padded block will be transformed in-place
265                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
266                                 break;
267                         default:
268                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
269                                 break;
270                         }
271                         return res;
272                 }
273
274                 private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
275                 {
276                         if ((inputCount % BlockSizeByte) > 0)
277                                 throw new CryptographicException ("Invalid input block size.");
278
279                         int total = inputCount;
280                         if (lastBlock)
281                                 total += BlockSizeByte;
282
283                         byte[] res = new byte [total];
284                         int outputOffset = 0;
285
286                         if (inputCount > 0)
287                                 outputOffset = InternalTransformBlock (inputBuffer, inputOffset, inputCount, res, 0);
288
289                         if (lastBlock) {
290                                 Transform (workBuff, 0, res, outputOffset, BlockSizeByte);
291                                 outputOffset += BlockSizeByte;
292                                 lastBlock = false;
293                         }
294
295                         // total may be 0 (e.g. PaddingMode.None)
296                         byte pad = ((total > 0) ? res [total - 1] : (byte) 0);
297                         switch (padding) {
298                         case PaddingMode.ANSIX923:
299                                 if ((pad == 0) || (pad > BlockSizeByte))
300                                         ThrowBadPaddingException (padding, pad, -1);
301                                 for (int i = pad - 1; i > 0; i--) {
302                                         if (res [total - 1 - i] != 0x00)
303                                                 ThrowBadPaddingException (padding, -1, i);
304                                 }
305                                 total -= pad;
306                                 break;
307                         case PaddingMode.ISO10126:
308                                 if ((pad == 0) || (pad > BlockSizeByte))
309                                         ThrowBadPaddingException (padding, pad, -1);
310                                 total -= pad;
311                                 break;
312                         case PaddingMode.PKCS7:
313                                 if ((pad == 0) || (pad > BlockSizeByte))
314                                         ThrowBadPaddingException (padding, pad, -1);
315                                 for (int i = pad - 1; i > 0; i--) {
316                                         if (res [total - 1 - i] != pad)
317                                                 ThrowBadPaddingException (padding, -1, i);
318                                 }
319                                 total -= pad;
320                                 break;
321                         case PaddingMode.None:  // nothing to do - it's a multiple of block size
322                         case PaddingMode.Zeros: // nothing to do - user must unpad himself
323                                 break;
324                         }
325
326                         // return output without padding
327                         if (total > 0) {
328                                 byte[] data = new byte [total];
329                                 Buffer.BlockCopy (res, 0, data, 0, total);
330                                 // zeroize decrypted data (copy with padding)
331                                 Array.Clear (res, 0, res.Length);
332                                 return data;
333                         }
334                         else
335                                 return new byte [0];
336                 }
337
338                 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount) 
339                 {
340                         CheckInput (inputBuffer, inputOffset, inputCount);
341
342                         if (encrypt)
343                                 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
344                         else
345                                 return FinalDecrypt (inputBuffer, inputOffset, inputCount);
346                 }
347         }
348 }