3c1f479b9d
Former-commit-id: 806294f5ded97629b74c85c09952f2a74fe182d9
595 lines
15 KiB
C#
595 lines
15 KiB
C#
// Transport Security Layer (TLS)
|
|
// Copyright (c) 2003-2004 Carlos Guzman Alvarez
|
|
// Copyright (C) 2006 Novell, Inc (http://www.novell.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Security.Cryptography;
|
|
|
|
using Mono.Security;
|
|
using Mono.Security.Cryptography;
|
|
using M = Mono.Security.Cryptography;
|
|
|
|
namespace Mono.Security.Protocol.Tls
|
|
{
|
|
internal abstract class CipherSuite
|
|
{
|
|
#region Static Fields
|
|
|
|
public static byte[] EmptyArray = new byte[0];
|
|
|
|
#endregion
|
|
|
|
#region Fields
|
|
|
|
private short code;
|
|
private string name;
|
|
private CipherAlgorithmType cipherAlgorithmType;
|
|
private HashAlgorithmType hashAlgorithmType;
|
|
private ExchangeAlgorithmType exchangeAlgorithmType;
|
|
private bool isExportable;
|
|
private CipherMode cipherMode;
|
|
private byte keyMaterialSize;
|
|
private int keyBlockSize;
|
|
private byte expandedKeyMaterialSize;
|
|
private short effectiveKeyBits;
|
|
private byte ivSize;
|
|
private byte blockSize;
|
|
private Context context;
|
|
private SymmetricAlgorithm encryptionAlgorithm;
|
|
private ICryptoTransform encryptionCipher;
|
|
private SymmetricAlgorithm decryptionAlgorithm;
|
|
private ICryptoTransform decryptionCipher;
|
|
private KeyedHashAlgorithm clientHMAC;
|
|
private KeyedHashAlgorithm serverHMAC;
|
|
|
|
#endregion
|
|
|
|
#region Protected Properties
|
|
|
|
protected ICryptoTransform EncryptionCipher
|
|
{
|
|
get { return this.encryptionCipher; }
|
|
}
|
|
|
|
protected ICryptoTransform DecryptionCipher
|
|
{
|
|
get { return this.decryptionCipher; }
|
|
}
|
|
|
|
protected KeyedHashAlgorithm ClientHMAC
|
|
{
|
|
get { return this.clientHMAC; }
|
|
}
|
|
|
|
protected KeyedHashAlgorithm ServerHMAC
|
|
{
|
|
get { return this.serverHMAC; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
public CipherAlgorithmType CipherAlgorithmType
|
|
{
|
|
get { return this.cipherAlgorithmType; }
|
|
}
|
|
|
|
public string HashAlgorithmName
|
|
{
|
|
get
|
|
{
|
|
switch (this.hashAlgorithmType)
|
|
{
|
|
case HashAlgorithmType.Md5:
|
|
return "MD5";
|
|
|
|
case HashAlgorithmType.Sha1:
|
|
return "SHA1";
|
|
|
|
default:
|
|
return "None";
|
|
}
|
|
}
|
|
}
|
|
|
|
internal HashAlgorithm CreateHashAlgorithm ()
|
|
{
|
|
switch (hashAlgorithmType) {
|
|
case HashAlgorithmType.Md5:
|
|
return MD5.Create ();
|
|
case HashAlgorithmType.Sha1:
|
|
return SHA1.Create ();
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public HashAlgorithmType HashAlgorithmType
|
|
{
|
|
get { return this.hashAlgorithmType; }
|
|
}
|
|
|
|
public int HashSize
|
|
{
|
|
get
|
|
{
|
|
switch (this.hashAlgorithmType)
|
|
{
|
|
case HashAlgorithmType.Md5:
|
|
return 16;
|
|
|
|
case HashAlgorithmType.Sha1:
|
|
return 20;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public ExchangeAlgorithmType ExchangeAlgorithmType
|
|
{
|
|
get { return this.exchangeAlgorithmType; }
|
|
}
|
|
|
|
public CipherMode CipherMode
|
|
{
|
|
get { return this.cipherMode; }
|
|
}
|
|
|
|
public short Code
|
|
{
|
|
get { return this.code; }
|
|
}
|
|
|
|
public string Name
|
|
{
|
|
get { return this.name; }
|
|
}
|
|
|
|
public bool IsExportable
|
|
{
|
|
get { return this.isExportable; }
|
|
}
|
|
|
|
public byte KeyMaterialSize
|
|
{
|
|
get { return this.keyMaterialSize; }
|
|
}
|
|
|
|
public int KeyBlockSize
|
|
{
|
|
get { return this.keyBlockSize; }
|
|
}
|
|
|
|
public byte ExpandedKeyMaterialSize
|
|
{
|
|
get { return this.expandedKeyMaterialSize; }
|
|
}
|
|
|
|
public short EffectiveKeyBits
|
|
{
|
|
get { return this.effectiveKeyBits; }
|
|
}
|
|
|
|
public byte IvSize
|
|
{
|
|
get { return this.ivSize; }
|
|
}
|
|
|
|
/*
|
|
public byte BlockSize
|
|
{
|
|
get { return this.blockSize; }
|
|
}
|
|
*/
|
|
|
|
public Context Context
|
|
{
|
|
get { return this.context; }
|
|
set
|
|
{
|
|
this.context = value;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
public CipherSuite(
|
|
short code, string name, CipherAlgorithmType cipherAlgorithmType,
|
|
HashAlgorithmType hashAlgorithmType, ExchangeAlgorithmType exchangeAlgorithmType,
|
|
bool exportable, bool blockMode, byte keyMaterialSize,
|
|
byte expandedKeyMaterialSize, short effectiveKeyBits,
|
|
byte ivSize, byte blockSize)
|
|
{
|
|
this.code = code;
|
|
this.name = name;
|
|
this.cipherAlgorithmType = cipherAlgorithmType;
|
|
this.hashAlgorithmType = hashAlgorithmType;
|
|
this.exchangeAlgorithmType = exchangeAlgorithmType;
|
|
this.isExportable = exportable;
|
|
if (blockMode)
|
|
{
|
|
this.cipherMode = CipherMode.CBC;
|
|
}
|
|
this.keyMaterialSize = keyMaterialSize;
|
|
this.expandedKeyMaterialSize= expandedKeyMaterialSize;
|
|
this.effectiveKeyBits = effectiveKeyBits;
|
|
this.ivSize = ivSize;
|
|
this.blockSize = blockSize;
|
|
this.keyBlockSize = (this.keyMaterialSize + this.HashSize + this.ivSize) << 1;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
internal void Write (byte[] array, int offset, short value)
|
|
{
|
|
if (offset > array.Length - 2)
|
|
throw new ArgumentException ("offset");
|
|
|
|
array [offset ] = (byte) (value >> 8);
|
|
array [offset + 1] = (byte) value;
|
|
}
|
|
|
|
internal void Write (byte[] array, int offset, ulong value)
|
|
{
|
|
if (offset > array.Length - 8)
|
|
throw new ArgumentException ("offset");
|
|
|
|
array [offset ] = (byte) (value >> 56);
|
|
array [offset + 1] = (byte) (value >> 48);
|
|
array [offset + 2] = (byte) (value >> 40);
|
|
array [offset + 3] = (byte) (value >> 32);
|
|
array [offset + 4] = (byte) (value >> 24);
|
|
array [offset + 5] = (byte) (value >> 16);
|
|
array [offset + 6] = (byte) (value >> 8);
|
|
array [offset + 7] = (byte) value;
|
|
}
|
|
|
|
public void InitializeCipher()
|
|
{
|
|
this.createEncryptionCipher();
|
|
this.createDecryptionCipher();
|
|
}
|
|
|
|
public byte[] EncryptRecord(byte[] fragment, byte[] mac)
|
|
{
|
|
// Encryption ( fragment + mac [+ padding + padding_length] )
|
|
int length = fragment.Length + mac.Length;
|
|
int padlen = 0;
|
|
if (this.CipherMode == CipherMode.CBC) {
|
|
// Calculate padding_length
|
|
length++; // keep an extra byte
|
|
padlen = (this.blockSize - length % this.blockSize);
|
|
if (padlen == this.blockSize) {
|
|
padlen = 0;
|
|
}
|
|
length += padlen;
|
|
}
|
|
|
|
byte[] plain = new byte [length];
|
|
Buffer.BlockCopy (fragment, 0, plain, 0, fragment.Length);
|
|
Buffer.BlockCopy (mac, 0, plain, fragment.Length, mac.Length);
|
|
if (padlen > 0) {
|
|
int start = fragment.Length + mac.Length;
|
|
for (int i = start; i < (start + padlen + 1); i++) {
|
|
plain[i] = (byte)padlen;
|
|
}
|
|
}
|
|
|
|
this.EncryptionCipher.TransformBlock (plain, 0, plain.Length, plain, 0);
|
|
return plain;
|
|
}
|
|
|
|
public void DecryptRecord(byte[] fragment, out byte[] dcrFragment, out byte[] dcrMAC)
|
|
{
|
|
int fragmentSize = 0;
|
|
int paddingLength = 0;
|
|
|
|
// Decrypt message fragment ( fragment + mac [+ padding + padding_length] )
|
|
this.DecryptionCipher.TransformBlock(fragment, 0, fragment.Length, fragment, 0);
|
|
// optimization: decrypt "in place", worst case: padding will reduce the size of the data
|
|
// this will cut in half the memory allocations (dcrFragment and dcrMAC remains)
|
|
|
|
// Calculate fragment size
|
|
if (this.CipherMode == CipherMode.CBC)
|
|
{
|
|
// Calculate padding_length
|
|
paddingLength = fragment[fragment.Length - 1];
|
|
fragmentSize = (fragment.Length - (paddingLength + 1)) - this.HashSize;
|
|
}
|
|
else
|
|
{
|
|
fragmentSize = fragment.Length - this.HashSize;
|
|
}
|
|
|
|
dcrFragment = new byte[fragmentSize];
|
|
dcrMAC = new byte[HashSize];
|
|
|
|
Buffer.BlockCopy(fragment, 0, dcrFragment, 0, dcrFragment.Length);
|
|
Buffer.BlockCopy(fragment, dcrFragment.Length, dcrMAC, 0, dcrMAC.Length);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Abstract Methods
|
|
|
|
public abstract byte[] ComputeClientRecordMAC(ContentType contentType, byte[] fragment);
|
|
|
|
public abstract byte[] ComputeServerRecordMAC(ContentType contentType, byte[] fragment);
|
|
|
|
public abstract void ComputeMasterSecret(byte[] preMasterSecret);
|
|
|
|
public abstract void ComputeKeys();
|
|
|
|
#endregion
|
|
|
|
#region Key Generation Methods
|
|
|
|
public byte[] CreatePremasterSecret()
|
|
{
|
|
ClientContext context = (ClientContext)this.context;
|
|
|
|
// Generate random bytes (total size)
|
|
byte[] preMasterSecret = this.context.GetSecureRandomBytes (48);
|
|
// and replace the first two bytes with the protocol version
|
|
// (maximum support version not actual)
|
|
preMasterSecret [0] = (byte)(context.ClientHelloProtocol >> 8);
|
|
preMasterSecret [1] = (byte)context.ClientHelloProtocol;
|
|
|
|
return preMasterSecret;
|
|
}
|
|
|
|
public byte[] PRF(byte[] secret, string label, byte[] data, int length)
|
|
{
|
|
/* Secret Length calc exmplain from the RFC2246. Section 5
|
|
*
|
|
* S1 and S2 are the two halves of the secret and each is the same
|
|
* length. S1 is taken from the first half of the secret, S2 from the
|
|
* second half. Their length is created by rounding up the length of the
|
|
* overall secret divided by two; thus, if the original secret is an odd
|
|
* number of bytes long, the last byte of S1 will be the same as the
|
|
* first byte of S2.
|
|
*/
|
|
|
|
// split secret in 2
|
|
int secretLen = secret.Length >> 1;
|
|
// rounding up
|
|
if ((secret.Length & 0x1) == 0x1)
|
|
secretLen++;
|
|
|
|
// Seed
|
|
TlsStream seedStream = new TlsStream();
|
|
seedStream.Write(Encoding.ASCII.GetBytes(label));
|
|
seedStream.Write(data);
|
|
byte[] seed = seedStream.ToArray();
|
|
seedStream.Reset();
|
|
|
|
// Secret 1
|
|
byte[] secret1 = new byte[secretLen];
|
|
Buffer.BlockCopy(secret, 0, secret1, 0, secretLen);
|
|
|
|
// Secret2
|
|
byte[] secret2 = new byte[secretLen];
|
|
Buffer.BlockCopy(secret, (secret.Length - secretLen), secret2, 0, secretLen);
|
|
|
|
// Secret 1 processing
|
|
byte[] p_md5 = Expand (MD5.Create (), secret1, seed, length);
|
|
|
|
// Secret 2 processing
|
|
byte[] p_sha = Expand (SHA1.Create (), secret2, seed, length);
|
|
|
|
// Perfor XOR of both results
|
|
byte[] masterSecret = new byte[length];
|
|
for (int i = 0; i < masterSecret.Length; i++)
|
|
{
|
|
masterSecret[i] = (byte)(p_md5[i] ^ p_sha[i]);
|
|
}
|
|
|
|
return masterSecret;
|
|
}
|
|
|
|
public byte[] Expand (HashAlgorithm hash, byte[] secret, byte[] seed, int length)
|
|
{
|
|
int hashLength = hash.HashSize / 8;
|
|
int iterations = (int)(length / hashLength);
|
|
if ((length % hashLength) > 0)
|
|
{
|
|
iterations++;
|
|
}
|
|
|
|
M.HMAC hmac = new M.HMAC (hash, secret);
|
|
TlsStream resMacs = new TlsStream();
|
|
|
|
byte[][] hmacs = new byte[iterations + 1][];
|
|
hmacs[0] = seed;
|
|
for (int i = 1; i <= iterations; i++)
|
|
{
|
|
TlsStream hcseed = new TlsStream();
|
|
hmac.TransformFinalBlock(hmacs[i-1], 0, hmacs[i-1].Length);
|
|
hmacs[i] = hmac.Hash;
|
|
hcseed.Write(hmacs[i]);
|
|
hcseed.Write(seed);
|
|
hmac.TransformFinalBlock(hcseed.ToArray(), 0, (int)hcseed.Length);
|
|
resMacs.Write(hmac.Hash);
|
|
hcseed.Reset();
|
|
}
|
|
|
|
byte[] res = new byte[length];
|
|
|
|
Buffer.BlockCopy(resMacs.ToArray(), 0, res, 0, res.Length);
|
|
|
|
resMacs.Reset();
|
|
|
|
return res;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private void createEncryptionCipher()
|
|
{
|
|
// Create and configure the symmetric algorithm
|
|
switch (this.cipherAlgorithmType)
|
|
{
|
|
case CipherAlgorithmType.Des:
|
|
this.encryptionAlgorithm = DES.Create();
|
|
break;
|
|
|
|
case CipherAlgorithmType.Rc2:
|
|
this.encryptionAlgorithm = RC2.Create();
|
|
break;
|
|
|
|
case CipherAlgorithmType.Rc4:
|
|
this.encryptionAlgorithm = new ARC4Managed();
|
|
break;
|
|
|
|
case CipherAlgorithmType.TripleDes:
|
|
this.encryptionAlgorithm = TripleDES.Create();
|
|
break;
|
|
|
|
case CipherAlgorithmType.Rijndael:
|
|
// only AES is really used - and we can use CommonCrypto for iOS and OSX this way
|
|
this.encryptionAlgorithm = Aes.Create();
|
|
break;
|
|
}
|
|
|
|
// If it's a block cipher
|
|
if (this.cipherMode == CipherMode.CBC)
|
|
{
|
|
// Configure encrypt algorithm
|
|
this.encryptionAlgorithm.Mode = this.cipherMode;
|
|
this.encryptionAlgorithm.Padding = PaddingMode.None;
|
|
this.encryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
|
|
this.encryptionAlgorithm.BlockSize = this.blockSize * 8;
|
|
}
|
|
|
|
// Set the key and IV for the algorithm
|
|
if (this.context is ClientContext)
|
|
{
|
|
this.encryptionAlgorithm.Key = this.context.ClientWriteKey;
|
|
this.encryptionAlgorithm.IV = this.context.ClientWriteIV;
|
|
}
|
|
else
|
|
{
|
|
this.encryptionAlgorithm.Key = this.context.ServerWriteKey;
|
|
this.encryptionAlgorithm.IV = this.context.ServerWriteIV;
|
|
}
|
|
|
|
// Create encryption cipher
|
|
this.encryptionCipher = this.encryptionAlgorithm.CreateEncryptor();
|
|
|
|
// Create the HMAC algorithm
|
|
if (this.context is ClientContext)
|
|
{
|
|
this.clientHMAC = new M.HMAC(
|
|
CreateHashAlgorithm (),
|
|
this.context.Negotiating.ClientWriteMAC);
|
|
}
|
|
else
|
|
{
|
|
this.serverHMAC = new M.HMAC(
|
|
CreateHashAlgorithm (),
|
|
this.context.Negotiating.ServerWriteMAC);
|
|
}
|
|
}
|
|
|
|
private void createDecryptionCipher()
|
|
{
|
|
// Create and configure the symmetric algorithm
|
|
switch (this.cipherAlgorithmType)
|
|
{
|
|
case CipherAlgorithmType.Des:
|
|
this.decryptionAlgorithm = DES.Create();
|
|
break;
|
|
|
|
case CipherAlgorithmType.Rc2:
|
|
this.decryptionAlgorithm = RC2.Create();
|
|
break;
|
|
|
|
case CipherAlgorithmType.Rc4:
|
|
this.decryptionAlgorithm = new ARC4Managed();
|
|
break;
|
|
|
|
case CipherAlgorithmType.TripleDes:
|
|
this.decryptionAlgorithm = TripleDES.Create();
|
|
break;
|
|
|
|
case CipherAlgorithmType.Rijndael:
|
|
// only AES is really used - and we can use CommonCrypto for iOS and OSX this way
|
|
this.decryptionAlgorithm = Aes.Create();
|
|
break;
|
|
}
|
|
|
|
// If it's a block cipher
|
|
if (this.cipherMode == CipherMode.CBC)
|
|
{
|
|
// Configure encrypt algorithm
|
|
this.decryptionAlgorithm.Mode = this.cipherMode;
|
|
this.decryptionAlgorithm.Padding = PaddingMode.None;
|
|
this.decryptionAlgorithm.KeySize = this.expandedKeyMaterialSize * 8;
|
|
this.decryptionAlgorithm.BlockSize = this.blockSize * 8;
|
|
}
|
|
|
|
// Set the key and IV for the algorithm
|
|
if (this.context is ClientContext)
|
|
{
|
|
this.decryptionAlgorithm.Key = this.context.ServerWriteKey;
|
|
this.decryptionAlgorithm.IV = this.context.ServerWriteIV;
|
|
}
|
|
else
|
|
{
|
|
this.decryptionAlgorithm.Key = this.context.ClientWriteKey;
|
|
this.decryptionAlgorithm.IV = this.context.ClientWriteIV;
|
|
}
|
|
|
|
// Create decryption cipher
|
|
this.decryptionCipher = this.decryptionAlgorithm.CreateDecryptor();
|
|
|
|
// Create the HMAC
|
|
if (this.context is ClientContext)
|
|
{
|
|
this.serverHMAC = new M.HMAC(
|
|
CreateHashAlgorithm (),
|
|
this.context.Negotiating.ServerWriteMAC);
|
|
}
|
|
else
|
|
{
|
|
this.clientHMAC = new M.HMAC(
|
|
CreateHashAlgorithm (),
|
|
this.context.Negotiating.ClientWriteMAC);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|