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 public HashAlgorithmType HashAlgorithmType
120 get { return this.hashAlgorithmType; }
127 switch (this.hashAlgorithmType)
129 case HashAlgorithmType.Md5:
132 case HashAlgorithmType.Sha1:
141 public ExchangeAlgorithmType ExchangeAlgorithmType
143 get { return this.exchangeAlgorithmType; }
146 public CipherMode CipherMode
148 get { return this.cipherMode; }
153 get { return this.code; }
158 get { return this.name; }
161 public bool IsExportable
163 get { return this.isExportable; }
166 public byte KeyMaterialSize
168 get { return this.keyMaterialSize; }
171 public int KeyBlockSize
173 get { return this.keyBlockSize; }
176 public byte ExpandedKeyMaterialSize
178 get { return this.expandedKeyMaterialSize; }
181 public short EffectiveKeyBits
183 get { return this.effectiveKeyBits; }
188 get { return this.ivSize; }
192 public byte BlockSize
194 get { return this.blockSize; }
198 public Context Context
200 get { return this.context; }
203 this.context = value;
212 short code, string name, CipherAlgorithmType cipherAlgorithmType,
213 HashAlgorithmType hashAlgorithmType, ExchangeAlgorithmType exchangeAlgorithmType,
214 bool exportable, bool blockMode, byte keyMaterialSize,
215 byte expandedKeyMaterialSize, short effectiveKeyBits,
216 byte ivSize, byte blockSize)
220 this.cipherAlgorithmType = cipherAlgorithmType;
221 this.hashAlgorithmType = hashAlgorithmType;
222 this.exchangeAlgorithmType = exchangeAlgorithmType;
223 this.isExportable = exportable;
226 this.cipherMode = CipherMode.CBC;
228 this.keyMaterialSize = keyMaterialSize;
229 this.expandedKeyMaterialSize= expandedKeyMaterialSize;
230 this.effectiveKeyBits = effectiveKeyBits;
231 this.ivSize = ivSize;
232 this.blockSize = blockSize;
233 this.keyBlockSize = (this.keyMaterialSize + this.HashSize + this.ivSize) << 1;
240 internal void Write (byte[] array, int offset, short value)
242 if (offset > array.Length - 2)
243 throw new ArgumentException ("offset");
245 array [offset ] = (byte) (value >> 8);
246 array [offset + 1] = (byte) value;
249 internal void Write (byte[] array, int offset, ulong value)
251 if (offset > array.Length - 8)
252 throw new ArgumentException ("offset");
254 array [offset ] = (byte) (value >> 56);
255 array [offset + 1] = (byte) (value >> 48);
256 array [offset + 2] = (byte) (value >> 40);
257 array [offset + 3] = (byte) (value >> 32);
258 array [offset + 4] = (byte) (value >> 24);
259 array [offset + 5] = (byte) (value >> 16);
260 array [offset + 6] = (byte) (value >> 8);
261 array [offset + 7] = (byte) value;
264 public void InitializeCipher()
266 this.createEncryptionCipher();
267 this.createDecryptionCipher();
270 public byte[] EncryptRecord(byte[] fragment, byte[] mac)
272 // Encryption ( fragment + mac [+ padding + padding_length] )
273 int length = fragment.Length + mac.Length;
275 if (this.CipherMode == CipherMode.CBC) {
276 // Calculate padding_length
277 length++; // keep an extra byte
278 padlen = (this.blockSize - length % this.blockSize);
279 if (padlen == this.blockSize) {
285 byte[] plain = new byte [length];
286 Buffer.BlockCopy (fragment, 0, plain, 0, fragment.Length);
287 Buffer.BlockCopy (mac, 0, plain, fragment.Length, mac.Length);
289 int start = fragment.Length + mac.Length;
290 for (int i = start; i < (start + padlen + 1); i++) {
291 plain[i] = (byte)padlen;
295 this.EncryptionCipher.TransformBlock (plain, 0, plain.Length, plain, 0);
299 public void DecryptRecord(byte[] fragment, out byte[] dcrFragment, out byte[] dcrMAC)
301 int fragmentSize = 0;
302 int paddingLength = 0;
304 // Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
305 this.DecryptionCipher.TransformBlock(fragment, 0, fragment.Length, fragment, 0);
306 // optimization: decrypt "in place", worst case: padding will reduce the size of the data
307 // this will cut in half the memory allocations (dcrFragment and dcrMAC remains)
309 // Calculate fragment size
310 if (this.CipherMode == CipherMode.CBC)
312 // Calculate padding_length
313 paddingLength = fragment[fragment.Length - 1];
314 fragmentSize = (fragment.Length - (paddingLength + 1)) - this.HashSize;
318 fragmentSize = fragment.Length - this.HashSize;
321 dcrFragment = new byte[fragmentSize];
322 dcrMAC = new byte[HashSize];
324 Buffer.BlockCopy(fragment, 0, dcrFragment, 0, dcrFragment.Length);
325 Buffer.BlockCopy(fragment, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
330 #region Abstract Methods
332 public abstract byte[] ComputeClientRecordMAC(ContentType contentType, byte[] fragment);
334 public abstract byte[] ComputeServerRecordMAC(ContentType contentType, byte[] fragment);
336 public abstract void ComputeMasterSecret(byte[] preMasterSecret);
338 public abstract void ComputeKeys();
342 #region Key Generation Methods
344 public byte[] CreatePremasterSecret()
346 ClientContext context = (ClientContext)this.context;
348 // Generate random bytes (total size)
349 byte[] preMasterSecret = this.context.GetSecureRandomBytes (48);
350 // and replace the first two bytes with the protocol version
351 // (maximum support version not actual)
352 preMasterSecret [0] = (byte)(context.ClientHelloProtocol >> 8);
353 preMasterSecret [1] = (byte)context.ClientHelloProtocol;
355 return preMasterSecret;
358 public byte[] PRF(byte[] secret, string label, byte[] data, int length)
360 /* Secret Length calc exmplain from the RFC2246. Section 5
362 * S1 and S2 are the two halves of the secret and each is the same
363 * length. S1 is taken from the first half of the secret, S2 from the
364 * second half. Their length is created by rounding up the length of the
365 * overall secret divided by two; thus, if the original secret is an odd
366 * number of bytes long, the last byte of S1 will be the same as the
371 int secretLen = secret.Length >> 1;
373 if ((secret.Length & 0x1) == 0x1)
377 TlsStream seedStream = new TlsStream();
378 seedStream.Write(Encoding.ASCII.GetBytes(label));
379 seedStream.Write(data);
380 byte[] seed = seedStream.ToArray();
384 byte[] secret1 = new byte[secretLen];
385 Buffer.BlockCopy(secret, 0, secret1, 0, secretLen);
388 byte[] secret2 = new byte[secretLen];
389 Buffer.BlockCopy(secret, (secret.Length - secretLen), secret2, 0, secretLen);
391 // Secret 1 processing
392 byte[] p_md5 = Expand("MD5", secret1, seed, length);
394 // Secret 2 processing
395 byte[] p_sha = Expand("SHA1", secret2, seed, length);
397 // Perfor XOR of both results
398 byte[] masterSecret = new byte[length];
399 for (int i = 0; i < masterSecret.Length; i++)
401 masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
407 public byte[] Expand(string hashName, byte[] secret, byte[] seed, int length)
409 int hashLength = hashName == "MD5" ? 16 : 20;
410 int iterations = (int)(length / hashLength);
411 if ((length % hashLength) > 0)
416 M.HMAC hmac = new M.HMAC(hashName, secret);
417 TlsStream resMacs = new TlsStream();
419 byte[][] hmacs = new byte[iterations + 1][];
421 for (int i = 1; i <= iterations; i++)
423 TlsStream hcseed = new TlsStream();
424 hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length);
425 hmacs[i] = hmac.Hash;
426 hcseed.Write(hmacs[i]);
428 hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length);
429 resMacs.Write(hmac.Hash);
433 byte[] res = new byte[length];
435 Buffer.BlockCopy(resMacs.ToArray(), 0, res, 0, res.Length);
444 #region Private Methods
446 private void createEncryptionCipher()
448 // Create and configure the symmetric algorithm
449 switch (this.cipherAlgorithmType)
451 case CipherAlgorithmType.Des:
452 this.encryptionAlgorithm = DES.Create();
455 case CipherAlgorithmType.Rc2:
456 this.encryptionAlgorithm = RC2.Create();
459 case CipherAlgorithmType.Rc4:
460 this.encryptionAlgorithm = new ARC4Managed();
463 case CipherAlgorithmType.TripleDes:
464 this.encryptionAlgorithm = TripleDES.Create();
467 case CipherAlgorithmType.Rijndael:
468 this.encryptionAlgorithm = Rijndael.Create();
472 // If it's a block cipher
473 if (this.cipherMode == CipherMode.CBC)
475 // Configure encrypt algorithm
476 this.encryptionAlgorithm.Mode = this.cipherMode;
477 this.encryptionAlgorithm.Padding = PaddingMode.None;
478 this.encryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
479 this.encryptionAlgorithm.BlockSize = this.blockSize * 8;
482 // Set the key and IV for the algorithm
483 if (this.context is ClientContext)
485 this.encryptionAlgorithm.Key = this.context.ClientWriteKey;
486 this.encryptionAlgorithm.IV = this.context.ClientWriteIV;
490 this.encryptionAlgorithm.Key = this.context.ServerWriteKey;
491 this.encryptionAlgorithm.IV = this.context.ServerWriteIV;
494 // Create encryption cipher
495 this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
497 // Create the HMAC algorithm
498 if (this.context is ClientContext)
500 this.clientHMAC = new M.HMAC(
501 this.HashAlgorithmName,
502 this.context.Negotiating.ClientWriteMAC);
506 this.serverHMAC = new M.HMAC(
507 this.HashAlgorithmName,
508 this.context.Negotiating.ServerWriteMAC);
512 private void createDecryptionCipher()
514 // Create and configure the symmetric algorithm
515 switch (this.cipherAlgorithmType)
517 case CipherAlgorithmType.Des:
518 this.decryptionAlgorithm = DES.Create();
521 case CipherAlgorithmType.Rc2:
522 this.decryptionAlgorithm = RC2.Create();
525 case CipherAlgorithmType.Rc4:
526 this.decryptionAlgorithm = new ARC4Managed();
529 case CipherAlgorithmType.TripleDes:
530 this.decryptionAlgorithm = TripleDES.Create();
533 case CipherAlgorithmType.Rijndael:
534 this.decryptionAlgorithm = Rijndael.Create();
538 // If it's a block cipher
539 if (this.cipherMode == CipherMode.CBC)
541 // Configure encrypt algorithm
542 this.decryptionAlgorithm.Mode = this.cipherMode;
543 this.decryptionAlgorithm.Padding = PaddingMode.None;
544 this.decryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
545 this.decryptionAlgorithm.BlockSize = this.blockSize * 8;
548 // Set the key and IV for the algorithm
549 if (this.context is ClientContext)
551 this.decryptionAlgorithm.Key = this.context.ServerWriteKey;
552 this.decryptionAlgorithm.IV = this.context.ServerWriteIV;
556 this.decryptionAlgorithm.Key = this.context.ClientWriteKey;
557 this.decryptionAlgorithm.IV = this.context.ClientWriteIV;
560 // Create decryption cipher
561 this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
564 if (this.context is ClientContext)
566 this.serverHMAC = new M.HMAC(
567 this.HashAlgorithmName,
568 this.context.Negotiating.ServerWriteMAC);
572 this.clientHMAC = new M.HMAC(
573 this.HashAlgorithmName,
574 this.context.Negotiating.ClientWriteMAC);