1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
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 Mono
.Security
.X509
;
33 using M
= Mono
.Security
.Cryptography
;
35 namespace Mono
.Security
.Protocol
.Tls
37 internal abstract class CipherSuite
41 public static byte[] EmptyArray
= new byte[0];
49 private CipherAlgorithmType cipherAlgorithmType
;
50 private HashAlgorithmType hashAlgorithmType
;
51 private ExchangeAlgorithmType exchangeAlgorithmType
;
52 private bool isExportable
;
53 private CipherMode cipherMode
;
54 private byte keyMaterialSize
;
55 private int keyBlockSize
;
56 private byte expandedKeyMaterialSize
;
57 private short effectiveKeyBits
;
59 private byte blockSize
;
60 private Context context
;
61 private SymmetricAlgorithm encryptionAlgorithm
;
62 private ICryptoTransform encryptionCipher
;
63 private SymmetricAlgorithm decryptionAlgorithm
;
64 private ICryptoTransform decryptionCipher
;
65 private KeyedHashAlgorithm clientHMAC
;
66 private KeyedHashAlgorithm serverHMAC
;
70 #region Protected Properties
72 protected ICryptoTransform EncryptionCipher
74 get { return this.encryptionCipher; }
77 protected ICryptoTransform DecryptionCipher
79 get { return this.decryptionCipher; }
82 protected KeyedHashAlgorithm ClientHMAC
84 get { return this.clientHMAC; }
87 protected KeyedHashAlgorithm ServerHMAC
89 get { return this.serverHMAC; }
96 public CipherAlgorithmType CipherAlgorithmType
98 get { return this.cipherAlgorithmType; }
101 public string HashAlgorithmName
105 switch (this.hashAlgorithmType
)
107 case HashAlgorithmType
.Md5
:
110 case HashAlgorithmType
.Sha1
:
119 public HashAlgorithmType HashAlgorithmType
121 get { return this.hashAlgorithmType; }
128 switch (this.hashAlgorithmType
)
130 case HashAlgorithmType
.Md5
:
133 case HashAlgorithmType
.Sha1
:
142 public ExchangeAlgorithmType ExchangeAlgorithmType
144 get { return this.exchangeAlgorithmType; }
147 public CipherMode CipherMode
149 get { return this.cipherMode; }
154 get { return this.code; }
159 get { return this.name; }
162 public bool IsExportable
164 get { return this.isExportable; }
167 public byte KeyMaterialSize
169 get { return this.keyMaterialSize; }
172 public int KeyBlockSize
174 get { return this.keyBlockSize; }
177 public byte ExpandedKeyMaterialSize
179 get { return this.expandedKeyMaterialSize; }
182 public byte EffectiveKeyBits
184 get { return this.EffectiveKeyBits; }
189 get { return this.ivSize; }
193 public byte BlockSize
195 get { return this.blockSize; }
199 public Context Context
201 get { return this.context; }
204 this.context
= value;
213 short code
, string name
, CipherAlgorithmType cipherAlgorithmType
,
214 HashAlgorithmType hashAlgorithmType
, ExchangeAlgorithmType exchangeAlgorithmType
,
215 bool exportable
, bool blockMode
, byte keyMaterialSize
,
216 byte expandedKeyMaterialSize
, short effectiveKeyBytes
,
217 byte ivSize
, byte blockSize
)
221 this.cipherAlgorithmType
= cipherAlgorithmType
;
222 this.hashAlgorithmType
= hashAlgorithmType
;
223 this.exchangeAlgorithmType
= exchangeAlgorithmType
;
224 this.isExportable
= exportable
;
227 this.cipherMode
= CipherMode
.CBC
;
229 this.keyMaterialSize
= keyMaterialSize
;
230 this.expandedKeyMaterialSize
= expandedKeyMaterialSize
;
231 this.effectiveKeyBits
= effectiveKeyBits
;
232 this.ivSize
= ivSize
;
233 this.blockSize
= blockSize
;
234 this.keyBlockSize
= (this.keyMaterialSize
+ this.HashSize
+ this.ivSize
) << 1;
241 public void InitializeCipher()
243 this.createEncryptionCipher();
244 this.createDecryptionCipher();
247 public void UpdateClientCipherIV(byte[] iv
)
249 if (this.cipherMode
== CipherMode
.CBC
)
252 this.encryptionAlgorithm
.IV
= iv
;
254 // Create encryption cipher with the new IV
255 this.encryptionCipher
= this.encryptionAlgorithm
.CreateEncryptor();
260 public void UpdateServerCipherIV(byte[] iv)
262 if (this.cipherMode == CipherMode.CBC)
265 this.decryptionAlgorithm.IV = iv;
267 // Create encryption cipher with the new IV
268 this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
273 public byte[] EncryptRecord(byte[] fragment
, byte[] mac
)
275 // Encryption ( fragment + mac [+ padding + padding_length] )
276 MemoryStream ms
= new MemoryStream();
277 CryptoStream cs
= new CryptoStream(ms
, this.EncryptionCipher
, CryptoStreamMode
.Write
);
279 cs
.Write(fragment
, 0, fragment
.Length
);
280 cs
.Write(mac
, 0, mac
.Length
);
281 if (this.CipherMode
== CipherMode
.CBC
)
283 // Calculate padding_length
284 byte fragmentLength
= (byte)(fragment
.Length
+ mac
.Length
+ 1);
285 byte paddingLength
= (byte)(this.blockSize
- fragmentLength
% this.blockSize
);
286 if (paddingLength
== this.blockSize
)
291 // Write padding length byte
292 byte[] padding
= new byte[(paddingLength
+ 1)];
293 for (int i
= 0; i
< (paddingLength
+ 1); i
++)
295 padding
[i
] = paddingLength
;
298 cs
.Write(padding
, 0, padding
.Length
);
300 cs
.FlushFinalBlock();
306 public void DecryptRecord(byte[] fragment
, ref byte[] dcrFragment
, ref byte[] dcrMAC
)
308 int fragmentSize
= 0;
309 int paddingLength
= 0;
311 // Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
312 byte[] buffer
= new byte[fragment
.Length
];
313 this.DecryptionCipher
.TransformBlock(fragment
, 0, fragment
.Length
, buffer
, 0);
315 // Calculate fragment size
316 if (this.CipherMode
== CipherMode
.CBC
)
318 // Calculate padding_length
319 paddingLength
= buffer
[buffer
.Length
- 1];
320 fragmentSize
= (buffer
.Length
- (paddingLength
+ 1)) - this.HashSize
;
324 fragmentSize
= buffer
.Length
- this.HashSize
;
327 dcrFragment
= new byte[fragmentSize
];
328 dcrMAC
= new byte[HashSize
];
330 Buffer
.BlockCopy(buffer
, 0, dcrFragment
, 0, dcrFragment
.Length
);
331 Buffer
.BlockCopy(buffer
, dcrFragment
.Length
, dcrMAC
, 0, dcrMAC
.Length
);
336 #region Abstract Methods
338 public abstract byte[] ComputeClientRecordMAC(ContentType contentType
, byte[] fragment
);
340 public abstract byte[] ComputeServerRecordMAC(ContentType contentType
, byte[] fragment
);
342 public abstract void ComputeMasterSecret(byte[] preMasterSecret
);
344 public abstract void ComputeKeys();
348 #region Key Generation Methods
350 public byte[] CreatePremasterSecret()
352 TlsStream stream
= new TlsStream();
353 ClientContext context
= (ClientContext
)this.context
;
355 // Write protocol version
356 // We need to send here the protocol version used in
357 // the ClientHello message, that can be different than the actual
359 stream
.Write(context
.ClientHelloProtocol
);
361 // Generate random bytes
362 stream
.Write(this.context
.GetSecureRandomBytes(46));
364 byte[] preMasterSecret
= stream
.ToArray();
368 return preMasterSecret
;
371 public byte[] PRF(byte[] secret
, string label
, byte[] data
, int length
)
373 HashAlgorithm md5
= MD5
.Create();
374 HashAlgorithm sha1
= SHA1
.Create();
376 /* Secret Length calc exmplain from the RFC2246. Section 5
378 * S1 and S2 are the two halves of the secret and each is the same
379 * length. S1 is taken from the first half of the secret, S2 from the
380 * second half. Their length is created by rounding up the length of the
381 * overall secret divided by two; thus, if the original secret is an odd
382 * number of bytes long, the last byte of S1 will be the same as the
387 int secretLen
= secret
.Length
>> 1;
389 if ((secret
.Length
& 0x1) == 0x1)
393 TlsStream seedStream
= new TlsStream();
394 seedStream
.Write(Encoding
.ASCII
.GetBytes(label
));
395 seedStream
.Write(data
);
396 byte[] seed
= seedStream
.ToArray();
400 byte[] secret1
= new byte[secretLen
];
401 Buffer
.BlockCopy(secret
, 0, secret1
, 0, secretLen
);
404 byte[] secret2
= new byte[secretLen
];
405 Buffer
.BlockCopy(secret
, (secret
.Length
- secretLen
), secret2
, 0, secretLen
);
407 // Secret 1 processing
408 byte[] p_md5
= Expand("MD5", secret1
, seed
, length
);
410 // Secret 2 processing
411 byte[] p_sha
= Expand("SHA1", secret2
, seed
, length
);
413 // Perfor XOR of both results
414 byte[] masterSecret
= new byte[length
];
415 for (int i
= 0; i
< masterSecret
.Length
; i
++)
417 masterSecret
[i
] = (byte)(p_md5
[i
] ^ p_sha
[i
]);
423 public byte[] Expand(string hashName
, byte[] secret
, byte[] seed
, int length
)
425 int hashLength
= hashName
== "MD5" ? 16 : 20;
426 int iterations
= (int)(length
/ hashLength
);
427 if ((length
% hashLength
) > 0)
432 M
.HMAC hmac
= new M
.HMAC(hashName
, secret
);
433 TlsStream resMacs
= new TlsStream();
435 byte[][] hmacs
= new byte[iterations
+ 1][];
437 for (int i
= 1; i
<= iterations
; i
++)
439 TlsStream hcseed
= new TlsStream();
440 hmac
.TransformFinalBlock(hmacs
[i
-1], 0, hmacs
[i
-1].Length
);
441 hmacs
[i
] = hmac
.Hash
;
442 hcseed
.Write(hmacs
[i
]);
444 hmac
.TransformFinalBlock(hcseed
.ToArray(), 0, (int)hcseed
.Length
);
445 resMacs
.Write(hmac
.Hash
);
449 byte[] res
= new byte[length
];
451 Buffer
.BlockCopy(resMacs
.ToArray(), 0, res
, 0, res
.Length
);
460 #region Private Methods
462 private void createEncryptionCipher()
464 // Create and configure the symmetric algorithm
465 switch (this.cipherAlgorithmType
)
467 case CipherAlgorithmType
.Des
:
468 this.encryptionAlgorithm
= DES
.Create();
471 case CipherAlgorithmType
.Rc2
:
472 this.encryptionAlgorithm
= RC2
.Create();
475 case CipherAlgorithmType
.Rc4
:
476 this.encryptionAlgorithm
= new ARC4Managed();
479 case CipherAlgorithmType
.TripleDes
:
480 this.encryptionAlgorithm
= TripleDES
.Create();
483 case CipherAlgorithmType
.Rijndael
:
484 this.encryptionAlgorithm
= Rijndael
.Create();
488 // If it's a block cipher
489 if (this.cipherMode
== CipherMode
.CBC
)
491 // Configure encrypt algorithm
492 this.encryptionAlgorithm
.Mode
= this.cipherMode
;
493 this.encryptionAlgorithm
.Padding
= PaddingMode
.None
;
494 this.encryptionAlgorithm
.KeySize
= this.expandedKeyMaterialSize
* 8;
495 this.encryptionAlgorithm
.BlockSize
= this.blockSize
* 8;
498 // Set the key and IV for the algorithm
499 if (this.context
is ClientContext
)
501 this.encryptionAlgorithm
.Key
= this.context
.ClientWriteKey
;
502 this.encryptionAlgorithm
.IV
= this.context
.ClientWriteIV
;
506 this.encryptionAlgorithm
.Key
= this.context
.ServerWriteKey
;
507 this.encryptionAlgorithm
.IV
= this.context
.ServerWriteIV
;
510 // Create encryption cipher
511 this.encryptionCipher
= this.encryptionAlgorithm
.CreateEncryptor();
513 // Create the HMAC algorithm
514 if (this.context
is ClientContext
)
516 this.clientHMAC
= new M
.HMAC(
517 this.HashAlgorithmName
,
518 this.context
.ClientWriteMAC
);
522 this.serverHMAC
= new M
.HMAC(
523 this.HashAlgorithmName
,
524 this.context
.ServerWriteMAC
);
528 private void createDecryptionCipher()
530 // Create and configure the symmetric algorithm
531 switch (this.cipherAlgorithmType
)
533 case CipherAlgorithmType
.Des
:
534 this.decryptionAlgorithm
= DES
.Create();
537 case CipherAlgorithmType
.Rc2
:
538 this.decryptionAlgorithm
= RC2
.Create();
541 case CipherAlgorithmType
.Rc4
:
542 this.decryptionAlgorithm
= new ARC4Managed();
545 case CipherAlgorithmType
.TripleDes
:
546 this.decryptionAlgorithm
= TripleDES
.Create();
549 case CipherAlgorithmType
.Rijndael
:
550 this.decryptionAlgorithm
= Rijndael
.Create();
554 // If it's a block cipher
555 if (this.cipherMode
== CipherMode
.CBC
)
557 // Configure encrypt algorithm
558 this.decryptionAlgorithm
.Mode
= this.cipherMode
;
559 this.decryptionAlgorithm
.Padding
= PaddingMode
.None
;
560 this.decryptionAlgorithm
.KeySize
= this.expandedKeyMaterialSize
* 8;
561 this.decryptionAlgorithm
.BlockSize
= this.blockSize
* 8;
564 // Set the key and IV for the algorithm
565 if (this.context
is ClientContext
)
567 this.decryptionAlgorithm
.Key
= this.context
.ServerWriteKey
;
568 this.decryptionAlgorithm
.IV
= this.context
.ServerWriteIV
;
572 this.decryptionAlgorithm
.Key
= this.context
.ClientWriteKey
;
573 this.decryptionAlgorithm
.IV
= this.context
.ClientWriteIV
;
576 // Create decryption cipher
577 this.decryptionCipher
= this.decryptionAlgorithm
.CreateDecryptor();
580 if (this.context
is ClientContext
)
582 this.serverHMAC
= new M
.HMAC(
583 this.HashAlgorithmName
,
584 this.context
.ServerWriteMAC
);
588 this.clientHMAC
= new M
.HMAC(
589 this.HashAlgorithmName
,
590 this.context
.ClientWriteMAC
);