1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Copyright (C) 2006 Novell, Inc (http://www.novell.com)
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 using System.Security.Cryptography;
31 using Mono.Security.Cryptography;
32 using M = Mono.Security.Cryptography;
34 namespace Mono.Security.Protocol.Tls
36 internal abstract class CipherSuite
40 public static byte[] EmptyArray = new byte[0];
48 private CipherAlgorithmType cipherAlgorithmType;
49 private HashAlgorithmType hashAlgorithmType;
50 private ExchangeAlgorithmType exchangeAlgorithmType;
51 private bool isExportable;
52 private CipherMode cipherMode;
53 private byte keyMaterialSize;
54 private int keyBlockSize;
55 private byte expandedKeyMaterialSize;
56 private short effectiveKeyBits;
58 private byte blockSize;
59 private Context context;
60 private SymmetricAlgorithm encryptionAlgorithm;
61 private ICryptoTransform encryptionCipher;
62 private SymmetricAlgorithm decryptionAlgorithm;
63 private ICryptoTransform decryptionCipher;
64 private KeyedHashAlgorithm clientHMAC;
65 private KeyedHashAlgorithm serverHMAC;
69 #region Protected Properties
71 protected ICryptoTransform EncryptionCipher
73 get { return this.encryptionCipher; }
76 protected ICryptoTransform DecryptionCipher
78 get { return this.decryptionCipher; }
81 protected KeyedHashAlgorithm ClientHMAC
83 get { return this.clientHMAC; }
86 protected KeyedHashAlgorithm ServerHMAC
88 get { return this.serverHMAC; }
95 public CipherAlgorithmType CipherAlgorithmType
97 get { return this.cipherAlgorithmType; }
100 public string HashAlgorithmName
104 switch (this.hashAlgorithmType)
106 case HashAlgorithmType.Md5:
109 case HashAlgorithmType.Sha1:
118 internal HashAlgorithm CreateHashAlgorithm ()
120 switch (hashAlgorithmType) {
121 case HashAlgorithmType.Md5:
122 return MD5.Create ();
123 case HashAlgorithmType.Sha1:
124 return SHA1.Create ();
130 public HashAlgorithmType HashAlgorithmType
132 get { return this.hashAlgorithmType; }
139 switch (this.hashAlgorithmType)
141 case HashAlgorithmType.Md5:
144 case HashAlgorithmType.Sha1:
153 public ExchangeAlgorithmType ExchangeAlgorithmType
155 get { return this.exchangeAlgorithmType; }
158 public CipherMode CipherMode
160 get { return this.cipherMode; }
165 get { return this.code; }
170 get { return this.name; }
173 public bool IsExportable
175 get { return this.isExportable; }
178 public byte KeyMaterialSize
180 get { return this.keyMaterialSize; }
183 public int KeyBlockSize
185 get { return this.keyBlockSize; }
188 public byte ExpandedKeyMaterialSize
190 get { return this.expandedKeyMaterialSize; }
193 public short EffectiveKeyBits
195 get { return this.effectiveKeyBits; }
200 get { return this.ivSize; }
204 public byte BlockSize
206 get { return this.blockSize; }
210 public Context Context
212 get { return this.context; }
215 this.context = value;
224 short code, string name, CipherAlgorithmType cipherAlgorithmType,
225 HashAlgorithmType hashAlgorithmType, ExchangeAlgorithmType exchangeAlgorithmType,
226 bool exportable, bool blockMode, byte keyMaterialSize,
227 byte expandedKeyMaterialSize, short effectiveKeyBits,
228 byte ivSize, byte blockSize)
232 this.cipherAlgorithmType = cipherAlgorithmType;
233 this.hashAlgorithmType = hashAlgorithmType;
234 this.exchangeAlgorithmType = exchangeAlgorithmType;
235 this.isExportable = exportable;
238 this.cipherMode = CipherMode.CBC;
240 this.keyMaterialSize = keyMaterialSize;
241 this.expandedKeyMaterialSize= expandedKeyMaterialSize;
242 this.effectiveKeyBits = effectiveKeyBits;
243 this.ivSize = ivSize;
244 this.blockSize = blockSize;
245 this.keyBlockSize = (this.keyMaterialSize + this.HashSize + this.ivSize) << 1;
252 internal void Write (byte[] array, int offset, short value)
254 if (offset > array.Length - 2)
255 throw new ArgumentException ("offset");
257 array [offset ] = (byte) (value >> 8);
258 array [offset + 1] = (byte) value;
261 internal void Write (byte[] array, int offset, ulong value)
263 if (offset > array.Length - 8)
264 throw new ArgumentException ("offset");
266 array [offset ] = (byte) (value >> 56);
267 array [offset + 1] = (byte) (value >> 48);
268 array [offset + 2] = (byte) (value >> 40);
269 array [offset + 3] = (byte) (value >> 32);
270 array [offset + 4] = (byte) (value >> 24);
271 array [offset + 5] = (byte) (value >> 16);
272 array [offset + 6] = (byte) (value >> 8);
273 array [offset + 7] = (byte) value;
276 public void InitializeCipher()
278 this.createEncryptionCipher();
279 this.createDecryptionCipher();
282 public byte[] EncryptRecord(byte[] fragment, byte[] mac)
284 // Encryption ( fragment + mac [+ padding + padding_length] )
285 int length = fragment.Length + mac.Length;
287 if (this.CipherMode == CipherMode.CBC) {
288 // Calculate padding_length
289 length++; // keep an extra byte
290 padlen = (this.blockSize - length % this.blockSize);
291 if (padlen == this.blockSize) {
297 byte[] plain = new byte [length];
298 Buffer.BlockCopy (fragment, 0, plain, 0, fragment.Length);
299 Buffer.BlockCopy (mac, 0, plain, fragment.Length, mac.Length);
301 int start = fragment.Length + mac.Length;
302 for (int i = start; i < (start + padlen + 1); i++) {
303 plain[i] = (byte)padlen;
307 this.EncryptionCipher.TransformBlock (plain, 0, plain.Length, plain, 0);
311 public void DecryptRecord(byte[] fragment, out byte[] dcrFragment, out byte[] dcrMAC)
313 int fragmentSize = 0;
314 int paddingLength = 0;
316 // Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
317 this.DecryptionCipher.TransformBlock(fragment, 0, fragment.Length, fragment, 0);
318 // optimization: decrypt "in place", worst case: padding will reduce the size of the data
319 // this will cut in half the memory allocations (dcrFragment and dcrMAC remains)
321 // Calculate fragment size
322 if (this.CipherMode == CipherMode.CBC)
324 // Calculate padding_length
325 paddingLength = fragment[fragment.Length - 1];
326 fragmentSize = (fragment.Length - (paddingLength + 1)) - this.HashSize;
330 fragmentSize = fragment.Length - this.HashSize;
333 dcrFragment = new byte[fragmentSize];
334 dcrMAC = new byte[HashSize];
336 Buffer.BlockCopy(fragment, 0, dcrFragment, 0, dcrFragment.Length);
337 Buffer.BlockCopy(fragment, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
342 #region Abstract Methods
344 public abstract byte[] ComputeClientRecordMAC(ContentType contentType, byte[] fragment);
346 public abstract byte[] ComputeServerRecordMAC(ContentType contentType, byte[] fragment);
348 public abstract void ComputeMasterSecret(byte[] preMasterSecret);
350 public abstract void ComputeKeys();
354 #region Key Generation Methods
356 public byte[] CreatePremasterSecret()
358 ClientContext context = (ClientContext)this.context;
360 // Generate random bytes (total size)
361 byte[] preMasterSecret = this.context.GetSecureRandomBytes (48);
362 // and replace the first two bytes with the protocol version
363 // (maximum support version not actual)
364 preMasterSecret [0] = (byte)(context.ClientHelloProtocol >> 8);
365 preMasterSecret [1] = (byte)context.ClientHelloProtocol;
367 return preMasterSecret;
370 public byte[] PRF(byte[] secret, string label, byte[] data, int length)
372 /* Secret Length calc exmplain from the RFC2246. Section 5
374 * S1 and S2 are the two halves of the secret and each is the same
375 * length. S1 is taken from the first half of the secret, S2 from the
376 * second half. Their length is created by rounding up the length of the
377 * overall secret divided by two; thus, if the original secret is an odd
378 * number of bytes long, the last byte of S1 will be the same as the
383 int secretLen = secret.Length >> 1;
385 if ((secret.Length & 0x1) == 0x1)
389 TlsStream seedStream = new TlsStream();
390 seedStream.Write(Encoding.ASCII.GetBytes(label));
391 seedStream.Write(data);
392 byte[] seed = seedStream.ToArray();
396 byte[] secret1 = new byte[secretLen];
397 Buffer.BlockCopy(secret, 0, secret1, 0, secretLen);
400 byte[] secret2 = new byte[secretLen];
401 Buffer.BlockCopy(secret, (secret.Length - secretLen), secret2, 0, secretLen);
403 // Secret 1 processing
404 byte[] p_md5 = Expand (MD5.Create (), secret1, seed, length);
406 // Secret 2 processing
407 byte[] p_sha = Expand (SHA1.Create (), secret2, seed, length);
409 // Perfor XOR of both results
410 byte[] masterSecret = new byte[length];
411 for (int i = 0; i < masterSecret.Length; i++)
413 masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
419 public byte[] Expand (HashAlgorithm hash, byte[] secret, byte[] seed, int length)
421 int hashLength = hash.HashSize / 8;
422 int iterations = (int)(length / hashLength);
423 if ((length % hashLength) > 0)
428 M.HMAC hmac = new M.HMAC (hash, secret);
429 TlsStream resMacs = new TlsStream();
431 byte[][] hmacs = new byte[iterations + 1][];
433 for (int i = 1; i <= iterations; i++)
435 TlsStream hcseed = new TlsStream();
436 hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length);
437 hmacs[i] = hmac.Hash;
438 hcseed.Write(hmacs[i]);
440 hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length);
441 resMacs.Write(hmac.Hash);
445 byte[] res = new byte[length];
447 Buffer.BlockCopy(resMacs.ToArray(), 0, res, 0, res.Length);
456 #region Private Methods
458 private void createEncryptionCipher()
460 // Create and configure the symmetric algorithm
461 switch (this.cipherAlgorithmType)
463 case CipherAlgorithmType.Des:
464 this.encryptionAlgorithm = DES.Create();
467 case CipherAlgorithmType.Rc2:
468 this.encryptionAlgorithm = RC2.Create();
471 case CipherAlgorithmType.Rc4:
472 this.encryptionAlgorithm = new ARC4Managed();
475 case CipherAlgorithmType.TripleDes:
476 this.encryptionAlgorithm = TripleDES.Create();
479 case CipherAlgorithmType.Rijndael:
480 #if MOBILE || NET_4_0
481 // only AES is really used - and we can use CommonCrypto for iOS and OSX this way
482 this.encryptionAlgorithm = Aes.Create();
484 this.encryptionAlgorithm = Rijndael.Create();
489 // If it's a block cipher
490 if (this.cipherMode == CipherMode.CBC)
492 // Configure encrypt algorithm
493 this.encryptionAlgorithm.Mode = this.cipherMode;
494 this.encryptionAlgorithm.Padding = PaddingMode.None;
495 this.encryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
496 this.encryptionAlgorithm.BlockSize = this.blockSize * 8;
499 // Set the key and IV for the algorithm
500 if (this.context is ClientContext)
502 this.encryptionAlgorithm.Key = this.context.ClientWriteKey;
503 this.encryptionAlgorithm.IV = this.context.ClientWriteIV;
507 this.encryptionAlgorithm.Key = this.context.ServerWriteKey;
508 this.encryptionAlgorithm.IV = this.context.ServerWriteIV;
511 // Create encryption cipher
512 this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
514 // Create the HMAC algorithm
515 if (this.context is ClientContext)
517 this.clientHMAC = new M.HMAC(
518 CreateHashAlgorithm (),
519 this.context.Negotiating.ClientWriteMAC);
523 this.serverHMAC = new M.HMAC(
524 CreateHashAlgorithm (),
525 this.context.Negotiating.ServerWriteMAC);
529 private void createDecryptionCipher()
531 // Create and configure the symmetric algorithm
532 switch (this.cipherAlgorithmType)
534 case CipherAlgorithmType.Des:
535 this.decryptionAlgorithm = DES.Create();
538 case CipherAlgorithmType.Rc2:
539 this.decryptionAlgorithm = RC2.Create();
542 case CipherAlgorithmType.Rc4:
543 this.decryptionAlgorithm = new ARC4Managed();
546 case CipherAlgorithmType.TripleDes:
547 this.decryptionAlgorithm = TripleDES.Create();
550 case CipherAlgorithmType.Rijndael:
551 #if MOBILE || NET_4_0
552 // only AES is really used - and we can use CommonCrypto for iOS and OSX this way
553 this.decryptionAlgorithm = Aes.Create();
555 this.decryptionAlgorithm = Rijndael.Create();
560 // If it's a block cipher
561 if (this.cipherMode == CipherMode.CBC)
563 // Configure encrypt algorithm
564 this.decryptionAlgorithm.Mode = this.cipherMode;
565 this.decryptionAlgorithm.Padding = PaddingMode.None;
566 this.decryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
567 this.decryptionAlgorithm.BlockSize = this.blockSize * 8;
570 // Set the key and IV for the algorithm
571 if (this.context is ClientContext)
573 this.decryptionAlgorithm.Key = this.context.ServerWriteKey;
574 this.decryptionAlgorithm.IV = this.context.ServerWriteIV;
578 this.decryptionAlgorithm.Key = this.context.ClientWriteKey;
579 this.decryptionAlgorithm.IV = this.context.ClientWriteIV;
582 // Create decryption cipher
583 this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
586 if (this.context is ClientContext)
588 this.serverHMAC = new M.HMAC(
589 CreateHashAlgorithm (),
590 this.context.Negotiating.ServerWriteMAC);
594 this.clientHMAC = new M.HMAC(
595 CreateHashAlgorithm (),
596 this.context.Negotiating.ClientWriteMAC);