diff options
Diffstat (limited to 'Crypto/src/crypto/tls')
67 files changed, 5934 insertions, 0 deletions
diff --git a/Crypto/src/crypto/tls/AlertDescription.cs b/Crypto/src/crypto/tls/AlertDescription.cs new file mode 100644 index 000000000..e1229a4a3 --- /dev/null +++ b/Crypto/src/crypto/tls/AlertDescription.cs @@ -0,0 +1,47 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 2246 7.2 + /// </summary> + public enum AlertDescription : byte + { + close_notify = 0, + unexpected_message = 10, + bad_record_mac = 20, + decryption_failed = 21, + record_overflow = 22, + decompression_failure = 30, + handshake_failure = 40, + /* 41 is not defined, for historical reasons */ + bad_certificate = 42, + unsupported_certificate = 43, + certificate_revoked = 44, + certificate_expired = 45, + certificate_unknown = 46, + illegal_parameter = 47, + unknown_ca = 48, + access_denied = 49, + decode_error = 50, + decrypt_error = 51, + export_restriction = 60, + protocol_version = 70, + insufficient_security = 71, + internal_error = 80, + user_canceled = 90, + no_renegotiation = 100, + + /* + * RFC 3546 + */ + unsupported_extension = 110, + certificate_unobtainable = 111, + unrecognized_name = 112, + bad_certificate_status_response = 113, + bad_certificate_hash_value = 114, + + /* + * RFC 4279 + */ + unknown_psk_identity = 115, + } +} diff --git a/Crypto/src/crypto/tls/AlertLevel.cs b/Crypto/src/crypto/tls/AlertLevel.cs new file mode 100644 index 000000000..afb04308b --- /dev/null +++ b/Crypto/src/crypto/tls/AlertLevel.cs @@ -0,0 +1,11 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 2246 7.2 + /// </summary> + public enum AlertLevel : byte + { + warning = 1, + fatal = 2, + } +} diff --git a/Crypto/src/crypto/tls/AlwaysValidVerifyer.cs b/Crypto/src/crypto/tls/AlwaysValidVerifyer.cs new file mode 100644 index 000000000..e26c6fc3f --- /dev/null +++ b/Crypto/src/crypto/tls/AlwaysValidVerifyer.cs @@ -0,0 +1,24 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <remarks> + /// A certificate verifyer, that will always return true. + /// <pre> + /// DO NOT USE THIS FILE UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING. + /// </pre> + /// </remarks> + [Obsolete("Perform certificate verification in TlsAuthentication implementation")] + public class AlwaysValidVerifyer + : ICertificateVerifyer + { + /// <summary>Return true.</summary> + public bool IsValid( + X509CertificateStructure[] certs) + { + return true; + } + } +} diff --git a/Crypto/src/crypto/tls/ByteQueue.cs b/Crypto/src/crypto/tls/ByteQueue.cs new file mode 100644 index 000000000..96062402b --- /dev/null +++ b/Crypto/src/crypto/tls/ByteQueue.cs @@ -0,0 +1,125 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <remarks> + /// A queue for bytes. + /// <p> + /// This file could be more optimized. + /// </p> + /// </remarks> + public class ByteQueue + { + /// <returns>The smallest number which can be written as 2^x which is bigger than i.</returns> + public static int NextTwoPow( + int i) + { + /* + * This code is based of a lot of code I found on the Internet + * which mostly referenced a book called "Hacking delight". + * + */ + i |= (i >> 1); + i |= (i >> 2); + i |= (i >> 4); + i |= (i >> 8); + i |= (i >> 16); + return i + 1; + } + + /** + * The initial size for our buffer. + */ + private const int InitBufSize = 1024; + + /** + * The buffer where we store our data. + */ + private byte[] databuf = new byte[ByteQueue.InitBufSize]; + + /** + * How many bytes at the beginning of the buffer are skipped. + */ + private int skipped = 0; + + /** + * How many bytes in the buffer are valid data. + */ + private int available = 0; + + /// <summary>Read data from the buffer.</summary> + /// <param name="buf">The buffer where the read data will be copied to.</param> + /// <param name="offset">How many bytes to skip at the beginning of buf.</param> + /// <param name="len">How many bytes to read at all.</param> + /// <param name="skip">How many bytes from our data to skip.</param> + public void Read( + byte[] buf, + int offset, + int len, + int skip) + { + if ((available - skip) < len) + { + throw new TlsException("Not enough data to read"); + } + if ((buf.Length - offset) < len) + { + throw new TlsException("Buffer size of " + buf.Length + " is too small for a read of " + len + " bytes"); + } + Array.Copy(databuf, skipped + skip, buf, offset, len); + } + + /// <summary>Add some data to our buffer.</summary> + /// <param name="data">A byte-array to read data from.</param> + /// <param name="offset">How many bytes to skip at the beginning of the array.</param> + /// <param name="len">How many bytes to read from the array.</param> + public void AddData( + byte[] data, + int offset, + int len) + { + if ((skipped + available + len) > databuf.Length) + { + byte[] tmp = new byte[ByteQueue.NextTwoPow(data.Length)]; + Array.Copy(databuf, skipped, tmp, 0, available); + skipped = 0; + databuf = tmp; + } + Array.Copy(data, offset, databuf, skipped + available, len); + available += len; + } + + /// <summary>Remove some bytes from our data from the beginning.</summary> + /// <param name="i">How many bytes to remove.</param> + public void RemoveData( + int i) + { + if (i > available) + { + throw new TlsException("Cannot remove " + i + " bytes, only got " + available); + } + + /* + * Skip the data. + */ + available -= i; + skipped += i; + + /* + * If more than half of our data is skipped, we will move the data + * in the buffer. + */ + if (skipped > (databuf.Length / 2)) + { + Array.Copy(databuf, skipped, databuf, 0, available); + skipped = 0; + } + } + + /// <summary>The number of bytes which are available in this buffer.</summary> + public int Available + { + get { return available; } + } + } +} diff --git a/Crypto/src/crypto/tls/Certificate.cs b/Crypto/src/crypto/tls/Certificate.cs new file mode 100644 index 000000000..e4df041e2 --- /dev/null +++ b/Crypto/src/crypto/tls/Certificate.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * A representation for a certificate chain. + */ + public class Certificate + { + public static readonly Certificate EmptyChain = new Certificate(new X509CertificateStructure[0]); + + /** + * The certificates. + */ + internal X509CertificateStructure[] certs; + + /** + * Parse the ServerCertificate message. + * + * @param inStr The stream where to parse from. + * @return A Certificate object with the certs, the server has sended. + * @throws IOException If something goes wrong during parsing. + */ + internal static Certificate Parse( + Stream inStr) + { + int left = TlsUtilities.ReadUint24(inStr); + if (left == 0) + { + return EmptyChain; + } + IList tmp = Platform.CreateArrayList(); + while (left > 0) + { + int size = TlsUtilities.ReadUint24(inStr); + left -= 3 + size; + byte[] buf = new byte[size]; + TlsUtilities.ReadFully(buf, inStr); + MemoryStream bis = new MemoryStream(buf, false); + Asn1Object o = Asn1Object.FromStream(bis); + tmp.Add(X509CertificateStructure.GetInstance(o)); + if (bis.Position < bis.Length) + { + throw new ArgumentException("Sorry, there is garbage data left after the certificate"); + } + } + X509CertificateStructure[] certs = new X509CertificateStructure[tmp.Count]; + for (int i = 0; i < tmp.Count; ++i) + { + certs[i] = (X509CertificateStructure)tmp[i]; + } + return new Certificate(certs); + } + + /** + * Encodes version of the ClientCertificate message + * + * @param outStr stream to write the message to + * @throws IOException If something goes wrong + */ + internal void Encode( + Stream outStr) + { + IList encCerts = Platform.CreateArrayList(); + int totalSize = 0; + foreach (X509CertificateStructure cert in certs) + { + byte[] encCert = cert.GetEncoded(Asn1Encodable.Der); + encCerts.Add(encCert); + totalSize += encCert.Length + 3; + } + + TlsUtilities.WriteUint24(totalSize, outStr); + + foreach (byte[] encCert in encCerts) + { + TlsUtilities.WriteOpaque24(encCert, outStr); + } + } + + /** + * Private constructor from a cert array. + * + * @param certs The certs the chain should contain. + */ + public Certificate(X509CertificateStructure[] certs) + { + if (certs == null) + throw new ArgumentNullException("certs"); + + this.certs = certs; + } + + /// <returns>An array which contains the certs, this chain contains.</returns> + public X509CertificateStructure[] GetCerts() + { + return (X509CertificateStructure[]) certs.Clone(); + } + + public bool IsEmpty + { + get { return certs.Length == 0; } + } + } +} diff --git a/Crypto/src/crypto/tls/CertificateRequest.cs b/Crypto/src/crypto/tls/CertificateRequest.cs new file mode 100644 index 000000000..49d8ba6fb --- /dev/null +++ b/Crypto/src/crypto/tls/CertificateRequest.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class CertificateRequest + { + private ClientCertificateType[] certificateTypes; + private IList certificateAuthorities; + + public CertificateRequest(ClientCertificateType[] certificateTypes, IList certificateAuthorities) + { + this.certificateTypes = certificateTypes; + this.certificateAuthorities = certificateAuthorities; + } + + public ClientCertificateType[] CertificateTypes + { + get { return certificateTypes; } + } + + /// <returns>A <see cref="IList"/> of X509Name</returns> + public IList CertificateAuthorities + { + get { return certificateAuthorities; } + } + } +} \ No newline at end of file diff --git a/Crypto/src/crypto/tls/CipherSuite.cs b/Crypto/src/crypto/tls/CipherSuite.cs new file mode 100644 index 000000000..6e1f7a545 --- /dev/null +++ b/Crypto/src/crypto/tls/CipherSuite.cs @@ -0,0 +1,136 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 2246 A.5 + /// </summary> + public enum CipherSuite : int + { + TLS_NULL_WITH_NULL_NULL = 0x0000, + TLS_RSA_WITH_NULL_MD5 = 0x0001, + TLS_RSA_WITH_NULL_SHA = 0x0002, + TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003, + TLS_RSA_WITH_RC4_128_MD5 = 0x0004, + TLS_RSA_WITH_RC4_128_SHA = 0x0005, + TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006, + TLS_RSA_WITH_IDEA_CBC_SHA = 0x0007, + TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008, + TLS_RSA_WITH_DES_CBC_SHA = 0x0009, + TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A, + TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B, + TLS_DH_DSS_WITH_DES_CBC_SHA = 0x000C, + TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D, + TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E, + TLS_DH_RSA_WITH_DES_CBC_SHA = 0x000F, + TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010, + TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011, + TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x0012, + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013, + TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014, + TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x0015, + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016, + TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017, + TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018, + TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x0019, + TLS_DH_anon_WITH_DES_CBC_SHA = 0x001A, + TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B, + + /* + * RFC 3268 + */ + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F, + TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x0030, + TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033, + TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034, + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035, + TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x0036, + TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038, + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039, + TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A, + + /* + * RFC 4279 + */ + TLS_PSK_WITH_RC4_128_SHA = 0x008A, + TLS_PSK_WITH_3DES_EDE_CBC_SHA = 0x008B, + TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C, + TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D, + TLS_DHE_PSK_WITH_RC4_128_SHA = 0x008E, + TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = 0x008F, + TLS_DHE_PSK_WITH_AES_128_CBC_SHA = 0x0090, + TLS_DHE_PSK_WITH_AES_256_CBC_SHA = 0x0091, + TLS_RSA_PSK_WITH_RC4_128_SHA = 0x0092, + TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = 0x0093, + TLS_RSA_PSK_WITH_AES_128_CBC_SHA = 0x0094, + TLS_RSA_PSK_WITH_AES_256_CBC_SHA = 0x0095, + + /* + * RFC 4492 + */ + TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001, + TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002, + TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003, + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004, + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005, + TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006, + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007, + TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A, + TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B, + TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C, + TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D, + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E, + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F, + TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010, + TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011, + TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014, + TLS_ECDH_anon_WITH_NULL_SHA = 0xC015, + TLS_ECDH_anon_WITH_RC4_128_SHA = 0xC016, + TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017, + TLS_ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018, + TLS_ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019, + + /* + * RFC 5054 + */ + TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A, + TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B, + TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C, + TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D, + TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E, + TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F, + TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020, + TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021, + TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022, + + /* + * RFC 5289 + */ + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024, + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025, + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028, + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029, + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C, + TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D, + TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030, + TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031, + TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032, + + /* + * RFC 5746 + */ + TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF, + } +} diff --git a/Crypto/src/crypto/tls/ClientCertificateType.cs b/Crypto/src/crypto/tls/ClientCertificateType.cs new file mode 100644 index 000000000..58f5d4276 --- /dev/null +++ b/Crypto/src/crypto/tls/ClientCertificateType.cs @@ -0,0 +1,20 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 2246 7.4.4 + /// </summary> + public enum ClientCertificateType : byte + { + rsa_sign = 1, + dss_sign = 2, + rsa_fixed_dh = 3, + dss_fixed_dh = 4, + + /* + * RFC 4492 5.5 + */ + ecdsa_sign = 64, + rsa_fixed_ecdh = 65, + ecdsa_fixed_ecdh = 66, + } +} \ No newline at end of file diff --git a/Crypto/src/crypto/tls/CombinedHash.cs b/Crypto/src/crypto/tls/CombinedHash.cs new file mode 100644 index 000000000..59ad87a7b --- /dev/null +++ b/Crypto/src/crypto/tls/CombinedHash.cs @@ -0,0 +1,82 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <remarks>A combined hash, which implements md5(m) || sha1(m).</remarks> + internal class CombinedHash + : IDigest + { + private readonly MD5Digest md5; + private readonly Sha1Digest sha1; + + internal CombinedHash() + { + this.md5 = new MD5Digest(); + this.sha1 = new Sha1Digest(); + } + + internal CombinedHash(CombinedHash t) + { + this.md5 = new MD5Digest(t.md5); + this.sha1 = new Sha1Digest(t.sha1); + } + + /// <seealso cref="IDigest.AlgorithmName"/> + public string AlgorithmName + { + get + { + return md5.AlgorithmName + " and " + sha1.AlgorithmName + " for TLS 1.0"; + } + } + + /// <seealso cref="IDigest.GetByteLength"/> + public int GetByteLength() + { + return System.Math.Max(md5.GetByteLength(), sha1.GetByteLength()); + } + + /// <seealso cref="IDigest.GetDigestSize"/> + public int GetDigestSize() + { + return md5.GetDigestSize() + sha1.GetDigestSize(); + } + + /// <seealso cref="IDigest.Update"/> + public void Update( + byte input) + { + md5.Update(input); + sha1.Update(input); + } + + /// <seealso cref="IDigest.BlockUpdate"/> + public void BlockUpdate( + byte[] input, + int inOff, + int len) + { + md5.BlockUpdate(input, inOff, len); + sha1.BlockUpdate(input, inOff, len); + } + + /// <seealso cref="IDigest.DoFinal"/> + public int DoFinal( + byte[] output, + int outOff) + { + int i1 = md5.DoFinal(output, outOff); + int i2 = sha1.DoFinal(output, outOff + i1); + return i1 + i2; + } + + /// <seealso cref="IDigest.Reset"/> + public void Reset() + { + md5.Reset(); + sha1.Reset(); + } + } +} diff --git a/Crypto/src/crypto/tls/CompressionMethod.cs b/Crypto/src/crypto/tls/CompressionMethod.cs new file mode 100644 index 000000000..4a127a63e --- /dev/null +++ b/Crypto/src/crypto/tls/CompressionMethod.cs @@ -0,0 +1,20 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 2246 6.1 + /// </summary> + public enum CompressionMethod : byte + { + NULL = 0, + + /* + * RFC 3749 2 + */ + DEFLATE = 1 + + /* + * Values from 224 decimal (0xE0) through 255 decimal (0xFF) + * inclusive are reserved for private use. + */ + } +} diff --git a/Crypto/src/crypto/tls/ContentType.cs b/Crypto/src/crypto/tls/ContentType.cs new file mode 100644 index 000000000..a664e3a38 --- /dev/null +++ b/Crypto/src/crypto/tls/ContentType.cs @@ -0,0 +1,13 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 2246 6.2.1 + /// </summary> + public enum ContentType : byte + { + change_cipher_spec = 20, + alert = 21, + handshake = 22, + application_data = 23, + } +} diff --git a/Crypto/src/crypto/tls/DefaultTlsAgreementCredentials.cs b/Crypto/src/crypto/tls/DefaultTlsAgreementCredentials.cs new file mode 100644 index 000000000..2dfe526d1 --- /dev/null +++ b/Crypto/src/crypto/tls/DefaultTlsAgreementCredentials.cs @@ -0,0 +1,67 @@ +using System; + +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DefaultTlsAgreementCredentials + : TlsAgreementCredentials + { + protected Certificate clientCert; + protected AsymmetricKeyParameter clientPrivateKey; + + protected IBasicAgreement basicAgreement; + + public DefaultTlsAgreementCredentials(Certificate clientCertificate, AsymmetricKeyParameter clientPrivateKey) + { + if (clientCertificate == null) + { + throw new ArgumentNullException("clientCertificate"); + } + if (clientCertificate.certs.Length == 0) + { + throw new ArgumentException("cannot be empty", "clientCertificate"); + } + if (clientPrivateKey == null) + { + throw new ArgumentNullException("clientPrivateKey"); + } + if (!clientPrivateKey.IsPrivate) + { + throw new ArgumentException("must be private", "clientPrivateKey"); + } + + if (clientPrivateKey is DHPrivateKeyParameters) + { + basicAgreement = new DHBasicAgreement(); + } + else if (clientPrivateKey is ECPrivateKeyParameters) + { + basicAgreement = new ECDHBasicAgreement(); + } + else + { + throw new ArgumentException("type not supported: " + + clientPrivateKey.GetType().FullName, "clientPrivateKey"); + } + + this.clientCert = clientCertificate; + this.clientPrivateKey = clientPrivateKey; + } + + public virtual Certificate Certificate + { + get { return clientCert; } + } + + public virtual byte[] GenerateAgreement(AsymmetricKeyParameter serverPublicKey) + { + basicAgreement.Init(clientPrivateKey); + BigInteger agreementValue = basicAgreement.CalculateAgreement(serverPublicKey); + return BigIntegers.AsUnsignedByteArray(agreementValue); + } + } +} diff --git a/Crypto/src/crypto/tls/DefaultTlsCipherFactory.cs b/Crypto/src/crypto/tls/DefaultTlsCipherFactory.cs new file mode 100644 index 000000000..53e3438d9 --- /dev/null +++ b/Crypto/src/crypto/tls/DefaultTlsCipherFactory.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DefaultTlsCipherFactory + : TlsCipherFactory + { + public virtual TlsCipher CreateCipher(TlsClientContext context, + EncryptionAlgorithm encryptionAlgorithm, DigestAlgorithm digestAlgorithm) + { + switch (encryptionAlgorithm) + { + case EncryptionAlgorithm.cls_3DES_EDE_CBC: + return CreateDesEdeCipher(context, 24, digestAlgorithm); + case EncryptionAlgorithm.AES_128_CBC: + return CreateAesCipher(context, 16, digestAlgorithm); + case EncryptionAlgorithm.AES_256_CBC: + return CreateAesCipher(context, 32, digestAlgorithm); + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + /// <exception cref="IOException"></exception> + protected virtual TlsCipher CreateAesCipher(TlsClientContext context, int cipherKeySize, + DigestAlgorithm digestAlgorithm) + { + return new TlsBlockCipher(context, CreateAesBlockCipher(), CreateAesBlockCipher(), + CreateDigest(digestAlgorithm), CreateDigest(digestAlgorithm), cipherKeySize); + } + + /// <exception cref="IOException"></exception> + protected virtual TlsCipher CreateDesEdeCipher(TlsClientContext context, int cipherKeySize, + DigestAlgorithm digestAlgorithm) + { + return new TlsBlockCipher(context, CreateDesEdeBlockCipher(), CreateDesEdeBlockCipher(), + CreateDigest(digestAlgorithm), CreateDigest(digestAlgorithm), cipherKeySize); + } + + protected virtual IBlockCipher CreateAesBlockCipher() + { + return new CbcBlockCipher(new AesFastEngine()); + } + + protected virtual IBlockCipher CreateDesEdeBlockCipher() + { + return new CbcBlockCipher(new DesEdeEngine()); + } + + /// <exception cref="IOException"></exception> + protected virtual IDigest CreateDigest(DigestAlgorithm digestAlgorithm) + { + switch (digestAlgorithm) + { + case DigestAlgorithm.MD5: + return new MD5Digest(); + case DigestAlgorithm.SHA: + return new Sha1Digest(); + case DigestAlgorithm.SHA256: + return new Sha256Digest(); + case DigestAlgorithm.SHA384: + return new Sha384Digest(); + default: + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + } +} diff --git a/Crypto/src/crypto/tls/DefaultTlsClient.cs b/Crypto/src/crypto/tls/DefaultTlsClient.cs new file mode 100644 index 000000000..c5b59a06b --- /dev/null +++ b/Crypto/src/crypto/tls/DefaultTlsClient.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Modes; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class DefaultTlsClient + : TlsClient + { + protected TlsCipherFactory cipherFactory; + + protected TlsClientContext context; + + protected CompressionMethod selectedCompressionMethod; + protected CipherSuite selectedCipherSuite; + + public DefaultTlsClient() + : this(new DefaultTlsCipherFactory()) + { + } + + public DefaultTlsClient(TlsCipherFactory cipherFactory) + { + this.cipherFactory = cipherFactory; + } + + public virtual void Init(TlsClientContext context) + { + this.context = context; + } + + public virtual CipherSuite[] GetCipherSuites() + { + return new CipherSuite[] { + CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + }; + } + + public virtual CompressionMethod[] GetCompressionMethods() + { + /* + * To offer DEFLATE compression, override this method: + * return new CompressionMethod[] { CompressionMethod.DEFLATE, CompressionMethod.NULL }; + */ + + return new CompressionMethod[] { CompressionMethod.NULL }; + } + + public virtual IDictionary GetClientExtensions() + { + return null; + } + + public virtual void NotifySessionID(byte[] sessionID) + { + // Currently ignored + } + + public virtual void NotifySelectedCipherSuite(CipherSuite selectedCipherSuite) + { + this.selectedCipherSuite = selectedCipherSuite; + } + + public virtual void NotifySelectedCompressionMethod(CompressionMethod selectedCompressionMethod) + { + this.selectedCompressionMethod = selectedCompressionMethod; + } + + public virtual void NotifySecureRenegotiation(bool secureRenegotiation) + { + if (!secureRenegotiation) + { + /* + * RFC 5746 3.4. + * If the extension is not present, the server does not support + * secure renegotiation; set secure_renegotiation flag to FALSE. + * In this case, some clients may want to terminate the handshake + * instead of continuing; see Section 4.1 for discussion. + */ +// throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + public virtual void ProcessServerExtensions(IDictionary serverExtensions) + { + } + + public virtual TlsKeyExchange GetKeyExchange() + { + switch (selectedCipherSuite) + { + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + return CreateRsaKeyExchange(); + + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + return CreateDHKeyExchange(KeyExchangeAlgorithm.DH_DSS); + + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + return CreateDHKeyExchange(KeyExchangeAlgorithm.DH_RSA); + + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + return CreateDheKeyExchange(KeyExchangeAlgorithm.DHE_DSS); + + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + return CreateDheKeyExchange(KeyExchangeAlgorithm.DHE_RSA); + + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + return CreateECDHKeyExchange(KeyExchangeAlgorithm.ECDH_ECDSA); + + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + return CreateECDheKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA); + + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + return CreateECDHKeyExchange(KeyExchangeAlgorithm.ECDH_RSA); + + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return CreateECDheKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected cipher suite was in the list of client-offered cipher + * suites, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public abstract TlsAuthentication GetAuthentication(); + + public virtual TlsCompression GetCompression() + { + switch (selectedCompressionMethod) + { + case CompressionMethod.NULL: + return new TlsNullCompression(); + + case CompressionMethod.DEFLATE: + return new TlsDeflateCompression(); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected compression method was in the list of client-offered compression + * methods, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public virtual TlsCipher GetCipher() + { + switch (selectedCipherSuite) + { + case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, DigestAlgorithm.SHA); + + case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, DigestAlgorithm.SHA); + + case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, DigestAlgorithm.SHA); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected cipher suite was in the list of client-offered cipher + * suites, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual TlsKeyExchange CreateDHKeyExchange(KeyExchangeAlgorithm keyExchange) + { + return new TlsDHKeyExchange(context, keyExchange); + } + + protected virtual TlsKeyExchange CreateDheKeyExchange(KeyExchangeAlgorithm keyExchange) + { + return new TlsDheKeyExchange(context, keyExchange); + } + + protected virtual TlsKeyExchange CreateECDHKeyExchange(KeyExchangeAlgorithm keyExchange) + { + return new TlsECDHKeyExchange(context, keyExchange); + } + + protected virtual TlsKeyExchange CreateECDheKeyExchange(KeyExchangeAlgorithm keyExchange) + { + return new TlsECDheKeyExchange(context, keyExchange); + } + + protected virtual TlsKeyExchange CreateRsaKeyExchange() + { + return new TlsRsaKeyExchange(context); + } + } +} diff --git a/Crypto/src/crypto/tls/DefaultTlsSignerCredentials.cs b/Crypto/src/crypto/tls/DefaultTlsSignerCredentials.cs new file mode 100644 index 000000000..23d607d85 --- /dev/null +++ b/Crypto/src/crypto/tls/DefaultTlsSignerCredentials.cs @@ -0,0 +1,76 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class DefaultTlsSignerCredentials + : TlsSignerCredentials + { + protected TlsClientContext context; + protected Certificate clientCert; + protected AsymmetricKeyParameter clientPrivateKey; + + protected TlsSigner clientSigner; + + public DefaultTlsSignerCredentials(TlsClientContext context, + Certificate clientCertificate, AsymmetricKeyParameter clientPrivateKey) + { + if (clientCertificate == null) + { + throw new ArgumentNullException("clientCertificate"); + } + if (clientCertificate.certs.Length == 0) + { + throw new ArgumentException("cannot be empty", "clientCertificate"); + } + if (clientPrivateKey == null) + { + throw new ArgumentNullException("clientPrivateKey"); + } + if (!clientPrivateKey.IsPrivate) + { + throw new ArgumentException("must be private", "clientPrivateKey"); + } + + if (clientPrivateKey is RsaKeyParameters) + { + clientSigner = new TlsRsaSigner(); + } + else if (clientPrivateKey is DsaPrivateKeyParameters) + { + clientSigner = new TlsDssSigner(); + } + else if (clientPrivateKey is ECPrivateKeyParameters) + { + clientSigner = new TlsECDsaSigner(); + } + else + { + throw new ArgumentException("type not supported: " + + clientPrivateKey.GetType().FullName, "clientPrivateKey"); + } + + this.context = context; + this.clientCert = clientCertificate; + this.clientPrivateKey = clientPrivateKey; + } + + public virtual Certificate Certificate + { + get { return clientCert; } + } + + public virtual byte[] GenerateCertificateSignature(byte[] md5andsha1) + { + try + { + return clientSigner.CalculateRawSignature(context.SecureRandom, clientPrivateKey, md5andsha1); + } + catch (CryptoException) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + } +} diff --git a/Crypto/src/crypto/tls/DigestAlgorithm.cs b/Crypto/src/crypto/tls/DigestAlgorithm.cs new file mode 100644 index 000000000..cede6b7f8 --- /dev/null +++ b/Crypto/src/crypto/tls/DigestAlgorithm.cs @@ -0,0 +1,21 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public enum DigestAlgorithm + { + /* + * Note that the values here are implementation-specific and arbitrary. + * It is recommended not to depend on the particular values (e.g. serialization). + */ + NULL, + MD5, + SHA, + + /* + * RFC 5289 + */ + SHA256, + SHA384, + } +} diff --git a/Crypto/src/crypto/tls/ECCurveType.cs b/Crypto/src/crypto/tls/ECCurveType.cs new file mode 100644 index 000000000..15d5d7b42 --- /dev/null +++ b/Crypto/src/crypto/tls/ECCurveType.cs @@ -0,0 +1,29 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 4492 5.4 + /// </summary> + public enum ECCurveType : byte + { + /** + * Indicates the elliptic curve domain parameters are conveyed verbosely, and the + * underlying finite field is a prime field. + */ + explicit_prime = 1, + + /** + * Indicates the elliptic curve domain parameters are conveyed verbosely, and the + * underlying finite field is a characteristic-2 field. + */ + explicit_char2 = 2, + + /** + * Indicates that a named curve is used. This option SHOULD be used when applicable. + */ + named_curve = 3, + + /* + * Values 248 through 255 are reserved for private use. + */ + } +} diff --git a/Crypto/src/crypto/tls/ECPointFormat.cs b/Crypto/src/crypto/tls/ECPointFormat.cs new file mode 100644 index 000000000..4e0dd0067 --- /dev/null +++ b/Crypto/src/crypto/tls/ECPointFormat.cs @@ -0,0 +1,16 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 4492 5.1.2 + /// </summary> + public enum ECPointFormat : byte + { + uncompressed = 0, + ansiX962_compressed_prime = 1, + ansiX962_compressed_char2 = 2, + + /* + * reserved (248..255) + */ + } +} diff --git a/Crypto/src/crypto/tls/EncryptionAlgorithm.cs b/Crypto/src/crypto/tls/EncryptionAlgorithm.cs new file mode 100644 index 000000000..79d3b63b5 --- /dev/null +++ b/Crypto/src/crypto/tls/EncryptionAlgorithm.cs @@ -0,0 +1,32 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public enum EncryptionAlgorithm + { + /* + * Note that the values here are implementation-specific and arbitrary. + * It is recommended not to depend on the particular values (e.g. serialization). + */ + NULL, + RC4_40, + RC4_128, + RC2_CBC_40, + IDEA_CBC, + DES40_CBC, + DES_CBC, + cls_3DES_EDE_CBC, + + /* + * RFC 3268 + */ + AES_128_CBC, + AES_256_CBC, + + /* + * RFC 5289 + */ + AES_128_GCM, + AES_256_GCM, + } +} diff --git a/Crypto/src/crypto/tls/ExtensionType.cs b/Crypto/src/crypto/tls/ExtensionType.cs new file mode 100644 index 000000000..f00e34e3f --- /dev/null +++ b/Crypto/src/crypto/tls/ExtensionType.cs @@ -0,0 +1,31 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 4366 2.3 + /// </summary> + public enum ExtensionType : int + { + server_name = 0, + max_fragment_length = 1, + client_certificate_url = 2, + trusted_ca_keys = 3, + truncated_hmac = 4, + status_request = 5, + + /* + * RFC 4492 + */ + elliptic_curves = 10, + ec_point_formats = 11, + + /* + * RFC 5054 2.8.1 + */ + srp = 12, + + /* + * RFC 5746 6 + */ + renegotiation_info = 0xff01, + } +} diff --git a/Crypto/src/crypto/tls/HandshakeType.cs b/Crypto/src/crypto/tls/HandshakeType.cs new file mode 100644 index 000000000..deedb1f84 --- /dev/null +++ b/Crypto/src/crypto/tls/HandshakeType.cs @@ -0,0 +1,19 @@ +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 2246 7.4 + /// </summary> + public enum HandshakeType : byte + { + hello_request = 0, + client_hello = 1, + server_hello = 2, + certificate = 11, + server_key_exchange = 12, + certificate_request = 13, + server_hello_done = 14, + certificate_verify = 15, + client_key_exchange = 16, + finished = 20, + } +} diff --git a/Crypto/src/crypto/tls/ICertificateVerifyer.cs b/Crypto/src/crypto/tls/ICertificateVerifyer.cs new file mode 100644 index 000000000..df5ea51d7 --- /dev/null +++ b/Crypto/src/crypto/tls/ICertificateVerifyer.cs @@ -0,0 +1,18 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <remarks> + /// This should be implemented by any class which can find out, if a given + /// certificate chain is being accepted by an client. + /// </remarks> + [Obsolete("Perform certificate verification in TlsAuthentication implementation")] + public interface ICertificateVerifyer + { + /// <param name="certs">The certs, which are part of the chain.</param> + /// <returns>True, if the chain is accepted, false otherwise</returns> + bool IsValid(X509CertificateStructure[] certs); + } +} diff --git a/Crypto/src/crypto/tls/KeyExchangeAlgorithm.cs b/Crypto/src/crypto/tls/KeyExchangeAlgorithm.cs new file mode 100644 index 000000000..3fdbeb2a6 --- /dev/null +++ b/Crypto/src/crypto/tls/KeyExchangeAlgorithm.cs @@ -0,0 +1,36 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public enum KeyExchangeAlgorithm + { + /* + * Note that the values here are implementation-specific and arbitrary. + * It is recommended not to depend on the particular values (e.g. serialization). + */ + NULL, + RSA, + RSA_EXPORT, + DHE_DSS, + DHE_DSS_EXPORT, + DHE_RSA, + DHE_RSA_EXPORT, + DH_DSS, + DH_DSS_EXPORT, + DH_RSA, + DH_RSA_EXPORT, + DH_anon, + DH_anon_export, + PSK, + DHE_PSK, + RSA_PSK, + ECDH_ECDSA, + ECDHE_ECDSA, + ECDH_RSA, + ECDHE_RSA, + ECDH_anon, + SRP, + SRP_DSS, + SRP_RSA, + } +} diff --git a/Crypto/src/crypto/tls/LegacyTlsAuthentication.cs b/Crypto/src/crypto/tls/LegacyTlsAuthentication.cs new file mode 100644 index 000000000..395f94208 --- /dev/null +++ b/Crypto/src/crypto/tls/LegacyTlsAuthentication.cs @@ -0,0 +1,30 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// A temporary class to wrap old CertificateVerifyer stuff for new TlsAuthentication. + /// </summary> + [Obsolete] + public class LegacyTlsAuthentication + : TlsAuthentication + { + protected ICertificateVerifyer verifyer; + + public LegacyTlsAuthentication(ICertificateVerifyer verifyer) + { + this.verifyer = verifyer; + } + + public virtual void NotifyServerCertificate(Certificate serverCertificate) + { + if (!this.verifyer.IsValid(serverCertificate.GetCerts())) + throw new TlsFatalAlert(AlertDescription.user_canceled); + } + + public virtual TlsCredentials GetClientCredentials(CertificateRequest certificateRequest) + { + return null; + } + } +} diff --git a/Crypto/src/crypto/tls/LegacyTlsClient.cs b/Crypto/src/crypto/tls/LegacyTlsClient.cs new file mode 100644 index 000000000..fbb9a732e --- /dev/null +++ b/Crypto/src/crypto/tls/LegacyTlsClient.cs @@ -0,0 +1,26 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// A temporary class to use LegacyTlsAuthentication + /// </summary> + [Obsolete] + public class LegacyTlsClient + : DefaultTlsClient + { + [Obsolete] + protected ICertificateVerifyer verifyer; + + [Obsolete] + public LegacyTlsClient(ICertificateVerifyer verifyer) + { + this.verifyer = verifyer; + } + + public override TlsAuthentication GetAuthentication() + { + return new LegacyTlsAuthentication(verifyer); + } + } +} \ No newline at end of file diff --git a/Crypto/src/crypto/tls/NamedCurve.cs b/Crypto/src/crypto/tls/NamedCurve.cs new file mode 100644 index 000000000..c8ee189aa --- /dev/null +++ b/Crypto/src/crypto/tls/NamedCurve.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Asn1.Sec; +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Crypto.Parameters; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// RFC 4492 5.1.1 + /// The named curves defined here are those specified in SEC 2 [13]. Note that many of + /// these curves are also recommended in ANSI X9.62 [7] and FIPS 186-2 [11]. Values 0xFE00 + /// through 0xFEFF are reserved for private use. Values 0xFF01 and 0xFF02 indicate that the + /// client supports arbitrary prime and characteristic-2 curves, respectively (the curve + /// parameters must be encoded explicitly in ECParameters). + /// </summary> + public enum NamedCurve : int + { + sect163k1 = 1, + sect163r1 = 2, + sect163r2 = 3, + sect193r1 = 4, + sect193r2 = 5, + sect233k1 = 6, + sect233r1 = 7, + sect239k1 = 8, + sect283k1 = 9, + sect283r1 = 10, + sect409k1 = 11, + sect409r1 = 12, + sect571k1 = 13, + sect571r1 = 14, + secp160k1 = 15, + secp160r1 = 16, + secp160r2 = 17, + secp192k1 = 18, + secp192r1 = 19, + secp224k1 = 20, + secp224r1 = 21, + secp256k1 = 22, + secp256r1 = 23, + secp384r1 = 24, + secp521r1 = 25, + + /* + * reserved (0xFE00..0xFEFF) + */ + + arbitrary_explicit_prime_curves = 0xFF01, + arbitrary_explicit_char2_curves = 0xFF02, + } + + internal class NamedCurveHelper + { + internal static ECDomainParameters GetECParameters(NamedCurve namedCurve) + { + if (!Enum.IsDefined(typeof(NamedCurve), namedCurve)) + return null; + + string curveName = namedCurve.ToString(); + + // Lazily created the first time a particular curve is accessed + X9ECParameters ecP = SecNamedCurves.GetByName(curveName); + + if (ecP == null) + return null; + + // It's a bit inefficient to do this conversion every time + return new ECDomainParameters(ecP.Curve, ecP.G, ecP.N, ecP.H, ecP.GetSeed()); + } + } +} diff --git a/Crypto/src/crypto/tls/PskTlsClient.cs b/Crypto/src/crypto/tls/PskTlsClient.cs new file mode 100644 index 000000000..16975e713 --- /dev/null +++ b/Crypto/src/crypto/tls/PskTlsClient.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class PskTlsClient + :TlsClient + { + protected TlsCipherFactory cipherFactory; + protected TlsPskIdentity pskIdentity; + + protected TlsClientContext context; + + protected CompressionMethod selectedCompressionMethod; + protected CipherSuite selectedCipherSuite; + + public PskTlsClient(TlsPskIdentity pskIdentity) + : this(new DefaultTlsCipherFactory(), pskIdentity) + { + } + + public PskTlsClient(TlsCipherFactory cipherFactory, TlsPskIdentity pskIdentity) + { + this.cipherFactory = cipherFactory; + this.pskIdentity = pskIdentity; + } + + public virtual void Init(TlsClientContext context) + { + this.context = context; + } + + public virtual CipherSuite[] GetCipherSuites() + { + return new CipherSuite[] { + CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA, + }; + } + + public virtual IDictionary GetClientExtensions() + { + return null; + } + + public virtual CompressionMethod[] GetCompressionMethods() + { + return new CompressionMethod[] { CompressionMethod.NULL }; + } + + public virtual void NotifySessionID(byte[] sessionID) + { + // Currently ignored + } + + public virtual void NotifySelectedCipherSuite(CipherSuite selectedCipherSuite) + { + this.selectedCipherSuite = selectedCipherSuite; + } + + public virtual void NotifySelectedCompressionMethod(CompressionMethod selectedCompressionMethod) + { + this.selectedCompressionMethod = selectedCompressionMethod; + } + + public virtual void NotifySecureRenegotiation(bool secureRenegotiation) + { + if (!secureRenegotiation) + { + /* + * RFC 5746 3.4. If the extension is not present, the server does not support + * secure renegotiation; set secure_renegotiation flag to FALSE. In this case, + * some clients may want to terminate the handshake instead of continuing; see + * Section 4.1 for discussion. + */ +// throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + public virtual void ProcessServerExtensions(IDictionary serverExtensions) + { + } + + public virtual TlsKeyExchange GetKeyExchange() + { + switch (selectedCipherSuite) + { + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + return CreatePskKeyExchange(KeyExchangeAlgorithm.PSK); + + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + return CreatePskKeyExchange(KeyExchangeAlgorithm.RSA_PSK); + + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + return CreatePskKeyExchange(KeyExchangeAlgorithm.DHE_PSK); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected cipher suite was in the list of client-offered cipher + * suites, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public virtual TlsAuthentication GetAuthentication() + { + return null; + } + + public virtual TlsCompression GetCompression() + { + switch (selectedCompressionMethod) + { + case CompressionMethod.NULL: + return new TlsNullCompression(); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected compression method was in the list of client-offered compression + * methods, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public virtual TlsCipher GetCipher() + { + switch (selectedCipherSuite) + { + case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, + DigestAlgorithm.SHA); + + case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, + DigestAlgorithm.SHA); + + case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, + DigestAlgorithm.SHA); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected cipher suite was in the list of client-offered cipher + * suites, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual TlsKeyExchange CreatePskKeyExchange(KeyExchangeAlgorithm keyExchange) + { + return new TlsPskKeyExchange(context, keyExchange, pskIdentity); + } + } +} diff --git a/Crypto/src/crypto/tls/RecordStream.cs b/Crypto/src/crypto/tls/RecordStream.cs new file mode 100644 index 000000000..e18894b4e --- /dev/null +++ b/Crypto/src/crypto/tls/RecordStream.cs @@ -0,0 +1,166 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <remarks>An implementation of the TLS 1.0 record layer.</remarks> + internal class RecordStream + { + private TlsProtocolHandler handler; + private Stream inStr; + private Stream outStr; + private CombinedHash hash; + private TlsCompression readCompression = null; + private TlsCompression writeCompression = null; + private TlsCipher readCipher = null; + private TlsCipher writeCipher = null; + private MemoryStream buffer = new MemoryStream(); + + internal RecordStream( + TlsProtocolHandler handler, + Stream inStr, + Stream outStr) + { + this.handler = handler; + this.inStr = inStr; + this.outStr = outStr; + this.hash = new CombinedHash(); + this.readCompression = new TlsNullCompression(); + this.writeCompression = this.readCompression; + this.readCipher = new TlsNullCipher(); + this.writeCipher = this.readCipher; + } + + internal void ClientCipherSpecDecided(TlsCompression tlsCompression, TlsCipher tlsCipher) + { + this.writeCompression = tlsCompression; + this.writeCipher = tlsCipher; + } + + internal void ServerClientSpecReceived() + { + this.readCompression = this.writeCompression; + this.readCipher = this.writeCipher; + } + + public void ReadData() + { + ContentType type = (ContentType)TlsUtilities.ReadUint8(inStr); + TlsUtilities.CheckVersion(inStr); + int size = TlsUtilities.ReadUint16(inStr); + byte[] buf = DecodeAndVerify(type, inStr, size); + handler.ProcessData(type, buf, 0, buf.Length); + } + + internal byte[] DecodeAndVerify( + ContentType type, + Stream inStr, + int len) + { + byte[] buf = new byte[len]; + TlsUtilities.ReadFully(buf, inStr); + byte[] decoded = readCipher.DecodeCiphertext(type, buf, 0, buf.Length); + + Stream cOut = readCompression.Decompress(buffer); + + if (cOut == buffer) + { + return decoded; + } + + cOut.Write(decoded, 0, decoded.Length); + cOut.Flush(); + byte[] contents = buffer.ToArray(); + buffer.SetLength(0); + return contents; + } + + internal void WriteMessage( + ContentType type, + byte[] message, + int offset, + int len) + { + if (type == ContentType.handshake) + { + UpdateHandshakeData(message, offset, len); + } + + Stream cOut = writeCompression.Compress(buffer); + + byte[] ciphertext; + if (cOut == buffer) + { + ciphertext = writeCipher.EncodePlaintext(type, message, offset, len); + } + else + { + cOut.Write(message, offset, len); + cOut.Flush(); + ciphertext = writeCipher.EncodePlaintext(type, buffer.ToArray(), 0, (int)buffer.Position); + buffer.SetLength(0); + } + + byte[] writeMessage = new byte[ciphertext.Length + 5]; + TlsUtilities.WriteUint8((byte)type, writeMessage, 0); + TlsUtilities.WriteVersion(writeMessage, 1); + TlsUtilities.WriteUint16(ciphertext.Length, writeMessage, 3); + Array.Copy(ciphertext, 0, writeMessage, 5, ciphertext.Length); + outStr.Write(writeMessage, 0, writeMessage.Length); + outStr.Flush(); + } + + internal void UpdateHandshakeData( + byte[] message, + int offset, + int len) + { + hash.BlockUpdate(message, offset, len); + } + + internal byte[] GetCurrentHash() + { + return DoFinal(new CombinedHash(hash)); + } + + internal void Close() + { + IOException e = null; + try + { + inStr.Dispose(); + } + catch (IOException ex) + { + e = ex; + } + + try + { + // NB: This is harmless if outStr == inStr + outStr.Dispose(); + } + catch (IOException ex) + { + e = ex; + } + + if (e != null) + { + throw e; + } + } + + internal void Flush() + { + outStr.Flush(); + } + + private static byte[] DoFinal(CombinedHash ch) + { + byte[] bs = new byte[ch.GetDigestSize()]; + ch.DoFinal(bs, 0); + return bs; + } + } +} diff --git a/Crypto/src/crypto/tls/SecurityParameters.cs b/Crypto/src/crypto/tls/SecurityParameters.cs new file mode 100644 index 000000000..9ed3969eb --- /dev/null +++ b/Crypto/src/crypto/tls/SecurityParameters.cs @@ -0,0 +1,26 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class SecurityParameters + { + internal byte[] clientRandom = null; + internal byte[] serverRandom = null; + internal byte[] masterSecret = null; + + public byte[] ClientRandom + { + get { return clientRandom; } + } + + public byte[] ServerRandom + { + get { return serverRandom; } + } + + public byte[] MasterSecret + { + get { return masterSecret; } + } + } +} diff --git a/Crypto/src/crypto/tls/SrpTlsClient.cs b/Crypto/src/crypto/tls/SrpTlsClient.cs new file mode 100644 index 000000000..6c2638bb3 --- /dev/null +++ b/Crypto/src/crypto/tls/SrpTlsClient.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class SrpTlsClient + : TlsClient + { + protected TlsCipherFactory cipherFactory; + protected byte[] identity; + protected byte[] password; + + protected TlsClientContext context; + + protected CompressionMethod selectedCompressionMethod; + protected CipherSuite selectedCipherSuite; + + public SrpTlsClient(byte[] identity, byte[] password) + : this(new DefaultTlsCipherFactory(), identity, password) + { + } + + public SrpTlsClient(TlsCipherFactory cipherFactory, byte[] identity, byte[] password) + { + this.cipherFactory = cipherFactory; + this.identity = Arrays.Clone(identity); + this.password = Arrays.Clone(password); + } + + public virtual void Init(TlsClientContext context) + { + this.context = context; + } + + public virtual CipherSuite[] GetCipherSuites() + { + return new CipherSuite[] { + CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA, + CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA, + CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA, + CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA, + }; + } + + public virtual IDictionary GetClientExtensions() + { + IDictionary clientExtensions = Platform.CreateHashtable(); + + MemoryStream srpData = new MemoryStream(); + TlsUtilities.WriteOpaque8(this.identity, srpData); + clientExtensions[ExtensionType.srp] = srpData.ToArray(); + + return clientExtensions; + } + + public virtual CompressionMethod[] GetCompressionMethods() + { + return new CompressionMethod[] { CompressionMethod.NULL }; + } + + public virtual void NotifySessionID(byte[] sessionID) + { + // Currently ignored + } + + public virtual void NotifySelectedCipherSuite(CipherSuite selectedCipherSuite) + { + this.selectedCipherSuite = selectedCipherSuite; + } + + public virtual void NotifySelectedCompressionMethod(CompressionMethod selectedCompressionMethod) + { + this.selectedCompressionMethod = selectedCompressionMethod; + } + + public virtual void NotifySecureRenegotiation(bool secureRenegotiation) + { + if (!secureRenegotiation) + { + /* + * RFC 5746 3.4. If the extension is not present, the server does not support + * secure renegotiation; set secure_renegotiation flag to FALSE. In this case, + * some clients may want to terminate the handshake instead of continuing; see + * Section 4.1 for discussion. + */ +// throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + public virtual void ProcessServerExtensions(IDictionary serverExtensions) + { + // There is no server response for the SRP extension + } + + public virtual TlsKeyExchange GetKeyExchange() + { + switch (selectedCipherSuite) + { + case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: + return CreateSrpKeyExchange(KeyExchangeAlgorithm.SRP); + + case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: + return CreateSrpKeyExchange(KeyExchangeAlgorithm.SRP_RSA); + + case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: + return CreateSrpKeyExchange(KeyExchangeAlgorithm.SRP_DSS); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected cipher suite was in the list of client-offered cipher + * suites, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public abstract TlsAuthentication GetAuthentication(); + + public virtual TlsCompression GetCompression() + { + switch (selectedCompressionMethod) + { + case CompressionMethod.NULL: + return new TlsNullCompression(); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected compression method was in the list of client-offered compression + * methods, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public virtual TlsCipher GetCipher() + { + switch (selectedCipherSuite) + { + case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.cls_3DES_EDE_CBC, DigestAlgorithm.SHA); + + case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_128_CBC, DigestAlgorithm.SHA); + + case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA: + case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA: + return cipherFactory.CreateCipher(context, EncryptionAlgorithm.AES_256_CBC, DigestAlgorithm.SHA); + + default: + /* + * Note: internal error here; the TlsProtocolHandler verifies that the + * server-selected cipher suite was in the list of client-offered cipher + * suites, so if we now can't produce an implementation, we shouldn't have + * offered it! + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual TlsKeyExchange CreateSrpKeyExchange(KeyExchangeAlgorithm keyExchange) + { + return new TlsSrpKeyExchange(context, keyExchange, identity, password); + } + } +} diff --git a/Crypto/src/crypto/tls/Ssl3Mac.cs b/Crypto/src/crypto/tls/Ssl3Mac.cs new file mode 100644 index 000000000..b2f3f309e --- /dev/null +++ b/Crypto/src/crypto/tls/Ssl3Mac.cs @@ -0,0 +1,114 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * HMAC implementation based on original internet draft for HMAC (RFC 2104) + * + * The difference is that padding is concatentated versus XORed with the key + * + * H(K + opad, H(K + ipad, text)) + */ + public class Ssl3Mac + : IMac + { + private const byte IPAD = 0x36; + private const byte OPAD = 0x5C; + + internal static readonly byte[] MD5_IPAD = GenPad(IPAD, 48); + internal static readonly byte[] MD5_OPAD = GenPad(OPAD, 48); + internal static readonly byte[] SHA1_IPAD = GenPad(IPAD, 40); + internal static readonly byte[] SHA1_OPAD = GenPad(OPAD, 40); + + private IDigest digest; + + private byte[] secret; + private byte[] ipad, opad; + + /** + * Base constructor for one of the standard digest algorithms that the byteLength of + * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1. + * + * @param digest the digest. + */ + public Ssl3Mac(IDigest digest) + { + this.digest = digest; + + if (digest.GetDigestSize() == 20) + { + this.ipad = SHA1_IPAD; + this.opad = SHA1_OPAD; + } + else + { + this.ipad = MD5_IPAD; + this.opad = MD5_OPAD; + } + } + + public virtual string AlgorithmName + { + get { return digest.AlgorithmName + "/SSL3MAC"; } + } + + public virtual void Init(ICipherParameters parameters) + { + secret = Arrays.Clone(((KeyParameter)parameters).GetKey()); + + Reset(); + } + + public virtual int GetMacSize() + { + return digest.GetDigestSize(); + } + + public virtual void Update(byte input) + { + digest.Update(input); + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + digest.BlockUpdate(input, inOff, len); + } + + public virtual int DoFinal(byte[] output, int outOff) + { + byte[] tmp = new byte[digest.GetDigestSize()]; + digest.DoFinal(tmp, 0); + + digest.BlockUpdate(secret, 0, secret.Length); + digest.BlockUpdate(opad, 0, opad.Length); + digest.BlockUpdate(tmp, 0, tmp.Length); + + int len = digest.DoFinal(output, outOff); + + Reset(); + + return len; + } + + /** + * Reset the mac generator. + */ + public virtual void Reset() + { + digest.Reset(); + digest.BlockUpdate(secret, 0, secret.Length); + digest.BlockUpdate(ipad, 0, ipad.Length); + } + + private static byte[] GenPad(byte b, int count) + { + byte[] padding = new byte[count]; + Arrays.Fill(padding, b); + return padding; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsAgreementCredentials.cs b/Crypto/src/crypto/tls/TlsAgreementCredentials.cs new file mode 100644 index 000000000..46ee4f90e --- /dev/null +++ b/Crypto/src/crypto/tls/TlsAgreementCredentials.cs @@ -0,0 +1,11 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsAgreementCredentials : TlsCredentials + { + /// <exception cref="IOException"></exception> + byte[] GenerateAgreement(AsymmetricKeyParameter serverPublicKey); + } +} diff --git a/Crypto/src/crypto/tls/TlsAuthentication.cs b/Crypto/src/crypto/tls/TlsAuthentication.cs new file mode 100644 index 000000000..9aea5e449 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsAuthentication.cs @@ -0,0 +1,31 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsAuthentication + { + /// <summary> + /// Called by the protocol handler to report the server certificate. + /// </summary> + /// <remarks> + /// This method is responsible for certificate verification and validation + /// </remarks> + /// <param name="serverCertificate">The server <see cref="Certificate"/> received</param> + /// <exception cref="IOException"></exception> + void NotifyServerCertificate(Certificate serverCertificate); + + /// <summary> + /// Return client credentials in response to server's certificate request + /// </summary> + /// <param name="certificateRequest"> + /// A <see cref="CertificateRequest"/> containing server certificate request details + /// </param> + /// <returns> + /// A <see cref="TlsCredentials"/> to be used for client authentication + /// (or <c>null</c> for no client authentication) + /// </returns> + /// <exception cref="IOException"></exception> + TlsCredentials GetClientCredentials(CertificateRequest certificateRequest); + } +} diff --git a/Crypto/src/crypto/tls/TlsBlockCipher.cs b/Crypto/src/crypto/tls/TlsBlockCipher.cs new file mode 100644 index 000000000..ef7be1913 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsBlockCipher.cs @@ -0,0 +1,248 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// A generic TLS 1.0 block cipher. This can be used for AES or 3DES for example. + /// </summary> + public class TlsBlockCipher + : TlsCipher + { + protected TlsClientContext context; + + protected IBlockCipher encryptCipher; + protected IBlockCipher decryptCipher; + + protected TlsMac wMac; + protected TlsMac rMac; + + public virtual TlsMac WriteMac + { + get { return wMac; } + } + + public virtual TlsMac ReadMac + { + get { return rMac; } + } + + public TlsBlockCipher(TlsClientContext context, IBlockCipher encryptCipher, + IBlockCipher decryptCipher, IDigest writeDigest, IDigest readDigest, int cipherKeySize) + { + this.context = context; + this.encryptCipher = encryptCipher; + this.decryptCipher = decryptCipher; + + int prfSize = (2 * cipherKeySize) + writeDigest.GetDigestSize() + + readDigest.GetDigestSize() + encryptCipher.GetBlockSize() + + decryptCipher.GetBlockSize(); + + SecurityParameters securityParameters = context.SecurityParameters; + + byte[] keyBlock = TlsUtilities.PRF(securityParameters.masterSecret, "key expansion", + TlsUtilities.Concat(securityParameters.serverRandom, securityParameters.clientRandom), + prfSize); + + int offset = 0; + + // Init MACs + wMac = CreateTlsMac(writeDigest, keyBlock, ref offset); + rMac = CreateTlsMac(readDigest, keyBlock, ref offset); + + // Build keys + KeyParameter encryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize); + KeyParameter decryptKey = CreateKeyParameter(keyBlock, ref offset, cipherKeySize); + + // Add IVs + ParametersWithIV encryptParams = CreateParametersWithIV(encryptKey, + keyBlock, ref offset, encryptCipher.GetBlockSize()); + ParametersWithIV decryptParams = CreateParametersWithIV(decryptKey, + keyBlock, ref offset, decryptCipher.GetBlockSize()); + + if (offset != prfSize) + throw new TlsFatalAlert(AlertDescription.internal_error); + + // Init Ciphers + encryptCipher.Init(true, encryptParams); + decryptCipher.Init(false, decryptParams); + } + + protected virtual TlsMac CreateTlsMac(IDigest digest, byte[] buf, ref int off) + { + int len = digest.GetDigestSize(); + TlsMac mac = new TlsMac(digest, buf, off, len); + off += len; + return mac; + } + + protected virtual KeyParameter CreateKeyParameter(byte[] buf, ref int off, int len) + { + KeyParameter key = new KeyParameter(buf, off, len); + off += len; + return key; + } + + protected virtual ParametersWithIV CreateParametersWithIV(KeyParameter key, + byte[] buf, ref int off, int len) + { + ParametersWithIV ivParams = new ParametersWithIV(key, buf, off, len); + off += len; + return ivParams; + } + + public virtual byte[] EncodePlaintext(ContentType type, byte[] plaintext, int offset, int len) + { + int blocksize = encryptCipher.GetBlockSize(); + + // Add a random number of extra blocks worth of padding + int minPaddingSize = blocksize - ((len + wMac.Size + 1) % blocksize); + int maxExtraPadBlocks = (255 - minPaddingSize) / blocksize; + int actualExtraPadBlocks = ChooseExtraPadBlocks(context.SecureRandom, maxExtraPadBlocks); + int paddingsize = minPaddingSize + (actualExtraPadBlocks * blocksize); + + int totalsize = len + wMac.Size + paddingsize + 1; + byte[] outbuf = new byte[totalsize]; + Array.Copy(plaintext, offset, outbuf, 0, len); + byte[] mac = wMac.CalculateMac(type, plaintext, offset, len); + Array.Copy(mac, 0, outbuf, len, mac.Length); + int paddoffset = len + mac.Length; + for (int i = 0; i <= paddingsize; i++) + { + outbuf[i + paddoffset] = (byte)paddingsize; + } + for (int i = 0; i < totalsize; i += blocksize) + { + encryptCipher.ProcessBlock(outbuf, i, outbuf, i); + } + return outbuf; + } + + public virtual byte[] DecodeCiphertext(ContentType type, byte[] ciphertext, int offset, int len) + { + // TODO TLS 1.1 (RFC 4346) introduces an explicit IV + + int minLength = rMac.Size + 1; + int blocksize = decryptCipher.GetBlockSize(); + bool decrypterror = false; + + /* + * ciphertext must be at least (macsize + 1) bytes long + */ + if (len < minLength) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + + /* + * ciphertext must be a multiple of blocksize + */ + if (len % blocksize != 0) + { + throw new TlsFatalAlert(AlertDescription.decryption_failed); + } + + /* + * Decrypt all the ciphertext using the blockcipher + */ + for (int i = 0; i < len; i += blocksize) + { + decryptCipher.ProcessBlock(ciphertext, i + offset, ciphertext, i + offset); + } + + /* + * Check if padding is correct + */ + int lastByteOffset = offset + len - 1; + + byte paddingsizebyte = ciphertext[lastByteOffset]; + + int paddingsize = paddingsizebyte; + + int maxPaddingSize = len - minLength; + if (paddingsize > maxPaddingSize) + { + decrypterror = true; + paddingsize = 0; + } + else + { + /* + * Now, check all the padding-bytes (constant-time comparison). + */ + byte diff = 0; + for (int i = lastByteOffset - paddingsize; i < lastByteOffset; ++i) + { + diff |= (byte)(ciphertext[i] ^ paddingsizebyte); + } + if (diff != 0) + { + /* Wrong padding */ + decrypterror = true; + paddingsize = 0; + } + } + + /* + * We now don't care if padding verification has failed or not, we will calculate + * the mac to give an attacker no kind of timing profile he can use to find out if + * mac verification failed or padding verification failed. + */ + int plaintextlength = len - minLength - paddingsize; + byte[] calculatedMac = rMac.CalculateMac(type, ciphertext, offset, plaintextlength); + + /* + * Check all bytes in the mac (constant-time comparison). + */ + byte[] decryptedMac = new byte[calculatedMac.Length]; + Array.Copy(ciphertext, offset + plaintextlength, decryptedMac, 0, calculatedMac.Length); + + if (!Arrays.ConstantTimeAreEqual(calculatedMac, decryptedMac)) + { + decrypterror = true; + } + + /* + * Now, it is safe to fail. + */ + if (decrypterror) + { + throw new TlsFatalAlert(AlertDescription.bad_record_mac); + } + + byte[] plaintext = new byte[plaintextlength]; + Array.Copy(ciphertext, offset, plaintext, 0, plaintextlength); + return plaintext; + } + + protected virtual int ChooseExtraPadBlocks(SecureRandom r, int max) + { +// return r.NextInt(max + 1); + + uint x = (uint)r.NextInt(); + int n = LowestBitSet(x); + return System.Math.Min(n, max); + } + + private int LowestBitSet(uint x) + { + if (x == 0) + { + return 32; + } + + int n = 0; + while ((x & 1) == 0) + { + ++n; + x >>= 1; + } + return n; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsCipher.cs b/Crypto/src/crypto/tls/TlsCipher.cs new file mode 100644 index 000000000..22c769d82 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsCipher.cs @@ -0,0 +1,14 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsCipher + { + /// <exception cref="IOException"></exception> + byte[] EncodePlaintext(ContentType type, byte[] plaintext, int offset, int len); + + /// <exception cref="IOException"></exception> + byte[] DecodeCiphertext(ContentType type, byte[] ciphertext, int offset, int len); + } +} diff --git a/Crypto/src/crypto/tls/TlsCipherFactory.cs b/Crypto/src/crypto/tls/TlsCipherFactory.cs new file mode 100644 index 000000000..0756603f4 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsCipherFactory.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsCipherFactory + { + /// <exception cref="IOException"></exception> + TlsCipher CreateCipher(TlsClientContext context, EncryptionAlgorithm encryptionAlgorithm, + DigestAlgorithm digestAlgorithm); + } +} diff --git a/Crypto/src/crypto/tls/TlsClient.cs b/Crypto/src/crypto/tls/TlsClient.cs new file mode 100644 index 000000000..eceaa3cd3 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsClient.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsClient + { + /// <summary> + /// Called at the start of a new TLS session, before any other methods. + /// </summary> + /// <param name="context"> + /// A <see cref="TlsProtocolHandler"/> + /// </param> + void Init(TlsClientContext context); + + /// <summary> + /// Get the list of cipher suites that this client supports. + /// </summary> + /// <returns> + /// An array of <see cref="CipherSuite"/>, each specifying a supported cipher suite. + /// </returns> + CipherSuite[] GetCipherSuites(); + + /// <summary> + /// Get the list of compression methods that this client supports. + /// </summary> + /// <returns> + /// An array of <see cref="CompressionMethod"/>, each specifying a supported compression method. + /// </returns> + CompressionMethod[] GetCompressionMethods(); + + /// <summary> + /// Get the (optional) table of client extensions to be included in (extended) client hello. + /// </summary> + /// <returns> + /// A <see cref="IDictionary"/> (<see cref="ExtensionType"/> -> byte[]). May be null. + /// </returns> + /// <exception cref="IOException"></exception> + IDictionary GetClientExtensions(); + + /// <summary> + /// Reports the session ID once it has been determined. + /// </summary> + /// <param name="sessionID"> + /// A <see cref="System.Byte"/> + /// </param> + void NotifySessionID(byte[] sessionID); + + /// <summary> + /// Report the cipher suite that was selected by the server. + /// </summary> + /// <remarks> + /// The protocol handler validates this value against the offered cipher suites + /// <seealso cref="GetCipherSuites"/> + /// </remarks> + /// <param name="selectedCipherSuite"> + /// A <see cref="CipherSuite"/> + /// </param> + void NotifySelectedCipherSuite(CipherSuite selectedCipherSuite); + + /// <summary> + /// Report the compression method that was selected by the server. + /// </summary> + /// <remarks> + /// The protocol handler validates this value against the offered compression methods + /// <seealso cref="GetCompressionMethods"/> + /// </remarks> + /// <param name="selectedCompressionMethod"> + /// A <see cref="CompressionMethod"/> + /// </param> + void NotifySelectedCompressionMethod(CompressionMethod selectedCompressionMethod); + + /// <summary> + /// Report whether the server supports secure renegotiation + /// </summary> + /// <remarks> + /// The protocol handler automatically processes the relevant extensions + /// </remarks> + /// <param name="secureRenegotiation"> + /// A <see cref="System.Boolean"/>, true if the server supports secure renegotiation + /// </param> + /// <exception cref="IOException"></exception> + void NotifySecureRenegotiation(bool secureRenegotiation); + + /// <summary> + /// Report the extensions from an extended server hello. + /// </summary> + /// <remarks> + /// Will only be called if we returned a non-null result from <see cref="GetClientExtensions"/>. + /// </remarks> + /// <param name="serverExtensions"> + /// A <see cref="IDictionary"/> (<see cref="ExtensionType"/> -> byte[]) + /// </param> + void ProcessServerExtensions(IDictionary serverExtensions); + + /// <summary> + /// Return an implementation of <see cref="TlsKeyExchange"/> to negotiate the key exchange + /// part of the protocol. + /// </summary> + /// <returns> + /// A <see cref="TlsKeyExchange"/> + /// </returns> + /// <exception cref="IOException"/> + TlsKeyExchange GetKeyExchange(); + + /// <summary> + /// Return an implementation of <see cref="TlsAuthentication"/> to handle authentication + /// part of the protocol. + /// </summary> + /// <exception cref="IOException"/> + TlsAuthentication GetAuthentication(); + + /// <summary> + /// Return an implementation of <see cref="TlsCompression"/> to handle record compression. + /// </summary> + /// <exception cref="IOException"/> + TlsCompression GetCompression(); + + /// <summary> + /// Return an implementation of <see cref="TlsCipher"/> to use for encryption/decryption. + /// </summary> + /// <returns> + /// A <see cref="TlsCipher"/> + /// </returns> + /// <exception cref="IOException"/> + TlsCipher GetCipher(); + } +} diff --git a/Crypto/src/crypto/tls/TlsClientContext.cs b/Crypto/src/crypto/tls/TlsClientContext.cs new file mode 100644 index 000000000..dbb10aa76 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsClientContext.cs @@ -0,0 +1,15 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsClientContext + { + SecureRandom SecureRandom { get; } + + SecurityParameters SecurityParameters { get; } + + object UserObject { get; set; } + } +} diff --git a/Crypto/src/crypto/tls/TlsClientContextImpl.cs b/Crypto/src/crypto/tls/TlsClientContextImpl.cs new file mode 100644 index 000000000..9d5dee232 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsClientContextImpl.cs @@ -0,0 +1,37 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsClientContextImpl + : TlsClientContext + { + private readonly SecureRandom secureRandom; + private readonly SecurityParameters securityParameters; + + private object userObject = null; + + internal TlsClientContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters) + { + this.secureRandom = secureRandom; + this.securityParameters = securityParameters; + } + + public virtual SecureRandom SecureRandom + { + get { return secureRandom; } + } + + public virtual SecurityParameters SecurityParameters + { + get { return securityParameters; } + } + + public virtual object UserObject + { + get { return userObject; } + set { this.userObject = value; } + } + } +} diff --git a/Crypto/src/crypto/tls/TlsCompression.cs b/Crypto/src/crypto/tls/TlsCompression.cs new file mode 100644 index 000000000..177d64b7e --- /dev/null +++ b/Crypto/src/crypto/tls/TlsCompression.cs @@ -0,0 +1,12 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsCompression + { + Stream Compress(Stream output); + + Stream Decompress(Stream output); + } +} diff --git a/Crypto/src/crypto/tls/TlsCredentials.cs b/Crypto/src/crypto/tls/TlsCredentials.cs new file mode 100644 index 000000000..5c5f1c02e --- /dev/null +++ b/Crypto/src/crypto/tls/TlsCredentials.cs @@ -0,0 +1,9 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsCredentials + { + Certificate Certificate { get; } + } +} diff --git a/Crypto/src/crypto/tls/TlsDHKeyExchange.cs b/Crypto/src/crypto/tls/TlsDHKeyExchange.cs new file mode 100644 index 000000000..40ac416e0 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsDHKeyExchange.cs @@ -0,0 +1,201 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// TLS 1.0 DH key exchange. + /// </summary> + internal class TlsDHKeyExchange + : TlsKeyExchange + { + protected TlsClientContext context; + protected KeyExchangeAlgorithm keyExchange; + protected TlsSigner tlsSigner; + + protected AsymmetricKeyParameter serverPublicKey = null; + protected DHPublicKeyParameters dhAgreeServerPublicKey = null; + protected TlsAgreementCredentials agreementCredentials; + protected DHPrivateKeyParameters dhAgreeClientPrivateKey = null; + + internal TlsDHKeyExchange(TlsClientContext context, KeyExchangeAlgorithm keyExchange) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.DH_RSA: + case KeyExchangeAlgorithm.DH_DSS: + this.tlsSigner = null; + break; + case KeyExchangeAlgorithm.DHE_RSA: + this.tlsSigner = new TlsRsaSigner(); + break; + case KeyExchangeAlgorithm.DHE_DSS: + this.tlsSigner = new TlsDssSigner(); + break; + default: + throw new ArgumentException("unsupported key exchange algorithm", "keyExchange"); + } + + this.context = context; + this.keyExchange = keyExchange; + } + + public virtual void SkipServerCertificate() + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void ProcessServerCertificate(Certificate serverCertificate) + { + X509CertificateStructure x509Cert = serverCertificate.certs[0]; + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + + try + { + this.serverPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } + catch (Exception) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + if (tlsSigner == null) + { + try + { + this.dhAgreeServerPublicKey = ValidateDHPublicKey((DHPublicKeyParameters)this.serverPublicKey); + } + catch (InvalidCastException) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.KeyAgreement); + } + else + { + if (!tlsSigner.IsValidPublicKey(this.serverPublicKey)) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + } + + // TODO + /* + * Perform various checks per RFC2246 7.4.2: "Unless otherwise specified, the + * signing algorithm for the certificate must be the same as the algorithm for the + * certificate key." + */ + } + + public virtual void SkipServerKeyExchange() + { + // OK + } + + public virtual void ProcessServerKeyExchange(Stream input) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + ClientCertificateType[] types = certificateRequest.CertificateTypes; + foreach (ClientCertificateType type in types) + { + switch (type) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.rsa_fixed_dh: + case ClientCertificateType.dss_fixed_dh: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public virtual void SkipClientCredentials() + { + this.agreementCredentials = null; + } + + public virtual void ProcessClientCredentials(TlsCredentials clientCredentials) + { + if (clientCredentials is TlsAgreementCredentials) + { + // TODO Validate client cert has matching parameters (see 'areCompatibleParameters')? + + this.agreementCredentials = (TlsAgreementCredentials)clientCredentials; + } + else if (clientCredentials is TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public virtual void GenerateClientKeyExchange(Stream output) + { + /* + * RFC 2246 7.4.7.2 If the client certificate already contains a suitable + * Diffie-Hellman key, then Yc is implicit and does not need to be sent again. In + * this case, the Client Key Exchange message will be sent, but will be empty. + */ + if (agreementCredentials == null) + { + GenerateEphemeralClientKeyExchange(dhAgreeServerPublicKey.Parameters, output); + } + } + + public virtual byte[] GeneratePremasterSecret() + { + if (agreementCredentials != null) + { + return agreementCredentials.GenerateAgreement(dhAgreeServerPublicKey); + } + + return CalculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey); + } + + protected virtual bool AreCompatibleParameters(DHParameters a, DHParameters b) + { + return a.P.Equals(b.P) && a.G.Equals(b.G); + } + + protected virtual byte[] CalculateDHBasicAgreement(DHPublicKeyParameters publicKey, + DHPrivateKeyParameters privateKey) + { + return TlsDHUtilities.CalculateDHBasicAgreement(publicKey, privateKey); + } + + protected virtual AsymmetricCipherKeyPair GenerateDHKeyPair(DHParameters dhParams) + { + return TlsDHUtilities.GenerateDHKeyPair(context.SecureRandom, dhParams); + } + + protected virtual void GenerateEphemeralClientKeyExchange(DHParameters dhParams, Stream output) + { + this.dhAgreeClientPrivateKey = TlsDHUtilities.GenerateEphemeralClientKeyExchange( + context.SecureRandom, dhParams, output); + } + + protected virtual DHPublicKeyParameters ValidateDHPublicKey(DHPublicKeyParameters key) + { + return TlsDHUtilities.ValidateDHPublicKey(key); + } + } +} diff --git a/Crypto/src/crypto/tls/TlsDHUtilities.cs b/Crypto/src/crypto/tls/TlsDHUtilities.cs new file mode 100644 index 000000000..733749ea1 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsDHUtilities.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsDHUtilities + { + public static byte[] CalculateDHBasicAgreement(DHPublicKeyParameters publicKey, + DHPrivateKeyParameters privateKey) + { + DHBasicAgreement dhAgree = new DHBasicAgreement(); + dhAgree.Init(privateKey); + BigInteger agreement = dhAgree.CalculateAgreement(publicKey); + return BigIntegers.AsUnsignedByteArray(agreement); + } + + public static AsymmetricCipherKeyPair GenerateDHKeyPair(SecureRandom random, DHParameters dhParams) + { + DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator(); + dhGen.Init(new DHKeyGenerationParameters(random, dhParams)); + return dhGen.GenerateKeyPair(); + } + + public static DHPrivateKeyParameters GenerateEphemeralClientKeyExchange(SecureRandom random, + DHParameters dhParams, Stream output) + { + AsymmetricCipherKeyPair dhAgreeClientKeyPair = GenerateDHKeyPair(random, dhParams); + DHPrivateKeyParameters dhAgreeClientPrivateKey = + (DHPrivateKeyParameters)dhAgreeClientKeyPair.Private; + + BigInteger Yc = ((DHPublicKeyParameters)dhAgreeClientKeyPair.Public).Y; + byte[] keData = BigIntegers.AsUnsignedByteArray(Yc); + TlsUtilities.WriteOpaque16(keData, output); + + return dhAgreeClientPrivateKey; + } + + public static DHPublicKeyParameters ValidateDHPublicKey(DHPublicKeyParameters key) + { + BigInteger Y = key.Y; + DHParameters parameters = key.Parameters; + BigInteger p = parameters.P; + BigInteger g = parameters.G; + + if (!p.IsProbablePrime(2)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + if (g.CompareTo(BigInteger.Two) < 0 || g.CompareTo(p.Subtract(BigInteger.Two)) > 0) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + if (Y.CompareTo(BigInteger.Two) < 0 || Y.CompareTo(p.Subtract(BigInteger.One)) > 0) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + // TODO See RFC 2631 for more discussion of Diffie-Hellman validation + + return key; + } + } +} \ No newline at end of file diff --git a/Crypto/src/crypto/tls/TlsDeflateCompression.cs b/Crypto/src/crypto/tls/TlsDeflateCompression.cs new file mode 100644 index 000000000..146c961c7 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsDeflateCompression.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Utilities.Zlib; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsDeflateCompression + : TlsCompression + { + protected ZStream zIn, zOut; + + public TlsDeflateCompression() + { + this.zIn = new ZStream(); + this.zIn.inflateInit(); + + this.zOut = new ZStream(); + // TODO Allow custom setting + this.zOut.deflateInit(JZlib.Z_DEFAULT_COMPRESSION); + } + + public virtual Stream Compress(Stream output) + { + return new DeflateOutputStream(output, zOut, true); + } + + public virtual Stream Decompress(Stream output) + { + return new DeflateOutputStream(output, zIn, false); + } + + protected class DeflateOutputStream : ZOutputStream + { + public DeflateOutputStream(Stream output, ZStream z, bool compress) + : base(output) + { + this.z = z; + this.compress = compress; + // TODO http://www.bolet.org/~pornin/deflate-flush.html says we should use Z_SYNC_FLUSH + this.FlushMode = JZlib.Z_PARTIAL_FLUSH; + } + } + } +} diff --git a/Crypto/src/crypto/tls/TlsDheKeyExchange.cs b/Crypto/src/crypto/tls/TlsDheKeyExchange.cs new file mode 100644 index 000000000..edadaeb38 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsDheKeyExchange.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsDheKeyExchange + : TlsDHKeyExchange + { + internal TlsDheKeyExchange(TlsClientContext context, KeyExchangeAlgorithm keyExchange) + : base(context, keyExchange) + { + } + + public override void SkipServerKeyExchange() + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessServerKeyExchange(Stream input) + { + SecurityParameters securityParameters = context.SecurityParameters; + + ISigner signer = InitSigner(tlsSigner, securityParameters); + Stream sigIn = new SignerStream(input, signer, null); + + byte[] pBytes = TlsUtilities.ReadOpaque16(sigIn); + byte[] gBytes = TlsUtilities.ReadOpaque16(sigIn); + byte[] YsBytes = TlsUtilities.ReadOpaque16(sigIn); + + byte[] sigByte = TlsUtilities.ReadOpaque16(input); + if (!signer.VerifySignature(sigByte)) + { + throw new TlsFatalAlert(AlertDescription.bad_certificate); + } + + BigInteger p = new BigInteger(1, pBytes); + BigInteger g = new BigInteger(1, gBytes); + BigInteger Ys = new BigInteger(1, YsBytes); + + this.dhAgreeServerPublicKey = ValidateDHPublicKey( + new DHPublicKeyParameters(Ys, new DHParameters(p, g))); + } + + protected virtual ISigner InitSigner(TlsSigner tlsSigner, SecurityParameters securityParameters) + { + ISigner signer = tlsSigner.CreateVerifyer(this.serverPublicKey); + signer.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + signer.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + return signer; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsDsaSigner.cs b/Crypto/src/crypto/tls/TlsDsaSigner.cs new file mode 100644 index 000000000..27d7b1f91 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsDsaSigner.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal abstract class TlsDsaSigner + : TlsSigner + { + public virtual byte[] CalculateRawSignature(SecureRandom random, + AsymmetricKeyParameter privateKey, byte[] md5andsha1) + { + ISigner s = MakeSigner(new NullDigest(), true, new ParametersWithRandom(privateKey, random)); + // Note: Only use the SHA1 part of the hash + s.BlockUpdate(md5andsha1, 16, 20); + return s.GenerateSignature(); + } + + public bool VerifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5andsha1) + { + ISigner s = MakeSigner(new NullDigest(), false, publicKey); + // Note: Only use the SHA1 part of the hash + s.BlockUpdate(md5andsha1, 16, 20); + return s.VerifySignature(sigBytes); + } + + public virtual ISigner CreateSigner(SecureRandom random, AsymmetricKeyParameter privateKey) + { + return MakeSigner(new Sha1Digest(), true, new ParametersWithRandom(privateKey, random)); + } + + public virtual ISigner CreateVerifyer(AsymmetricKeyParameter publicKey) + { + return MakeSigner(new Sha1Digest(), false, publicKey); + } + + public abstract bool IsValidPublicKey(AsymmetricKeyParameter publicKey); + + protected virtual ISigner MakeSigner(IDigest d, bool forSigning, ICipherParameters cp) + { + ISigner s = new DsaDigestSigner(CreateDsaImpl(), d); + s.Init(forSigning, cp); + return s; + } + + protected abstract IDsa CreateDsaImpl(); + } +} diff --git a/Crypto/src/crypto/tls/TlsDssSigner.cs b/Crypto/src/crypto/tls/TlsDssSigner.cs new file mode 100644 index 000000000..c6f1abcec --- /dev/null +++ b/Crypto/src/crypto/tls/TlsDssSigner.cs @@ -0,0 +1,21 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsDssSigner + : TlsDsaSigner + { + public override bool IsValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey is DsaPublicKeyParameters; + } + + protected override IDsa CreateDsaImpl() + { + return new DsaSigner(); + } + } +} diff --git a/Crypto/src/crypto/tls/TlsECDHKeyExchange.cs b/Crypto/src/crypto/tls/TlsECDHKeyExchange.cs new file mode 100644 index 000000000..83983ba47 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsECDHKeyExchange.cs @@ -0,0 +1,230 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * ECDH key exchange (see RFC 4492) + */ + internal class TlsECDHKeyExchange + : TlsKeyExchange + { + protected TlsClientContext context; + protected KeyExchangeAlgorithm keyExchange; + protected TlsSigner tlsSigner; + + protected AsymmetricKeyParameter serverPublicKey; + protected ECPublicKeyParameters ecAgreeServerPublicKey; + protected TlsAgreementCredentials agreementCredentials; + protected ECPrivateKeyParameters ecAgreeClientPrivateKey = null; + + internal TlsECDHKeyExchange(TlsClientContext context, KeyExchangeAlgorithm keyExchange) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.ECDHE_RSA: + this.tlsSigner = new TlsRsaSigner(); + break; + case KeyExchangeAlgorithm.ECDHE_ECDSA: + this.tlsSigner = new TlsECDsaSigner(); + break; + case KeyExchangeAlgorithm.ECDH_RSA: + case KeyExchangeAlgorithm.ECDH_ECDSA: + this.tlsSigner = null; + break; + default: + throw new ArgumentException("unsupported key exchange algorithm", "keyExchange"); + } + + this.context = context; + this.keyExchange = keyExchange; + } + + public virtual void SkipServerCertificate() + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void ProcessServerCertificate(Certificate serverCertificate) + { + X509CertificateStructure x509Cert = serverCertificate.certs[0]; + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + + try + { + this.serverPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } + catch (Exception) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + if (tlsSigner == null) + { + try + { + this.ecAgreeServerPublicKey = ValidateECPublicKey((ECPublicKeyParameters)this.serverPublicKey); + } + catch (InvalidCastException) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.KeyAgreement); + } + else + { + if (!tlsSigner.IsValidPublicKey(this.serverPublicKey)) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + } + + // TODO + /* + * Perform various checks per RFC2246 7.4.2: "Unless otherwise specified, the + * signing algorithm for the certificate must be the same as the algorithm for the + * certificate key." + */ + } + + public virtual void SkipServerKeyExchange() + { + // do nothing + } + + public virtual void ProcessServerKeyExchange(Stream input) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + /* + * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable + * with ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is + * prohibited because the use of a long-term ECDH client key would jeopardize the + * forward secrecy property of these algorithms. + */ + ClientCertificateType[] types = certificateRequest.CertificateTypes; + foreach (ClientCertificateType type in types) + { + switch (type) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + case ClientCertificateType.rsa_fixed_ecdh: + case ClientCertificateType.ecdsa_fixed_ecdh: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public virtual void SkipClientCredentials() + { + this.agreementCredentials = null; + } + + public virtual void ProcessClientCredentials(TlsCredentials clientCredentials) + { + if (clientCredentials is TlsAgreementCredentials) + { + // TODO Validate client cert has matching parameters (see 'AreOnSameCurve')? + + this.agreementCredentials = (TlsAgreementCredentials)clientCredentials; + } + else if (clientCredentials is TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public virtual void GenerateClientKeyExchange(Stream output) + { + if (agreementCredentials == null) + { + GenerateEphemeralClientKeyExchange(ecAgreeServerPublicKey.Parameters, output); + } + } + + public virtual byte[] GeneratePremasterSecret() + { + if (agreementCredentials != null) + { + return agreementCredentials.GenerateAgreement(ecAgreeServerPublicKey); + } + + return CalculateECDHBasicAgreement(ecAgreeServerPublicKey, ecAgreeClientPrivateKey); + } + + protected virtual bool AreOnSameCurve(ECDomainParameters a, ECDomainParameters b) + { + // TODO Move to ECDomainParameters.Equals() or other utility method? + return a.Curve.Equals(b.Curve) && a.G.Equals(b.G) && a.N.Equals(b.N) && a.H.Equals(b.H); + } + + protected virtual byte[] ExternalizeKey(ECPublicKeyParameters keyParameters) + { + // TODO Add support for compressed encoding and SPF extension + + /* + * RFC 4492 5.7. ...an elliptic curve point in uncompressed or compressed format. + * Here, the format MUST conform to what the server has requested through a + * Supported Point Formats Extension if this extension was used, and MUST be + * uncompressed if this extension was not used. + */ + return keyParameters.Q.GetEncoded(); + } + + protected virtual AsymmetricCipherKeyPair GenerateECKeyPair(ECDomainParameters ecParams) + { + ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator(); + ECKeyGenerationParameters keyGenerationParameters = new ECKeyGenerationParameters(ecParams, + context.SecureRandom); + keyPairGenerator.Init(keyGenerationParameters); + return keyPairGenerator.GenerateKeyPair(); + } + + protected virtual void GenerateEphemeralClientKeyExchange(ECDomainParameters ecParams, Stream output) + { + AsymmetricCipherKeyPair ecAgreeClientKeyPair = GenerateECKeyPair(ecParams); + this.ecAgreeClientPrivateKey = (ECPrivateKeyParameters)ecAgreeClientKeyPair.Private; + + byte[] keData = ExternalizeKey((ECPublicKeyParameters)ecAgreeClientKeyPair.Public); + TlsUtilities.WriteOpaque8(keData, output); + } + + protected virtual byte[] CalculateECDHBasicAgreement(ECPublicKeyParameters publicKey, + ECPrivateKeyParameters privateKey) + { + ECDHBasicAgreement basicAgreement = new ECDHBasicAgreement(); + basicAgreement.Init(privateKey); + BigInteger agreement = basicAgreement.CalculateAgreement(publicKey); + return BigIntegers.AsUnsignedByteArray(agreement); + } + + protected virtual ECPublicKeyParameters ValidateECPublicKey(ECPublicKeyParameters key) + { + // TODO Check RFC 4492 for validation + return key; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsECDheKeyExchange.cs b/Crypto/src/crypto/tls/TlsECDheKeyExchange.cs new file mode 100644 index 000000000..5516154ce --- /dev/null +++ b/Crypto/src/crypto/tls/TlsECDheKeyExchange.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math.EC; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /** + * ECDHE key exchange (see RFC 4492) + */ + internal class TlsECDheKeyExchange : TlsECDHKeyExchange + { + internal TlsECDheKeyExchange(TlsClientContext context, KeyExchangeAlgorithm keyExchange) + : base(context, keyExchange) + { + } + + public override void SkipServerKeyExchange() + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public override void ProcessServerKeyExchange(Stream input) + { + SecurityParameters securityParameters = context.SecurityParameters; + + ISigner signer = InitSigner(tlsSigner, securityParameters); + Stream sigIn = new SignerStream(input, signer, null); + + ECCurveType curveType = (ECCurveType)TlsUtilities.ReadUint8(sigIn); + ECDomainParameters curve_params; + + // Currently, we only support named curves + if (curveType == ECCurveType.named_curve) + { + NamedCurve namedCurve = (NamedCurve)TlsUtilities.ReadUint16(sigIn); + + // TODO Check namedCurve is one we offered? + + curve_params = NamedCurveHelper.GetECParameters(namedCurve); + } + else + { + // TODO Add support for explicit curve parameters (read from sigIn) + + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + byte[] publicBytes = TlsUtilities.ReadOpaque8(sigIn); + + byte[] sigByte = TlsUtilities.ReadOpaque16(input); + if (!signer.VerifySignature(sigByte)) + { + throw new TlsFatalAlert(AlertDescription.bad_certificate); + } + + // TODO Check curve_params not null + + ECPoint Q = curve_params.Curve.DecodePoint(publicBytes); + + this.ecAgreeServerPublicKey = ValidateECPublicKey(new ECPublicKeyParameters(Q, curve_params)); + } + + public override void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + /* + * RFC 4492 3. [...] The ECDSA_fixed_ECDH and RSA_fixed_ECDH mechanisms are usable + * with ECDH_ECDSA and ECDH_RSA. Their use with ECDHE_ECDSA and ECDHE_RSA is + * prohibited because the use of a long-term ECDH client key would jeopardize the + * forward secrecy property of these algorithms. + */ + ClientCertificateType[] types = certificateRequest.CertificateTypes; + foreach (ClientCertificateType type in types) + { + switch (type) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public override void ProcessClientCredentials(TlsCredentials clientCredentials) + { + if (clientCredentials is TlsSignerCredentials) + { + // OK + } + else + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + protected virtual ISigner InitSigner(TlsSigner tlsSigner, SecurityParameters securityParameters) + { + ISigner signer = tlsSigner.CreateVerifyer(this.serverPublicKey); + signer.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + signer.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + return signer; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsECDsaSigner.cs b/Crypto/src/crypto/tls/TlsECDsaSigner.cs new file mode 100644 index 000000000..3c30fdc0c --- /dev/null +++ b/Crypto/src/crypto/tls/TlsECDsaSigner.cs @@ -0,0 +1,21 @@ +using System; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsECDsaSigner + : TlsDsaSigner + { + public override bool IsValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey is ECPublicKeyParameters; + } + + protected override IDsa CreateDsaImpl() + { + return new ECDsaSigner(); + } + } +} diff --git a/Crypto/src/crypto/tls/TlsException.cs b/Crypto/src/crypto/tls/TlsException.cs new file mode 100644 index 000000000..fa3e73273 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsException : Exception + { + public TlsException() : base() { } + public TlsException(string message) : base(message) { } + public TlsException(string message, Exception exception) : base(message, exception) { } + } +} diff --git a/Crypto/src/crypto/tls/TlsFatalAlert.cs b/Crypto/src/crypto/tls/TlsFatalAlert.cs new file mode 100644 index 000000000..0a9cc6f3a --- /dev/null +++ b/Crypto/src/crypto/tls/TlsFatalAlert.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsFatalAlert + : IOException + { + private readonly AlertDescription alertDescription; + + public TlsFatalAlert(AlertDescription alertDescription) + { + this.alertDescription = alertDescription; + } + + public AlertDescription AlertDescription + { + get { return alertDescription; } + } + } +} diff --git a/Crypto/src/crypto/tls/TlsKeyExchange.cs b/Crypto/src/crypto/tls/TlsKeyExchange.cs new file mode 100644 index 000000000..5102edbec --- /dev/null +++ b/Crypto/src/crypto/tls/TlsKeyExchange.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// A generic interface for key exchange implementations in TLS 1.0. + /// </summary> + public interface TlsKeyExchange + { + /// <exception cref="IOException"/> + void SkipServerCertificate(); + + /// <exception cref="IOException"/> + void ProcessServerCertificate(Certificate serverCertificate); + + /// <exception cref="IOException"/> + void SkipServerKeyExchange(); + + /// <exception cref="IOException"/> + void ProcessServerKeyExchange(Stream input); + + /// <exception cref="IOException"/> + void ValidateCertificateRequest(CertificateRequest certificateRequest); + + /// <exception cref="IOException"/> + void SkipClientCredentials(); + + /// <exception cref="IOException"/> + void ProcessClientCredentials(TlsCredentials clientCredentials); + + /// <exception cref="IOException"/> + void GenerateClientKeyExchange(Stream output); + + /// <exception cref="IOException"/> + byte[] GeneratePremasterSecret(); + } +} diff --git a/Crypto/src/crypto/tls/TlsMac.cs b/Crypto/src/crypto/tls/TlsMac.cs new file mode 100644 index 000000000..0e58b89dc --- /dev/null +++ b/Crypto/src/crypto/tls/TlsMac.cs @@ -0,0 +1,106 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <remarks> + /// A generic TLS MAC implementation, which can be used with any kind of + /// IDigest to act as an HMAC. + /// </remarks> + public class TlsMac + { + protected long seqNo; + protected byte[] secret; + protected HMac mac; + + /** + * Generate a new instance of an TlsMac. + * + * @param digest The digest to use. + * @param key_block A byte-array where the key for this mac is located. + * @param offset The number of bytes to skip, before the key starts in the buffer. + * @param len The length of the key. + */ + public TlsMac( + IDigest digest, + byte[] key_block, + int offset, + int len) + { + this.seqNo = 0; + + KeyParameter param = new KeyParameter(key_block, offset, len); + + this.secret = Arrays.Clone(param.GetKey()); + + this.mac = new HMac(digest); + this.mac.Init(param); + } + + /** + * @return the MAC write secret + */ + public virtual byte[] GetMacSecret() + { + return this.secret; + } + + /** + * @return the current write sequence number + */ + public virtual long SequenceNumber + { + get { return this.seqNo; } + } + + /** + * Increment the current write sequence number + */ + public virtual void IncSequenceNumber() + { + this.seqNo++; + } + + /** + * @return The Keysize of the mac. + */ + public virtual int Size + { + get { return mac.GetMacSize(); } + } + + /** + * Calculate the mac for some given data. + * <p/> + * TlsMac will keep track of the sequence number internally. + * + * @param type The message type of the message. + * @param message A byte-buffer containing the message. + * @param offset The number of bytes to skip, before the message starts. + * @param len The length of the message. + * @return A new byte-buffer containing the mac value. + */ + public virtual byte[] CalculateMac( + ContentType type, + byte[] message, + int offset, + int len) + { + byte[] macHeader = new byte[13]; + TlsUtilities.WriteUint64(seqNo++, macHeader, 0); + TlsUtilities.WriteUint8((byte)type, macHeader, 8); + TlsUtilities.WriteVersion(macHeader, 9); + TlsUtilities.WriteUint16(len, macHeader, 11); + + mac.BlockUpdate(macHeader, 0, macHeader.Length); + mac.BlockUpdate(message, offset, len); + return MacUtilities.DoFinal(mac); + } + } +} diff --git a/Crypto/src/crypto/tls/TlsNullCipher.cs b/Crypto/src/crypto/tls/TlsNullCipher.cs new file mode 100644 index 000000000..b76f76d9c --- /dev/null +++ b/Crypto/src/crypto/tls/TlsNullCipher.cs @@ -0,0 +1,28 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// A NULL cipher suite, for use during handshake. + /// </summary> + public class TlsNullCipher + : TlsCipher + { + public virtual byte[] EncodePlaintext(ContentType type, byte[] plaintext, int offset, int len) + { + return CopyData(plaintext, offset, len); + } + + public virtual byte[] DecodeCiphertext(ContentType type, byte[] ciphertext, int offset, int len) + { + return CopyData(ciphertext, offset, len); + } + + protected virtual byte[] CopyData(byte[] text, int offset, int len) + { + byte[] result = new byte[len]; + Array.Copy(text, offset, result, 0, len); + return result; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsNullCompression.cs b/Crypto/src/crypto/tls/TlsNullCompression.cs new file mode 100644 index 000000000..45f8fc708 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsNullCompression.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsNullCompression + : TlsCompression + { + public virtual Stream Compress(Stream output) + { + return output; + } + + public virtual Stream Decompress(Stream output) + { + return output; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsProtocolHandler.cs b/Crypto/src/crypto/tls/TlsProtocolHandler.cs new file mode 100644 index 000000000..6d2b0b144 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsProtocolHandler.cs @@ -0,0 +1,1259 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Agreement.Srp; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Prng; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <remarks>An implementation of all high level protocols in TLS 1.0.</remarks> + public class TlsProtocolHandler + { + /* + * Our Connection states + */ + private const short CS_CLIENT_HELLO_SEND = 1; + private const short CS_SERVER_HELLO_RECEIVED = 2; + private const short CS_SERVER_CERTIFICATE_RECEIVED = 3; + private const short CS_SERVER_KEY_EXCHANGE_RECEIVED = 4; + private const short CS_CERTIFICATE_REQUEST_RECEIVED = 5; + private const short CS_SERVER_HELLO_DONE_RECEIVED = 6; + private const short CS_CLIENT_KEY_EXCHANGE_SEND = 7; + private const short CS_CERTIFICATE_VERIFY_SEND = 8; + private const short CS_CLIENT_CHANGE_CIPHER_SPEC_SEND = 9; + private const short CS_CLIENT_FINISHED_SEND = 10; + private const short CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED = 11; + private const short CS_DONE = 12; + + private static readonly byte[] emptybuf = new byte[0]; + + private static readonly string TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack"; + + /* + * Queues for data from some protocols. + */ + + private ByteQueue applicationDataQueue = new ByteQueue(); + private ByteQueue changeCipherSpecQueue = new ByteQueue(); + private ByteQueue alertQueue = new ByteQueue(); + private ByteQueue handshakeQueue = new ByteQueue(); + + /* + * The Record Stream we use + */ + private RecordStream rs; + private SecureRandom random; + + private TlsStream tlsStream = null; + + private bool closed = false; + private bool failedWithError = false; + private bool appDataReady = false; + private IDictionary clientExtensions; + + private SecurityParameters securityParameters = null; + + private TlsClientContextImpl tlsClientContext = null; + private TlsClient tlsClient = null; + private CipherSuite[] offeredCipherSuites = null; + private CompressionMethod[] offeredCompressionMethods = null; + private TlsKeyExchange keyExchange = null; + private TlsAuthentication authentication = null; + private CertificateRequest certificateRequest = null; + + private short connection_state = 0; + + private static SecureRandom CreateSecureRandom() + { + /* + * We use our threaded seed generator to generate a good random seed. If the user + * has a better random seed, he should use the constructor with a SecureRandom. + * + * Hopefully, 20 bytes in fast mode are good enough. + */ + byte[] seed = new ThreadedSeedGenerator().GenerateSeed(20, true); + + return new SecureRandom(seed); + } + + public TlsProtocolHandler( + Stream s) + : this(s, s) + { + } + + public TlsProtocolHandler( + Stream s, + SecureRandom sr) + : this(s, s, sr) + { + } + + /// <remarks>Both streams can be the same object</remarks> + public TlsProtocolHandler( + Stream inStr, + Stream outStr) + : this(inStr, outStr, CreateSecureRandom()) + { + } + + /// <remarks>Both streams can be the same object</remarks> + public TlsProtocolHandler( + Stream inStr, + Stream outStr, + SecureRandom sr) + { + this.rs = new RecordStream(this, inStr, outStr); + this.random = sr; + } + + internal void ProcessData( + ContentType protocol, + byte[] buf, + int offset, + int len) + { + /* + * Have a look at the protocol type, and add it to the correct queue. + */ + switch (protocol) + { + case ContentType.change_cipher_spec: + changeCipherSpecQueue.AddData(buf, offset, len); + ProcessChangeCipherSpec(); + break; + case ContentType.alert: + alertQueue.AddData(buf, offset, len); + ProcessAlert(); + break; + case ContentType.handshake: + handshakeQueue.AddData(buf, offset, len); + ProcessHandshake(); + break; + case ContentType.application_data: + if (!appDataReady) + { + this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + applicationDataQueue.AddData(buf, offset, len); + ProcessApplicationData(); + break; + default: + /* + * Uh, we don't know this protocol. + * + * RFC2246 defines on page 13, that we should ignore this. + */ + break; + } + } + + private void ProcessHandshake() + { + bool read; + do + { + read = false; + + /* + * We need the first 4 bytes, they contain type and length of + * the message. + */ + if (handshakeQueue.Available >= 4) + { + byte[] beginning = new byte[4]; + handshakeQueue.Read(beginning, 0, 4, 0); + MemoryStream bis = new MemoryStream(beginning, false); + HandshakeType type = (HandshakeType)TlsUtilities.ReadUint8(bis); + int len = TlsUtilities.ReadUint24(bis); + + /* + * Check if we have enough bytes in the buffer to read + * the full message. + */ + if (handshakeQueue.Available >= (len + 4)) + { + /* + * Read the message. + */ + byte[] buf = new byte[len]; + handshakeQueue.Read(buf, 0, len, 4); + handshakeQueue.RemoveData(len + 4); + + /* + * RFC 2246 7.4.9. The value handshake_messages includes all + * handshake messages starting at client hello up to, but not + * including, this finished message. [..] Note: [Also,] Hello Request + * messages are omitted from handshake hashes. + */ + switch (type) + { + case HandshakeType.hello_request: + case HandshakeType.finished: + break; + default: + rs.UpdateHandshakeData(beginning, 0, 4); + rs.UpdateHandshakeData(buf, 0, len); + break; + } + + /* + * Now, parse the message. + */ + ProcessHandshakeMessage(type, buf); + read = true; + } + } + } + while (read); + } + + private void ProcessHandshakeMessage(HandshakeType type, byte[] buf) + { + MemoryStream inStr = new MemoryStream(buf, false); + + /* + * Check the type. + */ + switch (type) + { + case HandshakeType.certificate: + { + switch (connection_state) + { + case CS_SERVER_HELLO_RECEIVED: + { + // Parse the Certificate message and send to cipher suite + + Certificate serverCertificate = Certificate.Parse(inStr); + + AssertEmpty(inStr); + + this.keyExchange.ProcessServerCertificate(serverCertificate); + + this.authentication = tlsClient.GetAuthentication(); + this.authentication.NotifyServerCertificate(serverCertificate); + + break; + } + default: + this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + break; + } + + connection_state = CS_SERVER_CERTIFICATE_RECEIVED; + break; + } + case HandshakeType.finished: + switch (connection_state) + { + case CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED: + /* + * Read the checksum from the finished message, it has always 12 bytes. + */ + byte[] serverVerifyData = new byte[12]; + TlsUtilities.ReadFully(serverVerifyData, inStr); + + AssertEmpty(inStr); + + /* + * Calculate our own checksum. + */ + byte[] expectedServerVerifyData = TlsUtilities.PRF( + securityParameters.masterSecret, "server finished", + rs.GetCurrentHash(), 12); + + /* + * Compare both checksums. + */ + if (!Arrays.ConstantTimeAreEqual(expectedServerVerifyData, serverVerifyData)) + { + /* + * Wrong checksum in the finished message. + */ + this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + + connection_state = CS_DONE; + + /* + * We are now ready to receive application data. + */ + this.appDataReady = true; + break; + default: + this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + break; + } + break; + case HandshakeType.server_hello: + switch (connection_state) + { + case CS_CLIENT_HELLO_SEND: + /* + * Read the server hello message + */ + TlsUtilities.CheckVersion(inStr); + + /* + * Read the server random + */ + securityParameters.serverRandom = new byte[32]; + TlsUtilities.ReadFully(securityParameters.serverRandom, inStr); + + byte[] sessionID = TlsUtilities.ReadOpaque8(inStr); + if (sessionID.Length > 32) + { + this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + this.tlsClient.NotifySessionID(sessionID); + + /* + * Find out which CipherSuite the server has chosen and check that + * it was one of the offered ones. + */ + CipherSuite selectedCipherSuite = (CipherSuite)TlsUtilities.ReadUint16(inStr); + if (!ArrayContains(offeredCipherSuites, selectedCipherSuite) + || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + { + this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + this.tlsClient.NotifySelectedCipherSuite(selectedCipherSuite); + + /* + * Find out which CompressionMethod the server has chosen and check that + * it was one of the offered ones. + */ + CompressionMethod selectedCompressionMethod = (CompressionMethod)TlsUtilities.ReadUint8(inStr); + if (!ArrayContains(offeredCompressionMethods, selectedCompressionMethod)) + { + this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + this.tlsClient.NotifySelectedCompressionMethod(selectedCompressionMethod); + + /* + * RFC3546 2.2 The extended server hello message format MAY be + * sent in place of the server hello message when the client has + * requested extended functionality via the extended client hello + * message specified in Section 2.1. + * ... + * Note that the extended server hello message is only sent in response + * to an extended client hello message. This prevents the possibility + * that the extended server hello message could "break" existing TLS 1.0 + * clients. + */ + + /* + * TODO RFC 3546 2.3 + * If [...] the older session is resumed, then the server MUST ignore + * extensions appearing in the client hello, and send a server hello + * containing no extensions. + */ + + // ExtensionType -> byte[] + IDictionary serverExtensions = Platform.CreateHashtable(); + + if (inStr.Position < inStr.Length) + { + // Process extensions from extended server hello + byte[] extBytes = TlsUtilities.ReadOpaque16(inStr); + + MemoryStream ext = new MemoryStream(extBytes, false); + while (ext.Position < ext.Length) + { + ExtensionType extType = (ExtensionType)TlsUtilities.ReadUint16(ext); + byte[] extValue = TlsUtilities.ReadOpaque16(ext); + + // Note: RFC 5746 makes a special case for EXT_RenegotiationInfo + if (extType != ExtensionType.renegotiation_info + && !clientExtensions.Contains(extType)) + { + /* + * RFC 3546 2.3 + * Note that for all extension types (including those defined in + * future), the extension type MUST NOT appear in the extended server + * hello unless the same extension type appeared in the corresponding + * client hello. Thus clients MUST abort the handshake if they receive + * an extension type in the extended server hello that they did not + * request in the associated (extended) client hello. + */ + this.FailWithError(AlertLevel.fatal, AlertDescription.unsupported_extension); + } + + if (serverExtensions.Contains(extType)) + { + /* + * RFC 3546 2.3 + * Also note that when multiple extensions of different types are + * present in the extended client hello or the extended server hello, + * the extensions may appear in any order. There MUST NOT be more than + * one extension of the same type. + */ + this.FailWithError(AlertLevel.fatal, AlertDescription.illegal_parameter); + } + + serverExtensions.Add(extType, extValue); + } + } + + AssertEmpty(inStr); + + /* + * RFC 5746 3.4. When a ServerHello is received, the client MUST check if it + * includes the "renegotiation_info" extension: + */ + { + bool secure_negotiation = serverExtensions.Contains(ExtensionType.renegotiation_info); + + /* + * If the extension is present, set the secure_renegotiation flag + * to TRUE. The client MUST then verify that the length of the + * "renegotiated_connection" field is zero, and if it is not, MUST + * abort the handshake (by sending a fatal handshake_failure + * alert). + */ + if (secure_negotiation) + { + byte[] renegExtValue = (byte[])serverExtensions[ExtensionType.renegotiation_info]; + + if (!Arrays.ConstantTimeAreEqual(renegExtValue, + CreateRenegotiationInfo(emptybuf))) + { + this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + } + + tlsClient.NotifySecureRenegotiation(secure_negotiation); + } + + if (clientExtensions != null) + { + tlsClient.ProcessServerExtensions(serverExtensions); + } + + this.keyExchange = tlsClient.GetKeyExchange(); + + connection_state = CS_SERVER_HELLO_RECEIVED; + break; + default: + this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + break; + } + break; + case HandshakeType.server_hello_done: + switch (connection_state) + { + case CS_SERVER_HELLO_RECEIVED: + case CS_SERVER_CERTIFICATE_RECEIVED: + case CS_SERVER_KEY_EXCHANGE_RECEIVED: + case CS_CERTIFICATE_REQUEST_RECEIVED: + + // NB: Original code used case label fall-through + + if (connection_state == CS_SERVER_HELLO_RECEIVED) + { + // There was no server certificate message; check it's OK + this.keyExchange.SkipServerCertificate(); + this.authentication = null; + + // There was no server key exchange message; check it's OK + this.keyExchange.SkipServerKeyExchange(); + } + else if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED) + { + // There was no server key exchange message; check it's OK + this.keyExchange.SkipServerKeyExchange(); + } + + AssertEmpty(inStr); + + connection_state = CS_SERVER_HELLO_DONE_RECEIVED; + + TlsCredentials clientCreds = null; + if (certificateRequest == null) + { + this.keyExchange.SkipClientCredentials(); + } + else + { + clientCreds = this.authentication.GetClientCredentials(certificateRequest); + + Certificate clientCert; + if (clientCreds == null) + { + this.keyExchange.SkipClientCredentials(); + clientCert = Certificate.EmptyChain; + } + else + { + this.keyExchange.ProcessClientCredentials(clientCreds); + clientCert = clientCreds.Certificate; + } + + SendClientCertificate(clientCert); + } + + /* + * Send the client key exchange message, depending on the key + * exchange we are using in our CipherSuite. + */ + SendClientKeyExchange(); + + connection_state = CS_CLIENT_KEY_EXCHANGE_SEND; + + if (clientCreds != null && clientCreds is TlsSignerCredentials) + { + TlsSignerCredentials signerCreds = (TlsSignerCredentials)clientCreds; + byte[] md5andsha1 = rs.GetCurrentHash(); + byte[] clientCertificateSignature = signerCreds.GenerateCertificateSignature( + md5andsha1); + SendCertificateVerify(clientCertificateSignature); + + connection_state = CS_CERTIFICATE_VERIFY_SEND; + } + + /* + * Now, we send change cipher state + */ + byte[] cmessage = new byte[1]; + cmessage[0] = 1; + rs.WriteMessage(ContentType.change_cipher_spec, cmessage, 0, cmessage.Length); + + connection_state = CS_CLIENT_CHANGE_CIPHER_SPEC_SEND; + + /* + * Calculate the master_secret + */ + byte[] pms = this.keyExchange.GeneratePremasterSecret(); + + securityParameters.masterSecret = TlsUtilities.PRF(pms, "master secret", + TlsUtilities.Concat(securityParameters.clientRandom, securityParameters.serverRandom), + 48); + + // TODO Is there a way to ensure the data is really overwritten? + /* + * RFC 2246 8.1. The pre_master_secret should be deleted from + * memory once the master_secret has been computed. + */ + Array.Clear(pms, 0, pms.Length); + + /* + * Initialize our cipher suite + */ + rs.ClientCipherSpecDecided(tlsClient.GetCompression(), tlsClient.GetCipher()); + + /* + * Send our finished message. + */ + byte[] clientVerifyData = TlsUtilities.PRF(securityParameters.masterSecret, + "client finished", rs.GetCurrentHash(), 12); + + MemoryStream bos = new MemoryStream(); + TlsUtilities.WriteUint8((byte)HandshakeType.finished, bos); + TlsUtilities.WriteOpaque24(clientVerifyData, bos); + byte[] message = bos.ToArray(); + + rs.WriteMessage(ContentType.handshake, message, 0, message.Length); + + this.connection_state = CS_CLIENT_FINISHED_SEND; + break; + default: + this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + break; + } + break; + case HandshakeType.server_key_exchange: + { + switch (connection_state) + { + case CS_SERVER_HELLO_RECEIVED: + case CS_SERVER_CERTIFICATE_RECEIVED: + { + // NB: Original code used case label fall-through + if (connection_state == CS_SERVER_HELLO_RECEIVED) + { + // There was no server certificate message; check it's OK + this.keyExchange.SkipServerCertificate(); + this.authentication = null; + } + + this.keyExchange.ProcessServerKeyExchange(inStr); + + AssertEmpty(inStr); + break; + } + default: + this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + break; + } + + this.connection_state = CS_SERVER_KEY_EXCHANGE_RECEIVED; + break; + } + case HandshakeType.certificate_request: + switch (connection_state) + { + case CS_SERVER_CERTIFICATE_RECEIVED: + case CS_SERVER_KEY_EXCHANGE_RECEIVED: + { + // NB: Original code used case label fall-through + if (connection_state == CS_SERVER_CERTIFICATE_RECEIVED) + { + // There was no server key exchange message; check it's OK + this.keyExchange.SkipServerKeyExchange(); + } + + if (this.authentication == null) + { + /* + * RFC 2246 7.4.4. It is a fatal handshake_failure alert + * for an anonymous server to request client identification. + */ + this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + + int numTypes = TlsUtilities.ReadUint8(inStr); + ClientCertificateType[] certificateTypes = new ClientCertificateType[numTypes]; + for (int i = 0; i < numTypes; ++i) + { + certificateTypes[i] = (ClientCertificateType)TlsUtilities.ReadUint8(inStr); + } + + byte[] authorities = TlsUtilities.ReadOpaque16(inStr); + + AssertEmpty(inStr); + + IList authorityDNs = Platform.CreateArrayList(); + + MemoryStream bis = new MemoryStream(authorities, false); + while (bis.Position < bis.Length) + { + byte[] dnBytes = TlsUtilities.ReadOpaque16(bis); + // TODO Switch to X500Name when available + authorityDNs.Add(X509Name.GetInstance(Asn1Object.FromByteArray(dnBytes))); + } + + this.certificateRequest = new CertificateRequest(certificateTypes, + authorityDNs); + this.keyExchange.ValidateCertificateRequest(this.certificateRequest); + + break; + } + default: + this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + break; + } + + this.connection_state = CS_CERTIFICATE_REQUEST_RECEIVED; + break; + case HandshakeType.hello_request: + /* + * RFC 2246 7.4.1.1 Hello request + * This message will be ignored by the client if the client is currently + * negotiating a session. This message may be ignored by the client if it + * does not wish to renegotiate a session, or the client may, if it wishes, + * respond with a no_renegotiation alert. + */ + if (connection_state == CS_DONE) + { + // Renegotiation not supported yet + SendAlert(AlertLevel.warning, AlertDescription.no_renegotiation); + } + break; + case HandshakeType.client_key_exchange: + case HandshakeType.certificate_verify: + case HandshakeType.client_hello: + default: + // We do not support this! + this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + break; + } + } + + private void ProcessApplicationData() + { + /* + * There is nothing we need to do here. + * + * This function could be used for callbacks when application + * data arrives in the future. + */ + } + + private void ProcessAlert() + { + while (alertQueue.Available >= 2) + { + /* + * An alert is always 2 bytes. Read the alert. + */ + byte[] tmp = new byte[2]; + alertQueue.Read(tmp, 0, 2, 0); + alertQueue.RemoveData(2); + byte level = tmp[0]; + byte description = tmp[1]; + if (level == (byte)AlertLevel.fatal) + { + /* + * This is a fatal error. + */ + this.failedWithError = true; + this.closed = true; + /* + * Now try to Close the stream, ignore errors. + */ + try + { + rs.Close(); + } + catch (Exception) + { + } + throw new IOException(TLS_ERROR_MESSAGE); + } + else + { + /* + * This is just a warning. + */ + if (description == (byte)AlertDescription.close_notify) + { + /* + * Close notify + */ + this.FailWithError(AlertLevel.warning, AlertDescription.close_notify); + } + /* + * If it is just a warning, we continue. + */ + } + } + } + + /** + * This method is called, when a change cipher spec message is received. + * + * @throws IOException If the message has an invalid content or the + * handshake is not in the correct state. + */ + private void ProcessChangeCipherSpec() + { + while (changeCipherSpecQueue.Available > 0) + { + /* + * A change cipher spec message is only one byte with the value 1. + */ + byte[] b = new byte[1]; + changeCipherSpecQueue.Read(b, 0, 1, 0); + changeCipherSpecQueue.RemoveData(1); + if (b[0] != 1) + { + /* + * This should never happen. + */ + this.FailWithError(AlertLevel.fatal, AlertDescription.unexpected_message); + } + + /* + * Check if we are in the correct connection state. + */ + if (this.connection_state != CS_CLIENT_FINISHED_SEND) + { + this.FailWithError(AlertLevel.fatal, AlertDescription.handshake_failure); + } + + rs.ServerClientSpecReceived(); + + this.connection_state = CS_SERVER_CHANGE_CIPHER_SPEC_RECEIVED; + } + } + + private void SendClientCertificate(Certificate clientCert) + { + MemoryStream bos = new MemoryStream(); + TlsUtilities.WriteUint8((byte)HandshakeType.certificate, bos); + + // Reserve space for length + TlsUtilities.WriteUint24(0, bos); + + clientCert.Encode(bos); + byte[] message = bos.ToArray(); + + // Patch actual length back in + TlsUtilities.WriteUint24(message.Length - 4, message, 1); + + rs.WriteMessage(ContentType.handshake, message, 0, message.Length); + } + + private void SendClientKeyExchange() + { + MemoryStream bos = new MemoryStream(); + TlsUtilities.WriteUint8((byte)HandshakeType.client_key_exchange, bos); + + // Reserve space for length + TlsUtilities.WriteUint24(0, bos); + + this.keyExchange.GenerateClientKeyExchange(bos); + byte[] message = bos.ToArray(); + + // Patch actual length back in + TlsUtilities.WriteUint24(message.Length - 4, message, 1); + + rs.WriteMessage(ContentType.handshake, message, 0, message.Length); + } + + private void SendCertificateVerify(byte[] data) + { + /* + * Send signature of handshake messages so far to prove we are the owner of + * the cert See RFC 2246 sections 4.7, 7.4.3 and 7.4.8 + */ + MemoryStream bos = new MemoryStream(); + TlsUtilities.WriteUint8((byte)HandshakeType.certificate_verify, bos); + TlsUtilities.WriteUint24(data.Length + 2, bos); + TlsUtilities.WriteOpaque16(data, bos); + byte[] message = bos.ToArray(); + + rs.WriteMessage(ContentType.handshake, message, 0, message.Length); + } + + /// <summary>Connects to the remote system.</summary> + /// <param name="verifyer">Will be used when a certificate is received to verify + /// that this certificate is accepted by the client.</param> + /// <exception cref="IOException">If handshake was not successful</exception> + [Obsolete("Use version taking TlsClient")] + public virtual void Connect( + ICertificateVerifyer verifyer) + { + this.Connect(new LegacyTlsClient(verifyer)); + } + + public virtual void Connect(TlsClient tlsClient) + { + if (tlsClient == null) + throw new ArgumentNullException("tlsClient"); + if (this.tlsClient != null) + throw new InvalidOperationException("Connect can only be called once"); + + /* + * Send Client hello + * + * First, generate some random data. + */ + this.securityParameters = new SecurityParameters(); + this.securityParameters.clientRandom = new byte[32]; + random.NextBytes(securityParameters.clientRandom, 4, 28); + TlsUtilities.WriteGmtUnixTime(securityParameters.clientRandom, 0); + + this.tlsClientContext = new TlsClientContextImpl(random, securityParameters); + this.tlsClient = tlsClient; + this.tlsClient.Init(tlsClientContext); + + MemoryStream outStr = new MemoryStream(); + TlsUtilities.WriteVersion(outStr); + outStr.Write(securityParameters.clientRandom, 0, 32); + + /* + * Length of Session id + */ + TlsUtilities.WriteUint8(0, outStr); + + this.offeredCipherSuites = this.tlsClient.GetCipherSuites(); + + // ExtensionType -> byte[] + this.clientExtensions = this.tlsClient.GetClientExtensions(); + + // Cipher Suites (and SCSV) + { + /* + * RFC 5746 3.4. + * The client MUST include either an empty "renegotiation_info" + * extension, or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling + * cipher suite value in the ClientHello. Including both is NOT + * RECOMMENDED. + */ + bool noRenegExt = clientExtensions == null + || !clientExtensions.Contains(ExtensionType.renegotiation_info); + + int count = offeredCipherSuites.Length; + if (noRenegExt) + { + // Note: 1 extra slot for TLS_EMPTY_RENEGOTIATION_INFO_SCSV + ++count; + } + + TlsUtilities.WriteUint16(2 * count, outStr); + + for (int i = 0; i < offeredCipherSuites.Length; ++i) + { + TlsUtilities.WriteUint16((int)offeredCipherSuites[i], outStr); + } + + if (noRenegExt) + { + TlsUtilities.WriteUint16((int)CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV, outStr); + } + } + + /* + * Compression methods, just the null method. + */ + this.offeredCompressionMethods = tlsClient.GetCompressionMethods(); + + { + TlsUtilities.WriteUint8((byte)offeredCompressionMethods.Length, outStr); + for (int i = 0; i < offeredCompressionMethods.Length; ++i) + { + TlsUtilities.WriteUint8((byte)offeredCompressionMethods[i], outStr); + } + } + + // Extensions + if (clientExtensions != null) + { + MemoryStream ext = new MemoryStream(); + + foreach (ExtensionType extType in clientExtensions.Keys) + { + WriteExtension(ext, extType, (byte[])clientExtensions[extType]); + } + + TlsUtilities.WriteOpaque16(ext.ToArray(), outStr); + } + + MemoryStream bos = new MemoryStream(); + TlsUtilities.WriteUint8((byte)HandshakeType.client_hello, bos); + TlsUtilities.WriteUint24((int)outStr.Length, bos); + byte[] outBytes = outStr.ToArray(); + bos.Write(outBytes, 0, outBytes.Length); + byte[] message = bos.ToArray(); + SafeWriteMessage(ContentType.handshake, message, 0, message.Length); + connection_state = CS_CLIENT_HELLO_SEND; + + /* + * We will now read data, until we have completed the handshake. + */ + while (connection_state != CS_DONE) + { + SafeReadData(); + } + + this.tlsStream = new TlsStream(this); + } + + /** + * Read data from the network. The method will return immediately, if there is + * still some data left in the buffer, or block until some application + * data has been read from the network. + * + * @param buf The buffer where the data will be copied to. + * @param offset The position where the data will be placed in the buffer. + * @param len The maximum number of bytes to read. + * @return The number of bytes read. + * @throws IOException If something goes wrong during reading data. + */ + internal int ReadApplicationData(byte[] buf, int offset, int len) + { + while (applicationDataQueue.Available == 0) + { + if (this.closed) + { + /* + * We need to read some data. + */ + if (this.failedWithError) + { + /* + * Something went terribly wrong, we should throw an IOException + */ + throw new IOException(TLS_ERROR_MESSAGE); + } + + /* + * Connection has been closed, there is no more data to read. + */ + return 0; + } + + SafeReadData(); + } + len = System.Math.Min(len, applicationDataQueue.Available); + applicationDataQueue.Read(buf, offset, len, 0); + applicationDataQueue.RemoveData(len); + return len; + } + + private void SafeReadData() + { + try + { + rs.ReadData(); + } + catch (TlsFatalAlert e) + { + if (!this.closed) + { + this.FailWithError(e.AlertDescription, e); + } + throw e; + } + catch (IOException e) + { + if (!this.closed) + { + this.FailWithError(AlertDescription.internal_error, e); + } + throw e; + } + catch (Exception e) + { + if (!this.closed) + { + this.FailWithError(AlertDescription.internal_error, e); + } + throw e; + } + } + + private void SafeWriteMessage(ContentType type, byte[] buf, int offset, int len) + { + try + { + rs.WriteMessage(type, buf, offset, len); + } + catch (TlsFatalAlert e) + { + if (!this.closed) + { + this.FailWithError(e.AlertDescription, e); + } + throw e; + } + catch (IOException e) + { + if (!closed) + { + this.FailWithError(AlertDescription.internal_error, e); + } + throw e; + } + catch (Exception e) + { + if (!closed) + { + this.FailWithError(AlertDescription.internal_error, e); + } + throw e; + } + } + + /** + * Send some application data to the remote system. + * <p/> + * The method will handle fragmentation internally. + * + * @param buf The buffer with the data. + * @param offset The position in the buffer where the data is placed. + * @param len The length of the data. + * @throws IOException If something goes wrong during sending. + */ + internal void WriteData(byte[] buf, int offset, int len) + { + if (this.closed) + { + if (this.failedWithError) + throw new IOException(TLS_ERROR_MESSAGE); + + throw new IOException("Sorry, connection has been closed, you cannot write more data"); + } + + /* + * Protect against known IV attack! + * + * DO NOT REMOVE THIS LINE, EXCEPT YOU KNOW EXACTLY WHAT + * YOU ARE DOING HERE. + */ + SafeWriteMessage(ContentType.application_data, emptybuf, 0, 0); + + do + { + /* + * We are only allowed to write fragments up to 2^14 bytes. + */ + int toWrite = System.Math.Min(len, 1 << 14); + + SafeWriteMessage(ContentType.application_data, buf, offset, toWrite); + + offset += toWrite; + len -= toWrite; + } + while (len > 0); + } + + /// <summary>A Stream which can be used to send data.</summary> + [Obsolete("Use 'Stream' property instead")] + public virtual Stream OutputStream + { + get { return this.tlsStream; } + } + + /// <summary>A Stream which can be used to read data.</summary> + [Obsolete("Use 'Stream' property instead")] + public virtual Stream InputStream + { + get { return this.tlsStream; } + } + + /// <summary>The secure bidirectional stream for this connection</summary> + public virtual Stream Stream + { + get { return this.tlsStream; } + } + + /** + * Terminate this connection with an alert. + * <p/> + * Can be used for normal closure too. + * + * @param alertLevel The level of the alert, an be AlertLevel.fatal or AL_warning. + * @param alertDescription The exact alert message. + * @throws IOException If alert was fatal. + */ + private void FailWithError(AlertLevel alertLevel, AlertDescription alertDescription) + { + this.FailWithError(alertLevel, alertDescription, null); + } + + private void FailWithError(AlertDescription alertDescription, Exception ex) + { + this.FailWithError(AlertLevel.fatal, alertDescription, ex); + } + + private void FailWithError(AlertLevel alertLevel, AlertDescription alertDescription, Exception ex) + { + /* + * Check if the connection is still open. + */ + if (!closed) + { + /* + * Prepare the message + */ + this.closed = true; + + if (alertLevel == AlertLevel.fatal) + { + /* + * This is a fatal message. + */ + this.failedWithError = true; + } + SendAlert(alertLevel, alertDescription); + rs.Close(); + if (alertLevel == AlertLevel.fatal) + { + throw new IOException(TLS_ERROR_MESSAGE, ex); + } + } + else + { + throw new IOException(TLS_ERROR_MESSAGE, ex); + } + } + + internal void SendAlert(AlertLevel alertLevel, AlertDescription alertDescription) + { + byte[] error = new byte[2]; + error[0] = (byte)alertLevel; + error[1] = (byte)alertDescription; + + rs.WriteMessage(ContentType.alert, error, 0, 2); + } + + /// <summary>Closes this connection</summary> + /// <exception cref="IOException">If something goes wrong during closing.</exception> + public virtual void Close() + { + if (!closed) + { + this.FailWithError(AlertLevel.warning, AlertDescription.close_notify); + } + } + + /** + * Make sure the Stream is now empty. Fail otherwise. + * + * @param is The Stream to check. + * @throws IOException If is is not empty. + */ + internal void AssertEmpty( + MemoryStream inStr) + { + if (inStr.Position < inStr.Length) + { + throw new TlsFatalAlert(AlertDescription.decode_error); + } + } + + internal void Flush() + { + rs.Flush(); + } + + internal bool IsClosed + { + get { return closed; } + } + + private static bool ArrayContains(CipherSuite[] a, CipherSuite n) + { + for (int i = 0; i < a.Length; ++i) + { + if (a[i] == n) + return true; + } + return false; + } + + private static bool ArrayContains(CompressionMethod[] a, CompressionMethod n) + { + for (int i = 0; i < a.Length; ++i) + { + if (a[i] == n) + return true; + } + return false; + } + + private static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection) + { + MemoryStream buf = new MemoryStream(); + TlsUtilities.WriteOpaque8(renegotiated_connection, buf); + return buf.ToArray(); + } + + private static void WriteExtension(Stream output, ExtensionType extType, byte[] extValue) + { + TlsUtilities.WriteUint16((int)extType, output); + TlsUtilities.WriteOpaque16(extValue, output); + } + } +} diff --git a/Crypto/src/crypto/tls/TlsPskIdentity.cs b/Crypto/src/crypto/tls/TlsPskIdentity.cs new file mode 100644 index 000000000..119064ee7 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsPskIdentity.cs @@ -0,0 +1,15 @@ +using System; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsPskIdentity + { + void SkipIdentityHint(); + + void NotifyIdentityHint(byte[] psk_identity_hint); + + byte[] GetPskIdentity(); + + byte[] GetPsk(); + } +} diff --git a/Crypto/src/crypto/tls/TlsPskKeyExchange.cs b/Crypto/src/crypto/tls/TlsPskKeyExchange.cs new file mode 100644 index 000000000..226153a97 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsPskKeyExchange.cs @@ -0,0 +1,149 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsPskKeyExchange + : TlsKeyExchange + { + protected TlsClientContext context; + protected KeyExchangeAlgorithm keyExchange; + protected TlsPskIdentity pskIdentity; + + protected byte[] psk_identity_hint = null; + + protected DHPublicKeyParameters dhAgreeServerPublicKey = null; + protected DHPrivateKeyParameters dhAgreeClientPrivateKey = null; + + protected RsaKeyParameters rsaServerPublicKey = null; + protected byte[] premasterSecret; + + internal TlsPskKeyExchange(TlsClientContext context, KeyExchangeAlgorithm keyExchange, + TlsPskIdentity pskIdentity) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.PSK: + case KeyExchangeAlgorithm.RSA_PSK: + case KeyExchangeAlgorithm.DHE_PSK: + break; + default: + throw new ArgumentException("unsupported key exchange algorithm", "keyExchange"); + } + + this.context = context; + this.keyExchange = keyExchange; + this.pskIdentity = pskIdentity; + } + + public virtual void SkipServerCertificate() + { + // OK + } + + public virtual void ProcessServerCertificate(Certificate serverCertificate) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void SkipServerKeyExchange() + { + this.psk_identity_hint = new byte[0]; + } + + public virtual void ProcessServerKeyExchange(Stream input) + { + this.psk_identity_hint = TlsUtilities.ReadOpaque16(input); + + if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + byte[] pBytes = TlsUtilities.ReadOpaque16(input); + byte[] gBytes = TlsUtilities.ReadOpaque16(input); + byte[] YsBytes = TlsUtilities.ReadOpaque16(input); + + BigInteger p = new BigInteger(1, pBytes); + BigInteger g = new BigInteger(1, gBytes); + BigInteger Ys = new BigInteger(1, YsBytes); + + this.dhAgreeServerPublicKey = TlsDHUtilities.ValidateDHPublicKey( + new DHPublicKeyParameters(Ys, new DHParameters(p, g))); + } + else if (this.psk_identity_hint.Length == 0) + { + // TODO Should we enforce that this message should have been skipped if hint is empty? + //throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public virtual void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void SkipClientCredentials() + { + // OK + } + + public virtual void ProcessClientCredentials(TlsCredentials clientCredentials) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public virtual void GenerateClientKeyExchange(Stream output) + { + if (psk_identity_hint == null || psk_identity_hint.Length == 0) + { + pskIdentity.SkipIdentityHint(); + } + else + { + pskIdentity.NotifyIdentityHint(psk_identity_hint); + } + + byte[] psk_identity = pskIdentity.GetPskIdentity(); + + TlsUtilities.WriteOpaque16(psk_identity, output); + + if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + this.premasterSecret = TlsRsaUtilities.GenerateEncryptedPreMasterSecret( + context.SecureRandom, this.rsaServerPublicKey, output); + } + else if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + this.dhAgreeClientPrivateKey = TlsDHUtilities.GenerateEphemeralClientKeyExchange( + context.SecureRandom, this.dhAgreeServerPublicKey.Parameters, output); + } + } + + public virtual byte[] GeneratePremasterSecret() + { + byte[] psk = pskIdentity.GetPsk(); + byte[] other_secret = GenerateOtherSecret(psk.Length); + + MemoryStream buf = new MemoryStream(4 + other_secret.Length + psk.Length); + TlsUtilities.WriteOpaque16(other_secret, buf); + TlsUtilities.WriteOpaque16(psk, buf); + return buf.ToArray(); + } + + protected virtual byte[] GenerateOtherSecret(int pskLength) + { + if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK) + { + return TlsDHUtilities.CalculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey); + } + + if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK) + { + return this.premasterSecret; + } + + return new byte[pskLength]; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsRsaKeyExchange.cs b/Crypto/src/crypto/tls/TlsRsaKeyExchange.cs new file mode 100644 index 000000000..4538a2a81 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsRsaKeyExchange.cs @@ -0,0 +1,165 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// TLS 1.0 RSA key exchange. + /// </summary> + internal class TlsRsaKeyExchange + : TlsKeyExchange + { + protected TlsClientContext context; + + protected AsymmetricKeyParameter serverPublicKey = null; + + protected RsaKeyParameters rsaServerPublicKey = null; + + protected byte[] premasterSecret; + + internal TlsRsaKeyExchange(TlsClientContext context) + { + this.context = context; + } + + public virtual void SkipServerCertificate() + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void ProcessServerCertificate(Certificate serverCertificate) + { + X509CertificateStructure x509Cert = serverCertificate.certs[0]; + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + + try + { + this.serverPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } +// catch (RuntimeException) + catch (Exception) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + // Sanity check the PublicKeyFactory + if (this.serverPublicKey.IsPrivate) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + this.rsaServerPublicKey = ValidateRsaPublicKey((RsaKeyParameters)this.serverPublicKey); + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.KeyEncipherment); + + // TODO + /* + * Perform various checks per RFC2246 7.4.2: "Unless otherwise specified, the + * signing algorithm for the certificate must be the same as the algorithm for the + * certificate key." + */ + } + + public virtual void SkipServerKeyExchange() + { + // OK + } + + public virtual void ProcessServerKeyExchange(Stream input) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + ClientCertificateType[] types = certificateRequest.CertificateTypes; + foreach (ClientCertificateType type in types) + { + switch (type) + { + case ClientCertificateType.rsa_sign: + case ClientCertificateType.dss_sign: + case ClientCertificateType.ecdsa_sign: + break; + default: + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + } + + public virtual void SkipClientCredentials() + { + // OK + } + + public virtual void ProcessClientCredentials(TlsCredentials clientCredentials) + { + if (!(clientCredentials is TlsSignerCredentials)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + } + + public virtual void GenerateClientKeyExchange(Stream output) + { + this.premasterSecret = TlsRsaUtilities.GenerateEncryptedPreMasterSecret( + context.SecureRandom, this.rsaServerPublicKey, output); + } + + public virtual byte[] GeneratePremasterSecret() + { + byte[] tmp = this.premasterSecret; + this.premasterSecret = null; + return tmp; + } + + // Would be needed to process RSA_EXPORT server key exchange +// protected virtual void ProcessRsaServerKeyExchange(Stream input, ISigner signer) +// { +// Stream sigIn = input; +// if (signer != null) +// { +// sigIn = new SignerStream(input, signer, null); +// } +// +// byte[] modulusBytes = TlsUtilities.ReadOpaque16(sigIn); +// byte[] exponentBytes = TlsUtilities.ReadOpaque16(sigIn); +// +// if (signer != null) +// { +// byte[] sigByte = TlsUtilities.ReadOpaque16(input); +// +// if (!signer.VerifySignature(sigByte)) +// { +// handler.FailWithError(AlertLevel.fatal, AlertDescription.bad_certificate); +// } +// } +// +// BigInteger modulus = new BigInteger(1, modulusBytes); +// BigInteger exponent = new BigInteger(1, exponentBytes); +// +// this.rsaServerPublicKey = ValidateRSAPublicKey(new RsaKeyParameters(false, modulus, exponent)); +// } + + protected virtual RsaKeyParameters ValidateRsaPublicKey(RsaKeyParameters key) + { + // TODO What is the minimum bit length required? +// key.Modulus.BitLength; + + if (!key.Exponent.IsProbablePrime(2)) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + return key; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsRsaSigner.cs b/Crypto/src/crypto/tls/TlsRsaSigner.cs new file mode 100644 index 000000000..a50ff9558 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsRsaSigner.cs @@ -0,0 +1,53 @@ +using System; + +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Signers; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsRsaSigner + : TlsSigner + { + public virtual byte[] CalculateRawSignature(SecureRandom random, + AsymmetricKeyParameter privateKey, byte[] md5andsha1) + { + ISigner s = MakeSigner(new NullDigest(), true, new ParametersWithRandom(privateKey, random)); + s.BlockUpdate(md5andsha1, 0, md5andsha1.Length); + return s.GenerateSignature(); + } + + public virtual bool VerifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, + byte[] md5andsha1) + { + ISigner s = MakeSigner(new NullDigest(), false, publicKey); + s.BlockUpdate(md5andsha1, 0, md5andsha1.Length); + return s.VerifySignature(sigBytes); + } + + public virtual ISigner CreateSigner(SecureRandom random, AsymmetricKeyParameter privateKey) + { + return MakeSigner(new CombinedHash(), true, new ParametersWithRandom(privateKey, random)); + } + + public virtual ISigner CreateVerifyer(AsymmetricKeyParameter publicKey) + { + return MakeSigner(new CombinedHash(), false, publicKey); + } + + public virtual bool IsValidPublicKey(AsymmetricKeyParameter publicKey) + { + return publicKey is RsaKeyParameters && !publicKey.IsPrivate; + } + + protected virtual ISigner MakeSigner(IDigest d, bool forSigning, ICipherParameters cp) + { + ISigner s = new GenericSigner(new Pkcs1Encoding(new RsaBlindedEngine()), d); + s.Init(forSigning, cp); + return s; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsRsaUtilities.cs b/Crypto/src/crypto/tls/TlsRsaUtilities.cs new file mode 100644 index 000000000..4450ba452 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsRsaUtilities.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Crypto.Encodings; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Engines; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public abstract class TlsRsaUtilities + { + public static byte[] GenerateEncryptedPreMasterSecret(SecureRandom random, + RsaKeyParameters rsaServerPublicKey, Stream output) + { + /* + * Choose a PremasterSecret and send it encrypted to the server + */ + byte[] premasterSecret = new byte[48]; + random.NextBytes(premasterSecret); + TlsUtilities.WriteVersion(premasterSecret, 0); + + Pkcs1Encoding encoding = new Pkcs1Encoding(new RsaBlindedEngine()); + encoding.Init(true, new ParametersWithRandom(rsaServerPublicKey, random)); + + try + { + byte[] keData = encoding.ProcessBlock(premasterSecret, 0, premasterSecret.Length); + TlsUtilities.WriteOpaque16(keData, output); + } + catch (InvalidCipherTextException) + { + /* + * This should never happen, only during decryption. + */ + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + return premasterSecret; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsSigner.cs b/Crypto/src/crypto/tls/TlsSigner.cs new file mode 100644 index 000000000..e59b90705 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsSigner.cs @@ -0,0 +1,18 @@ +using System; + +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsSigner + { + byte[] CalculateRawSignature(SecureRandom random, AsymmetricKeyParameter privateKey, + byte[] md5andsha1); + bool VerifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5andsha1); + + ISigner CreateSigner(SecureRandom random, AsymmetricKeyParameter privateKey); + ISigner CreateVerifyer(AsymmetricKeyParameter publicKey); + + bool IsValidPublicKey(AsymmetricKeyParameter publicKey); + } +} diff --git a/Crypto/src/crypto/tls/TlsSignerCredentials.cs b/Crypto/src/crypto/tls/TlsSignerCredentials.cs new file mode 100644 index 000000000..2adb06c26 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsSignerCredentials.cs @@ -0,0 +1,11 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public interface TlsSignerCredentials : TlsCredentials + { + /// <exception cref="IOException"></exception> + byte[] GenerateCertificateSignature(byte[] md5andsha1); + } +} diff --git a/Crypto/src/crypto/tls/TlsSrpKeyExchange.cs b/Crypto/src/crypto/tls/TlsSrpKeyExchange.cs new file mode 100644 index 000000000..852aace41 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsSrpKeyExchange.cs @@ -0,0 +1,203 @@ +using System; +using System.IO; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Agreement; +using Org.BouncyCastle.Crypto.Agreement.Srp; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.IO; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <summary> + /// TLS 1.1 SRP key exchange. + /// </summary> + internal class TlsSrpKeyExchange + : TlsKeyExchange + { + protected TlsClientContext context; + protected KeyExchangeAlgorithm keyExchange; + protected TlsSigner tlsSigner; + protected byte[] identity; + protected byte[] password; + + protected AsymmetricKeyParameter serverPublicKey = null; + + protected byte[] s = null; + protected BigInteger B = null; + protected Srp6Client srpClient = new Srp6Client(); + + internal TlsSrpKeyExchange(TlsClientContext context, KeyExchangeAlgorithm keyExchange, + byte[] identity, byte[] password) + { + switch (keyExchange) + { + case KeyExchangeAlgorithm.SRP: + this.tlsSigner = null; + break; + case KeyExchangeAlgorithm.SRP_RSA: + this.tlsSigner = new TlsRsaSigner(); + break; + case KeyExchangeAlgorithm.SRP_DSS: + this.tlsSigner = new TlsDssSigner(); + break; + default: + throw new ArgumentException("unsupported key exchange algorithm", "keyExchange"); + } + + this.context = context; + this.keyExchange = keyExchange; + this.identity = identity; + this.password = password; + } + + public virtual void SkipServerCertificate() + { + if (tlsSigner != null) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + public virtual void ProcessServerCertificate(Certificate serverCertificate) + { + if (tlsSigner == null) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + X509CertificateStructure x509Cert = serverCertificate.certs[0]; + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + + try + { + this.serverPublicKey = PublicKeyFactory.CreateKey(keyInfo); + } +// catch (RuntimeException) + catch (Exception) + { + throw new TlsFatalAlert(AlertDescription.unsupported_certificate); + } + + if (!tlsSigner.IsValidPublicKey(this.serverPublicKey)) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + TlsUtilities.ValidateKeyUsage(x509Cert, KeyUsage.DigitalSignature); + + // TODO + /* + * Perform various checks per RFC2246 7.4.2: "Unless otherwise specified, the + * signing algorithm for the certificate must be the same as the algorithm for the + * certificate key." + */ + } + + public virtual void SkipServerKeyExchange() + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void ProcessServerKeyExchange(Stream input) + { + SecurityParameters securityParameters = context.SecurityParameters; + + Stream sigIn = input; + ISigner signer = null; + + if (tlsSigner != null) + { + signer = InitSigner(tlsSigner, securityParameters); + sigIn = new SignerStream(input, signer, null); + } + + byte[] NBytes = TlsUtilities.ReadOpaque16(sigIn); + byte[] gBytes = TlsUtilities.ReadOpaque16(sigIn); + byte[] sBytes = TlsUtilities.ReadOpaque8(sigIn); + byte[] BBytes = TlsUtilities.ReadOpaque16(sigIn); + + if (signer != null) + { + byte[] sigByte = TlsUtilities.ReadOpaque16(input); + + if (!signer.VerifySignature(sigByte)) + { + throw new TlsFatalAlert(AlertDescription.bad_certificate); + } + } + + BigInteger N = new BigInteger(1, NBytes); + BigInteger g = new BigInteger(1, gBytes); + + // TODO Validate group parameters (see RFC 5054) + //throw new TlsFatalAlert(AlertDescription.insufficient_security); + + this.s = sBytes; + + /* + * RFC 5054 2.5.3: The client MUST abort the handshake with an "illegal_parameter" + * alert if B % N = 0. + */ + try + { + this.B = Srp6Utilities.ValidatePublicValue(N, new BigInteger(1, BBytes)); + } + catch (CryptoException) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + + this.srpClient.Init(N, g, new Sha1Digest(), context.SecureRandom); + } + + public virtual void ValidateCertificateRequest(CertificateRequest certificateRequest) + { + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public virtual void SkipClientCredentials() + { + // OK + } + + public virtual void ProcessClientCredentials(TlsCredentials clientCredentials) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + public virtual void GenerateClientKeyExchange(Stream output) + { + byte[] keData = BigIntegers.AsUnsignedByteArray(srpClient.GenerateClientCredentials(s, + this.identity, this.password)); + TlsUtilities.WriteOpaque16(keData, output); + } + + public virtual byte[] GeneratePremasterSecret() + { + try + { + // TODO Check if this needs to be a fixed size + return BigIntegers.AsUnsignedByteArray(srpClient.CalculateSecret(B)); + } + catch (CryptoException) + { + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + } + } + + protected virtual ISigner InitSigner(TlsSigner tlsSigner, SecurityParameters securityParameters) + { + ISigner signer = tlsSigner.CreateVerifyer(this.serverPublicKey); + signer.BlockUpdate(securityParameters.clientRandom, 0, securityParameters.clientRandom.Length); + signer.BlockUpdate(securityParameters.serverRandom, 0, securityParameters.serverRandom.Length); + return signer; + } + } +} diff --git a/Crypto/src/crypto/tls/TlsStream.cs b/Crypto/src/crypto/tls/TlsStream.cs new file mode 100644 index 000000000..e3d05686b --- /dev/null +++ b/Crypto/src/crypto/tls/TlsStream.cs @@ -0,0 +1,89 @@ +using System; +using System.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + internal class TlsStream + : Stream + { + private readonly TlsProtocolHandler handler; + + internal TlsStream( + TlsProtocolHandler handler) + { + this.handler = handler; + } + + public override bool CanRead + { + get { return !handler.IsClosed; } + } + + public override bool CanSeek + { + get { return false; } + } + + public override bool CanWrite + { + get { return !handler.IsClosed; } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + handler.Close(); + } + } + + public override void Flush() + { + handler.Flush(); + } + + public override long Length + { + get { throw new NotSupportedException(); } + } + + public override long Position + { + get { throw new NotSupportedException(); } + set { throw new NotSupportedException(); } + } + + public override int Read(byte[] buf, int off, int len) + { + return this.handler.ReadApplicationData(buf, off, len); + } + + public override int ReadByte() + { + byte[] buf = new byte[1]; + if (this.Read(buf, 0, 1) <= 0) + return -1; + return buf[0]; + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buf, int off, int len) + { + this.handler.WriteData(buf, off, len); + } + + public override void WriteByte(byte b) + { + this.handler.WriteData(new byte[] { b }, 0, 1); + } + } +} diff --git a/Crypto/src/crypto/tls/TlsUtilities.cs b/Crypto/src/crypto/tls/TlsUtilities.cs new file mode 100644 index 000000000..0e2452689 --- /dev/null +++ b/Crypto/src/crypto/tls/TlsUtilities.cs @@ -0,0 +1,286 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls +{ + /// <remarks>Some helper fuctions for MicroTLS.</remarks> + public class TlsUtilities + { + internal static void WriteUint8(byte i, Stream os) + { + os.WriteByte(i); + } + + internal static void WriteUint8(byte i, byte[] buf, int offset) + { + buf[offset] = i; + } + + internal static void WriteUint16(int i, Stream os) + { + os.WriteByte((byte)(i >> 8)); + os.WriteByte((byte)i); + } + + internal static void WriteUint16(int i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 8); + buf[offset + 1] = (byte)i; + } + + internal static void WriteUint24(int i, Stream os) + { + os.WriteByte((byte)(i >> 16)); + os.WriteByte((byte)(i >> 8)); + os.WriteByte((byte)i); + } + + internal static void WriteUint24(int i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 16); + buf[offset + 1] = (byte)(i >> 8); + buf[offset + 2] = (byte)(i); + } + + internal static void WriteUint64(long i, Stream os) + { + os.WriteByte((byte)(i >> 56)); + os.WriteByte((byte)(i >> 48)); + os.WriteByte((byte)(i >> 40)); + os.WriteByte((byte)(i >> 32)); + os.WriteByte((byte)(i >> 24)); + os.WriteByte((byte)(i >> 16)); + os.WriteByte((byte)(i >> 8)); + os.WriteByte((byte)i); + } + + internal static void WriteUint64(long i, byte[] buf, int offset) + { + buf[offset] = (byte)(i >> 56); + buf[offset + 1] = (byte)(i >> 48); + buf[offset + 2] = (byte)(i >> 40); + buf[offset + 3] = (byte)(i >> 32); + buf[offset + 4] = (byte)(i >> 24); + buf[offset + 5] = (byte)(i >> 16); + buf[offset + 6] = (byte)(i >> 8); + buf[offset + 7] = (byte)(i); + } + + internal static void WriteOpaque8(byte[] buf, Stream os) + { + WriteUint8((byte)buf.Length, os); + os.Write(buf, 0, buf.Length); + } + + internal static void WriteOpaque16(byte[] buf, Stream os) + { + WriteUint16(buf.Length, os); + os.Write(buf, 0, buf.Length); + } + + internal static void WriteOpaque24(byte[] buf, Stream os) + { + WriteUint24(buf.Length, os); + os.Write(buf, 0, buf.Length); + } + + internal static void WriteUint8Array(byte[] uints, Stream os) + { + os.Write(uints, 0, uints.Length); + } + + internal static void WriteUint16Array(int[] uints, Stream os) + { + for (int i = 0; i < uints.Length; ++i) + { + WriteUint16(uints[i], os); + } + } + + internal static byte ReadUint8(Stream inStr) + { + int i = inStr.ReadByte(); + if (i < 0) + { + throw new EndOfStreamException(); + } + return (byte)i; + } + + internal static int ReadUint16(Stream inStr) + { + int i1 = inStr.ReadByte(); + int i2 = inStr.ReadByte(); + if ((i1 | i2) < 0) + { + throw new EndOfStreamException(); + } + return i1 << 8 | i2; + } + + internal static int ReadUint24(Stream inStr) + { + int i1 = inStr.ReadByte(); + int i2 = inStr.ReadByte(); + int i3 = inStr.ReadByte(); + if ((i1 | i2 | i3) < 0) + { + throw new EndOfStreamException(); + } + return (i1 << 16) | (i2 << 8) | i3; + } + + internal static void ReadFully(byte[] buf, Stream inStr) + { + if (Streams.ReadFully(inStr, buf, 0, buf.Length) < buf.Length) + throw new EndOfStreamException(); + } + + internal static byte[] ReadOpaque8(Stream inStr) + { + byte length = ReadUint8(inStr); + byte[] bytes = new byte[length]; + ReadFully(bytes, inStr); + return bytes; + } + + internal static byte[] ReadOpaque16(Stream inStr) + { + int length = ReadUint16(inStr); + byte[] bytes = new byte[length]; + ReadFully(bytes, inStr); + return bytes; + } + + internal static void CheckVersion(byte[] readVersion) + { + if ((readVersion[0] != 3) || (readVersion[1] != 1)) + { + throw new TlsFatalAlert(AlertDescription.protocol_version); + } + } + + internal static void CheckVersion(Stream inStr) + { + int i1 = inStr.ReadByte(); + int i2 = inStr.ReadByte(); + if ((i1 != 3) || (i2 != 1)) + { + throw new TlsFatalAlert(AlertDescription.protocol_version); + } + } + + internal static void WriteGmtUnixTime(byte[] buf, int offset) + { + int t = (int)(DateTimeUtilities.CurrentUnixMs() / 1000L); + buf[offset] = (byte)(t >> 24); + buf[offset + 1] = (byte)(t >> 16); + buf[offset + 2] = (byte)(t >> 8); + buf[offset + 3] = (byte)t; + } + + internal static void WriteVersion(Stream os) + { + os.WriteByte(3); + os.WriteByte(1); + } + + internal static void WriteVersion(byte[] buf, int offset) + { + buf[offset] = 3; + buf[offset + 1] = 1; + } + + private static void hmac_hash(IDigest digest, byte[] secret, byte[] seed, byte[] output) + { + HMac mac = new HMac(digest); + KeyParameter param = new KeyParameter(secret); + byte[] a = seed; + int size = digest.GetDigestSize(); + int iterations = (output.Length + size - 1) / size; + byte[] buf = new byte[mac.GetMacSize()]; + byte[] buf2 = new byte[mac.GetMacSize()]; + for (int i = 0; i < iterations; i++) + { + mac.Init(param); + mac.BlockUpdate(a, 0, a.Length); + mac.DoFinal(buf, 0); + a = buf; + mac.Init(param); + mac.BlockUpdate(a, 0, a.Length); + mac.BlockUpdate(seed, 0, seed.Length); + mac.DoFinal(buf2, 0); + Array.Copy(buf2, 0, output, (size * i), System.Math.Min(size, output.Length - (size * i))); + } + } + + internal static byte[] PRF(byte[] secret, string asciiLabel, byte[] seed, int size) + { + byte[] label = Strings.ToAsciiByteArray(asciiLabel); + + int s_half = (secret.Length + 1) / 2; + byte[] s1 = new byte[s_half]; + byte[] s2 = new byte[s_half]; + Array.Copy(secret, 0, s1, 0, s_half); + Array.Copy(secret, secret.Length - s_half, s2, 0, s_half); + + byte[] ls = Concat(label, seed); + + byte[] buf = new byte[size]; + byte[] prf = new byte[size]; + hmac_hash(new MD5Digest(), s1, ls, prf); + hmac_hash(new Sha1Digest(), s2, ls, buf); + for (int i = 0; i < size; i++) + { + buf[i] ^= prf[i]; + } + return buf; + } + + internal static byte[] PRF_1_2(IDigest digest, byte[] secret, string asciiLabel, byte[] seed, int size) + { + byte[] label = Strings.ToAsciiByteArray(asciiLabel); + byte[] labelSeed = Concat(label, seed); + + byte[] buf = new byte[size]; + hmac_hash(digest, secret, labelSeed, buf); + return buf; + } + + internal static byte[] Concat(byte[] a, byte[] b) + { + byte[] c = new byte[a.Length + b.Length]; + Array.Copy(a, 0, c, 0, a.Length); + Array.Copy(b, 0, c, a.Length, b.Length); + return c; + } + + internal static void ValidateKeyUsage(X509CertificateStructure c, int keyUsageBits) + { + X509Extensions exts = c.TbsCertificate.Extensions; + if (exts != null) + { + X509Extension ext = exts.GetExtension(X509Extensions.KeyUsage); + if (ext != null) + { + DerBitString ku = KeyUsage.GetInstance(ext); + //int bits = ku.GetBytes()[0]; + //if ((bits & keyUsageBits) != keyUsageBits) + //{ + // throw new TlsFatalAlert(AlertDescription.certificate_unknown); + //} + } + } + } + } +} |