summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crypto/BouncyCastle.Android.csproj266
-rw-r--r--crypto/BouncyCastle.csproj266
-rw-r--r--crypto/BouncyCastle.iOS.csproj266
-rw-r--r--crypto/crypto.csproj1540
-rw-r--r--crypto/src/tls/AbstractTlsClient.cs435
-rw-r--r--crypto/src/tls/AbstractTlsContext.cs282
-rw-r--r--crypto/src/tls/AbstractTlsKeyExchange.cs90
-rw-r--r--crypto/src/tls/AbstractTlsKeyExchangeFactory.cs90
-rw-r--r--crypto/src/tls/AbstractTlsPeer.cs157
-rw-r--r--crypto/src/tls/AbstractTlsServer.cs578
-rw-r--r--crypto/src/tls/AlertDescription.cs323
-rw-r--r--crypto/src/tls/AlertLevel.cs29
-rw-r--r--crypto/src/tls/BasicTlsPskIdentity.cs44
-rw-r--r--crypto/src/tls/BasicTlsSrpIdentity.cs36
-rw-r--r--crypto/src/tls/ByteQueue.cs195
-rw-r--r--crypto/src/tls/ByteQueueInputStream.cs72
-rw-r--r--crypto/src/tls/ByteQueueOutputStream.cs33
-rw-r--r--crypto/src/tls/CachedInformationType.cs28
-rw-r--r--crypto/src/tls/CertChainType.cs34
-rw-r--r--crypto/src/tls/Certificate.cs294
-rw-r--r--crypto/src/tls/CertificateEntry.cs32
-rw-r--r--crypto/src/tls/CertificateRequest.cs276
-rw-r--r--crypto/src/tls/CertificateStatus.cs220
-rw-r--r--crypto/src/tls/CertificateStatusRequest.cs91
-rw-r--r--crypto/src/tls/CertificateStatusRequestItemV2.cs100
-rw-r--r--crypto/src/tls/CertificateStatusType.cs17
-rw-r--r--crypto/src/tls/CertificateType.cs16
-rw-r--r--crypto/src/tls/CertificateUrl.cs116
-rw-r--r--crypto/src/tls/ChangeCipherSpec.cs9
-rw-r--r--crypto/src/tls/ChannelBinding.cs19
-rw-r--r--crypto/src/tls/CipherSuite.cs461
-rw-r--r--crypto/src/tls/CipherType.cs20
-rw-r--r--crypto/src/tls/ClientAuthenticationType.cs14
-rw-r--r--crypto/src/tls/ClientCertificateType.cs69
-rw-r--r--crypto/src/tls/ClientHello.cs167
-rw-r--r--crypto/src/tls/CombinedHash.cs67
-rw-r--r--crypto/src/tls/CompressionMethod.cs20
-rw-r--r--crypto/src/tls/ConnectionEnd.cs15
-rw-r--r--crypto/src/tls/ContentType.cs38
-rw-r--r--crypto/src/tls/DatagramReceiver.cs14
-rw-r--r--crypto/src/tls/DatagramSender.cs14
-rw-r--r--crypto/src/tls/DatagramTransport.cs10
-rw-r--r--crypto/src/tls/DefaultTlsClient.cs48
-rw-r--r--crypto/src/tls/DefaultTlsCredentialedSigner.cs66
-rw-r--r--crypto/src/tls/DefaultTlsDHGroupVerifier.cs107
-rw-r--r--crypto/src/tls/DefaultTlsHeartbeat.cs44
-rw-r--r--crypto/src/tls/DefaultTlsKeyExchangeFactory.cs89
-rw-r--r--crypto/src/tls/DefaultTlsServer.cs108
-rw-r--r--crypto/src/tls/DefaultTlsSrpConfigVerifier.cs63
-rw-r--r--crypto/src/tls/DeferredHash.cs249
-rw-r--r--crypto/src/tls/DigestInputBuffer.cs26
-rw-r--r--crypto/src/tls/DigitallySigned.cs62
-rw-r--r--crypto/src/tls/DtlsClientProtocol.cs976
-rw-r--r--crypto/src/tls/DtlsEpoch.cs61
-rw-r--r--crypto/src/tls/DtlsHandshakeRetransmit.cs11
-rw-r--r--crypto/src/tls/DtlsProtocol.cs107
-rw-r--r--crypto/src/tls/DtlsReassembler.cs120
-rw-r--r--crypto/src/tls/DtlsRecordLayer.cs817
-rw-r--r--crypto/src/tls/DtlsReliableHandshake.cs558
-rw-r--r--crypto/src/tls/DtlsReplayWindow.cs84
-rw-r--r--crypto/src/tls/DtlsRequest.cs38
-rw-r--r--crypto/src/tls/DtlsServerProtocol.cs835
-rw-r--r--crypto/src/tls/DtlsTransport.cs139
-rw-r--r--crypto/src/tls/DtlsVerifier.cs89
-rw-r--r--crypto/src/tls/ECCurveType.cs29
-rw-r--r--crypto/src/tls/ECPointFormat.cs16
-rw-r--r--crypto/src/tls/EncryptionAlgorithm.cs82
-rw-r--r--crypto/src/tls/ExporterLabel.cs42
-rw-r--r--crypto/src/tls/ExtensionType.cs279
-rw-r--r--crypto/src/tls/HandshakeMessageInput.cs22
-rw-r--r--crypto/src/tls/HandshakeMessageOutput.cs62
-rw-r--r--crypto/src/tls/HandshakeType.cs131
-rw-r--r--crypto/src/tls/HashAlgorithm.cs94
-rw-r--r--crypto/src/tls/HeartbeatExtension.cs44
-rw-r--r--crypto/src/tls/HeartbeatMessage.cs118
-rw-r--r--crypto/src/tls/HeartbeatMessageType.cs34
-rw-r--r--crypto/src/tls/HeartbeatMode.cs36
-rw-r--r--crypto/src/tls/IdentifierType.cs35
-rw-r--r--crypto/src/tls/KeyExchangeAlgorithm.cs63
-rw-r--r--crypto/src/tls/KeyShareEntry.cs62
-rw-r--r--crypto/src/tls/KeyUpdateRequest.cs34
-rw-r--r--crypto/src/tls/MacAlgorithm.cs66
-rw-r--r--crypto/src/tls/MaxFragmentLength.cs20
-rw-r--r--crypto/src/tls/NameType.cs38
-rw-r--r--crypto/src/tls/NamedGroup.cs416
-rw-r--r--crypto/src/tls/NamedGroupRole.cs15
-rw-r--r--crypto/src/tls/NewSessionTicket.cs47
-rw-r--r--crypto/src/tls/OcspStatusRequest.cs111
-rw-r--r--crypto/src/tls/OfferedPsks.cs111
-rw-r--r--crypto/src/tls/PrfAlgorithm.cs49
-rw-r--r--crypto/src/tls/ProtocolName.cs88
-rw-r--r--crypto/src/tls/ProtocolVersion.cs406
-rw-r--r--crypto/src/tls/PskIdentity.cs47
-rw-r--r--crypto/src/tls/PskKeyExchangeMode.cs32
-rw-r--r--crypto/src/tls/PskTlsClient.cs60
-rw-r--r--crypto/src/tls/PskTlsServer.cs76
-rw-r--r--crypto/src/tls/RecordFormat.cs12
-rw-r--r--crypto/src/tls/RecordPreview.cs36
-rw-r--r--crypto/src/tls/RecordStream.cs533
-rw-r--r--crypto/src/tls/SecurityParameters.cs334
-rw-r--r--crypto/src/tls/ServerHello.cs108
-rw-r--r--crypto/src/tls/ServerName.cs63
-rw-r--r--crypto/src/tls/ServerNameList.cs87
-rw-r--r--crypto/src/tls/ServerOnlyTlsAuthentication.cs15
-rw-r--r--crypto/src/tls/ServerSrpParams.cs67
-rw-r--r--crypto/src/tls/SessionParameters.cs195
-rw-r--r--crypto/src/tls/SignatureAlgorithm.cs125
-rw-r--r--crypto/src/tls/SignatureAndHashAlgorithm.cs171
-rw-r--r--crypto/src/tls/SignatureScheme.cs235
-rw-r--r--crypto/src/tls/SimulatedTlsSrpIdentityManager.cs69
-rw-r--r--crypto/src/tls/SrpTlsClient.cs83
-rw-r--r--crypto/src/tls/SrpTlsServer.cs106
-rw-r--r--crypto/src/tls/SrtpProtectionProfile.cs21
-rw-r--r--crypto/src/tls/Ssl3Utilities.cs69
-rw-r--r--crypto/src/tls/SupplementalDataEntry.cs26
-rw-r--r--crypto/src/tls/SupplementalDataType.cs13
-rw-r--r--crypto/src/tls/Timeout.cs119
-rw-r--r--crypto/src/tls/TlsAuthentication.cs29
-rw-r--r--crypto/src/tls/TlsClient.cs101
-rw-r--r--crypto/src/tls/TlsClientContext.cs10
-rw-r--r--crypto/src/tls/TlsClientContextImpl.cs20
-rw-r--r--crypto/src/tls/TlsClientProtocol.cs1715
-rw-r--r--crypto/src/tls/TlsCloseable.cs11
-rw-r--r--crypto/src/tls/TlsContext.cs79
-rw-r--r--crypto/src/tls/TlsCredentialedAgreement.cs19
-rw-r--r--crypto/src/tls/TlsCredentialedDecryptor.cs19
-rw-r--r--crypto/src/tls/TlsCredentialedSigner.cs26
-rw-r--r--crypto/src/tls/TlsCredentials.cs12
-rw-r--r--crypto/src/tls/TlsDHGroupVerifier.cs15
-rw-r--r--crypto/src/tls/TlsDHKeyExchange.cs93
-rw-r--r--crypto/src/tls/TlsDHUtilities.cs159
-rw-r--r--crypto/src/tls/TlsDHanonKeyExchange.cs124
-rw-r--r--crypto/src/tls/TlsDheKeyExchange.cs129
-rw-r--r--crypto/src/tls/TlsECDHKeyExchange.cs95
-rw-r--r--crypto/src/tls/TlsECDHanonKeyExchange.cs127
-rw-r--r--crypto/src/tls/TlsECDheKeyExchange.cs141
-rw-r--r--crypto/src/tls/TlsEccUtilities.cs117
-rw-r--r--crypto/src/tls/TlsException.cs24
-rw-r--r--crypto/src/tls/TlsExtensionsUtilities.cs1377
-rw-r--r--crypto/src/tls/TlsFatalAlert.cs46
-rw-r--r--crypto/src/tls/TlsFatalAlertReceived.cs21
-rw-r--r--crypto/src/tls/TlsHandshakeHash.cs29
-rw-r--r--crypto/src/tls/TlsHeartbeat.cs13
-rw-r--r--crypto/src/tls/TlsKeyExchange.cs55
-rw-r--r--crypto/src/tls/TlsKeyExchangeFactory.cs59
-rw-r--r--crypto/src/tls/TlsNoCloseNotifyException.cs21
-rw-r--r--crypto/src/tls/TlsObjectIdentifiers.cs14
-rw-r--r--crypto/src/tls/TlsPeer.cs121
-rw-r--r--crypto/src/tls/TlsProtocol.cs1867
-rw-r--r--crypto/src/tls/TlsPskIdentity.cs16
-rw-r--r--crypto/src/tls/TlsPskIdentityManager.cs12
-rw-r--r--crypto/src/tls/TlsPskKeyExchange.cs305
-rw-r--r--crypto/src/tls/TlsRsaKeyExchange.cs80
-rw-r--r--crypto/src/tls/TlsRsaUtilities.cs24
-rw-r--r--crypto/src/tls/TlsServer.cs119
-rw-r--r--crypto/src/tls/TlsServerCertificate.cs12
-rw-r--r--crypto/src/tls/TlsServerCertificateImpl.cs27
-rw-r--r--crypto/src/tls/TlsServerContext.cs10
-rw-r--r--crypto/src/tls/TlsServerContextImpl.cs20
-rw-r--r--crypto/src/tls/TlsServerProtocol.cs1471
-rw-r--r--crypto/src/tls/TlsSession.cs16
-rw-r--r--crypto/src/tls/TlsSessionImpl.cs52
-rw-r--r--crypto/src/tls/TlsSrpConfigVerifier.cs15
-rw-r--r--crypto/src/tls/TlsSrpIdentity.cs12
-rw-r--r--crypto/src/tls/TlsSrpIdentityManager.cs18
-rw-r--r--crypto/src/tls/TlsSrpKeyExchange.cs186
-rw-r--r--crypto/src/tls/TlsSrpLoginParameters.cs44
-rw-r--r--crypto/src/tls/TlsSrpUtilities.cs69
-rw-r--r--crypto/src/tls/TlsSrtpUtilities.cs63
-rw-r--r--crypto/src/tls/TlsStream.cs96
-rw-r--r--crypto/src/tls/TlsTimeoutException.cs24
-rw-r--r--crypto/src/tls/TlsUtilities.cs5305
-rw-r--r--crypto/src/tls/TrustedAuthority.cs149
-rw-r--r--crypto/src/tls/UrlAndHash.cs83
-rw-r--r--crypto/src/tls/UseSrtpData.cs43
-rw-r--r--crypto/src/tls/UserMappingType.cs13
-rw-r--r--crypto/src/tls/crypto/CryptoHashAlgorithm.cs15
-rw-r--r--crypto/src/tls/crypto/CryptoSignatureAlgorithm.cs22
-rw-r--r--crypto/src/tls/crypto/DHGroup.cs46
-rw-r--r--crypto/src/tls/crypto/DHStandardGroups.cs248
-rw-r--r--crypto/src/tls/crypto/Srp6Group.cs31
-rw-r--r--crypto/src/tls/crypto/Srp6StandardGroups.cs159
-rw-r--r--crypto/src/tls/crypto/TlsAgreement.cs24
-rw-r--r--crypto/src/tls/crypto/TlsCertificate.cs52
-rw-r--r--crypto/src/tls/crypto/TlsCertificateRole.cs12
-rw-r--r--crypto/src/tls/crypto/TlsCipher.cs61
-rw-r--r--crypto/src/tls/crypto/TlsCrypto.cs181
-rw-r--r--crypto/src/tls/crypto/TlsCryptoException.cs19
-rw-r--r--crypto/src/tls/crypto/TlsCryptoParameters.cs49
-rw-r--r--crypto/src/tls/crypto/TlsCryptoUtilities.cs180
-rw-r--r--crypto/src/tls/crypto/TlsDHConfig.cs41
-rw-r--r--crypto/src/tls/crypto/TlsDHDomain.cs12
-rw-r--r--crypto/src/tls/crypto/TlsDecodeResult.cs19
-rw-r--r--crypto/src/tls/crypto/TlsECConfig.cs22
-rw-r--r--crypto/src/tls/crypto/TlsECDomain.cs12
-rw-r--r--crypto/src/tls/crypto/TlsEncodeResult.cs19
-rw-r--r--crypto/src/tls/crypto/TlsHash.cs25
-rw-r--r--crypto/src/tls/crypto/TlsHashSink.cs35
-rw-r--r--crypto/src/tls/crypto/TlsHmac.cs13
-rw-r--r--crypto/src/tls/crypto/TlsMac.cs36
-rw-r--r--crypto/src/tls/crypto/TlsMacSink.cs35
-rw-r--r--crypto/src/tls/crypto/TlsNonceGenerator.cs12
-rw-r--r--crypto/src/tls/crypto/TlsNullNullCipher.cs55
-rw-r--r--crypto/src/tls/crypto/TlsSecret.cs61
-rw-r--r--crypto/src/tls/crypto/TlsSigner.cs19
-rw-r--r--crypto/src/tls/crypto/TlsSrp6Client.cs24
-rw-r--r--crypto/src/tls/crypto/TlsSrp6Server.cs22
-rw-r--r--crypto/src/tls/crypto/TlsSrp6VerifierGenerator.cs17
-rw-r--r--crypto/src/tls/crypto/TlsSrpConfig.cs26
-rw-r--r--crypto/src/tls/crypto/TlsStreamSigner.cs14
-rw-r--r--crypto/src/tls/crypto/TlsStreamVerifier.cs14
-rw-r--r--crypto/src/tls/crypto/TlsVerifier.cs20
-rw-r--r--crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs90
-rw-r--r--crypto/src/tls/crypto/impl/AbstractTlsSecret.cs87
-rw-r--r--crypto/src/tls/crypto/impl/RsaUtilities.cs136
-rw-r--r--crypto/src/tls/crypto/impl/TlsAeadCipher.cs377
-rw-r--r--crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs41
-rw-r--r--crypto/src/tls/crypto/impl/TlsBlockCipher.cs423
-rw-r--r--crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs40
-rw-r--r--crypto/src/tls/crypto/impl/TlsEncryptor.cs17
-rw-r--r--crypto/src/tls/crypto/impl/TlsImplUtilities.cs75
-rw-r--r--crypto/src/tls/crypto/impl/TlsNullCipher.cs104
-rw-r--r--crypto/src/tls/crypto/impl/TlsSuiteHmac.cs121
-rw-r--r--crypto/src/tls/crypto/impl/TlsSuiteMac.cs33
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs124
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedAgreement.cs112
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedDecryptor.cs139
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedSigner.cs85
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs112
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs54
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs49
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs484
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs655
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsDH.cs43
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs119
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsDsaSigner.cs29
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsDsaVerifier.cs29
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsDssSigner.cs54
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsDssVerifier.cs47
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsECDH.cs39
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsECDomain.cs125
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Signer.cs47
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Verifier.cs41
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsECDsaSigner.cs29
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsECDsaVerifier.cs29
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsEd25519Signer.cs32
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsEd25519Verifier.cs33
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsEd448Signer.cs32
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsEd448Verifier.cs33
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsHash.cs49
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs55
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsNonceGenerator.cs24
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsRsaEncryptor.cs47
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsRsaPssSigner.cs44
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsRsaPssVerifier.cs45
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsRsaSigner.cs71
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsRsaVerifier.cs52
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs242
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsSigner.cs33
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsSrp6Client.cs36
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsSrp6Server.cs36
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsSrp6VerifierGenerator.cs23
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsStreamSigner.cs36
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsStreamVerifier.cs31
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcTlsVerifier.cs33
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcVerifyingStreamSigner.cs48
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcX25519.cs54
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcX25519Domain.cs20
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcX448.cs54
-rw-r--r--crypto/src/tls/crypto/impl/bc/BcX448Domain.cs20
-rw-r--r--crypto/test/UnitTests.csproj42
-rw-r--r--crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs820
-rw-r--r--crypto/test/src/tls/test/ByteQueueInputStreamTest.cs134
-rw-r--r--crypto/test/src/tls/test/CertChainUtilities.cs123
-rw-r--r--crypto/test/src/tls/test/DtlsProtocolTest.cs102
-rw-r--r--crypto/test/src/tls/test/DtlsPskProtocolTest.cs102
-rw-r--r--crypto/test/src/tls/test/DtlsTestCase.cs164
-rw-r--r--crypto/test/src/tls/test/DtlsTestClientProtocol.cs27
-rw-r--r--crypto/test/src/tls/test/DtlsTestServerProtocol.cs16
-rw-r--r--crypto/test/src/tls/test/DtlsTestSuite.cs260
-rw-r--r--crypto/test/src/tls/test/LoggingDatagramTransport.cs87
-rw-r--r--crypto/test/src/tls/test/MockDatagramAssociation.cs107
-rw-r--r--crypto/test/src/tls/test/MockDtlsClient.cs170
-rw-r--r--crypto/test/src/tls/test/MockDtlsServer.cs146
-rw-r--r--crypto/test/src/tls/test/MockPskDtlsClient.cs161
-rw-r--r--crypto/test/src/tls/test/MockPskDtlsServer.cs113
-rw-r--r--crypto/test/src/tls/test/MockPskTlsClient.cs186
-rw-r--r--crypto/test/src/tls/test/MockPskTlsServer.cs122
-rw-r--r--crypto/test/src/tls/test/MockSrpTlsClient.cs182
-rw-r--r--crypto/test/src/tls/test/MockSrpTlsServer.cs143
-rw-r--r--crypto/test/src/tls/test/MockTlsClient.cs190
-rw-r--r--crypto/test/src/tls/test/MockTlsServer.cs149
-rw-r--r--crypto/test/src/tls/test/NetworkStream.cs101
-rw-r--r--crypto/test/src/tls/test/PipedStream.cs134
-rw-r--r--crypto/test/src/tls/test/PrfTest.cs97
-rw-r--r--crypto/test/src/tls/test/PskTlsClientTest.cs113
-rw-r--r--crypto/test/src/tls/test/PskTlsServerTest.cs82
-rw-r--r--crypto/test/src/tls/test/TlsClientTest.cs97
-rw-r--r--crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs126
-rw-r--r--crypto/test/src/tls/test/TlsProtocolTest.cs75
-rw-r--r--crypto/test/src/tls/test/TlsPskProtocolTest.cs75
-rw-r--r--crypto/test/src/tls/test/TlsServerTest.cs82
-rw-r--r--crypto/test/src/tls/test/TlsSrpProtocolTest.cs75
-rw-r--r--crypto/test/src/tls/test/TlsTestCase.cs182
-rw-r--r--crypto/test/src/tls/test/TlsTestClientImpl.cs370
-rw-r--r--crypto/test/src/tls/test/TlsTestClientProtocol.cs32
-rw-r--r--crypto/test/src/tls/test/TlsTestConfig.cs128
-rw-r--r--crypto/test/src/tls/test/TlsTestServerImpl.cs310
-rw-r--r--crypto/test/src/tls/test/TlsTestServerProtocol.cs22
-rw-r--r--crypto/test/src/tls/test/TlsTestSuite.cs300
-rw-r--r--crypto/test/src/tls/test/TlsTestUtilities.cs412
-rw-r--r--crypto/test/src/tls/test/TlsUtilitiesTest.cs66
-rw-r--r--crypto/test/src/tls/test/UnreliableDatagramTransport.cs79
313 files changed, 45112 insertions, 0 deletions
diff --git a/crypto/BouncyCastle.Android.csproj b/crypto/BouncyCastle.Android.csproj
index 592dcc977..575c753ea 100644
--- a/crypto/BouncyCastle.Android.csproj
+++ b/crypto/BouncyCastle.Android.csproj
@@ -1560,6 +1560,272 @@
     <Compile Include="src\security\cert\CertificateNotYetValidException.cs" />
     <Compile Include="src\security\cert\CertificateParsingException.cs" />
     <Compile Include="src\security\cert\CrlException.cs" />
+    <Compile Include="src\tls\AbstractTlsClient.cs" />
+    <Compile Include="src\tls\AbstractTlsContext.cs" />
+    <Compile Include="src\tls\AbstractTlsKeyExchange.cs" />
+    <Compile Include="src\tls\AbstractTlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\AbstractTlsPeer.cs" />
+    <Compile Include="src\tls\AbstractTlsServer.cs" />
+    <Compile Include="src\tls\AlertDescription.cs" />
+    <Compile Include="src\tls\AlertLevel.cs" />
+    <Compile Include="src\tls\BasicTlsPskIdentity.cs" />
+    <Compile Include="src\tls\BasicTlsSrpIdentity.cs" />
+    <Compile Include="src\tls\ByteQueue.cs" />
+    <Compile Include="src\tls\ByteQueueInputStream.cs" />
+    <Compile Include="src\tls\ByteQueueOutputStream.cs" />
+    <Compile Include="src\tls\CachedInformationType.cs" />
+    <Compile Include="src\tls\CertChainType.cs" />
+    <Compile Include="src\tls\Certificate.cs" />
+    <Compile Include="src\tls\CertificateEntry.cs" />
+    <Compile Include="src\tls\CertificateRequest.cs" />
+    <Compile Include="src\tls\CertificateStatus.cs" />
+    <Compile Include="src\tls\CertificateStatusRequest.cs" />
+    <Compile Include="src\tls\CertificateStatusRequestItemV2.cs" />
+    <Compile Include="src\tls\CertificateStatusType.cs" />
+    <Compile Include="src\tls\CertificateType.cs" />
+    <Compile Include="src\tls\CertificateUrl.cs" />
+    <Compile Include="src\tls\ChangeCipherSpec.cs" />
+    <Compile Include="src\tls\ChannelBinding.cs" />
+    <Compile Include="src\tls\CipherSuite.cs" />
+    <Compile Include="src\tls\CipherType.cs" />
+    <Compile Include="src\tls\ClientAuthenticationType.cs" />
+    <Compile Include="src\tls\ClientCertificateType.cs" />
+    <Compile Include="src\tls\ClientHello.cs" />
+    <Compile Include="src\tls\CombinedHash.cs" />
+    <Compile Include="src\tls\CompressionMethod.cs" />
+    <Compile Include="src\tls\ConnectionEnd.cs" />
+    <Compile Include="src\tls\ContentType.cs" />
+    <Compile Include="src\tls\crypto\CryptoHashAlgorithm.cs" />
+    <Compile Include="src\tls\crypto\CryptoSignatureAlgorithm.cs" />
+    <Compile Include="src\tls\crypto\DHGroup.cs" />
+    <Compile Include="src\tls\crypto\DHStandardGroups.cs" />
+    <Compile Include="src\tls\crypto\impl\AbstractTlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\impl\AbstractTlsSecret.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcChaCha20Poly1305.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedAgreement.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedDecryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcSsl3Hmac.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsAeadCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsBlockCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsCertificate.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDH.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDHDomain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDssSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDssVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDH.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDomain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsa13Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsa13Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd25519Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd25519Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd448Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd448Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsHash.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsHmac.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsNonceGenerator.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaEncryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaPssSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaPssVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSecret.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6Client.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6Server.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6VerifierGenerator.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsStreamVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcVerifyingStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX25519.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX25519Domain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX448.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX448Domain.cs" />
+    <Compile Include="src\tls\crypto\impl\RsaUtilities.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsAeadCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsAeadCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsBlockCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsBlockCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsEncryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsImplUtilities.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsNullCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsSuiteHmac.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsSuiteMac.cs" />
+    <Compile Include="src\tls\crypto\Srp6Group.cs" />
+    <Compile Include="src\tls\crypto\Srp6StandardGroups.cs" />
+    <Compile Include="src\tls\crypto\TlsAgreement.cs" />
+    <Compile Include="src\tls\crypto\TlsCertificate.cs" />
+    <Compile Include="src\tls\crypto\TlsCertificateRole.cs" />
+    <Compile Include="src\tls\crypto\TlsCipher.cs" />
+    <Compile Include="src\tls\crypto\TlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoException.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoParameters.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoUtilities.cs" />
+    <Compile Include="src\tls\crypto\TlsDecodeResult.cs" />
+    <Compile Include="src\tls\crypto\TlsDHConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsDHDomain.cs" />
+    <Compile Include="src\tls\crypto\TlsECConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsECDomain.cs" />
+    <Compile Include="src\tls\crypto\TlsEncodeResult.cs" />
+    <Compile Include="src\tls\crypto\TlsHash.cs" />
+    <Compile Include="src\tls\crypto\TlsHashSink.cs" />
+    <Compile Include="src\tls\crypto\TlsHmac.cs" />
+    <Compile Include="src\tls\crypto\TlsMac.cs" />
+    <Compile Include="src\tls\crypto\TlsMacSink.cs" />
+    <Compile Include="src\tls\crypto\TlsNonceGenerator.cs" />
+    <Compile Include="src\tls\crypto\TlsNullNullCipher.cs" />
+    <Compile Include="src\tls\crypto\TlsSecret.cs" />
+    <Compile Include="src\tls\crypto\TlsSigner.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6Client.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6Server.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6VerifierGenerator.cs" />
+    <Compile Include="src\tls\crypto\TlsSrpConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\TlsStreamVerifier.cs" />
+    <Compile Include="src\tls\crypto\TlsVerifier.cs" />
+    <Compile Include="src\tls\DatagramReceiver.cs" />
+    <Compile Include="src\tls\DatagramSender.cs" />
+    <Compile Include="src\tls\DatagramTransport.cs" />
+    <Compile Include="src\tls\DefaultTlsClient.cs" />
+    <Compile Include="src\tls\DefaultTlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\DefaultTlsDHGroupVerifier.cs" />
+    <Compile Include="src\tls\DefaultTlsHeartbeat.cs" />
+    <Compile Include="src\tls\DefaultTlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\DefaultTlsServer.cs" />
+    <Compile Include="src\tls\DefaultTlsSrpConfigVerifier.cs" />
+    <Compile Include="src\tls\DeferredHash.cs" />
+    <Compile Include="src\tls\DigestInputBuffer.cs" />
+    <Compile Include="src\tls\DigitallySigned.cs" />
+    <Compile Include="src\tls\DtlsClientProtocol.cs" />
+    <Compile Include="src\tls\DtlsEpoch.cs" />
+    <Compile Include="src\tls\DtlsHandshakeRetransmit.cs" />
+    <Compile Include="src\tls\DtlsProtocol.cs" />
+    <Compile Include="src\tls\DtlsReassembler.cs" />
+    <Compile Include="src\tls\DtlsRecordLayer.cs" />
+    <Compile Include="src\tls\DtlsReliableHandshake.cs" />
+    <Compile Include="src\tls\DtlsReplayWindow.cs" />
+    <Compile Include="src\tls\DtlsRequest.cs" />
+    <Compile Include="src\tls\DtlsServerProtocol.cs" />
+    <Compile Include="src\tls\DtlsTransport.cs" />
+    <Compile Include="src\tls\DtlsVerifier.cs" />
+    <Compile Include="src\tls\ECCurveType.cs" />
+    <Compile Include="src\tls\ECPointFormat.cs" />
+    <Compile Include="src\tls\EncryptionAlgorithm.cs" />
+    <Compile Include="src\tls\ExporterLabel.cs" />
+    <Compile Include="src\tls\ExtensionType.cs" />
+    <Compile Include="src\tls\HandshakeMessageInput.cs" />
+    <Compile Include="src\tls\HandshakeMessageOutput.cs" />
+    <Compile Include="src\tls\HandshakeType.cs" />
+    <Compile Include="src\tls\HashAlgorithm.cs" />
+    <Compile Include="src\tls\HeartbeatExtension.cs" />
+    <Compile Include="src\tls\HeartbeatMessage.cs" />
+    <Compile Include="src\tls\HeartbeatMessageType.cs" />
+    <Compile Include="src\tls\HeartbeatMode.cs" />
+    <Compile Include="src\tls\IdentifierType.cs" />
+    <Compile Include="src\tls\KeyExchangeAlgorithm.cs" />
+    <Compile Include="src\tls\KeyShareEntry.cs" />
+    <Compile Include="src\tls\KeyUpdateRequest.cs" />
+    <Compile Include="src\tls\MacAlgorithm.cs" />
+    <Compile Include="src\tls\MaxFragmentLength.cs" />
+    <Compile Include="src\tls\NamedGroup.cs" />
+    <Compile Include="src\tls\NamedGroupRole.cs" />
+    <Compile Include="src\tls\NameType.cs" />
+    <Compile Include="src\tls\NewSessionTicket.cs" />
+    <Compile Include="src\tls\OcspStatusRequest.cs" />
+    <Compile Include="src\tls\OfferedPsks.cs" />
+    <Compile Include="src\tls\PrfAlgorithm.cs" />
+    <Compile Include="src\tls\ProtocolName.cs" />
+    <Compile Include="src\tls\ProtocolVersion.cs" />
+    <Compile Include="src\tls\PskIdentity.cs" />
+    <Compile Include="src\tls\PskKeyExchangeMode.cs" />
+    <Compile Include="src\tls\PskTlsClient.cs" />
+    <Compile Include="src\tls\PskTlsServer.cs" />
+    <Compile Include="src\tls\RecordFormat.cs" />
+    <Compile Include="src\tls\RecordPreview.cs" />
+    <Compile Include="src\tls\RecordStream.cs" />
+    <Compile Include="src\tls\SecurityParameters.cs" />
+    <Compile Include="src\tls\ServerHello.cs" />
+    <Compile Include="src\tls\ServerName.cs" />
+    <Compile Include="src\tls\ServerNameList.cs" />
+    <Compile Include="src\tls\ServerOnlyTlsAuthentication.cs" />
+    <Compile Include="src\tls\ServerSrpParams.cs" />
+    <Compile Include="src\tls\SessionParameters.cs" />
+    <Compile Include="src\tls\SignatureAlgorithm.cs" />
+    <Compile Include="src\tls\SignatureAndHashAlgorithm.cs" />
+    <Compile Include="src\tls\SignatureScheme.cs" />
+    <Compile Include="src\tls\SimulatedTlsSrpIdentityManager.cs" />
+    <Compile Include="src\tls\SrpTlsClient.cs" />
+    <Compile Include="src\tls\SrpTlsServer.cs" />
+    <Compile Include="src\tls\SrtpProtectionProfile.cs" />
+    <Compile Include="src\tls\Ssl3Utilities.cs" />
+    <Compile Include="src\tls\SupplementalDataEntry.cs" />
+    <Compile Include="src\tls\SupplementalDataType.cs" />
+    <Compile Include="src\tls\Timeout.cs" />
+    <Compile Include="src\tls\TlsAuthentication.cs" />
+    <Compile Include="src\tls\TlsClient.cs" />
+    <Compile Include="src\tls\TlsClientContext.cs" />
+    <Compile Include="src\tls\TlsClientContextImpl.cs" />
+    <Compile Include="src\tls\TlsClientProtocol.cs" />
+    <Compile Include="src\tls\TlsCloseable.cs" />
+    <Compile Include="src\tls\TlsContext.cs" />
+    <Compile Include="src\tls\TlsCredentialedAgreement.cs" />
+    <Compile Include="src\tls\TlsCredentialedDecryptor.cs" />
+    <Compile Include="src\tls\TlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\TlsCredentials.cs" />
+    <Compile Include="src\tls\TlsDHanonKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDheKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDHGroupVerifier.cs" />
+    <Compile Include="src\tls\TlsDHKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDHUtilities.cs" />
+    <Compile Include="src\tls\TlsEccUtilities.cs" />
+    <Compile Include="src\tls\TlsECDHanonKeyExchange.cs" />
+    <Compile Include="src\tls\TlsECDheKeyExchange.cs" />
+    <Compile Include="src\tls\TlsECDHKeyExchange.cs" />
+    <Compile Include="src\tls\TlsException.cs" />
+    <Compile Include="src\tls\TlsExtensionsUtilities.cs" />
+    <Compile Include="src\tls\TlsFatalAlert.cs" />
+    <Compile Include="src\tls\TlsFatalAlertReceived.cs" />
+    <Compile Include="src\tls\TlsHandshakeHash.cs" />
+    <Compile Include="src\tls\TlsHeartbeat.cs" />
+    <Compile Include="src\tls\TlsKeyExchange.cs" />
+    <Compile Include="src\tls\TlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\TlsNoCloseNotifyException.cs" />
+    <Compile Include="src\tls\TlsObjectIdentifiers.cs" />
+    <Compile Include="src\tls\TlsPeer.cs" />
+    <Compile Include="src\tls\TlsProtocol.cs" />
+    <Compile Include="src\tls\TlsPskIdentity.cs" />
+    <Compile Include="src\tls\TlsPskIdentityManager.cs" />
+    <Compile Include="src\tls\TlsPskKeyExchange.cs" />
+    <Compile Include="src\tls\TlsRsaKeyExchange.cs" />
+    <Compile Include="src\tls\TlsRsaUtilities.cs" />
+    <Compile Include="src\tls\TlsServer.cs" />
+    <Compile Include="src\tls\TlsServerCertificate.cs" />
+    <Compile Include="src\tls\TlsServerCertificateImpl.cs" />
+    <Compile Include="src\tls\TlsServerContext.cs" />
+    <Compile Include="src\tls\TlsServerContextImpl.cs" />
+    <Compile Include="src\tls\TlsServerProtocol.cs" />
+    <Compile Include="src\tls\TlsSession.cs" />
+    <Compile Include="src\tls\TlsSessionImpl.cs" />
+    <Compile Include="src\tls\TlsSrpConfigVerifier.cs" />
+    <Compile Include="src\tls\TlsSrpIdentity.cs" />
+    <Compile Include="src\tls\TlsSrpIdentityManager.cs" />
+    <Compile Include="src\tls\TlsSrpKeyExchange.cs" />
+    <Compile Include="src\tls\TlsSrpLoginParameters.cs" />
+    <Compile Include="src\tls\TlsSrpUtilities.cs" />
+    <Compile Include="src\tls\TlsSrtpUtilities.cs" />
+    <Compile Include="src\tls\TlsStream.cs" />
+    <Compile Include="src\tls\TlsTimeoutException.cs" />
+    <Compile Include="src\tls\TlsUtilities.cs" />
+    <Compile Include="src\tls\TrustedAuthority.cs" />
+    <Compile Include="src\tls\UrlAndHash.cs" />
+    <Compile Include="src\tls\UserMappingType.cs" />
+    <Compile Include="src\tls\UseSrtpData.cs" />
     <Compile Include="src\tsp\GenTimeAccuracy.cs" />
     <Compile Include="src\tsp\TSPAlgorithms.cs" />
     <Compile Include="src\tsp\TSPException.cs" />
diff --git a/crypto/BouncyCastle.csproj b/crypto/BouncyCastle.csproj
index 26db97e1b..f972a7694 100644
--- a/crypto/BouncyCastle.csproj
+++ b/crypto/BouncyCastle.csproj
@@ -1554,6 +1554,272 @@
     <Compile Include="src\security\cert\CertificateNotYetValidException.cs" />
     <Compile Include="src\security\cert\CertificateParsingException.cs" />
     <Compile Include="src\security\cert\CrlException.cs" />
+    <Compile Include="src\tls\AbstractTlsClient.cs" />
+    <Compile Include="src\tls\AbstractTlsContext.cs" />
+    <Compile Include="src\tls\AbstractTlsKeyExchange.cs" />
+    <Compile Include="src\tls\AbstractTlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\AbstractTlsPeer.cs" />
+    <Compile Include="src\tls\AbstractTlsServer.cs" />
+    <Compile Include="src\tls\AlertDescription.cs" />
+    <Compile Include="src\tls\AlertLevel.cs" />
+    <Compile Include="src\tls\BasicTlsPskIdentity.cs" />
+    <Compile Include="src\tls\BasicTlsSrpIdentity.cs" />
+    <Compile Include="src\tls\ByteQueue.cs" />
+    <Compile Include="src\tls\ByteQueueInputStream.cs" />
+    <Compile Include="src\tls\ByteQueueOutputStream.cs" />
+    <Compile Include="src\tls\CachedInformationType.cs" />
+    <Compile Include="src\tls\CertChainType.cs" />
+    <Compile Include="src\tls\Certificate.cs" />
+    <Compile Include="src\tls\CertificateEntry.cs" />
+    <Compile Include="src\tls\CertificateRequest.cs" />
+    <Compile Include="src\tls\CertificateStatus.cs" />
+    <Compile Include="src\tls\CertificateStatusRequest.cs" />
+    <Compile Include="src\tls\CertificateStatusRequestItemV2.cs" />
+    <Compile Include="src\tls\CertificateStatusType.cs" />
+    <Compile Include="src\tls\CertificateType.cs" />
+    <Compile Include="src\tls\CertificateUrl.cs" />
+    <Compile Include="src\tls\ChangeCipherSpec.cs" />
+    <Compile Include="src\tls\ChannelBinding.cs" />
+    <Compile Include="src\tls\CipherSuite.cs" />
+    <Compile Include="src\tls\CipherType.cs" />
+    <Compile Include="src\tls\ClientAuthenticationType.cs" />
+    <Compile Include="src\tls\ClientCertificateType.cs" />
+    <Compile Include="src\tls\ClientHello.cs" />
+    <Compile Include="src\tls\CombinedHash.cs" />
+    <Compile Include="src\tls\CompressionMethod.cs" />
+    <Compile Include="src\tls\ConnectionEnd.cs" />
+    <Compile Include="src\tls\ContentType.cs" />
+    <Compile Include="src\tls\crypto\CryptoHashAlgorithm.cs" />
+    <Compile Include="src\tls\crypto\CryptoSignatureAlgorithm.cs" />
+    <Compile Include="src\tls\crypto\DHGroup.cs" />
+    <Compile Include="src\tls\crypto\DHStandardGroups.cs" />
+    <Compile Include="src\tls\crypto\impl\AbstractTlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\impl\AbstractTlsSecret.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcChaCha20Poly1305.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedAgreement.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedDecryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcSsl3Hmac.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsAeadCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsBlockCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsCertificate.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDH.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDHDomain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDssSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDssVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDH.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDomain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsa13Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsa13Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd25519Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd25519Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd448Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd448Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsHash.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsHmac.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsNonceGenerator.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaEncryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaPssSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaPssVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSecret.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6Client.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6Server.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6VerifierGenerator.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsStreamVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcVerifyingStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX25519.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX25519Domain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX448.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX448Domain.cs" />
+    <Compile Include="src\tls\crypto\impl\RsaUtilities.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsAeadCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsAeadCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsBlockCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsBlockCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsEncryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsImplUtilities.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsNullCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsSuiteHmac.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsSuiteMac.cs" />
+    <Compile Include="src\tls\crypto\Srp6Group.cs" />
+    <Compile Include="src\tls\crypto\Srp6StandardGroups.cs" />
+    <Compile Include="src\tls\crypto\TlsAgreement.cs" />
+    <Compile Include="src\tls\crypto\TlsCertificate.cs" />
+    <Compile Include="src\tls\crypto\TlsCertificateRole.cs" />
+    <Compile Include="src\tls\crypto\TlsCipher.cs" />
+    <Compile Include="src\tls\crypto\TlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoException.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoParameters.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoUtilities.cs" />
+    <Compile Include="src\tls\crypto\TlsDecodeResult.cs" />
+    <Compile Include="src\tls\crypto\TlsDHConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsDHDomain.cs" />
+    <Compile Include="src\tls\crypto\TlsECConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsECDomain.cs" />
+    <Compile Include="src\tls\crypto\TlsEncodeResult.cs" />
+    <Compile Include="src\tls\crypto\TlsHash.cs" />
+    <Compile Include="src\tls\crypto\TlsHashSink.cs" />
+    <Compile Include="src\tls\crypto\TlsHmac.cs" />
+    <Compile Include="src\tls\crypto\TlsMac.cs" />
+    <Compile Include="src\tls\crypto\TlsMacSink.cs" />
+    <Compile Include="src\tls\crypto\TlsNonceGenerator.cs" />
+    <Compile Include="src\tls\crypto\TlsNullNullCipher.cs" />
+    <Compile Include="src\tls\crypto\TlsSecret.cs" />
+    <Compile Include="src\tls\crypto\TlsSigner.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6Client.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6Server.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6VerifierGenerator.cs" />
+    <Compile Include="src\tls\crypto\TlsSrpConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\TlsStreamVerifier.cs" />
+    <Compile Include="src\tls\crypto\TlsVerifier.cs" />
+    <Compile Include="src\tls\DatagramReceiver.cs" />
+    <Compile Include="src\tls\DatagramSender.cs" />
+    <Compile Include="src\tls\DatagramTransport.cs" />
+    <Compile Include="src\tls\DefaultTlsClient.cs" />
+    <Compile Include="src\tls\DefaultTlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\DefaultTlsDHGroupVerifier.cs" />
+    <Compile Include="src\tls\DefaultTlsHeartbeat.cs" />
+    <Compile Include="src\tls\DefaultTlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\DefaultTlsServer.cs" />
+    <Compile Include="src\tls\DefaultTlsSrpConfigVerifier.cs" />
+    <Compile Include="src\tls\DeferredHash.cs" />
+    <Compile Include="src\tls\DigestInputBuffer.cs" />
+    <Compile Include="src\tls\DigitallySigned.cs" />
+    <Compile Include="src\tls\DtlsClientProtocol.cs" />
+    <Compile Include="src\tls\DtlsEpoch.cs" />
+    <Compile Include="src\tls\DtlsHandshakeRetransmit.cs" />
+    <Compile Include="src\tls\DtlsProtocol.cs" />
+    <Compile Include="src\tls\DtlsReassembler.cs" />
+    <Compile Include="src\tls\DtlsRecordLayer.cs" />
+    <Compile Include="src\tls\DtlsReliableHandshake.cs" />
+    <Compile Include="src\tls\DtlsReplayWindow.cs" />
+    <Compile Include="src\tls\DtlsRequest.cs" />
+    <Compile Include="src\tls\DtlsServerProtocol.cs" />
+    <Compile Include="src\tls\DtlsTransport.cs" />
+    <Compile Include="src\tls\DtlsVerifier.cs" />
+    <Compile Include="src\tls\ECCurveType.cs" />
+    <Compile Include="src\tls\ECPointFormat.cs" />
+    <Compile Include="src\tls\EncryptionAlgorithm.cs" />
+    <Compile Include="src\tls\ExporterLabel.cs" />
+    <Compile Include="src\tls\ExtensionType.cs" />
+    <Compile Include="src\tls\HandshakeMessageInput.cs" />
+    <Compile Include="src\tls\HandshakeMessageOutput.cs" />
+    <Compile Include="src\tls\HandshakeType.cs" />
+    <Compile Include="src\tls\HashAlgorithm.cs" />
+    <Compile Include="src\tls\HeartbeatExtension.cs" />
+    <Compile Include="src\tls\HeartbeatMessage.cs" />
+    <Compile Include="src\tls\HeartbeatMessageType.cs" />
+    <Compile Include="src\tls\HeartbeatMode.cs" />
+    <Compile Include="src\tls\IdentifierType.cs" />
+    <Compile Include="src\tls\KeyExchangeAlgorithm.cs" />
+    <Compile Include="src\tls\KeyShareEntry.cs" />
+    <Compile Include="src\tls\KeyUpdateRequest.cs" />
+    <Compile Include="src\tls\MacAlgorithm.cs" />
+    <Compile Include="src\tls\MaxFragmentLength.cs" />
+    <Compile Include="src\tls\NamedGroup.cs" />
+    <Compile Include="src\tls\NamedGroupRole.cs" />
+    <Compile Include="src\tls\NameType.cs" />
+    <Compile Include="src\tls\NewSessionTicket.cs" />
+    <Compile Include="src\tls\OcspStatusRequest.cs" />
+    <Compile Include="src\tls\OfferedPsks.cs" />
+    <Compile Include="src\tls\PrfAlgorithm.cs" />
+    <Compile Include="src\tls\ProtocolName.cs" />
+    <Compile Include="src\tls\ProtocolVersion.cs" />
+    <Compile Include="src\tls\PskIdentity.cs" />
+    <Compile Include="src\tls\PskKeyExchangeMode.cs" />
+    <Compile Include="src\tls\PskTlsClient.cs" />
+    <Compile Include="src\tls\PskTlsServer.cs" />
+    <Compile Include="src\tls\RecordFormat.cs" />
+    <Compile Include="src\tls\RecordPreview.cs" />
+    <Compile Include="src\tls\RecordStream.cs" />
+    <Compile Include="src\tls\SecurityParameters.cs" />
+    <Compile Include="src\tls\ServerHello.cs" />
+    <Compile Include="src\tls\ServerName.cs" />
+    <Compile Include="src\tls\ServerNameList.cs" />
+    <Compile Include="src\tls\ServerOnlyTlsAuthentication.cs" />
+    <Compile Include="src\tls\ServerSrpParams.cs" />
+    <Compile Include="src\tls\SessionParameters.cs" />
+    <Compile Include="src\tls\SignatureAlgorithm.cs" />
+    <Compile Include="src\tls\SignatureAndHashAlgorithm.cs" />
+    <Compile Include="src\tls\SignatureScheme.cs" />
+    <Compile Include="src\tls\SimulatedTlsSrpIdentityManager.cs" />
+    <Compile Include="src\tls\SrpTlsClient.cs" />
+    <Compile Include="src\tls\SrpTlsServer.cs" />
+    <Compile Include="src\tls\SrtpProtectionProfile.cs" />
+    <Compile Include="src\tls\Ssl3Utilities.cs" />
+    <Compile Include="src\tls\SupplementalDataEntry.cs" />
+    <Compile Include="src\tls\SupplementalDataType.cs" />
+    <Compile Include="src\tls\Timeout.cs" />
+    <Compile Include="src\tls\TlsAuthentication.cs" />
+    <Compile Include="src\tls\TlsClient.cs" />
+    <Compile Include="src\tls\TlsClientContext.cs" />
+    <Compile Include="src\tls\TlsClientContextImpl.cs" />
+    <Compile Include="src\tls\TlsClientProtocol.cs" />
+    <Compile Include="src\tls\TlsCloseable.cs" />
+    <Compile Include="src\tls\TlsContext.cs" />
+    <Compile Include="src\tls\TlsCredentialedAgreement.cs" />
+    <Compile Include="src\tls\TlsCredentialedDecryptor.cs" />
+    <Compile Include="src\tls\TlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\TlsCredentials.cs" />
+    <Compile Include="src\tls\TlsDHanonKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDheKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDHGroupVerifier.cs" />
+    <Compile Include="src\tls\TlsDHKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDHUtilities.cs" />
+    <Compile Include="src\tls\TlsEccUtilities.cs" />
+    <Compile Include="src\tls\TlsECDHanonKeyExchange.cs" />
+    <Compile Include="src\tls\TlsECDheKeyExchange.cs" />
+    <Compile Include="src\tls\TlsECDHKeyExchange.cs" />
+    <Compile Include="src\tls\TlsException.cs" />
+    <Compile Include="src\tls\TlsExtensionsUtilities.cs" />
+    <Compile Include="src\tls\TlsFatalAlert.cs" />
+    <Compile Include="src\tls\TlsFatalAlertReceived.cs" />
+    <Compile Include="src\tls\TlsHandshakeHash.cs" />
+    <Compile Include="src\tls\TlsHeartbeat.cs" />
+    <Compile Include="src\tls\TlsKeyExchange.cs" />
+    <Compile Include="src\tls\TlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\TlsNoCloseNotifyException.cs" />
+    <Compile Include="src\tls\TlsObjectIdentifiers.cs" />
+    <Compile Include="src\tls\TlsPeer.cs" />
+    <Compile Include="src\tls\TlsProtocol.cs" />
+    <Compile Include="src\tls\TlsPskIdentity.cs" />
+    <Compile Include="src\tls\TlsPskIdentityManager.cs" />
+    <Compile Include="src\tls\TlsPskKeyExchange.cs" />
+    <Compile Include="src\tls\TlsRsaKeyExchange.cs" />
+    <Compile Include="src\tls\TlsRsaUtilities.cs" />
+    <Compile Include="src\tls\TlsServer.cs" />
+    <Compile Include="src\tls\TlsServerCertificate.cs" />
+    <Compile Include="src\tls\TlsServerCertificateImpl.cs" />
+    <Compile Include="src\tls\TlsServerContext.cs" />
+    <Compile Include="src\tls\TlsServerContextImpl.cs" />
+    <Compile Include="src\tls\TlsServerProtocol.cs" />
+    <Compile Include="src\tls\TlsSession.cs" />
+    <Compile Include="src\tls\TlsSessionImpl.cs" />
+    <Compile Include="src\tls\TlsSrpConfigVerifier.cs" />
+    <Compile Include="src\tls\TlsSrpIdentity.cs" />
+    <Compile Include="src\tls\TlsSrpIdentityManager.cs" />
+    <Compile Include="src\tls\TlsSrpKeyExchange.cs" />
+    <Compile Include="src\tls\TlsSrpLoginParameters.cs" />
+    <Compile Include="src\tls\TlsSrpUtilities.cs" />
+    <Compile Include="src\tls\TlsSrtpUtilities.cs" />
+    <Compile Include="src\tls\TlsStream.cs" />
+    <Compile Include="src\tls\TlsTimeoutException.cs" />
+    <Compile Include="src\tls\TlsUtilities.cs" />
+    <Compile Include="src\tls\TrustedAuthority.cs" />
+    <Compile Include="src\tls\UrlAndHash.cs" />
+    <Compile Include="src\tls\UserMappingType.cs" />
+    <Compile Include="src\tls\UseSrtpData.cs" />
     <Compile Include="src\tsp\GenTimeAccuracy.cs" />
     <Compile Include="src\tsp\TSPAlgorithms.cs" />
     <Compile Include="src\tsp\TSPException.cs" />
diff --git a/crypto/BouncyCastle.iOS.csproj b/crypto/BouncyCastle.iOS.csproj
index 17fe3dda5..682b74d5e 100644
--- a/crypto/BouncyCastle.iOS.csproj
+++ b/crypto/BouncyCastle.iOS.csproj
@@ -1555,6 +1555,272 @@
     <Compile Include="src\security\cert\CertificateNotYetValidException.cs" />
     <Compile Include="src\security\cert\CertificateParsingException.cs" />
     <Compile Include="src\security\cert\CrlException.cs" />
+    <Compile Include="src\tls\AbstractTlsClient.cs" />
+    <Compile Include="src\tls\AbstractTlsContext.cs" />
+    <Compile Include="src\tls\AbstractTlsKeyExchange.cs" />
+    <Compile Include="src\tls\AbstractTlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\AbstractTlsPeer.cs" />
+    <Compile Include="src\tls\AbstractTlsServer.cs" />
+    <Compile Include="src\tls\AlertDescription.cs" />
+    <Compile Include="src\tls\AlertLevel.cs" />
+    <Compile Include="src\tls\BasicTlsPskIdentity.cs" />
+    <Compile Include="src\tls\BasicTlsSrpIdentity.cs" />
+    <Compile Include="src\tls\ByteQueue.cs" />
+    <Compile Include="src\tls\ByteQueueInputStream.cs" />
+    <Compile Include="src\tls\ByteQueueOutputStream.cs" />
+    <Compile Include="src\tls\CachedInformationType.cs" />
+    <Compile Include="src\tls\CertChainType.cs" />
+    <Compile Include="src\tls\Certificate.cs" />
+    <Compile Include="src\tls\CertificateEntry.cs" />
+    <Compile Include="src\tls\CertificateRequest.cs" />
+    <Compile Include="src\tls\CertificateStatus.cs" />
+    <Compile Include="src\tls\CertificateStatusRequest.cs" />
+    <Compile Include="src\tls\CertificateStatusRequestItemV2.cs" />
+    <Compile Include="src\tls\CertificateStatusType.cs" />
+    <Compile Include="src\tls\CertificateType.cs" />
+    <Compile Include="src\tls\CertificateUrl.cs" />
+    <Compile Include="src\tls\ChangeCipherSpec.cs" />
+    <Compile Include="src\tls\ChannelBinding.cs" />
+    <Compile Include="src\tls\CipherSuite.cs" />
+    <Compile Include="src\tls\CipherType.cs" />
+    <Compile Include="src\tls\ClientAuthenticationType.cs" />
+    <Compile Include="src\tls\ClientCertificateType.cs" />
+    <Compile Include="src\tls\ClientHello.cs" />
+    <Compile Include="src\tls\CombinedHash.cs" />
+    <Compile Include="src\tls\CompressionMethod.cs" />
+    <Compile Include="src\tls\ConnectionEnd.cs" />
+    <Compile Include="src\tls\ContentType.cs" />
+    <Compile Include="src\tls\crypto\CryptoHashAlgorithm.cs" />
+    <Compile Include="src\tls\crypto\CryptoSignatureAlgorithm.cs" />
+    <Compile Include="src\tls\crypto\DHGroup.cs" />
+    <Compile Include="src\tls\crypto\DHStandardGroups.cs" />
+    <Compile Include="src\tls\crypto\impl\AbstractTlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\impl\AbstractTlsSecret.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcChaCha20Poly1305.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedAgreement.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedDecryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcDefaultTlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcSsl3Hmac.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsAeadCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsBlockCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsCertificate.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDH.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDHDomain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDssSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsDssVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDH.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDomain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsa13Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsa13Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsECDsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd25519Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd25519Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd448Signer.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsEd448Verifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsHash.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsHmac.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsNonceGenerator.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaEncryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaPssSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaPssVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsRsaVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSecret.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6Client.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6Server.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsSrp6VerifierGenerator.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsStreamVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcTlsVerifier.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcVerifyingStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX25519.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX25519Domain.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX448.cs" />
+    <Compile Include="src\tls\crypto\impl\bc\BcX448Domain.cs" />
+    <Compile Include="src\tls\crypto\impl\RsaUtilities.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsAeadCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsAeadCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsBlockCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsBlockCipherImpl.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsEncryptor.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsImplUtilities.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsNullCipher.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsSuiteHmac.cs" />
+    <Compile Include="src\tls\crypto\impl\TlsSuiteMac.cs" />
+    <Compile Include="src\tls\crypto\Srp6Group.cs" />
+    <Compile Include="src\tls\crypto\Srp6StandardGroups.cs" />
+    <Compile Include="src\tls\crypto\TlsAgreement.cs" />
+    <Compile Include="src\tls\crypto\TlsCertificate.cs" />
+    <Compile Include="src\tls\crypto\TlsCertificateRole.cs" />
+    <Compile Include="src\tls\crypto\TlsCipher.cs" />
+    <Compile Include="src\tls\crypto\TlsCrypto.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoException.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoParameters.cs" />
+    <Compile Include="src\tls\crypto\TlsCryptoUtilities.cs" />
+    <Compile Include="src\tls\crypto\TlsDecodeResult.cs" />
+    <Compile Include="src\tls\crypto\TlsDHConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsDHDomain.cs" />
+    <Compile Include="src\tls\crypto\TlsECConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsECDomain.cs" />
+    <Compile Include="src\tls\crypto\TlsEncodeResult.cs" />
+    <Compile Include="src\tls\crypto\TlsHash.cs" />
+    <Compile Include="src\tls\crypto\TlsHashSink.cs" />
+    <Compile Include="src\tls\crypto\TlsHmac.cs" />
+    <Compile Include="src\tls\crypto\TlsMac.cs" />
+    <Compile Include="src\tls\crypto\TlsMacSink.cs" />
+    <Compile Include="src\tls\crypto\TlsNonceGenerator.cs" />
+    <Compile Include="src\tls\crypto\TlsNullNullCipher.cs" />
+    <Compile Include="src\tls\crypto\TlsSecret.cs" />
+    <Compile Include="src\tls\crypto\TlsSigner.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6Client.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6Server.cs" />
+    <Compile Include="src\tls\crypto\TlsSrp6VerifierGenerator.cs" />
+    <Compile Include="src\tls\crypto\TlsSrpConfig.cs" />
+    <Compile Include="src\tls\crypto\TlsStreamSigner.cs" />
+    <Compile Include="src\tls\crypto\TlsStreamVerifier.cs" />
+    <Compile Include="src\tls\crypto\TlsVerifier.cs" />
+    <Compile Include="src\tls\DatagramReceiver.cs" />
+    <Compile Include="src\tls\DatagramSender.cs" />
+    <Compile Include="src\tls\DatagramTransport.cs" />
+    <Compile Include="src\tls\DefaultTlsClient.cs" />
+    <Compile Include="src\tls\DefaultTlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\DefaultTlsDHGroupVerifier.cs" />
+    <Compile Include="src\tls\DefaultTlsHeartbeat.cs" />
+    <Compile Include="src\tls\DefaultTlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\DefaultTlsServer.cs" />
+    <Compile Include="src\tls\DefaultTlsSrpConfigVerifier.cs" />
+    <Compile Include="src\tls\DeferredHash.cs" />
+    <Compile Include="src\tls\DigestInputBuffer.cs" />
+    <Compile Include="src\tls\DigitallySigned.cs" />
+    <Compile Include="src\tls\DtlsClientProtocol.cs" />
+    <Compile Include="src\tls\DtlsEpoch.cs" />
+    <Compile Include="src\tls\DtlsHandshakeRetransmit.cs" />
+    <Compile Include="src\tls\DtlsProtocol.cs" />
+    <Compile Include="src\tls\DtlsReassembler.cs" />
+    <Compile Include="src\tls\DtlsRecordLayer.cs" />
+    <Compile Include="src\tls\DtlsReliableHandshake.cs" />
+    <Compile Include="src\tls\DtlsReplayWindow.cs" />
+    <Compile Include="src\tls\DtlsRequest.cs" />
+    <Compile Include="src\tls\DtlsServerProtocol.cs" />
+    <Compile Include="src\tls\DtlsTransport.cs" />
+    <Compile Include="src\tls\DtlsVerifier.cs" />
+    <Compile Include="src\tls\ECCurveType.cs" />
+    <Compile Include="src\tls\ECPointFormat.cs" />
+    <Compile Include="src\tls\EncryptionAlgorithm.cs" />
+    <Compile Include="src\tls\ExporterLabel.cs" />
+    <Compile Include="src\tls\ExtensionType.cs" />
+    <Compile Include="src\tls\HandshakeMessageInput.cs" />
+    <Compile Include="src\tls\HandshakeMessageOutput.cs" />
+    <Compile Include="src\tls\HandshakeType.cs" />
+    <Compile Include="src\tls\HashAlgorithm.cs" />
+    <Compile Include="src\tls\HeartbeatExtension.cs" />
+    <Compile Include="src\tls\HeartbeatMessage.cs" />
+    <Compile Include="src\tls\HeartbeatMessageType.cs" />
+    <Compile Include="src\tls\HeartbeatMode.cs" />
+    <Compile Include="src\tls\IdentifierType.cs" />
+    <Compile Include="src\tls\KeyExchangeAlgorithm.cs" />
+    <Compile Include="src\tls\KeyShareEntry.cs" />
+    <Compile Include="src\tls\KeyUpdateRequest.cs" />
+    <Compile Include="src\tls\MacAlgorithm.cs" />
+    <Compile Include="src\tls\MaxFragmentLength.cs" />
+    <Compile Include="src\tls\NamedGroup.cs" />
+    <Compile Include="src\tls\NamedGroupRole.cs" />
+    <Compile Include="src\tls\NameType.cs" />
+    <Compile Include="src\tls\NewSessionTicket.cs" />
+    <Compile Include="src\tls\OcspStatusRequest.cs" />
+    <Compile Include="src\tls\OfferedPsks.cs" />
+    <Compile Include="src\tls\PrfAlgorithm.cs" />
+    <Compile Include="src\tls\ProtocolName.cs" />
+    <Compile Include="src\tls\ProtocolVersion.cs" />
+    <Compile Include="src\tls\PskIdentity.cs" />
+    <Compile Include="src\tls\PskKeyExchangeMode.cs" />
+    <Compile Include="src\tls\PskTlsClient.cs" />
+    <Compile Include="src\tls\PskTlsServer.cs" />
+    <Compile Include="src\tls\RecordFormat.cs" />
+    <Compile Include="src\tls\RecordPreview.cs" />
+    <Compile Include="src\tls\RecordStream.cs" />
+    <Compile Include="src\tls\SecurityParameters.cs" />
+    <Compile Include="src\tls\ServerHello.cs" />
+    <Compile Include="src\tls\ServerName.cs" />
+    <Compile Include="src\tls\ServerNameList.cs" />
+    <Compile Include="src\tls\ServerOnlyTlsAuthentication.cs" />
+    <Compile Include="src\tls\ServerSrpParams.cs" />
+    <Compile Include="src\tls\SessionParameters.cs" />
+    <Compile Include="src\tls\SignatureAlgorithm.cs" />
+    <Compile Include="src\tls\SignatureAndHashAlgorithm.cs" />
+    <Compile Include="src\tls\SignatureScheme.cs" />
+    <Compile Include="src\tls\SimulatedTlsSrpIdentityManager.cs" />
+    <Compile Include="src\tls\SrpTlsClient.cs" />
+    <Compile Include="src\tls\SrpTlsServer.cs" />
+    <Compile Include="src\tls\SrtpProtectionProfile.cs" />
+    <Compile Include="src\tls\Ssl3Utilities.cs" />
+    <Compile Include="src\tls\SupplementalDataEntry.cs" />
+    <Compile Include="src\tls\SupplementalDataType.cs" />
+    <Compile Include="src\tls\Timeout.cs" />
+    <Compile Include="src\tls\TlsAuthentication.cs" />
+    <Compile Include="src\tls\TlsClient.cs" />
+    <Compile Include="src\tls\TlsClientContext.cs" />
+    <Compile Include="src\tls\TlsClientContextImpl.cs" />
+    <Compile Include="src\tls\TlsClientProtocol.cs" />
+    <Compile Include="src\tls\TlsCloseable.cs" />
+    <Compile Include="src\tls\TlsContext.cs" />
+    <Compile Include="src\tls\TlsCredentialedAgreement.cs" />
+    <Compile Include="src\tls\TlsCredentialedDecryptor.cs" />
+    <Compile Include="src\tls\TlsCredentialedSigner.cs" />
+    <Compile Include="src\tls\TlsCredentials.cs" />
+    <Compile Include="src\tls\TlsDHanonKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDheKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDHGroupVerifier.cs" />
+    <Compile Include="src\tls\TlsDHKeyExchange.cs" />
+    <Compile Include="src\tls\TlsDHUtilities.cs" />
+    <Compile Include="src\tls\TlsEccUtilities.cs" />
+    <Compile Include="src\tls\TlsECDHanonKeyExchange.cs" />
+    <Compile Include="src\tls\TlsECDheKeyExchange.cs" />
+    <Compile Include="src\tls\TlsECDHKeyExchange.cs" />
+    <Compile Include="src\tls\TlsException.cs" />
+    <Compile Include="src\tls\TlsExtensionsUtilities.cs" />
+    <Compile Include="src\tls\TlsFatalAlert.cs" />
+    <Compile Include="src\tls\TlsFatalAlertReceived.cs" />
+    <Compile Include="src\tls\TlsHandshakeHash.cs" />
+    <Compile Include="src\tls\TlsHeartbeat.cs" />
+    <Compile Include="src\tls\TlsKeyExchange.cs" />
+    <Compile Include="src\tls\TlsKeyExchangeFactory.cs" />
+    <Compile Include="src\tls\TlsNoCloseNotifyException.cs" />
+    <Compile Include="src\tls\TlsObjectIdentifiers.cs" />
+    <Compile Include="src\tls\TlsPeer.cs" />
+    <Compile Include="src\tls\TlsProtocol.cs" />
+    <Compile Include="src\tls\TlsPskIdentity.cs" />
+    <Compile Include="src\tls\TlsPskIdentityManager.cs" />
+    <Compile Include="src\tls\TlsPskKeyExchange.cs" />
+    <Compile Include="src\tls\TlsRsaKeyExchange.cs" />
+    <Compile Include="src\tls\TlsRsaUtilities.cs" />
+    <Compile Include="src\tls\TlsServer.cs" />
+    <Compile Include="src\tls\TlsServerCertificate.cs" />
+    <Compile Include="src\tls\TlsServerCertificateImpl.cs" />
+    <Compile Include="src\tls\TlsServerContext.cs" />
+    <Compile Include="src\tls\TlsServerContextImpl.cs" />
+    <Compile Include="src\tls\TlsServerProtocol.cs" />
+    <Compile Include="src\tls\TlsSession.cs" />
+    <Compile Include="src\tls\TlsSessionImpl.cs" />
+    <Compile Include="src\tls\TlsSrpConfigVerifier.cs" />
+    <Compile Include="src\tls\TlsSrpIdentity.cs" />
+    <Compile Include="src\tls\TlsSrpIdentityManager.cs" />
+    <Compile Include="src\tls\TlsSrpKeyExchange.cs" />
+    <Compile Include="src\tls\TlsSrpLoginParameters.cs" />
+    <Compile Include="src\tls\TlsSrpUtilities.cs" />
+    <Compile Include="src\tls\TlsSrtpUtilities.cs" />
+    <Compile Include="src\tls\TlsStream.cs" />
+    <Compile Include="src\tls\TlsTimeoutException.cs" />
+    <Compile Include="src\tls\TlsUtilities.cs" />
+    <Compile Include="src\tls\TrustedAuthority.cs" />
+    <Compile Include="src\tls\UrlAndHash.cs" />
+    <Compile Include="src\tls\UserMappingType.cs" />
+    <Compile Include="src\tls\UseSrtpData.cs" />
     <Compile Include="src\tsp\GenTimeAccuracy.cs" />
     <Compile Include="src\tsp\TSPAlgorithms.cs" />
     <Compile Include="src\tsp\TSPException.cs" />
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj
index 1ba78a83c..97fcb3a5b 100644
--- a/crypto/crypto.csproj
+++ b/crypto/crypto.csproj
@@ -7659,6 +7659,1336 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "src\tls\AbstractTlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\AbstractTlsContext.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\AbstractTlsKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\AbstractTlsKeyExchangeFactory.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\AbstractTlsPeer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\AbstractTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\AlertDescription.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\AlertLevel.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\BasicTlsPskIdentity.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\BasicTlsSrpIdentity.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ByteQueue.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ByteQueueInputStream.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ByteQueueOutputStream.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CachedInformationType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertChainType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\Certificate.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertificateEntry.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertificateRequest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertificateStatus.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertificateStatusRequest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertificateStatusRequestItemV2.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertificateStatusType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertificateType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CertificateUrl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ChangeCipherSpec.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ChannelBinding.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CipherSuite.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CipherType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ClientAuthenticationType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ClientCertificateType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ClientHello.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CombinedHash.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\CompressionMethod.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ConnectionEnd.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ContentType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\CryptoHashAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\CryptoSignatureAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\DHGroup.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\DHStandardGroups.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\AbstractTlsCrypto.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\AbstractTlsSecret.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcChaCha20Poly1305.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcDefaultTlsCredentialedAgreement.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcDefaultTlsCredentialedDecryptor.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcDefaultTlsCredentialedSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcSsl3Hmac.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsAeadCipherImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsBlockCipherImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsCertificate.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsCrypto.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsDH.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsDHDomain.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsDsaSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsDsaVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsDssSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsDssVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsECDH.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsECDomain.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsECDsa13Signer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsECDsa13Verifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsECDsaSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsECDsaVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsEd25519Signer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsEd25519Verifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsEd448Signer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsEd448Verifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsHash.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsHmac.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsNonceGenerator.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsRsaEncryptor.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsRsaPssSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsRsaPssVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsRsaSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsRsaVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsSecret.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsSrp6Client.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsSrp6Server.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsSrp6VerifierGenerator.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsStreamSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsStreamVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcTlsVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcVerifyingStreamSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcX25519.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcX25519Domain.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcX448.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\bc\BcX448Domain.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\RsaUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsAeadCipher.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsAeadCipherImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsBlockCipher.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsBlockCipherImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsEncryptor.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsImplUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsNullCipher.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsSuiteHmac.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\impl\TlsSuiteMac.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\Srp6Group.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\Srp6StandardGroups.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsAgreement.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsCertificate.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsCertificateRole.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsCipher.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsCrypto.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsCryptoException.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsCryptoParameters.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsCryptoUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsDecodeResult.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsDHConfig.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsDHDomain.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsECConfig.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsECDomain.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsEncodeResult.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsHash.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsHashSink.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsHmac.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsMac.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsMacSink.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsNonceGenerator.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsNullNullCipher.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsSecret.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsSrp6Client.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsSrp6Server.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsSrp6VerifierGenerator.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsSrpConfig.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsStreamSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsStreamVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\crypto\TlsVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DatagramReceiver.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DatagramSender.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DatagramTransport.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DefaultTlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DefaultTlsCredentialedSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DefaultTlsDHGroupVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DefaultTlsHeartbeat.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DefaultTlsKeyExchangeFactory.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DefaultTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DefaultTlsSrpConfigVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DeferredHash.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DigestInputBuffer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DigitallySigned.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsClientProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsEpoch.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsHandshakeRetransmit.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsReassembler.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsRecordLayer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsReliableHandshake.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsReplayWindow.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsRequest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsServerProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsTransport.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\DtlsVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ECCurveType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ECPointFormat.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\EncryptionAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ExporterLabel.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ExtensionType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\HandshakeMessageInput.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\HandshakeMessageOutput.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\HandshakeType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\HashAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\HeartbeatExtension.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\HeartbeatMessage.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\HeartbeatMessageType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\HeartbeatMode.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\IdentifierType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\KeyExchangeAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\KeyShareEntry.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\KeyUpdateRequest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\MacAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\MaxFragmentLength.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\NamedGroup.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\NamedGroupRole.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\NameType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\NewSessionTicket.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\OcspStatusRequest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\OfferedPsks.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\PrfAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ProtocolName.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ProtocolVersion.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\PskIdentity.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\PskKeyExchangeMode.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\PskTlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\PskTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\RecordFormat.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\RecordPreview.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\RecordStream.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SecurityParameters.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ServerHello.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ServerName.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ServerNameList.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ServerOnlyTlsAuthentication.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\ServerSrpParams.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SessionParameters.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SignatureAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SignatureAndHashAlgorithm.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SignatureScheme.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SimulatedTlsSrpIdentityManager.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SrpTlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SrpTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SrtpProtectionProfile.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\Ssl3Utilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SupplementalDataEntry.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\SupplementalDataType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\Timeout.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsAuthentication.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsClientContext.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsClientContextImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsClientProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsCloseable.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsContext.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsCredentialedAgreement.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsCredentialedDecryptor.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsCredentialedSigner.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsCredentials.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsDHanonKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsDheKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsDHGroupVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsDHKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsDHUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsEccUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsECDHanonKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsECDheKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsECDHKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsException.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsExtensionsUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsFatalAlert.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsFatalAlertReceived.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsHandshakeHash.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsHeartbeat.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsKeyExchangeFactory.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsNoCloseNotifyException.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsObjectIdentifiers.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsPeer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsPskIdentity.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsPskIdentityManager.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsPskKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsRsaKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsRsaUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsServerCertificate.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsServerCertificateImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsServerContext.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsServerContextImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsServerProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSession.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSessionImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSrpConfigVerifier.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSrpIdentity.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSrpIdentityManager.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSrpKeyExchange.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSrpLoginParameters.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSrpUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsSrtpUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsStream.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsTimeoutException.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TlsUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\TrustedAuthority.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\UrlAndHash.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\UserMappingType.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "src\tls\UseSrtpData.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "src\tsp\GenTimeAccuracy.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
@@ -13969,6 +15299,216 @@
                     BuildAction = "Compile"
                 />
                 <File
+                    RelPath = "test\src\tls\crypto\test\BcTlsCryptoTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\ByteQueueInputStreamTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\CertChainUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\DtlsProtocolTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\DtlsPskProtocolTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\DtlsTestCase.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\DtlsTestClientProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\DtlsTestServerProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\DtlsTestSuite.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\LoggingDatagramTransport.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockDatagramAssociation.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockDtlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockDtlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockPskDtlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockPskDtlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockPskTlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockPskTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockSrpTlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockSrpTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockTlsClient.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\MockTlsServer.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\NetworkStream.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\PipedStream.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\PrfTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\PskTlsClientTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\PskTlsServerTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsClientTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsProtocolNonBlockingTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsProtocolTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsPskProtocolTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsServerTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsSrpProtocolTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsTestCase.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsTestClientImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsTestClientProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsTestConfig.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsTestServerImpl.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsTestServerProtocol.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsTestSuite.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsTestUtilities.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\TlsUtilitiesTest.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
+                    RelPath = "test\src\tls\test\UnreliableDatagramTransport.cs"
+                    SubType = "Code"
+                    BuildAction = "Compile"
+                />
+                <File
                     RelPath = "test\src\tsp\test\AllTests.cs"
                     SubType = "Code"
                     BuildAction = "Compile"
diff --git a/crypto/src/tls/AbstractTlsClient.cs b/crypto/src/tls/AbstractTlsClient.cs
new file mode 100644
index 000000000..0a9410cd9
--- /dev/null
+++ b/crypto/src/tls/AbstractTlsClient.cs
@@ -0,0 +1,435 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base class for a TLS client.</summary>
+    public abstract class AbstractTlsClient
+        : AbstractTlsPeer, TlsClient
+    {
+        protected TlsClientContext m_context;
+        protected ProtocolVersion[] m_protocolVersions;
+        protected int[] m_cipherSuites;
+
+        protected IList m_supportedGroups;
+        protected IList m_supportedSignatureAlgorithms;
+        protected IList m_supportedSignatureAlgorithmsCert;
+
+        protected AbstractTlsClient(TlsCrypto crypto)
+            : base(crypto)
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual bool AllowUnexpectedServerExtension(int extensionType, byte[] extensionData)
+        {
+            switch (extensionType)
+            {
+            case ExtensionType.supported_groups:
+                /*
+                 * Exception added based on field reports that some servers do send this, although the
+                 * Supported Elliptic Curves Extension is clearly intended to be client-only. If
+                 * present, we still require that it is a valid EllipticCurveList.
+                 */
+                TlsExtensionsUtilities.ReadSupportedGroupsExtension(extensionData);
+                return true;
+
+            case ExtensionType.ec_point_formats:
+                /*
+                 * Exception added based on field reports that some servers send this even when they
+                 * didn't negotiate an ECC cipher suite. If present, we still require that it is a valid
+                 * ECPointFormatList.
+                 */
+                TlsExtensionsUtilities.ReadSupportedPointFormatsExtension(extensionData);
+                return true;
+
+            default:
+                return false;
+            }
+        }
+
+        protected virtual IList GetNamedGroupRoles()
+        {
+            IList namedGroupRoles = TlsUtilities.GetNamedGroupRoles(GetCipherSuites());
+            IList sigAlgs = m_supportedSignatureAlgorithms, sigAlgsCert = m_supportedSignatureAlgorithmsCert;
+
+            if ((null == sigAlgs || TlsUtilities.ContainsAnySignatureAlgorithm(sigAlgs, SignatureAlgorithm.ecdsa))
+                || (null != sigAlgsCert
+                    && TlsUtilities.ContainsAnySignatureAlgorithm(sigAlgsCert, SignatureAlgorithm.ecdsa)))
+            {
+                TlsUtilities.AddToSet(namedGroupRoles, NamedGroupRole.ecdsa);
+            }
+
+            return namedGroupRoles;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void CheckForUnexpectedServerExtension(IDictionary serverExtensions, int extensionType)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(serverExtensions, extensionType);
+            if (extensionData != null && !AllowUnexpectedServerExtension(extensionType, extensionData))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsPskIdentity GetPskIdentity()
+        {
+            return null;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsSrpIdentity GetSrpIdentity()
+        {
+            return null;
+        }
+
+        public virtual TlsDHGroupVerifier GetDHGroupVerifier()
+        {
+            return new DefaultTlsDHGroupVerifier();
+        }
+
+        public virtual TlsSrpConfigVerifier GetSrpConfigVerifier()
+        {
+            return new DefaultTlsSrpConfigVerifier();
+        }
+
+        protected virtual IList GetCertificateAuthorities()
+        {
+            return null;
+        }
+
+        protected virtual IList GetProtocolNames()
+        {
+            return null;
+        }
+
+        protected virtual CertificateStatusRequest GetCertificateStatusRequest()
+        {
+            return new CertificateStatusRequest(CertificateStatusType.ocsp, new OcspStatusRequest(null, null));
+        }
+
+        /// <returns>an <see cref="IList"/> of <see cref="CertificateStatusRequestItemV2"/> (or null).</returns>
+        protected virtual IList GetMultiCertStatusRequest()
+        {
+            return null;
+        }
+
+        protected virtual IList GetSniServerNames()
+        {
+            return null;
+        }
+
+        /// <summary>The default <see cref="GetClientExtensions"/> implementation calls this to determine which named
+        /// groups to include in the supported_groups extension for the ClientHello.</summary>
+        /// <param name="namedGroupRoles">The <see cref="NamedGroupRole">named group roles</see> for which there should
+        /// be at least one supported group. By default this is inferred from the offered cipher suites and signature
+        /// algorithms.</param>
+        /// <returns>an <see cref="IList"/> of <see cref="Int32"/>. See <see cref="NamedGroup"/> for group constants.
+        /// </returns>
+        protected virtual IList GetSupportedGroups(IList namedGroupRoles)
+        {
+            TlsCrypto crypto = Crypto;
+            IList supportedGroups = Platform.CreateArrayList();
+
+            if (namedGroupRoles.Contains(NamedGroupRole.ecdh))
+            {
+                TlsUtilities.AddIfSupported(supportedGroups, crypto,
+                    new int[]{ NamedGroup.x25519, NamedGroup.x448 });
+            }
+
+            if (namedGroupRoles.Contains(NamedGroupRole.ecdh) ||
+                namedGroupRoles.Contains(NamedGroupRole.ecdsa))
+            {
+                TlsUtilities.AddIfSupported(supportedGroups, crypto,
+                    new int[]{ NamedGroup.secp256r1, NamedGroup.secp384r1 });
+            }
+
+            if (namedGroupRoles.Contains(NamedGroupRole.dh))
+            {
+                TlsUtilities.AddIfSupported(supportedGroups, crypto,
+                    new int[]{ NamedGroup.ffdhe2048, NamedGroup.ffdhe3072, NamedGroup.ffdhe4096 });
+            }
+
+            return supportedGroups;
+        }
+
+        protected virtual IList GetSupportedSignatureAlgorithms()
+        {
+            return TlsUtilities.GetDefaultSupportedSignatureAlgorithms(m_context);
+        }
+
+        protected virtual IList GetSupportedSignatureAlgorithmsCert()
+        {
+            return null;
+        }
+
+        protected virtual IList GetTrustedCAIndication()
+        {
+            return null;
+        }
+
+        public virtual void Init(TlsClientContext context)
+        {
+            this.m_context = context;
+
+            this.m_protocolVersions = GetSupportedVersions();
+            this.m_cipherSuites = GetSupportedCipherSuites();
+        }
+
+        public override ProtocolVersion[] GetProtocolVersions()
+        {
+            return m_protocolVersions;
+        }
+
+        public override int[] GetCipherSuites()
+        {
+            return m_cipherSuites;
+        }
+
+        /// <exception cref="IOException"/>
+        public override void NotifyHandshakeBeginning()
+        {
+            base.NotifyHandshakeBeginning();
+
+            this.m_supportedGroups = null;
+            this.m_supportedSignatureAlgorithms = null;
+            this.m_supportedSignatureAlgorithmsCert = null;
+        }
+
+        public virtual TlsSession GetSessionToResume()
+        {
+            return null;
+        }
+
+        public virtual bool IsFallback()
+        {
+            /*
+             * RFC 7507 4. The TLS_FALLBACK_SCSV cipher suite value is meant for use by clients that
+             * repeat a connection attempt with a downgraded protocol (perform a "fallback retry") in
+             * order to work around interoperability problems with legacy servers.
+             */
+            return false;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = Platform.CreateHashtable();
+
+            bool offeringTlsV13Plus = false;
+            bool offeringPreTlsV13 = false;
+            {
+                ProtocolVersion[] supportedVersions = GetProtocolVersions();
+                for (int i = 0; i < supportedVersions.Length; ++i)
+                {
+                    if (TlsUtilities.IsTlsV13(supportedVersions[i]))
+                    {
+                        offeringTlsV13Plus = true;
+                    }
+                    else
+                    {
+                        offeringPreTlsV13 = true;
+                    }
+                }
+            }
+
+            IList protocolNames = GetProtocolNames();
+            if (protocolNames != null)
+            {
+                TlsExtensionsUtilities.AddAlpnExtensionClient(clientExtensions, protocolNames);
+            }
+
+            IList sniServerNames = GetSniServerNames();
+            if (sniServerNames != null)
+            {
+                TlsExtensionsUtilities.AddServerNameExtensionClient(clientExtensions, sniServerNames);
+            }
+
+            CertificateStatusRequest statusRequest = GetCertificateStatusRequest();
+            if (statusRequest != null)
+            {
+                TlsExtensionsUtilities.AddStatusRequestExtension(clientExtensions, statusRequest);
+            }
+
+            if (offeringTlsV13Plus)
+            {
+                IList certificateAuthorities = GetCertificateAuthorities();
+                if (certificateAuthorities != null)
+                {
+                    TlsExtensionsUtilities.AddCertificateAuthoritiesExtension(clientExtensions, certificateAuthorities);
+                }
+            }
+
+            if (offeringPreTlsV13)
+            {
+                // TODO Shouldn't add if no offered cipher suite uses a block cipher?
+                TlsExtensionsUtilities.AddEncryptThenMacExtension(clientExtensions);
+
+                IList statusRequestV2 = GetMultiCertStatusRequest();
+                if (statusRequestV2 != null)
+                {
+                    TlsExtensionsUtilities.AddStatusRequestV2Extension(clientExtensions, statusRequestV2);
+                }
+
+                IList trustedCAKeys = GetTrustedCAIndication();
+                if (trustedCAKeys != null)
+                {
+                    TlsExtensionsUtilities.AddTrustedCAKeysExtensionClient(clientExtensions, trustedCAKeys);
+                }
+            }
+
+            ProtocolVersion clientVersion = m_context.ClientVersion;
+
+            /*
+             * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior to 1.2.
+             * Clients MUST NOT offer it if they are offering prior versions.
+             */
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(clientVersion))
+            {
+                IList supportedSigAlgs = GetSupportedSignatureAlgorithms();
+                if (null != supportedSigAlgs && supportedSigAlgs.Count > 0)
+                {
+                    this.m_supportedSignatureAlgorithms = supportedSigAlgs;
+
+                    TlsExtensionsUtilities.AddSignatureAlgorithmsExtension(clientExtensions, supportedSigAlgs);
+                }
+
+                IList supportedSigAlgsCert = GetSupportedSignatureAlgorithmsCert();
+                if (null != supportedSigAlgsCert && supportedSigAlgsCert.Count > 0)
+                {
+                    this.m_supportedSignatureAlgorithmsCert = supportedSigAlgsCert;
+
+                    TlsExtensionsUtilities.AddSignatureAlgorithmsCertExtension(clientExtensions, supportedSigAlgsCert);
+                }
+            }
+
+            IList namedGroupRoles = GetNamedGroupRoles();
+
+            IList supportedGroups = GetSupportedGroups(namedGroupRoles);
+            if (supportedGroups != null && supportedGroups.Count > 0)
+            {
+                this.m_supportedGroups = supportedGroups;
+
+                TlsExtensionsUtilities.AddSupportedGroupsExtension(clientExtensions, supportedGroups);
+            }
+
+            if (offeringPreTlsV13)
+            {
+                if (namedGroupRoles.Contains(NamedGroupRole.ecdh) ||
+                    namedGroupRoles.Contains(NamedGroupRole.ecdsa))
+                {
+                    TlsExtensionsUtilities.AddSupportedPointFormatsExtension(clientExtensions,
+                        new short[]{ ECPointFormat.uncompressed });
+                }
+            }
+
+            return clientExtensions;
+        }
+
+        public virtual IList GetEarlyKeyShareGroups()
+        {
+            /*
+             * RFC 8446 4.2.8. Each KeyShareEntry value MUST correspond to a group offered in the
+             * "supported_groups" extension and MUST appear in the same order. However, the values MAY
+             * be a non-contiguous subset of the "supported_groups" extension and MAY omit the most
+             * preferred groups.
+             */
+
+            if (null == m_supportedGroups || m_supportedGroups.Count < 1)
+                return null;
+
+            if (m_supportedGroups.Contains(NamedGroup.x25519))
+                return TlsUtilities.VectorOfOne(NamedGroup.x25519);
+
+            if (m_supportedGroups.Contains(NamedGroup.secp256r1))
+                return TlsUtilities.VectorOfOne(NamedGroup.secp256r1);
+
+            return TlsUtilities.VectorOfOne(m_supportedGroups[0]);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+        }
+
+        public virtual void NotifySessionToResume(TlsSession session)
+        {
+        }
+
+        public virtual void NotifySessionID(byte[] sessionID)
+        {
+        }
+
+        public virtual void NotifySelectedCipherSuite(int selectedCipherSuite)
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void ProcessServerExtensions(IDictionary serverExtensions)
+        {
+            if (null == serverExtensions)
+                return;
+
+            SecurityParameters securityParameters = m_context.SecurityParameters;
+            bool isTlsV13 = TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion);
+
+            if (isTlsV13)
+            {
+                /*
+                 * NOTE: From TLS 1.3 the protocol classes are strict about what extensions can appear.
+                 */
+            }
+            else
+            {
+                /*
+                 * RFC 5246 7.4.1.4.1. Servers MUST NOT send this extension.
+                 */
+                CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.signature_algorithms);
+                CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.signature_algorithms_cert);
+
+                CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.supported_groups);
+
+                int selectedCipherSuite = securityParameters.CipherSuite;
+
+                if (TlsEccUtilities.IsEccCipherSuite(selectedCipherSuite))
+                {
+                    // We only support uncompressed format, this is just to validate the extension, if present.
+                    TlsExtensionsUtilities.GetSupportedPointFormatsExtension(serverExtensions);
+                }
+                else
+                {
+                    CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.ec_point_formats);
+                }
+
+                /*
+                 * RFC 7685 3. The server MUST NOT echo the extension.
+                 */
+                CheckForUnexpectedServerExtension(serverExtensions, ExtensionType.padding);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void ProcessServerSupplementalData(IList serverSupplementalData)
+        {
+            if (serverSupplementalData != null)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public abstract TlsAuthentication GetAuthentication();
+
+        /// <exception cref="IOException"/>
+        public virtual IList GetClientSupplementalData()
+        {
+            return null;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void NotifyNewSessionTicket(NewSessionTicket newSessionTicket)
+        {
+        }
+    }
+}
diff --git a/crypto/src/tls/AbstractTlsContext.cs b/crypto/src/tls/AbstractTlsContext.cs
new file mode 100644
index 000000000..e8071bfab
--- /dev/null
+++ b/crypto/src/tls/AbstractTlsContext.cs
@@ -0,0 +1,282 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal abstract class AbstractTlsContext
+        : TlsContext
+    {
+        private static long counter = Times.NanoTime();
+
+#if NETCF_1_0
+        private static object counterLock = new object();
+        private static long NextCounterValue()
+        {
+            lock (counterLock)
+            {
+                return ++counter;
+            }
+        }
+#else
+        private static long NextCounterValue()
+        {
+            return Interlocked.Increment(ref counter);
+        }
+#endif
+
+        private static TlsNonceGenerator CreateNonceGenerator(TlsCrypto crypto, int connectionEnd)
+        {
+            byte[] additionalSeedMaterial = new byte[16];
+            Pack.UInt64_To_BE((ulong)NextCounterValue(), additionalSeedMaterial, 0);
+            Pack.UInt64_To_BE((ulong)Times.NanoTime(), additionalSeedMaterial, 8);
+            additionalSeedMaterial[0] &= 0x7F;
+            additionalSeedMaterial[0] |= (byte)(connectionEnd << 7);
+
+            return crypto.CreateNonceGenerator(additionalSeedMaterial);
+        }
+
+        private readonly TlsCrypto m_crypto;
+        private readonly int m_connectionEnd;
+        private readonly TlsNonceGenerator m_nonceGenerator;
+
+        private SecurityParameters m_securityParameters = null;
+        private ProtocolVersion[] m_clientSupportedVersions = null;
+        private ProtocolVersion m_clientVersion = null;
+        private ProtocolVersion m_rsaPreMasterSecretVersion = null;
+        private TlsSession m_session = null;
+        private object m_userObject = null;
+        private bool m_connected = false;
+
+        internal AbstractTlsContext(TlsCrypto crypto, int connectionEnd)
+        {
+            this.m_crypto = crypto;
+            this.m_connectionEnd = connectionEnd;
+            this.m_nonceGenerator = CreateNonceGenerator(crypto, connectionEnd);
+        }
+
+        /// <exception cref="IOException"/>
+        internal void HandshakeBeginning(TlsPeer peer)
+        {
+            lock (this)
+            {
+                if (null != m_securityParameters)
+                    throw new TlsFatalAlert(AlertDescription.internal_error, "Handshake already started");
+
+                m_securityParameters = new SecurityParameters();
+                m_securityParameters.m_entity = m_connectionEnd;
+            }
+
+            peer.NotifyHandshakeBeginning();
+        }
+
+        /// <exception cref="IOException"/>
+        internal void HandshakeComplete(TlsPeer peer, TlsSession session)
+        {
+            lock (this)
+            {
+                if (null == m_securityParameters)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                this.m_session = session;
+                this.m_connected = true;
+            }
+
+            peer.NotifyHandshakeComplete();
+        }
+
+        internal bool IsConnected
+        {
+            get { lock (this) return m_connected; }
+        }
+
+        public TlsCrypto Crypto
+        {
+            get { return m_crypto; }
+        }
+
+        public virtual TlsNonceGenerator NonceGenerator
+        {
+            get { return m_nonceGenerator; }
+        }
+
+        public SecurityParameters SecurityParameters
+        {
+            get { lock (this) return m_securityParameters; }
+        }
+
+        public abstract bool IsServer { get; }
+
+        public virtual ProtocolVersion[] ClientSupportedVersions
+        {
+            get { return m_clientSupportedVersions; }
+        }
+
+        internal void SetClientSupportedVersions(ProtocolVersion[] clientSupportedVersions)
+        {
+            this.m_clientSupportedVersions = clientSupportedVersions;
+        }
+
+        public virtual ProtocolVersion ClientVersion
+        {
+            get { return m_clientVersion; }
+        }
+
+        internal void SetClientVersion(ProtocolVersion clientVersion)
+        {
+            this.m_clientVersion = clientVersion;
+        }
+
+        public virtual ProtocolVersion RsaPreMasterSecretVersion
+        {
+            get { return m_rsaPreMasterSecretVersion; }
+        }
+
+        internal void SetRsaPreMasterSecretVersion(ProtocolVersion rsaPreMasterSecretVersion)
+        {
+            this.m_rsaPreMasterSecretVersion = rsaPreMasterSecretVersion;
+        }
+
+        public virtual ProtocolVersion ServerVersion
+        {
+            get { return SecurityParameters.NegotiatedVersion; }
+        }
+
+        public virtual TlsSession ResumableSession
+        {
+            get
+            {
+                TlsSession session = Session;
+                if (session == null || !session.IsResumable)
+                    return null;
+
+                return session;
+            }
+        }
+
+        public virtual TlsSession Session
+        {
+            get { return m_session; }
+        }
+
+        public virtual object UserObject
+        {
+            get { return m_userObject; }
+            set { this.m_userObject = value; }
+        }
+
+        public virtual byte[] ExportChannelBinding(int channelBinding)
+        {
+            if (!IsConnected)
+                throw new InvalidOperationException("Export of channel bindings unavailable before handshake completion");
+
+            SecurityParameters securityParameters = SecurityParameters;
+
+            if (TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
+                return null;
+
+            switch (channelBinding)
+            {
+            case ChannelBinding.tls_server_end_point:
+            {
+                byte[] tlsServerEndPoint = securityParameters.TlsServerEndPoint;
+
+                return TlsUtilities.IsNullOrEmpty(tlsServerEndPoint) ? null : Arrays.Clone(tlsServerEndPoint);
+            }
+
+            case ChannelBinding.tls_unique:
+            {
+                return Arrays.Clone(securityParameters.TlsUnique);
+            }
+
+            case ChannelBinding.tls_unique_for_telnet:
+            default:
+                throw new NotSupportedException();
+            }
+        }
+
+        public virtual byte[] ExportEarlyKeyingMaterial(string asciiLabel, byte[] context, int length)
+        {
+            // TODO[tls13] Ensure early_exporter_master_secret is available suitably early!
+            if (!IsConnected)
+                throw new InvalidOperationException("Export of early key material only available during handshake");
+
+            SecurityParameters sp = SecurityParameters;
+
+            return ExportKeyingMaterial13(CheckEarlyExportSecret(sp.EarlyExporterMasterSecret),
+                sp.PrfHashAlgorithm, asciiLabel, context, length);
+        }
+
+        public virtual byte[] ExportKeyingMaterial(string asciiLabel, byte[] context, int length)
+        {
+            if (!IsConnected)
+                throw new InvalidOperationException("Export of key material unavailable before handshake completion");
+
+            /*
+             * TODO[tls13] Introduce a TlsExporter interface? Avoid calculating (early) exporter
+             * secret(s) unless the peer actually uses it.
+             */
+            SecurityParameters sp = SecurityParameters;
+
+            if (!sp.IsExtendedMasterSecret)
+            {
+                /*
+                 * RFC 7627 5.4. If a client or server chooses to continue with a full handshake without
+                 * the extended master secret extension, [..] the client or server MUST NOT export any
+                 * key material based on the new master secret for any subsequent application-level
+                 * authentication. In particular, it MUST disable [RFC5705] [..].
+                 */
+                throw new InvalidOperationException("Export of key material requires extended_master_secret");
+            }
+
+            if (TlsUtilities.IsTlsV13(sp.NegotiatedVersion))
+            {
+                return ExportKeyingMaterial13(CheckExportSecret(sp.ExporterMasterSecret), sp.PrfHashAlgorithm,
+                    asciiLabel, context, length);
+            }
+
+            byte[] seed = TlsUtilities.CalculateExporterSeed(sp, context);
+
+            return TlsUtilities.Prf(sp, CheckExportSecret(sp.MasterSecret), asciiLabel, seed, length).Extract();
+        }
+
+        protected virtual byte[] ExportKeyingMaterial13(TlsSecret secret, short hashAlgorithm, string asciiLabel,
+            byte[] context, int length)
+        {
+            if (null == context)
+            {
+                context = TlsUtilities.EmptyBytes;
+            }
+            else if (!TlsUtilities.IsValidUint16(context.Length))
+            {
+                throw new ArgumentException("must have length less than 2^16 (or be null)", "context");
+            }
+
+            return TlsCryptoUtilities.HkdfExpandLabel(secret, hashAlgorithm, asciiLabel, context, length).Extract();
+        }
+
+        protected virtual TlsSecret CheckEarlyExportSecret(TlsSecret secret)
+        {
+            if (null == secret)
+            {
+                // TODO[tls13] For symmetry with normal export, ideally available for NotifyHandshakeBeginning() only
+                //throw new InvalidOperationException("Export of early key material only available from NotifyHandshakeBeginning()");
+                throw new InvalidOperationException("Export of early key material not available for this handshake");
+            }
+            return secret;
+        }
+
+        protected virtual TlsSecret CheckExportSecret(TlsSecret secret)
+        {
+            if (null == secret)
+                throw new InvalidOperationException(
+                    "Export of key material only available from NotifyHandshakeComplete()");
+
+            return secret;
+        }
+    }
+}
diff --git a/crypto/src/tls/AbstractTlsKeyExchange.cs b/crypto/src/tls/AbstractTlsKeyExchange.cs
new file mode 100644
index 000000000..4e61f4c5a
--- /dev/null
+++ b/crypto/src/tls/AbstractTlsKeyExchange.cs
@@ -0,0 +1,90 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base class for supporting a TLS key exchange implementation.</summary>
+    public abstract class AbstractTlsKeyExchange
+        : TlsKeyExchange
+    {
+        protected readonly int m_keyExchange;
+
+        protected TlsContext m_context;
+
+        protected AbstractTlsKeyExchange(int keyExchange)
+        {
+            this.m_keyExchange = keyExchange;
+        }
+
+        public virtual void Init(TlsContext context)
+        {
+            this.m_context = context;
+        }
+
+        public abstract void SkipServerCredentials();
+
+        public abstract void ProcessServerCredentials(TlsCredentials serverCredentials);
+
+        public virtual void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual bool RequiresServerKeyExchange
+        {
+            get { return false; }
+        }
+
+        public virtual byte[] GenerateServerKeyExchange()
+        {
+            if (RequiresServerKeyExchange)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return null;
+        }
+
+        public virtual void SkipServerKeyExchange()
+        {
+            if (RequiresServerKeyExchange)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public virtual void ProcessServerKeyExchange(Stream input)
+        {
+            if (!RequiresServerKeyExchange)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public virtual short[] GetClientCertificateTypes()
+        {
+            return null;
+        }
+
+        public virtual void SkipClientCredentials()
+        {
+        }
+
+        public abstract void ProcessClientCredentials(TlsCredentials clientCredentials);
+
+        public virtual void ProcessClientCertificate(Certificate clientCertificate)
+        {
+        }
+
+        public abstract void GenerateClientKeyExchange(Stream output);
+
+        public virtual void ProcessClientKeyExchange(Stream input)
+        {
+            // Key exchange implementation MUST support client key exchange
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual bool RequiresCertificateVerify
+        {
+            get { return true; }
+        }
+
+        public abstract TlsSecret GeneratePreMasterSecret();
+    }
+}
diff --git a/crypto/src/tls/AbstractTlsKeyExchangeFactory.cs b/crypto/src/tls/AbstractTlsKeyExchangeFactory.cs
new file mode 100644
index 000000000..946ad5f0a
--- /dev/null
+++ b/crypto/src/tls/AbstractTlsKeyExchangeFactory.cs
@@ -0,0 +1,90 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base class for supporting a TLS key exchange factory implementation.</summary>
+    public abstract class AbstractTlsKeyExchangeFactory
+        : TlsKeyExchangeFactory
+    {
+        public virtual TlsKeyExchange CreateDHKeyExchange(int keyExchange)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateDHanonKeyExchangeClient(int keyExchange, TlsDHGroupVerifier dhGroupVerifier)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateDHanonKeyExchangeServer(int keyExchange, TlsDHConfig dhConfig)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateDheKeyExchangeClient(int keyExchange, TlsDHGroupVerifier dhGroupVerifier)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateDheKeyExchangeServer(int keyExchange, TlsDHConfig dhConfig)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateECDHKeyExchange(int keyExchange)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateECDHanonKeyExchangeClient(int keyExchange)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateECDHanonKeyExchangeServer(int keyExchange, TlsECConfig ecConfig)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateECDheKeyExchangeClient(int keyExchange)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateECDheKeyExchangeServer(int keyExchange, TlsECConfig ecConfig)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreatePskKeyExchangeClient(int keyExchange, TlsPskIdentity pskIdentity,
+            TlsDHGroupVerifier dhGroupVerifier)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreatePskKeyExchangeServer(int keyExchange,
+            TlsPskIdentityManager pskIdentityManager, TlsDHConfig dhConfig, TlsECConfig ecConfig)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateRsaKeyExchange(int keyExchange)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateSrpKeyExchangeClient(int keyExchange, TlsSrpIdentity srpIdentity,
+            TlsSrpConfigVerifier srpConfigVerifier)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual TlsKeyExchange CreateSrpKeyExchangeServer(int keyExchange,
+            TlsSrpLoginParameters loginParameters)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+}
diff --git a/crypto/src/tls/AbstractTlsPeer.cs b/crypto/src/tls/AbstractTlsPeer.cs
new file mode 100644
index 000000000..ad9d83e76
--- /dev/null
+++ b/crypto/src/tls/AbstractTlsPeer.cs
@@ -0,0 +1,157 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base class for a TLS client or server.</summary>
+    public abstract class AbstractTlsPeer
+        : TlsPeer
+    {
+        private readonly TlsCrypto m_crypto;
+
+        private volatile TlsCloseable m_closeHandle;
+
+        protected AbstractTlsPeer(TlsCrypto crypto)
+        {
+            this.m_crypto = crypto;
+        }
+
+        /// <summary>Get the <see cref="ProtocolVersion"/> values that are supported by this peer.</summary>
+        /// <remarks>
+        /// WARNING: Mixing DTLS and TLS versions in the returned array is currently NOT supported. Use a separate
+        /// (sub-)class for each case.
+        /// </remarks>
+        /// <returns>an array of supported <see cref="ProtocolVersion"/> values.</returns>
+        protected virtual ProtocolVersion[] GetSupportedVersions()
+        {
+            // TODO[tls13] Enable TLSv13 by default in due course
+            return ProtocolVersion.TLSv12.DownTo(ProtocolVersion.TLSv10);
+        }
+
+        protected abstract int[] GetSupportedCipherSuites();
+
+        /// <exception cref="IOException"/>
+        public virtual void Cancel()
+        {
+            TlsCloseable closeHandle = this.m_closeHandle;
+            if (null != closeHandle)
+            {
+                closeHandle.Close();
+            }
+        }
+
+        public virtual TlsCrypto Crypto
+        {
+            get { return m_crypto; }
+        }
+
+        public virtual void NotifyCloseHandle(TlsCloseable closeHandle)
+        {
+            this.m_closeHandle = closeHandle;
+        }
+
+        public abstract ProtocolVersion[] GetProtocolVersions();
+
+        public abstract int[] GetCipherSuites();
+
+        /// <exception cref="IOException"/>
+        public virtual void NotifyHandshakeBeginning()
+        {
+        }
+
+        public virtual int GetHandshakeTimeoutMillis()
+        {
+            return 0;
+        }
+
+        public virtual bool AllowLegacyResumption()
+        {
+            return false;
+        }
+
+        public virtual int GetMaxCertificateChainLength()
+        {
+            return 10;
+        }
+
+        public virtual int GetMaxHandshakeMessageSize()
+        {
+            return 32768;
+        }
+
+        public virtual bool RequiresCloseNotify()
+        {
+            return true;
+        }
+
+        public virtual bool RequiresExtendedMasterSecret()
+        {
+            return false;
+        }
+
+        public virtual bool ShouldCheckSigAlgOfPeerCerts()
+        {
+            return true;
+        }
+
+        public virtual bool ShouldUseExtendedMasterSecret()
+        {
+            return true;
+        }
+
+        public virtual bool ShouldUseExtendedPadding()
+        {
+            return false;
+        }
+
+        public virtual bool ShouldUseGmtUnixTime()
+        {
+            /*
+             * draft-mathewson-no-gmtunixtime-00 2. For the reasons we discuss above, we recommend that
+             * TLS implementors MUST by default set the entire value the ClientHello.Random and
+             * ServerHello.Random fields, including gmt_unix_time, to a cryptographically random
+             * sequence.
+             */
+            return false;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void NotifySecureRenegotiation(bool secureRenegotiation)
+        {
+            if (!secureRenegotiation)
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsKeyExchangeFactory GetKeyExchangeFactory()
+        {
+            return new DefaultTlsKeyExchangeFactory();
+        }
+
+        public virtual void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+        }
+
+        public virtual void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void NotifyHandshakeComplete()
+        {
+        }
+
+        public virtual TlsHeartbeat GetHeartbeat()
+        {
+            return null;
+        }
+
+        public virtual short GetHeartbeatPolicy()
+        {
+            return HeartbeatMode.peer_not_allowed_to_send;
+        }
+    }
+}
diff --git a/crypto/src/tls/AbstractTlsServer.cs b/crypto/src/tls/AbstractTlsServer.cs
new file mode 100644
index 000000000..7514e3618
--- /dev/null
+++ b/crypto/src/tls/AbstractTlsServer.cs
@@ -0,0 +1,578 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base class for a TLS server.</summary>
+    public abstract class AbstractTlsServer
+        : AbstractTlsPeer, TlsServer
+    {
+        protected TlsServerContext m_context;
+        protected ProtocolVersion[] m_protocolVersions;
+        protected int[] m_cipherSuites;
+
+        protected int[] m_offeredCipherSuites;
+        protected IDictionary m_clientExtensions;
+
+        protected bool m_encryptThenMACOffered;
+        protected short m_maxFragmentLengthOffered;
+        protected bool m_truncatedHMacOffered;
+        protected bool m_clientSentECPointFormats;
+        protected CertificateStatusRequest m_certificateStatusRequest;
+        protected IList m_statusRequestV2;
+        protected IList m_trustedCAKeys;
+
+        protected int m_selectedCipherSuite;
+        protected IList m_clientProtocolNames;
+        protected ProtocolName m_selectedProtocolName;
+
+        protected readonly IDictionary m_serverExtensions = Platform.CreateHashtable();
+
+        public AbstractTlsServer(TlsCrypto crypto)
+            : base(crypto)
+        {
+        }
+
+        protected virtual bool AllowCertificateStatus()
+        {
+            return true;
+        }
+
+        protected virtual bool AllowEncryptThenMac()
+        {
+            return true;
+        }
+
+        protected virtual bool AllowMultiCertStatus()
+        {
+            return false;
+        }
+
+        protected virtual bool AllowTruncatedHmac()
+        {
+            return false;
+        }
+
+        protected virtual bool AllowTrustedCAIndication()
+        {
+            return false;
+        }
+
+        protected virtual int GetMaximumNegotiableCurveBits()
+        {
+            int[] clientSupportedGroups = m_context.SecurityParameters.ClientSupportedGroups;
+            if (clientSupportedGroups == null)
+            {
+                /*
+                 * RFC 4492 4. A client that proposes ECC cipher suites may choose not to include these
+                 * extensions. In this case, the server is free to choose any one of the elliptic curves
+                 * or point formats [...].
+                 */
+                return NamedGroup.GetMaximumCurveBits();
+            }
+
+            int maxBits = 0;
+            for (int i = 0; i < clientSupportedGroups.Length; ++i)
+            {
+                maxBits = System.Math.Max(maxBits, NamedGroup.GetCurveBits(clientSupportedGroups[i]));
+            }
+            return maxBits;
+        }
+
+        protected virtual int GetMaximumNegotiableFiniteFieldBits()
+        {
+            int[] clientSupportedGroups = m_context.SecurityParameters.ClientSupportedGroups;
+            if (clientSupportedGroups == null)
+            {
+                return NamedGroup.GetMaximumFiniteFieldBits();
+            }
+
+            int maxBits = 0;
+            for (int i = 0; i < clientSupportedGroups.Length; ++i)
+            {
+                maxBits = System.Math.Max(maxBits, NamedGroup.GetFiniteFieldBits(clientSupportedGroups[i]));
+            }
+            return maxBits;
+        }
+
+        protected virtual IList GetProtocolNames()
+        {
+            return null;
+        }
+
+        protected virtual bool IsSelectableCipherSuite(int cipherSuite, int availCurveBits, int availFiniteFieldBits,
+            IList sigAlgs)
+        {
+            // TODO[tls13] The version check should be separated out (eventually select ciphersuite before version)
+            return TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, m_context.ServerVersion)
+                && availCurveBits >= TlsEccUtilities.GetMinimumCurveBits(cipherSuite)
+                && availFiniteFieldBits >= TlsDHUtilities.GetMinimumFiniteFieldBits(cipherSuite)
+                && TlsUtilities.IsValidCipherSuiteForSignatureAlgorithms(cipherSuite, sigAlgs);
+        }
+
+        protected virtual bool PreferLocalCipherSuites()
+        {
+            return false;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual bool SelectCipherSuite(int cipherSuite)
+        {
+            this.m_selectedCipherSuite = cipherSuite;
+            return true;
+        }
+
+        protected virtual int SelectDH(int minimumFiniteFieldBits)
+        {
+            int[] clientSupportedGroups = m_context.SecurityParameters.ClientSupportedGroups;
+            if (clientSupportedGroups == null)
+                return SelectDHDefault(minimumFiniteFieldBits);
+
+            // Try to find a supported named group of the required size from the client's list.
+            for (int i = 0; i < clientSupportedGroups.Length; ++i)
+            {
+                int namedGroup = clientSupportedGroups[i];
+                if (NamedGroup.GetFiniteFieldBits(namedGroup) >= minimumFiniteFieldBits)
+                    return namedGroup;
+            }
+
+            return -1;
+        }
+
+        protected virtual int SelectDHDefault(int minimumFiniteFieldBits)
+        {
+            return minimumFiniteFieldBits <= 2048 ? NamedGroup.ffdhe2048
+                :  minimumFiniteFieldBits <= 3072 ? NamedGroup.ffdhe3072
+                :  minimumFiniteFieldBits <= 4096 ? NamedGroup.ffdhe4096
+                :  minimumFiniteFieldBits <= 6144 ? NamedGroup.ffdhe6144
+                :  minimumFiniteFieldBits <= 8192 ? NamedGroup.ffdhe8192
+                :  -1;
+        }
+
+        protected virtual int SelectECDH(int minimumCurveBits)
+        {
+            int[] clientSupportedGroups = m_context.SecurityParameters.ClientSupportedGroups;
+            if (clientSupportedGroups == null)
+                return SelectECDHDefault(minimumCurveBits);
+
+            // Try to find a supported named group of the required size from the client's list.
+            for (int i = 0; i < clientSupportedGroups.Length; ++i)
+            {
+                int namedGroup = clientSupportedGroups[i];
+                if (NamedGroup.GetCurveBits(namedGroup) >= minimumCurveBits)
+                    return namedGroup;
+            }
+
+            return -1;
+        }
+
+        protected virtual int SelectECDHDefault(int minimumCurveBits)
+        {
+            return minimumCurveBits <= 256 ? NamedGroup.secp256r1
+                :  minimumCurveBits <= 384 ? NamedGroup.secp384r1
+                :  minimumCurveBits <= 521 ? NamedGroup.secp521r1
+                :  -1;
+        }
+
+        protected virtual ProtocolName SelectProtocolName()
+        {
+            IList serverProtocolNames = GetProtocolNames();
+            if (null == serverProtocolNames || serverProtocolNames.Count < 1)
+                return null;
+
+            ProtocolName result = SelectProtocolName(m_clientProtocolNames, serverProtocolNames);
+            if (null == result)
+                throw new TlsFatalAlert(AlertDescription.no_application_protocol);
+
+            return result;
+        }
+
+        protected virtual ProtocolName SelectProtocolName(IList clientProtocolNames, IList serverProtocolNames)
+        {
+            foreach (ProtocolName serverProtocolName in serverProtocolNames)
+            {
+                if (clientProtocolNames.Contains(serverProtocolName))
+                    return serverProtocolName;
+            }
+            return null;
+        }
+
+        protected virtual bool ShouldSelectProtocolNameEarly()
+        {
+            return true;
+        }
+
+        public virtual void Init(TlsServerContext context)
+        {
+            this.m_context = context;
+
+            this.m_protocolVersions = GetSupportedVersions();
+            this.m_cipherSuites = GetSupportedCipherSuites();
+        }
+
+        public override ProtocolVersion[] GetProtocolVersions()
+        {
+            return m_protocolVersions;
+        }
+
+        public override int[] GetCipherSuites()
+        {
+            return m_cipherSuites;
+        }
+
+        public override void NotifyHandshakeBeginning()
+        {
+            base.NotifyHandshakeBeginning();
+
+            this.m_offeredCipherSuites = null;
+            this.m_clientExtensions = null;
+            this.m_encryptThenMACOffered = false;
+            this.m_maxFragmentLengthOffered = 0;
+            this.m_truncatedHMacOffered = false;
+            this.m_clientSentECPointFormats = false;
+            this.m_certificateStatusRequest = null;
+            this.m_selectedCipherSuite = -1;
+            this.m_selectedProtocolName = null;
+            this.m_serverExtensions.Clear();
+        }
+
+        public virtual TlsSession GetSessionToResume(byte[] sessionID)
+        {
+            return null;
+        }
+
+        public virtual byte[] GetNewSessionID()
+        {
+            return null;
+        }
+
+        public virtual void NotifySession(TlsSession session)
+        {
+        }
+
+        public virtual void NotifyClientVersion(ProtocolVersion clientVersion)
+        {
+        }
+
+        public virtual void NotifyFallback(bool isFallback)
+        {
+            /*
+             * RFC 7507 3. If TLS_FALLBACK_SCSV appears in ClientHello.cipher_suites and the highest
+             * protocol version supported by the server is higher than the version indicated in
+             * ClientHello.client_version, the server MUST respond with a fatal inappropriate_fallback
+             * alert [..].
+             */
+            if (isFallback)
+            {
+                ProtocolVersion[] serverVersions = GetProtocolVersions();
+                ProtocolVersion clientVersion = m_context.ClientVersion;
+
+                ProtocolVersion latestServerVersion;
+                if (clientVersion.IsTls)
+                {
+                    latestServerVersion = ProtocolVersion.GetLatestTls(serverVersions);
+                }
+                else if (clientVersion.IsDtls)
+                {
+                    latestServerVersion = ProtocolVersion.GetLatestDtls(serverVersions);
+                }
+                else
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                if (null != latestServerVersion && latestServerVersion.IsLaterVersionOf(clientVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.inappropriate_fallback);
+                }
+            }
+        }
+
+        public virtual void NotifyOfferedCipherSuites(int[] offeredCipherSuites)
+        {
+            this.m_offeredCipherSuites = offeredCipherSuites;
+        }
+
+        public virtual void ProcessClientExtensions(IDictionary clientExtensions)
+        {
+            this.m_clientExtensions = clientExtensions;
+
+            if (null != clientExtensions)
+            {
+                this.m_clientProtocolNames = TlsExtensionsUtilities.GetAlpnExtensionClient(clientExtensions);
+
+                if (ShouldSelectProtocolNameEarly())
+                {
+                    if (null != m_clientProtocolNames && m_clientProtocolNames.Count > 0)
+                    {
+                        this.m_selectedProtocolName = SelectProtocolName();
+                    }
+                }
+
+                // TODO[tls13] Don't need these if we have negotiated (D)TLS 1.3+
+                {
+                    this.m_encryptThenMACOffered = TlsExtensionsUtilities.HasEncryptThenMacExtension(clientExtensions);
+                    this.m_truncatedHMacOffered = TlsExtensionsUtilities.HasTruncatedHmacExtension(clientExtensions);
+                    this.m_statusRequestV2 = TlsExtensionsUtilities.GetStatusRequestV2Extension(clientExtensions);
+                    this.m_trustedCAKeys = TlsExtensionsUtilities.GetTrustedCAKeysExtensionClient(clientExtensions);
+
+                    // We only support uncompressed format, this is just to validate the extension, and note its presence.
+                    this.m_clientSentECPointFormats =
+                        null != TlsExtensionsUtilities.GetSupportedPointFormatsExtension(clientExtensions);
+                }
+
+                this.m_certificateStatusRequest = TlsExtensionsUtilities.GetStatusRequestExtension(clientExtensions);
+
+                this.m_maxFragmentLengthOffered = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(clientExtensions);
+                if (m_maxFragmentLengthOffered >= 0 && !MaxFragmentLength.IsValid(m_maxFragmentLengthOffered))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+
+        public virtual ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion[] serverVersions = GetProtocolVersions();
+            ProtocolVersion[] clientVersions = m_context.ClientSupportedVersions;
+
+            foreach (ProtocolVersion clientVersion in clientVersions)
+            {
+                if (ProtocolVersion.Contains(serverVersions, clientVersion))
+                    return clientVersion;
+            }
+
+            throw new TlsFatalAlert(AlertDescription.protocol_version);
+        }
+
+        public virtual int[] GetSupportedGroups()
+        {
+            // TODO[tls13] The rest of this class assumes all named groups are supported
+            return new int[]{ NamedGroup.x25519, NamedGroup.x448, NamedGroup.secp256r1, NamedGroup.secp384r1,
+                NamedGroup.ffdhe2048, NamedGroup.ffdhe3072, NamedGroup.ffdhe4096 };
+        }
+
+        public virtual int GetSelectedCipherSuite()
+        {
+            SecurityParameters securityParameters = m_context.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (TlsUtilities.IsTlsV13(negotiatedVersion))
+            {
+                int commonCipherSuite13 = TlsUtilities.GetCommonCipherSuite13(negotiatedVersion, m_offeredCipherSuites,
+                    GetCipherSuites(), PreferLocalCipherSuites());
+
+                if (commonCipherSuite13 >= 0 && SelectCipherSuite(commonCipherSuite13))
+                {
+                    return commonCipherSuite13;
+                }
+            }
+            else
+            {
+                /*
+                 * RFC 5246 7.4.3. In order to negotiate correctly, the server MUST check any candidate
+                 * cipher suites against the "signature_algorithms" extension before selecting them. This is
+                 * somewhat inelegant but is a compromise designed to minimize changes to the original
+                 * cipher suite design.
+                 */
+                IList sigAlgs = TlsUtilities.GetUsableSignatureAlgorithms(securityParameters.ClientSigAlgs);
+
+                /*
+                 * RFC 4429 5.1. A server that receives a ClientHello containing one or both of these
+                 * extensions MUST use the client's enumerated capabilities to guide its selection of an
+                 * appropriate cipher suite. One of the proposed ECC cipher suites must be negotiated only
+                 * if the server can successfully complete the handshake while using the curves and point
+                 * formats supported by the client [...].
+                 */
+                int availCurveBits = GetMaximumNegotiableCurveBits();
+                int availFiniteFieldBits = GetMaximumNegotiableFiniteFieldBits();
+
+                int[] cipherSuites = TlsUtilities.GetCommonCipherSuites(m_offeredCipherSuites, GetCipherSuites(),
+                    PreferLocalCipherSuites());
+
+                for (int i = 0; i < cipherSuites.Length; ++i)
+                {
+                    int cipherSuite = cipherSuites[i];
+                    if (IsSelectableCipherSuite(cipherSuite, availCurveBits, availFiniteFieldBits, sigAlgs)
+                        && SelectCipherSuite(cipherSuite))
+                    {
+                        return cipherSuite;
+                    }
+                }
+            }
+
+            throw new TlsFatalAlert(AlertDescription.handshake_failure, "No selectable cipher suite");
+        }
+
+        // IDictionary is (Int32 -> byte[])
+        public virtual IDictionary GetServerExtensions()
+        {
+            bool isTlsV13 = TlsUtilities.IsTlsV13(m_context);
+
+            if (isTlsV13)
+            {
+                if (null != m_certificateStatusRequest && AllowCertificateStatus())
+                {
+                    /*
+                     * TODO[tls13] RFC 8446 4.4.2.1. OCSP Status and SCT Extensions.
+                     * 
+                     * OCSP information is carried in an extension for a CertificateEntry.
+                     */
+                }
+            }
+            else
+            {
+                if (m_encryptThenMACOffered && AllowEncryptThenMac())
+                {
+                    /*
+                     * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
+                     * and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
+                     * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
+                     * client.
+                     */
+                    if (TlsUtilities.IsBlockCipherSuite(m_selectedCipherSuite))
+                    {
+                        TlsExtensionsUtilities.AddEncryptThenMacExtension(m_serverExtensions);
+                    }
+                }
+
+                if (m_truncatedHMacOffered && AllowTruncatedHmac())
+                {
+                    TlsExtensionsUtilities.AddTruncatedHmacExtension(m_serverExtensions);
+                }
+
+                if (m_clientSentECPointFormats && TlsEccUtilities.IsEccCipherSuite(m_selectedCipherSuite))
+                {
+                    /*
+                     * RFC 4492 5.2. A server that selects an ECC cipher suite in response to a ClientHello
+                     * message including a Supported Point Formats Extension appends this extension (along
+                     * with others) to its ServerHello message, enumerating the point formats it can parse.
+                     */
+                    TlsExtensionsUtilities.AddSupportedPointFormatsExtension(m_serverExtensions,
+                        new short[]{ ECPointFormat.uncompressed });
+                }
+
+                // TODO[tls13] See RFC 8446 4.4.2.1
+                if (null != m_statusRequestV2 && AllowMultiCertStatus())
+                {
+                    /*
+                     * RFC 6961 2.2. If a server returns a "CertificateStatus" message in response to a
+                     * "status_request_v2" request, then the server MUST have included an extension of type
+                     * "status_request_v2" with empty "extension_data" in the extended server hello..
+                     */
+                    TlsExtensionsUtilities.AddEmptyExtensionData(m_serverExtensions, ExtensionType.status_request_v2);
+                }
+                else if (null != this.m_certificateStatusRequest && AllowCertificateStatus())
+                {
+                    /*
+                     * RFC 6066 8. If a server returns a "CertificateStatus" message, then the server MUST
+                     * have included an extension of type "status_request" with empty "extension_data" in
+                     * the extended server hello.
+                     */
+                    TlsExtensionsUtilities.AddEmptyExtensionData(m_serverExtensions, ExtensionType.status_request);
+                }
+
+                if (null != m_trustedCAKeys && AllowTrustedCAIndication())
+                {
+                    TlsExtensionsUtilities.AddTrustedCAKeysExtensionServer(m_serverExtensions);
+                }
+            }
+
+            if (m_maxFragmentLengthOffered >= 0 && MaxFragmentLength.IsValid(m_maxFragmentLengthOffered))
+            {
+                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(m_serverExtensions, m_maxFragmentLengthOffered);
+            }
+
+            return m_serverExtensions;
+        }
+
+        public virtual void GetServerExtensionsForConnection(IDictionary serverExtensions)
+        {
+            if (!ShouldSelectProtocolNameEarly())
+            {
+                if (null != m_clientProtocolNames && m_clientProtocolNames.Count > 0)
+                {
+                    this.m_selectedProtocolName = SelectProtocolName();
+                }
+            }
+
+            /*
+             * RFC 7301 3.1. When session resumption or session tickets [...] are used, the previous
+             * contents of this extension are irrelevant, and only the values in the new handshake
+             * messages are considered.
+             */
+            if (null == m_selectedProtocolName)
+            {
+                serverExtensions.Remove(ExtensionType.application_layer_protocol_negotiation);
+            }
+            else
+            {
+                TlsExtensionsUtilities.AddAlpnExtensionServer(serverExtensions, m_selectedProtocolName);
+            }
+        }
+
+        public virtual IList GetServerSupplementalData()
+        {
+            return null;
+        }
+
+        public abstract TlsCredentials GetCredentials();
+
+        public virtual CertificateStatus GetCertificateStatus()
+        {
+            return null;
+        }
+
+        public virtual CertificateRequest GetCertificateRequest()
+        {
+            return null;
+        }
+
+        public virtual TlsPskIdentityManager GetPskIdentityManager()
+        {
+            return null;
+        }
+
+        public virtual TlsSrpLoginParameters GetSrpLoginParameters()
+        {
+            return null;
+        }
+
+        public virtual TlsDHConfig GetDHConfig()
+        {
+            int minimumFiniteFieldBits = TlsDHUtilities.GetMinimumFiniteFieldBits(m_selectedCipherSuite);
+            int namedGroup = SelectDH(minimumFiniteFieldBits);
+            return TlsDHUtilities.CreateNamedDHConfig(m_context, namedGroup);
+        }
+
+        public virtual TlsECConfig GetECDHConfig()
+        {
+            int minimumCurveBits = TlsEccUtilities.GetMinimumCurveBits(m_selectedCipherSuite);
+            int namedGroup = SelectECDH(minimumCurveBits);
+            return TlsEccUtilities.CreateNamedECConfig(m_context, namedGroup);
+        }
+
+        public virtual void ProcessClientSupplementalData(IList clientSupplementalData)
+        {
+            if (clientSupplementalData != null)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public virtual void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual NewSessionTicket GetNewSessionTicket()
+        {
+            /*
+             * RFC 5077 3.3. If the server determines that it does not want to include a ticket after it
+             * has included the SessionTicket extension in the ServerHello, then it sends a zero-length
+             * ticket in the NewSessionTicket handshake message.
+             */
+            return new NewSessionTicket(0L, TlsUtilities.EmptyBytes);
+        }
+    }
+}
diff --git a/crypto/src/tls/AlertDescription.cs b/crypto/src/tls/AlertDescription.cs
new file mode 100644
index 000000000..dc207bb54
--- /dev/null
+++ b/crypto/src/tls/AlertDescription.cs
@@ -0,0 +1,323 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5246 7.2.</summary>
+    public abstract class AlertDescription
+    {
+        /// <summary>This message notifies the recipient that the sender will not send any more messages on this
+        /// connection.</summary>
+        /// <remarks>
+        /// Note that as of TLS 1.1, failure to properly close a connection no longer requires that a session not be
+        /// resumed. This is a change from TLS 1.0 ("The session becomes unresumable if any connection is terminated
+        /// without proper close_notify messages with level equal to warning.") to conform with widespread
+        /// implementation practice.
+        /// </remarks>
+        public const short close_notify = 0;
+
+        /// <summary>An inappropriate message was received.</summary>
+        /// <remarks>
+        /// This alert is always fatal and should never be observed in communication between proper implementations.
+        /// </remarks>
+        public const short unexpected_message = 10;
+
+        /// <summary>This alert is returned if a record is received with an incorrect MAC.</summary>
+        /// <remarks>
+        /// This alert also MUST be returned if an alert is sent because a TLSCiphertext decrypted in an invalid way:
+        /// either it wasn't an even multiple of the block length, or its padding values, when checked, weren't
+        /// correct. This message is always fatal and should never be observed in communication between proper
+        /// implementations (except when messages were corrupted in the network).
+        /// </remarks>
+        public const short bad_record_mac = 20;
+
+        /// <remarks>
+        /// This alert was used in some earlier versions of TLS, and may have permitted certain attacks against the CBC
+        /// mode [CBCATT]. It MUST NOT be sent by compliant implementations.
+        /// </remarks>
+        public const short decryption_failed = 21;
+
+        /// <summary>A TLSCiphertext record was received that had a length more than 2^14+2048 bytes, or a record
+        /// decrypted to a TLSCompressed record with more than 2^14+1024 bytes.</summary>
+        /// <remarks>
+        /// This message is always fatal and should never be observed in communication between proper implementations
+        /// (except when messages were corrupted in the network).
+        /// </remarks>
+        public const short record_overflow = 22;
+
+        /// <summary>The decompression function received improper input (e.g., data that would expand to excessive
+        /// length).</summary>
+        /// <remarks>
+        /// This message is always fatal and should never be observed in communication between proper implementations.
+        /// </remarks>
+        public const short decompression_failure = 30;
+
+        /// <summary>Reception of a handshake_failure alert message indicates that the sender was unable to negotiate
+        /// an acceptable set of security parameters given the options available.</summary>
+        /// <remarks>
+        /// This is a fatal error.
+        /// </remarks>
+        public const short handshake_failure = 40;
+
+        /// <remarks>
+        /// This alert was used in SSLv3 but not any version of TLS. It MUST NOT be sent by compliant implementations.
+        /// </remarks>
+        public const short no_certificate = 41;
+
+        /// <summary>A certificate was corrupt, contained signatures that did not verify correctly, etc.</summary>
+        public const short bad_certificate = 42;
+
+        /// <summary>A certificate was of an unsupported type.</summary>
+        public const short unsupported_certificate = 43;
+
+        /// <summary>A certificate was revoked by its signer.</summary>
+        public const short certificate_revoked = 44;
+
+        /// <summary>A certificate has expired or is not currently valid.</summary>
+        public const short certificate_expired = 45;
+
+        /// <summary>Some other (unspecified) issue arose in processing the certificate, rendering it unacceptable.
+        /// </summary>
+        public const short certificate_unknown = 46;
+
+        /// <summary>A field in the handshake was out of range or inconsistent with other fields.</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short illegal_parameter = 47;
+
+        /// <summary>A valid certificate chain or partial chain was received, but the certificate was not accepted
+        /// because the CA certificate could not be located or couldn't be matched with a known, trusted CA.</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short unknown_ca = 48;
+
+        /// <summary>A valid certificate was received, but when access control was applied, the sender decided not to
+        /// proceed with negotiation.</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short access_denied = 49;
+
+        /// <summary>A message could not be decoded because some field was out of the specified range or the length of
+        /// the message was incorrect.</summary>
+        /// <remarks>
+        /// This message is always fatal and should never be observed in communication between proper
+        /// implementations (except when messages were corrupted in the network).
+        /// </remarks>
+        public const short decode_error = 50;
+
+        /// <summary>A handshake cryptographic operation failed, including being unable to correctly verify a signature
+        /// or validate a Finished message.</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short decrypt_error = 51;
+
+        /// <remarks>
+        /// This alert was used in some earlier versions of TLS. It MUST NOT be sent by compliant implementations.
+        /// </remarks>
+        public const short export_restriction = 60;
+
+        /// <summary>The protocol version the client has attempted to negotiate is recognized but not supported.
+        /// </summary>
+        /// <remarks>
+        /// (For example, old protocol versions might be avoided for security reasons.) This message is always fatal.
+        /// </remarks>
+        public const short protocol_version = 70;
+
+        /// <summary>Returned instead of handshake_failure when a negotiation has failed specifically because the
+        /// server requires ciphers more secure than those supported by the client.</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short insufficient_security = 71;
+
+        /// <summary>An internal error unrelated to the peer or the correctness of the protocol (such as a memory
+        /// allocation failure) makes it impossible to continue.</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short internal_error = 80;
+
+        /// <summary>This handshake is being canceled for some reason unrelated to a protocol failure.</summary>
+        /// <remarks>
+        /// If the user cancels an operation after the handshake is complete, just closing the connection by sending a
+        /// close_notify is more appropriate. This alert should be followed by a close_notify. This message is
+        /// generally a warning.
+        /// </remarks>
+        public const short user_canceled = 90;
+
+        /// <summary>Sent by the client in response to a hello request or by the server in response to a client hello
+        /// after initial handshaking.</summary>
+        /// <remarks>
+        /// Either of these would normally lead to renegotiation; when that is not appropriate, the recipient should
+        /// respond with this alert. At that point, the original requester can decide whether to proceed with the
+        /// connection. One case where this would be appropriate is where a server has spawned a process to satisfy a
+        /// request; the process might receive security parameters (key length, authentication, etc.) at startup, and
+        /// it might be difficult to communicate changes to these parameters after that point. This message is always a
+        /// warning.
+        /// </remarks>
+        public const short no_renegotiation = 100;
+
+        /// <summary>Sent by clients that receive an extended server hello containing an extension that they did not
+        /// put in the corresponding client hello.</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short unsupported_extension = 110;
+
+        /*
+         * RFC 3546
+         */
+
+        /// <summary>This alert is sent by servers who are unable to retrieve a certificate chain from the URL supplied
+        /// by the client(see Section 3.3).</summary>
+        /// <remarks>
+        /// This message MAY be fatal - for example if client authentication is required by the server for the
+        /// handshake to continue and the server is unable to retrieve the certificate chain, it may send a fatal
+        /// alert.
+        /// </remarks>
+        public const short certificate_unobtainable = 111;
+
+        /// <summary>This alert is sent by servers that receive a server_name extension request, but do not recognize
+        /// the server name.</summary>
+        /// <remarks>
+        /// This message MAY be fatal.
+        /// </remarks>
+        public const short unrecognized_name = 112;
+
+        /// <summary>This alert is sent by clients that receive an invalid certificate status response (see Section 3.6
+        /// ).</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short bad_certificate_status_response = 113;
+
+        /// <summary>This alert is sent by servers when a certificate hash does not match a client provided
+        /// certificate_hash.</summary>
+        /// <remarks>
+        /// This message is always fatal.
+        /// </remarks>
+        public const short bad_certificate_hash_value = 114;
+
+        /*
+         * RFC 4279
+         */
+
+        /// <summary>If the server does not recognize the PSK identity, it MAY respond with an "unknown_psk_identity"
+        /// alert message.</summary>
+        public const short unknown_psk_identity = 115;
+
+        /*
+         * RFC 7301
+         */
+
+        /// <summary>In the event that the server supports no protocols that the client advertises, then the server
+        /// SHALL respond with a fatal "no_application_protocol" alert.</summary>
+        public const short no_application_protocol = 120;
+
+        /*
+         * RFC 7507
+         */
+
+        /// <summary>If TLS_FALLBACK_SCSV appears in ClientHello.cipher_suites and the highest protocol version
+        /// supported by the server is higher than the version indicated in ClientHello.client_version, the server MUST
+        /// respond with a fatal inappropriate_fallback alert[..].</summary>
+        public const short inappropriate_fallback = 86;
+
+        /*
+         * RFC 8446
+         */
+
+        /// <summary>Sent by endpoints that receive a handshake message not containing an extension that is mandatory
+        /// to send for the offered TLS version or other negotiated parameters.</summary>
+        public const short missing_extension = 109;
+
+        /// <summary>Sent by servers when a client certificate is desired but none was provided by the client.
+        /// </summary>
+        public const short certificate_required = 116;
+
+        public static string GetName(short alertDescription)
+        {
+            switch (alertDescription)
+            {
+            case close_notify:
+                return "close_notify";
+            case unexpected_message:
+                return "unexpected_message";
+            case bad_record_mac:
+                return "bad_record_mac";
+            case decryption_failed:
+                return "decryption_failed";
+            case record_overflow:
+                return "record_overflow";
+            case decompression_failure:
+                return "decompression_failure";
+            case handshake_failure:
+                return "handshake_failure";
+            case no_certificate:
+                return "no_certificate";
+            case bad_certificate:
+                return "bad_certificate";
+            case unsupported_certificate:
+                return "unsupported_certificate";
+            case certificate_revoked:
+                return "certificate_revoked";
+            case certificate_expired:
+                return "certificate_expired";
+            case certificate_unknown:
+                return "certificate_unknown";
+            case illegal_parameter:
+                return "illegal_parameter";
+            case unknown_ca:
+                return "unknown_ca";
+            case access_denied:
+                return "access_denied";
+            case decode_error:
+                return "decode_error";
+            case decrypt_error:
+                return "decrypt_error";
+            case export_restriction:
+                return "export_restriction";
+            case protocol_version:
+                return "protocol_version";
+            case insufficient_security:
+                return "insufficient_security";
+            case internal_error:
+                return "internal_error";
+            case user_canceled:
+                return "user_canceled";
+            case no_renegotiation:
+                return "no_renegotiation";
+            case unsupported_extension:
+                return "unsupported_extension";
+            case certificate_unobtainable:
+                return "certificate_unobtainable";
+            case unrecognized_name:
+                return "unrecognized_name";
+            case bad_certificate_status_response:
+                return "bad_certificate_status_response";
+            case bad_certificate_hash_value:
+                return "bad_certificate_hash_value";
+            case unknown_psk_identity:
+                return "unknown_psk_identity";
+            case no_application_protocol:
+                return "no_application_protocol";
+            case inappropriate_fallback:
+                return "inappropriate_fallback";
+            case missing_extension:
+                return "missing_extension";
+            case certificate_required:
+                return "certificate_required";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short alertDescription)
+        {
+            return GetName(alertDescription) + "(" + alertDescription + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/AlertLevel.cs b/crypto/src/tls/AlertLevel.cs
new file mode 100644
index 000000000..6e128e04d
--- /dev/null
+++ b/crypto/src/tls/AlertLevel.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5246 7.2</summary>
+    public abstract class AlertLevel
+    {
+        public const short warning = 1;
+        public const short fatal = 2;
+
+        public static string GetName(short alertDescription)
+        {
+            switch (alertDescription)
+            {
+            case warning:
+                return "warning";
+            case fatal:
+                return "fatal";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short alertDescription)
+        {
+            return GetName(alertDescription) + "(" + alertDescription + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/BasicTlsPskIdentity.cs b/crypto/src/tls/BasicTlsPskIdentity.cs
new file mode 100644
index 000000000..4ed385a6b
--- /dev/null
+++ b/crypto/src/tls/BasicTlsPskIdentity.cs
@@ -0,0 +1,44 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>A basic PSK Identity holder.</summary>
+    public class BasicTlsPskIdentity
+        : TlsPskIdentity
+    {
+        protected readonly byte[] m_identity;
+        protected readonly byte[] m_psk;
+
+        public BasicTlsPskIdentity(byte[] identity, byte[] psk)
+        {
+            this.m_identity = Arrays.Clone(identity);
+            this.m_psk = Arrays.Clone(psk);
+        }
+
+        public BasicTlsPskIdentity(string identity, byte[] psk)
+        {
+            this.m_identity = Strings.ToUtf8ByteArray(identity);
+            this.m_psk = Arrays.Clone(psk);
+        }
+
+        public virtual void SkipIdentityHint()
+        {
+        }
+
+        public virtual void NotifyIdentityHint(byte[] psk_identity_hint)
+        {
+        }
+
+        public virtual byte[] GetPskIdentity()
+        {
+            return m_identity;
+        }
+
+        public byte[] GetPsk()
+        {
+            return Arrays.Clone(m_psk);
+        }
+    }
+}
diff --git a/crypto/src/tls/BasicTlsSrpIdentity.cs b/crypto/src/tls/BasicTlsSrpIdentity.cs
new file mode 100644
index 000000000..9a527a705
--- /dev/null
+++ b/crypto/src/tls/BasicTlsSrpIdentity.cs
@@ -0,0 +1,36 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>A basic SRP Identity holder.</summary>
+    public class BasicTlsSrpIdentity
+        : TlsSrpIdentity
+    {
+        protected readonly byte[] m_identity;
+        protected readonly byte[] m_password;
+
+        public BasicTlsSrpIdentity(byte[] identity, byte[] password)
+        {
+            this.m_identity = Arrays.Clone(identity);
+            this.m_password = Arrays.Clone(password);
+        }
+
+        public BasicTlsSrpIdentity(string identity, string password)
+        {
+            this.m_identity = Strings.ToUtf8ByteArray(identity);
+            this.m_password = Strings.ToUtf8ByteArray(password);
+        }
+
+        public virtual byte[] GetSrpIdentity()
+        {
+            return m_identity;
+        }
+
+        public virtual byte[] GetSrpPassword()
+        {
+            return m_password;
+        }
+    }
+}
diff --git a/crypto/src/tls/ByteQueue.cs b/crypto/src/tls/ByteQueue.cs
new file mode 100644
index 000000000..b0241972e
--- /dev/null
+++ b/crypto/src/tls/ByteQueue.cs
@@ -0,0 +1,195 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>A queue for bytes. This file could be more optimized.</summary>
+    public sealed 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;
+        }
+
+        /// <summary>The buffer where we store our data.</summary>
+        private byte[] m_databuf;
+
+        /// <summary>How many bytes at the beginning of the buffer are skipped.</summary>
+        private int m_skipped = 0;
+
+        /// <summary>How many bytes in the buffer are valid data.</summary>
+        private int m_available = 0;
+
+        private bool m_readOnlyBuf = false;
+
+        public ByteQueue()
+            : this(0)
+        {
+        }
+
+        public ByteQueue(int capacity)
+        {
+            this.m_databuf = capacity == 0 ? TlsUtilities.EmptyBytes : new byte[capacity];
+        }
+
+        public ByteQueue(byte[] buf, int off, int len)
+        {
+            this.m_databuf = buf;
+            this.m_skipped = off;
+            this.m_available = len;
+            this.m_readOnlyBuf = true;
+        }
+
+        /// <summary>Add some data to our buffer.</summary>
+        /// <param name="buf">A byte-array to read data from.</param>
+        /// <param name="off">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[] buf, int off, int len)
+        {
+            if (m_readOnlyBuf)
+                throw new InvalidOperationException("Cannot add data to read-only buffer");
+
+            if ((m_skipped + m_available + len) > m_databuf.Length)
+            {
+                int desiredSize = ByteQueue.NextTwoPow(m_available + len);
+                if (desiredSize > m_databuf.Length)
+                {
+                    byte[] tmp = new byte[desiredSize];
+                    Array.Copy(m_databuf, m_skipped, tmp, 0, m_available);
+                    m_databuf = tmp;
+                }
+                else
+                {
+                    Array.Copy(m_databuf, m_skipped, m_databuf, 0, m_available);
+                }
+                m_skipped = 0;
+            }
+
+            Array.Copy(buf, off, m_databuf, m_skipped + m_available, len);
+            m_available += len;
+        }
+
+        /// <returns>The number of bytes which are available in this buffer.</returns>
+        public int Available
+        {
+            get { return m_available; }
+        }
+
+        /// <summary>Copy some bytes from the beginning of the data to the provided <see cref="Stream"/>.</summary>
+        /// <param name="output">The <see cref="Stream"/> to copy the bytes to.</param>
+        /// <param name="length">How many bytes to copy.</param>
+        public void CopyTo(Stream output, int length)
+        {
+            if (length > m_available)
+                throw new InvalidOperationException("Cannot copy " + length + " bytes, only got " + m_available);
+
+            output.Write(m_databuf, m_skipped, length);
+        }
+
+        /// <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 ((buf.Length - offset) < len)
+            {
+                throw new ArgumentException("Buffer size of " + buf.Length
+                    + " is too small for a read of " + len + " bytes");
+            }
+            if ((m_available - skip) < len)
+            {
+                throw new InvalidOperationException("Not enough data to read");
+            }
+            Array.Copy(m_databuf, m_skipped + skip, buf, offset, len);
+        }
+
+        /// <summary>Return a <see cref="HandshakeMessageInput"/> over some bytes at the beginning of the data.
+        /// </summary>
+        /// <param name="length">How many bytes will be readable.</param>
+        /// <returns>A <see cref="HandshakeMessageInput"/> over the data.</returns>
+        internal HandshakeMessageInput ReadHandshakeMessage(int length)
+        {
+            if (length > m_available)
+                throw new InvalidOperationException("Cannot read " + length + " bytes, only got " + m_available);
+
+            int position = m_skipped;
+
+            m_available -= length;
+            m_skipped += length;
+
+            return new HandshakeMessageInput(m_databuf, position, length);
+        }
+
+        public int ReadInt32()
+        {
+            if (m_available < 4)
+                throw new InvalidOperationException("Not enough data to read");
+
+            return TlsUtilities.ReadInt32(m_databuf, m_skipped);
+        }
+
+        /// <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 > m_available)
+                throw new InvalidOperationException("Cannot remove " + i + " bytes, only got " + m_available);
+
+            /*
+             * Skip the data.
+             */
+            m_available -= i;
+            m_skipped += i;
+        }
+
+        /// <summary>Remove data from the buffer.</summary>
+        /// <param name="buf">The buffer where the removed data will be copied to.</param>
+        /// <param name="off">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 RemoveData(byte[] buf, int off, int len, int skip)
+        {
+            Read(buf, off, len, skip);
+            RemoveData(skip + len);
+        }
+
+        public byte[] RemoveData(int len, int skip)
+        {
+            byte[] buf = new byte[len];
+            RemoveData(buf, 0, len, skip);
+            return buf;
+        }
+
+        public void Shrink()
+        {
+            if (m_available == 0)
+            {
+                m_databuf = TlsUtilities.EmptyBytes;
+                m_skipped = 0;
+            }
+            else
+            {
+                int desiredSize = ByteQueue.NextTwoPow(m_available);
+                if (desiredSize < m_databuf.Length)
+                {
+                    byte[] tmp = new byte[desiredSize];
+                    Array.Copy(m_databuf, m_skipped, tmp, 0, m_available);
+                    m_databuf = tmp;
+                    m_skipped = 0;
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/ByteQueueInputStream.cs b/crypto/src/tls/ByteQueueInputStream.cs
new file mode 100644
index 000000000..b59b5d1e7
--- /dev/null
+++ b/crypto/src/tls/ByteQueueInputStream.cs
@@ -0,0 +1,72 @@
+using System;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class ByteQueueInputStream
+        : BaseInputStream
+    {
+        private readonly ByteQueue m_buffer;
+
+        public ByteQueueInputStream()
+        {
+            this.m_buffer = new ByteQueue();
+        }
+
+        public void AddBytes(byte[] buf)
+        {
+            m_buffer.AddData(buf, 0, buf.Length);
+        }
+
+        public void AddBytes(byte[] buf, int bufOff, int bufLen)
+        {
+            m_buffer.AddData(buf, bufOff, bufLen);
+        }
+
+        public int Peek(byte[] buf)
+        {
+            int bytesToRead = System.Math.Min(m_buffer.Available, buf.Length);
+            m_buffer.Read(buf, 0, bytesToRead, 0);
+            return bytesToRead;
+        }
+
+        public override int ReadByte()
+        {
+            if (m_buffer.Available == 0)
+                return -1;
+
+            return m_buffer.RemoveData(1, 0)[0];
+        }
+
+        public override int Read(byte[] buf, int off, int len)
+        {
+            int bytesToRead = System.Math.Min(m_buffer.Available, len);
+            m_buffer.RemoveData(buf, off, bytesToRead, 0);
+            return bytesToRead;
+        }
+
+        public long Skip(long n)
+        {
+            int bytesToRemove = System.Math.Min((int)n, m_buffer.Available);
+            m_buffer.RemoveData(bytesToRemove);
+            return bytesToRemove;
+        }
+
+        public int Available
+        {
+            get { return m_buffer.Available; }
+        }
+
+#if PORTABLE
+        //protected override void Dispose(bool disposing)
+        //{
+        //    base.Dispose(disposing);
+        //}
+#else
+        public override void Close()
+        {
+        }
+#endif
+    }
+}
diff --git a/crypto/src/tls/ByteQueueOutputStream.cs b/crypto/src/tls/ByteQueueOutputStream.cs
new file mode 100644
index 000000000..76f04916f
--- /dev/null
+++ b/crypto/src/tls/ByteQueueOutputStream.cs
@@ -0,0 +1,33 @@
+using System;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>OutputStream based on a ByteQueue implementation.</summary>
+    public sealed class ByteQueueOutputStream
+        : BaseOutputStream
+    {
+        private readonly ByteQueue m_buffer;
+
+        public ByteQueueOutputStream()
+        {
+            this.m_buffer = new ByteQueue();
+        }
+
+        public ByteQueue Buffer
+        {
+            get { return m_buffer; }
+        }
+
+        public override void WriteByte(byte b)
+        {
+            m_buffer.AddData(new byte[]{ b }, 0, 1);
+        }
+
+        public override void Write(byte[] buf, int off, int len)
+        {
+            m_buffer.AddData(buf, off, len);
+        }
+    }
+}
diff --git a/crypto/src/tls/CachedInformationType.cs b/crypto/src/tls/CachedInformationType.cs
new file mode 100644
index 000000000..273efab0b
--- /dev/null
+++ b/crypto/src/tls/CachedInformationType.cs
@@ -0,0 +1,28 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class CachedInformationType
+    {
+        public const short cert = 1;
+        public const short cert_req = 2;
+
+        public static string GetName(short cachedInformationType)
+        {
+            switch (cachedInformationType)
+            {
+            case cert:
+                return "cert";
+            case cert_req:
+                return "cert_req";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short cachedInformationType)
+        {
+            return GetName(cachedInformationType) + "(" + cachedInformationType + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/CertChainType.cs b/crypto/src/tls/CertChainType.cs
new file mode 100644
index 000000000..8b989e8a2
--- /dev/null
+++ b/crypto/src/tls/CertChainType.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Implementation of the RFC 3546 3.3. CertChainType.</summary>
+    public abstract class CertChainType
+    {
+        public const short individual_certs = 0;
+        public const short pkipath = 1;
+
+        public static string GetName(short certChainType)
+        {
+            switch (certChainType)
+            {
+            case individual_certs:
+                return "individual_certs";
+            case pkipath:
+                return "pkipath";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short certChainType)
+        {
+            return GetName(certChainType) + "(" + certChainType + ")";
+        }
+
+        public static bool IsValid(short certChainType)
+        {
+            return certChainType >= individual_certs && certChainType <= pkipath;
+        }
+    }
+}
diff --git a/crypto/src/tls/Certificate.cs b/crypto/src/tls/Certificate.cs
new file mode 100644
index 000000000..fef35fc1e
--- /dev/null
+++ b/crypto/src/tls/Certificate.cs
@@ -0,0 +1,294 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Parsing and encoding of a <i>Certificate</i> struct from RFC 4346.</summary>
+    /// <remarks>
+    /// <pre>
+    /// opaque ASN.1Cert&lt;2^24-1&gt;;
+    /// struct {
+    ///   ASN.1Cert certificate_list&lt;0..2^24-1&gt;;
+    /// } Certificate;
+    /// </pre>
+    /// </remarks>
+    public sealed class Certificate
+    {
+        private static readonly TlsCertificate[] EmptyCerts = new TlsCertificate[0];
+        private static readonly CertificateEntry[] EmptyCertEntries = new CertificateEntry[0];
+
+        public static readonly Certificate EmptyChain = new Certificate(EmptyCerts);
+        public static readonly Certificate EmptyChainTls13 = new Certificate(TlsUtilities.EmptyBytes, EmptyCertEntries);
+
+        public sealed class ParseOptions
+        {
+            private int m_maxChainLength = int.MaxValue;
+
+            public int MaxChainLength
+            {
+                get { return m_maxChainLength; }
+            }
+
+            public ParseOptions SetMaxChainLength(int maxChainLength)
+            {
+                this.m_maxChainLength = maxChainLength;
+                return this;
+            }
+        }
+
+        private static CertificateEntry[] Convert(TlsCertificate[] certificateList)
+        {
+            if (TlsUtilities.IsNullOrContainsNull(certificateList))
+                throw new ArgumentException("cannot be null or contain any nulls", "certificateList");
+
+            int count = certificateList.Length;
+            CertificateEntry[] result = new CertificateEntry[count];
+            for (int i = 0; i < count; ++i)
+            {
+                result[i] = new CertificateEntry(certificateList[i], null);
+            }
+            return result;
+        }
+
+        private readonly byte[] m_certificateRequestContext;
+        private readonly CertificateEntry[] m_certificateEntryList;
+
+        public Certificate(TlsCertificate[] certificateList)
+            : this(null, Convert(certificateList))
+        {
+        }
+
+        // TODO[tls13] Prefer to manage the certificateRequestContext internally only? 
+        public Certificate(byte[] certificateRequestContext, CertificateEntry[] certificateEntryList)
+        {
+            if (null != certificateRequestContext && !TlsUtilities.IsValidUint8(certificateRequestContext.Length))
+                throw new ArgumentException("cannot be longer than 255", "certificateRequestContext");
+            if (TlsUtilities.IsNullOrContainsNull(certificateEntryList))
+                throw new ArgumentException("cannot be null or contain any nulls", "certificateEntryList");
+
+            this.m_certificateRequestContext = TlsUtilities.Clone(certificateRequestContext);
+            this.m_certificateEntryList = certificateEntryList;
+        }
+
+        public byte[] GetCertificateRequestContext()
+        {
+            return TlsUtilities.Clone(m_certificateRequestContext);
+        }
+
+        /// <returns>an array of <see cref="TlsCertificate"/> representing a certificate chain.</returns>
+        public TlsCertificate[] GetCertificateList()
+        {
+            return CloneCertificateList();
+        }
+
+        public TlsCertificate GetCertificateAt(int index)
+        {
+            return m_certificateEntryList[index].Certificate;
+        }
+
+        public CertificateEntry GetCertificateEntryAt(int index)
+        {
+            return m_certificateEntryList[index];
+        }
+
+        public CertificateEntry[] GetCertificateEntryList()
+        {
+            return CloneCertificateEntryList();
+        }
+
+        public short CertificateType
+        {
+            get { return Tls.CertificateType.X509; }
+        }
+
+        public int Length
+        {
+            get { return m_certificateEntryList.Length; }
+        }
+
+        /// <returns><c>true</c> if this certificate chain contains no certificates, or <c>false</c> otherwise.
+        /// </returns>
+        public bool IsEmpty
+        {
+            get { return m_certificateEntryList.Length == 0; }
+        }
+
+        /// <summary>Encode this <see cref="Certificate"/> to a <see cref="Stream"/>, and optionally calculate the
+        /// "end point hash" (per RFC 5929's tls-server-end-point binding).</summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="messageOutput">the <see cref="Stream"/> to encode to.</param>
+        /// <param name="endPointHashOutput">the <see cref="Stream"/> to write the "end point hash" to (or null).
+        /// </param>
+        /// <exception cref="IOException"/>
+        public void Encode(TlsContext context, Stream messageOutput, Stream endPointHashOutput)
+        {
+            bool isTlsV13 = TlsUtilities.IsTlsV13(context);
+
+            if ((null != m_certificateRequestContext) != isTlsV13)
+                throw new InvalidOperationException();
+
+            if (isTlsV13)
+            {
+                TlsUtilities.WriteOpaque8(m_certificateRequestContext, messageOutput);
+            }
+
+            int count = m_certificateEntryList.Length;
+            IList certEncodings = Platform.CreateArrayList(count);
+            IList extEncodings = isTlsV13 ? Platform.CreateArrayList(count) : null;
+
+            long totalLength = 0;
+            for (int i = 0; i < count; ++i)
+            {
+                CertificateEntry entry = m_certificateEntryList[i];
+                TlsCertificate cert = entry.Certificate;
+                byte[] derEncoding = cert.GetEncoded();
+
+                if (i == 0 && endPointHashOutput != null)
+                {
+                    CalculateEndPointHash(context, cert, derEncoding, endPointHashOutput);
+                }
+
+                certEncodings.Add(derEncoding);
+                totalLength += derEncoding.Length;
+                totalLength += 3;
+
+                if (isTlsV13)
+                {
+                    IDictionary extensions = entry.Extensions;
+                    byte[] extEncoding = (null == extensions)
+                        ?   TlsUtilities.EmptyBytes
+                        :   TlsProtocol.WriteExtensionsData(extensions);
+
+                    extEncodings.Add(extEncoding);
+                    totalLength += extEncoding.Length;
+                    totalLength += 2;
+                }
+            }
+
+            TlsUtilities.CheckUint24(totalLength);
+            TlsUtilities.WriteUint24((int)totalLength, messageOutput);
+
+            for (int i = 0; i < count; ++i)
+            {
+                byte[] certEncoding = (byte[])certEncodings[i];
+                TlsUtilities.WriteOpaque24(certEncoding, messageOutput);
+
+                if (isTlsV13)
+                {
+                    byte[] extEncoding = (byte[])extEncodings[i];
+                    TlsUtilities.WriteOpaque16(extEncoding, messageOutput);
+                }
+            }
+        }
+
+        /// <summary>Parse a <see cref="Certificate"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="options">the <see cref="ParseOptions"/> to apply during parsing.</param>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="messageInput">the <see cref="Stream"/> to parse from.</param>
+        /// <param name="endPointHashOutput">the <see cref="Stream"/> to write the "end point hash" to (or null).
+        /// </param>
+        /// <returns>a <see cref="Certificate"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static Certificate Parse(ParseOptions options, TlsContext context, Stream messageInput,
+            Stream endPointHashOutput)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            bool isTlsV13 = TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion);
+
+            byte[] certificateRequestContext = null;
+            if (isTlsV13)
+            {
+                certificateRequestContext = TlsUtilities.ReadOpaque8(messageInput);
+            }
+
+            int totalLength = TlsUtilities.ReadUint24(messageInput);
+            if (totalLength == 0)
+            {
+                return !isTlsV13 ? EmptyChain
+                    :  certificateRequestContext.Length < 1 ? EmptyChainTls13
+                    :  new Certificate(certificateRequestContext, EmptyCertEntries);
+            }
+
+            byte[] certListData = TlsUtilities.ReadFully(totalLength, messageInput);
+            MemoryStream buf = new MemoryStream(certListData, false);
+
+            TlsCrypto crypto = context.Crypto;
+            int maxChainLength = System.Math.Max(1, options.MaxChainLength);
+
+            IList certificate_list = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                if (certificate_list.Count >= maxChainLength)
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error,
+                        "Certificate chain longer than maximum (" + maxChainLength + ")");
+                }
+
+                byte[] derEncoding = TlsUtilities.ReadOpaque24(buf, 1);
+                TlsCertificate cert = crypto.CreateCertificate(derEncoding);
+
+                if (certificate_list.Count < 1 && endPointHashOutput != null)
+                {
+                    CalculateEndPointHash(context, cert, derEncoding, endPointHashOutput);
+                }
+
+                IDictionary extensions = null;
+                if (isTlsV13)
+                {
+                    byte[] extEncoding = TlsUtilities.ReadOpaque16(buf);
+
+                    extensions = TlsProtocol.ReadExtensionsData13(HandshakeType.certificate, extEncoding);
+                }
+
+                certificate_list.Add(new CertificateEntry(cert, extensions));
+            }
+
+            CertificateEntry[] certificateList = new CertificateEntry[certificate_list.Count];
+            for (int i = 0; i < certificate_list.Count; i++)
+            {
+                certificateList[i] = (CertificateEntry)certificate_list[i];
+            }
+
+            return new Certificate(certificateRequestContext, certificateList);
+        }
+
+        private static void CalculateEndPointHash(TlsContext context, TlsCertificate cert, byte[] encoding,
+            Stream output)
+        {
+            byte[] endPointHash = TlsUtilities.CalculateEndPointHash(context, cert, encoding);
+            if (endPointHash != null && endPointHash.Length > 0)
+            {
+                output.Write(endPointHash, 0, endPointHash.Length);
+            }
+        }
+
+        private TlsCertificate[] CloneCertificateList()
+        {
+            int count = m_certificateEntryList.Length;
+            if (0 == count)
+                return EmptyCerts;
+
+            TlsCertificate[] result = new TlsCertificate[count];
+            for (int i = 0; i < count; ++i)
+            {
+                result[i] = m_certificateEntryList[i].Certificate;
+            }
+            return result;
+        }
+
+        private CertificateEntry[] CloneCertificateEntryList()
+        {
+            int count = m_certificateEntryList.Length;
+            if (0 == count)
+                return EmptyCertEntries;
+
+            CertificateEntry[] result = new CertificateEntry[count];
+            Array.Copy(m_certificateEntryList, 0, result, 0, count);
+            return result;
+        }
+    }
+}
diff --git a/crypto/src/tls/CertificateEntry.cs b/crypto/src/tls/CertificateEntry.cs
new file mode 100644
index 000000000..b88677536
--- /dev/null
+++ b/crypto/src/tls/CertificateEntry.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class CertificateEntry
+    {
+        private readonly TlsCertificate m_certificate;
+        private readonly IDictionary m_extensions;
+
+        public CertificateEntry(TlsCertificate certificate, IDictionary extensions)
+        {
+            if (null == certificate)
+                throw new ArgumentNullException("certificate");
+
+            this.m_certificate = certificate;
+            this.m_extensions = extensions;
+        }
+
+        public TlsCertificate Certificate
+        {
+            get { return m_certificate; }
+        }
+
+        public IDictionary Extensions
+        {
+            get { return m_extensions; }
+        }
+    }
+}
diff --git a/crypto/src/tls/CertificateRequest.cs b/crypto/src/tls/CertificateRequest.cs
new file mode 100644
index 000000000..1abf01aed
--- /dev/null
+++ b/crypto/src/tls/CertificateRequest.cs
@@ -0,0 +1,276 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Parsing and encoding of a <i>CertificateRequest</i> struct from RFC 4346.</summary>
+    /// <remarks>
+    /// <pre>
+    /// struct {
+    ///   ClientCertificateType certificate_types&lt;1..2^8-1&gt;;
+    ///   DistinguishedName certificate_authorities&lt;3..2^16-1&gt;;
+    /// } CertificateRequest;
+    /// </pre>
+    /// Updated for RFC 5246:
+    /// <pre>
+    /// struct {
+    ///   ClientCertificateType certificate_types &lt;1..2 ^ 8 - 1&gt;;
+    ///   SignatureAndHashAlgorithm supported_signature_algorithms &lt;2 ^ 16 - 1&gt;;
+    ///   DistinguishedName certificate_authorities &lt;0..2 ^ 16 - 1&gt;;
+    /// } CertificateRequest;
+    /// </pre>
+    /// Revised for RFC 8446:
+    /// <pre>
+    /// struct {
+    ///   opaque certificate_request_context &lt;0..2 ^ 8 - 1&gt;;
+    ///   Extension extensions &lt;2..2 ^ 16 - 1&gt;;
+    /// } CertificateRequest;
+    /// </pre>
+    /// </remarks>
+    /// <seealso cref="ClientCertificateType"/>
+    /// <seealso cref="X509Name"/>
+    public sealed class CertificateRequest
+    {
+        /// <exception cref="IOException"/>
+        private static IList CheckSupportedSignatureAlgorithms(IList supportedSignatureAlgorithms,
+            short alertDescription)
+        {
+            if (null == supportedSignatureAlgorithms)
+                throw new TlsFatalAlert(alertDescription, "'signature_algorithms' is required");
+
+            return supportedSignatureAlgorithms;
+        }
+
+        private readonly byte[] m_certificateRequestContext;
+        private readonly short[] m_certificateTypes;
+        private readonly IList m_supportedSignatureAlgorithms;
+        private readonly IList m_supportedSignatureAlgorithmsCert;
+        private readonly IList m_certificateAuthorities;
+
+        /// <param name="certificateTypes">see <see cref="ClientCertificateType"/> for valid constants.</param>
+        /// <param name="supportedSignatureAlgorithms"></param>
+        /// <param name="certificateAuthorities">an <see cref="IList"/> of <see cref="X509Name"/>.</param>
+        public CertificateRequest(short[] certificateTypes, IList supportedSignatureAlgorithms,
+            IList certificateAuthorities)
+            : this(null, certificateTypes, supportedSignatureAlgorithms, null, certificateAuthorities)
+        {
+        }
+
+        // TODO[tls13] Prefer to manage the certificateRequestContext internally only? 
+        /// <exception cref="IOException"/>
+        public CertificateRequest(byte[] certificateRequestContext, IList supportedSignatureAlgorithms,
+            IList supportedSignatureAlgorithmsCert, IList certificateAuthorities)
+            : this(certificateRequestContext, null,
+                 CheckSupportedSignatureAlgorithms(supportedSignatureAlgorithms, AlertDescription.internal_error),
+                supportedSignatureAlgorithmsCert, certificateAuthorities)
+        {
+            /*
+             * TODO[tls13] Removed certificateTypes, added certificate_request_context, added extensions
+             * (required: signature_algorithms, optional: status_request, signed_certificate_timestamp,
+             * certificate_authorities, oid_filters, signature_algorithms_cert)
+             */
+        }
+
+        private CertificateRequest(byte[] certificateRequestContext, short[] certificateTypes,
+            IList supportedSignatureAlgorithms, IList supportedSignatureAlgorithmsCert, IList certificateAuthorities)
+        {
+            if (null != certificateRequestContext && !TlsUtilities.IsValidUint8(certificateRequestContext.Length))
+                throw new ArgumentException("cannot be longer than 255", "certificateRequestContext");
+            if (null != certificateTypes
+                && (certificateTypes.Length < 1 || !TlsUtilities.IsValidUint8(certificateTypes.Length)))
+            {
+                throw new ArgumentException("should have length from 1 to 255", "certificateTypes");
+            }
+
+            this.m_certificateRequestContext = TlsUtilities.Clone(certificateRequestContext);
+            this.m_certificateTypes = certificateTypes;
+            this.m_supportedSignatureAlgorithms = supportedSignatureAlgorithms;
+            this.m_supportedSignatureAlgorithmsCert = supportedSignatureAlgorithmsCert;
+            this.m_certificateAuthorities = certificateAuthorities;
+        }
+
+        public byte[] GetCertificateRequestContext()
+        {
+            return TlsUtilities.Clone(m_certificateRequestContext);
+        }
+
+        /// <returns>an array of certificate types</returns>
+        /// <seealso cref="ClientCertificateType"/>
+        public short[] CertificateTypes
+        {
+            get { return m_certificateTypes; }
+        }
+
+        /// <returns>an <see cref="IList"/> of <see cref="SignatureAndHashAlgorithm"/> (or null before TLS 1.2).
+        /// </returns>
+        public IList SupportedSignatureAlgorithms
+        {
+            get { return m_supportedSignatureAlgorithms; }
+        }
+
+        /// <returns>an optional <see cref="IList"/> of <see cref="SignatureAndHashAlgorithm"/>. May be non-null from
+        /// TLS 1.3 onwards.</returns>
+        public IList SupportedSignatureAlgorithmsCert
+        {
+            get { return m_supportedSignatureAlgorithmsCert; }
+        }
+
+        /// <returns>an <see cref="IList"/> of <see cref="X509Name"/>.</returns>
+        public IList CertificateAuthorities
+        {
+            get { return m_certificateAuthorities; }
+        }
+
+        public bool HasCertificateRequestContext(byte[] certificateRequestContext)
+        {
+            return Arrays.AreEqual(m_certificateRequestContext, certificateRequestContext);
+        }
+
+        /// <summary>Encode this <see cref="CertificateRequest"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(TlsContext context, Stream output)
+        {
+            ProtocolVersion negotiatedVersion = context.ServerVersion;
+            bool isTlsV12 = TlsUtilities.IsTlsV12(negotiatedVersion);
+            bool isTlsV13 = TlsUtilities.IsTlsV13(negotiatedVersion);
+
+            if (isTlsV13 != (null != m_certificateRequestContext) ||
+                isTlsV13 != (null == m_certificateTypes) ||
+                isTlsV12 != (null != m_supportedSignatureAlgorithms) ||
+                (!isTlsV13 && (null != m_supportedSignatureAlgorithmsCert)))
+            {
+                throw new InvalidOperationException();
+            }
+
+            if (isTlsV13)
+            {
+                TlsUtilities.WriteOpaque8(m_certificateRequestContext, output);
+
+                IDictionary extensions = Platform.CreateHashtable();
+                TlsExtensionsUtilities.AddSignatureAlgorithmsExtension(extensions, m_supportedSignatureAlgorithms);
+
+                if (null != m_supportedSignatureAlgorithmsCert)
+                {
+                    TlsExtensionsUtilities.AddSignatureAlgorithmsCertExtension(extensions,
+                        m_supportedSignatureAlgorithmsCert);
+                }
+
+                if (null != m_certificateAuthorities)
+                {
+                    TlsExtensionsUtilities.AddCertificateAuthoritiesExtension(extensions, m_certificateAuthorities);
+                }
+
+                byte[] extEncoding = TlsProtocol.WriteExtensionsData(extensions);
+
+                TlsUtilities.WriteOpaque16(extEncoding, output);
+                return;
+            }
+
+            TlsUtilities.WriteUint8ArrayWithUint8Length(m_certificateTypes, output);
+
+            if (isTlsV12)
+            {
+                // TODO Check whether SignatureAlgorithm.anonymous is allowed here
+                TlsUtilities.EncodeSupportedSignatureAlgorithms(m_supportedSignatureAlgorithms, output);
+            }
+
+            if (m_certificateAuthorities == null || m_certificateAuthorities.Count < 1)
+            {
+                TlsUtilities.WriteUint16(0, output);
+            }
+            else
+            {
+                IList derEncodings = Platform.CreateArrayList(m_certificateAuthorities.Count);
+
+                int totalLength = 0;
+                foreach (X509Name certificateAuthority in m_certificateAuthorities)
+                {
+                    byte[] derEncoding = certificateAuthority.GetEncoded(Asn1Encodable.Der);
+                    derEncodings.Add(derEncoding);
+                    totalLength += derEncoding.Length + 2;
+                }
+
+                TlsUtilities.CheckUint16(totalLength);
+                TlsUtilities.WriteUint16(totalLength, output);
+
+                foreach (byte[] derEncoding in derEncodings)
+                {
+                    TlsUtilities.WriteOpaque16(derEncoding, output);
+                }
+            }
+        }
+
+        /// <summary>Parse a <see cref="CertificateRequest"/> from a <see cref="Stream"/></summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="CertificateRequest"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static CertificateRequest Parse(TlsContext context, Stream input)
+        {
+            ProtocolVersion negotiatedVersion = context.ServerVersion;
+            bool isTlsV13 = TlsUtilities.IsTlsV13(negotiatedVersion);
+
+            if (isTlsV13)
+            {
+                byte[] certificateRequestContext = TlsUtilities.ReadOpaque8(input);
+
+                /*
+                 * TODO[tls13] required: signature_algorithms; optional: status_request,
+                 * signed_certificate_timestamp, certificate_authorities, oid_filters,
+                 * signature_algorithms_cert
+                 */
+
+                byte[] extEncoding = TlsUtilities.ReadOpaque16(input);
+
+                IDictionary extensions = TlsProtocol.ReadExtensionsData13(HandshakeType.certificate_request,
+                    extEncoding);
+
+                IList supportedSignatureAlgorithms13 = CheckSupportedSignatureAlgorithms(
+                    TlsExtensionsUtilities.GetSignatureAlgorithmsExtension(extensions),
+                    AlertDescription.missing_extension);
+                IList supportedSignatureAlgorithmsCert13 = TlsExtensionsUtilities
+                    .GetSignatureAlgorithmsCertExtension(extensions);
+                IList certificateAuthorities13 = TlsExtensionsUtilities.GetCertificateAuthoritiesExtension(extensions);
+
+                return new CertificateRequest(certificateRequestContext, supportedSignatureAlgorithms13,
+                    supportedSignatureAlgorithmsCert13, certificateAuthorities13);
+            }
+
+            bool isTLSv12 = TlsUtilities.IsTlsV12(negotiatedVersion);
+
+            short[] certificateTypes = TlsUtilities.ReadUint8ArrayWithUint8Length(input, 1);
+
+            IList supportedSignatureAlgorithms = null;
+            if (isTLSv12)
+            {
+                supportedSignatureAlgorithms = TlsUtilities.ParseSupportedSignatureAlgorithms(input);
+            }
+
+            IList certificateAuthorities = null;
+            {
+                byte[] certAuthData = TlsUtilities.ReadOpaque16(input);
+                if (certAuthData.Length > 0)
+                {
+                    certificateAuthorities = Platform.CreateArrayList();
+                    MemoryStream bis = new MemoryStream(certAuthData, false);
+                    do
+                    {
+                        byte[] derEncoding = TlsUtilities.ReadOpaque16(bis, 1);
+                        Asn1Object asn1 = TlsUtilities.ReadDerObject(derEncoding);
+                        certificateAuthorities.Add(X509Name.GetInstance(asn1));
+                    }
+                    while (bis.Position < bis.Length);
+                }
+            }
+
+            return new CertificateRequest(certificateTypes, supportedSignatureAlgorithms, certificateAuthorities);
+        }
+    }
+}
diff --git a/crypto/src/tls/CertificateStatus.cs b/crypto/src/tls/CertificateStatus.cs
new file mode 100644
index 000000000..61f4336a8
--- /dev/null
+++ b/crypto/src/tls/CertificateStatus.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Ocsp;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class CertificateStatus
+    {
+        private readonly short m_statusType;
+        private readonly object m_response;
+
+        public CertificateStatus(short statusType, object response)
+        {
+            if (!IsCorrectType(statusType, response))
+                throw new ArgumentException("not an instance of the correct type", "response");
+
+            this.m_statusType = statusType;
+            this.m_response = response;
+        }
+
+        public short StatusType
+        {
+            get { return m_statusType; }
+        }
+
+        public object Response
+        {
+            get { return m_response; }
+        }
+
+        public OcspResponse OcspResponse
+        {
+            get
+            {
+                if (!IsCorrectType(CertificateStatusType.ocsp, m_response))
+                    throw new InvalidOperationException("'response' is not an OCSPResponse");
+
+                return (OcspResponse)m_response;
+            }
+        }
+
+        /// <summary>an <see cref="IList"/> of (possibly null) <see cref="Asn1.Ocsp.OcspResponse"/>.</summary>
+        public IList OcspResponseList
+        {
+            get
+            {
+                if (!IsCorrectType(CertificateStatusType.ocsp_multi, m_response))
+                    throw new InvalidOperationException("'response' is not an OCSPResponseList");
+
+                return (IList)m_response;
+            }
+        }
+
+        /// <summary>Encode this <see cref="CertificateStatus"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(m_statusType, output);
+
+            switch (m_statusType)
+            {
+            case CertificateStatusType.ocsp:
+            {
+                OcspResponse ocspResponse = (OcspResponse)m_response;
+                byte[] derEncoding = ocspResponse.GetEncoded(Asn1Encodable.Der);
+                TlsUtilities.WriteOpaque24(derEncoding, output);
+                break;
+            }
+            case CertificateStatusType.ocsp_multi:
+            {
+                IList ocspResponseList = (IList)m_response;
+                int count = ocspResponseList.Count;
+
+                IList derEncodings = Platform.CreateArrayList(count);
+                long totalLength = 0;
+                foreach (OcspResponse ocspResponse in ocspResponseList)
+                {
+                    if (ocspResponse == null)
+                    {
+                        derEncodings.Add(TlsUtilities.EmptyBytes);
+                    }
+                    else
+                    {
+                        byte[] derEncoding = ocspResponse.GetEncoded(Asn1Encodable.Der);
+                        derEncodings.Add(derEncoding);
+                        totalLength += derEncoding.Length;
+                    }
+                    totalLength += 3;
+                }
+
+                TlsUtilities.CheckUint24(totalLength);
+                TlsUtilities.WriteUint24((int)totalLength, output);
+
+                foreach (byte[] derEncoding in derEncodings)
+                {
+                    TlsUtilities.WriteOpaque24(derEncoding, output);
+                }
+
+                break;
+            }
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        /// <summary>Parse a <see cref="CertificateStatus"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="CertificateStatus"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static CertificateStatus Parse(TlsContext context, Stream input)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            Certificate peerCertificate = securityParameters.PeerCertificate;
+            if (null == peerCertificate || peerCertificate.IsEmpty
+                || CertificateType.X509 != peerCertificate.CertificateType)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            int certificateCount = peerCertificate.Length;
+            int statusRequestVersion = securityParameters.StatusRequestVersion;
+
+            short status_type = TlsUtilities.ReadUint8(input);
+            object response;
+
+            switch (status_type)
+            {
+            case CertificateStatusType.ocsp:
+            {
+                RequireStatusRequestVersion(1, statusRequestVersion);
+
+                byte[] derEncoding = TlsUtilities.ReadOpaque24(input, 1);
+                Asn1Object derObject = TlsUtilities.ReadDerObject(derEncoding);
+                response = OcspResponse.GetInstance(derObject);
+                break;
+            }
+            case CertificateStatusType.ocsp_multi:
+            {
+                RequireStatusRequestVersion(2, statusRequestVersion);
+
+                byte[] ocsp_response_list = TlsUtilities.ReadOpaque24(input, 1);
+                MemoryStream buf = new MemoryStream(ocsp_response_list, false);
+
+                IList ocspResponseList = Platform.CreateArrayList();
+                while (buf.Position < buf.Length)
+                {
+                    if (ocspResponseList.Count >= certificateCount)
+                        throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                    int length = TlsUtilities.ReadUint24(buf);
+                    if (length < 1)
+                    {
+                        ocspResponseList.Add(null);
+                    }
+                    else
+                    {
+                        byte[] derEncoding = TlsUtilities.ReadFully(length, buf);
+                        Asn1Object derObject = TlsUtilities.ReadDerObject(derEncoding);
+                        OcspResponse ocspResponse = OcspResponse.GetInstance(derObject);
+                        ocspResponseList.Add(ocspResponse);
+                    }
+                }
+
+                // Match IList capacity to actual size
+                response = Platform.CreateArrayList(ocspResponseList);
+                break;
+            }
+            default:
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+
+            return new CertificateStatus(status_type, response);
+        }
+
+        private static bool IsCorrectType(short statusType, Object response)
+        {
+            switch (statusType)
+            {
+            case CertificateStatusType.ocsp:
+                return response is OcspResponse;
+            case CertificateStatusType.ocsp_multi:
+                return IsOcspResponseList(response);
+            default:
+                throw new ArgumentException("unsupported CertificateStatusType", "statusType");
+            }
+        }
+
+        private static bool IsOcspResponseList(object response)
+        {
+            if (!(response is IList))
+                return false;
+
+            IList v = (IList)response;
+            int count = v.Count;
+            if (count < 1)
+                return false;
+
+            foreach (object e in v)
+            {
+                if (null != e && !(e is OcspResponse))
+                    return false;
+            }
+            return true;
+        }
+
+        /// <exception cref="IOException"/>
+        private static void RequireStatusRequestVersion(int minVersion, int statusRequestVersion)
+        {
+            if (statusRequestVersion < minVersion)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+    }
+}
diff --git a/crypto/src/tls/CertificateStatusRequest.cs b/crypto/src/tls/CertificateStatusRequest.cs
new file mode 100644
index 000000000..4731316cd
--- /dev/null
+++ b/crypto/src/tls/CertificateStatusRequest.cs
@@ -0,0 +1,91 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Implementation of the RFC 3546 3.6. CertificateStatusRequest.</summary>
+    public sealed class CertificateStatusRequest
+    {
+        private short m_statusType;
+        private object m_request;
+
+        public CertificateStatusRequest(short statusType, object request)
+        {
+            if (!IsCorrectType(statusType, request))
+                throw new ArgumentException("not an instance of the correct type", "request");
+
+            this.m_statusType = statusType;
+            this.m_request = request;
+        }
+
+        public short StatusType
+        {
+            get { return m_statusType; }
+        }
+
+        public object Request
+        {
+            get { return m_request; }
+        }
+
+        public OcspStatusRequest OcspStatusRequest
+        {
+            get
+            {
+                if (!IsCorrectType(CertificateStatusType.ocsp, m_request))
+                    throw new InvalidOperationException("'request' is not an OCSPStatusRequest");
+
+                return (OcspStatusRequest)m_request;
+            }
+        }
+
+        /// <summary>Encode this <see cref="CertificateStatusRequest"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(m_statusType, output);
+
+            switch (m_statusType)
+            {
+            case CertificateStatusType.ocsp:
+                ((OcspStatusRequest)m_request).Encode(output);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        /// <summary>Parse a <see cref="CertificateStatusRequest"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="CertificateStatusRequest"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static CertificateStatusRequest Parse(Stream input)
+        {
+            short status_type = TlsUtilities.ReadUint8(input);
+            object request;
+
+            switch (status_type)
+            {
+            case CertificateStatusType.ocsp:
+                request = OcspStatusRequest.Parse(input);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+
+            return new CertificateStatusRequest(status_type, request);
+        }
+
+        private static bool IsCorrectType(short statusType, object request)
+        {
+            switch (statusType)
+            {
+            case CertificateStatusType.ocsp:
+                return request is OcspStatusRequest;
+            default:
+                throw new ArgumentException("unsupported CertificateStatusType", "statusType");
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/CertificateStatusRequestItemV2.cs b/crypto/src/tls/CertificateStatusRequestItemV2.cs
new file mode 100644
index 000000000..a30d9ad9f
--- /dev/null
+++ b/crypto/src/tls/CertificateStatusRequestItemV2.cs
@@ -0,0 +1,100 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Implementation of the RFC 6961 2.2. CertificateStatusRequestItemV2.</summary>
+    public sealed class CertificateStatusRequestItemV2
+    {
+        private readonly short m_statusType;
+        private readonly object m_request;
+
+        public CertificateStatusRequestItemV2(short statusType, object request)
+        {
+            if (!IsCorrectType(statusType, request))
+                throw new ArgumentException("not an instance of the correct type", "request");
+
+            this.m_statusType = statusType;
+            this.m_request = request;
+        }
+
+        public short StatusType
+        {
+            get { return m_statusType; }
+        }
+
+        public object Request
+        {
+            get { return m_request; }
+        }
+
+        public OcspStatusRequest OcspStatusRequest
+        {
+            get
+            {
+                if (!(m_request is OcspStatusRequest))
+                    throw new InvalidOperationException("'request' is not an OcspStatusRequest");
+
+                return (OcspStatusRequest)m_request;
+            }
+        }
+
+        /// <summary>Encode this <see cref="CertificateStatusRequestItemV2"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(m_statusType, output);
+
+            MemoryStream buf = new MemoryStream();
+            switch (m_statusType)
+            {
+            case CertificateStatusType.ocsp:
+            case CertificateStatusType.ocsp_multi:
+                ((OcspStatusRequest)m_request).Encode(buf);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+            byte[] requestBytes = buf.ToArray();
+            TlsUtilities.WriteOpaque16(requestBytes, output);
+        }
+
+        /// <summary>Parse a <see cref="CertificateStatusRequestItemV2"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="CertificateStatusRequestItemV2"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static CertificateStatusRequestItemV2 Parse(Stream input)
+        {
+            short status_type = TlsUtilities.ReadUint8(input);
+
+            object request;
+            byte[] requestBytes = TlsUtilities.ReadOpaque16(input);
+            MemoryStream buf = new MemoryStream(requestBytes, false);
+            switch (status_type)
+            {
+            case CertificateStatusType.ocsp:
+            case CertificateStatusType.ocsp_multi:
+                request = OcspStatusRequest.Parse(buf);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+            TlsProtocol.AssertEmpty(buf);
+
+            return new CertificateStatusRequestItemV2(status_type, request);
+        }
+
+        private static bool IsCorrectType(short statusType, object request)
+        {
+            switch (statusType)
+            {
+            case CertificateStatusType.ocsp:
+            case CertificateStatusType.ocsp_multi:
+                return request is OcspStatusRequest;
+            default:
+                throw new ArgumentException("unsupported CertificateStatusType", "statusType");
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/CertificateStatusType.cs b/crypto/src/tls/CertificateStatusType.cs
new file mode 100644
index 000000000..bb0c42455
--- /dev/null
+++ b/crypto/src/tls/CertificateStatusType.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class CertificateStatusType
+    {
+        /*
+         *  RFC 6066
+         */
+        public const short ocsp = 1;
+
+        /*
+         *  RFC 6961
+         */
+        public const short ocsp_multi = 2;
+    }
+}
diff --git a/crypto/src/tls/CertificateType.cs b/crypto/src/tls/CertificateType.cs
new file mode 100644
index 000000000..eed302416
--- /dev/null
+++ b/crypto/src/tls/CertificateType.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 6091</summary>
+    public abstract class CertificateType
+    {
+        public const short X509 = 0;
+        public const short OpenPGP = 1;
+
+        /*
+         * RFC 7250
+         */
+        public const short RawPublicKey = 2;
+    }
+}
diff --git a/crypto/src/tls/CertificateUrl.cs b/crypto/src/tls/CertificateUrl.cs
new file mode 100644
index 000000000..d2445772a
--- /dev/null
+++ b/crypto/src/tls/CertificateUrl.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 3546 3.3</summary>
+    public sealed class CertificateUrl
+    {
+        private readonly short m_type;
+        private readonly IList m_urlAndHashList;
+
+        /// <param name="type">see <see cref="CertChainType"/> for valid constants.</param>
+        /// <param name="urlAndHashList">an <see cref="IList"/> of <see cref="UrlAndHash"/>.</param>
+        public CertificateUrl(short type, IList urlAndHashList)
+        {
+            if (!CertChainType.IsValid(type))
+                throw new ArgumentException("not a valid CertChainType value", "type");
+            if (urlAndHashList == null || urlAndHashList.Count < 1)
+                throw new ArgumentException("must have length > 0", "urlAndHashList");
+            if (type == CertChainType.pkipath && urlAndHashList.Count != 1)
+                throw new ArgumentException("must contain exactly one entry when type is "
+                    + CertChainType.GetText(type), "urlAndHashList");
+
+            this.m_type = type;
+            this.m_urlAndHashList = urlAndHashList;
+        }
+
+        /// <returns><see cref="CertChainType"/></returns>
+        public short Type
+        {
+            get { return m_type; }
+        }
+
+        /// <returns>an <see cref="IList"/> of <see cref="UrlAndHash"/>.</returns>
+        public IList UrlAndHashList
+        {
+            get { return m_urlAndHashList; }
+        }
+
+        /// <summary>Encode this <see cref="CertificateUrl"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(m_type, output);
+
+            ListBuffer16 buf = new ListBuffer16();
+            foreach (UrlAndHash urlAndHash in m_urlAndHashList)
+            {
+                urlAndHash.Encode(buf);
+            }
+            buf.EncodeTo(output);
+        }
+
+        /// <summary>Parse a <see cref="CertificateUrl"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="CertificateUrl"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static CertificateUrl Parse(TlsContext context, Stream input)
+        {
+            short type = TlsUtilities.ReadUint8(input);
+            if (!CertChainType.IsValid(type))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int totalLength = TlsUtilities.ReadUint16(input);
+            if (totalLength < 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            byte[] urlAndHashListData = TlsUtilities.ReadFully(totalLength, input);
+
+            MemoryStream buf = new MemoryStream(urlAndHashListData, false);
+
+            IList url_and_hash_list = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                UrlAndHash url_and_hash = UrlAndHash.Parse(context, buf);
+                url_and_hash_list.Add(url_and_hash);
+            }
+
+            if (type == CertChainType.pkipath && url_and_hash_list.Count != 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return new CertificateUrl(type, url_and_hash_list);
+        }
+
+        // TODO Could be more generally useful
+        internal class ListBuffer16
+             : MemoryStream
+        {
+            internal ListBuffer16()
+            {
+                // Reserve space for length
+                TlsUtilities.WriteUint16(0, this);
+            }
+
+            internal void EncodeTo(Stream output)
+            {
+                // Patch actual length back in
+                int length = (int)Length - 2;
+                TlsUtilities.CheckUint16(length);
+
+                Seek(0L, SeekOrigin.Begin);
+                TlsUtilities.WriteUint16(length, this);
+
+                Streams.WriteBufTo(this, output);
+
+                Platform.Dispose(this);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/ChangeCipherSpec.cs b/crypto/src/tls/ChangeCipherSpec.cs
new file mode 100644
index 000000000..f83fdb499
--- /dev/null
+++ b/crypto/src/tls/ChangeCipherSpec.cs
@@ -0,0 +1,9 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class ChangeCipherSpec
+    {
+        public const short change_cipher_spec = 1;
+    }
+}
diff --git a/crypto/src/tls/ChannelBinding.cs b/crypto/src/tls/ChannelBinding.cs
new file mode 100644
index 000000000..84f8bc4df
--- /dev/null
+++ b/crypto/src/tls/ChannelBinding.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5056</summary>
+    /// <remarks>
+    /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the
+    /// particular values (e.g.serialization).
+    /// </remarks>
+    public abstract class ChannelBinding
+    {
+        /*
+         * RFC 5929
+         */
+        public const int tls_server_end_point = 0;
+        public const int tls_unique = 1;
+        public const int tls_unique_for_telnet = 2;
+    }
+}
diff --git a/crypto/src/tls/CipherSuite.cs b/crypto/src/tls/CipherSuite.cs
new file mode 100644
index 000000000..5c53268e2
--- /dev/null
+++ b/crypto/src/tls/CipherSuite.cs
@@ -0,0 +1,461 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 2246 A.5</summary>
+    public abstract class CipherSuite
+    {
+        public static bool IsScsv(int cipherSuite)
+        {
+            switch (cipherSuite)
+            {
+            case TLS_EMPTY_RENEGOTIATION_INFO_SCSV:
+            case TLS_FALLBACK_SCSV:
+                return true;
+            default:
+                return false;
+            }
+        }
+
+        public const int TLS_NULL_WITH_NULL_NULL = 0x0000;
+        public const int TLS_RSA_WITH_NULL_MD5 = 0x0001;
+        public const int TLS_RSA_WITH_NULL_SHA = 0x0002;
+        public const int TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003;
+        public const int TLS_RSA_WITH_RC4_128_MD5 = 0x0004;
+        public const int TLS_RSA_WITH_RC4_128_SHA = 0x0005;
+        public const int TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006;
+        public const int TLS_RSA_WITH_IDEA_CBC_SHA = 0x0007;
+        public const int TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008;
+        public const int TLS_RSA_WITH_DES_CBC_SHA = 0x0009;
+        public const int TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A;
+        public const int TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B;
+        public const int TLS_DH_DSS_WITH_DES_CBC_SHA = 0x000C;
+        public const int TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D;
+        public const int TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E;
+        public const int TLS_DH_RSA_WITH_DES_CBC_SHA = 0x000F;
+        public const int TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010;
+        public const int TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011;
+        public const int TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x0012;
+        public const int TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013;
+        public const int TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014;
+        public const int TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x0015;
+        public const int TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016;
+        public const int TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017;
+        public const int TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018;
+        public const int TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x0019;
+        public const int TLS_DH_anon_WITH_DES_CBC_SHA = 0x001A;
+        public const int TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B;
+
+        /*
+         * Note: The cipher suite values { 0x00, 0x1C } and { 0x00, 0x1D } are reserved to avoid
+         * collision with Fortezza-based cipher suites in SSL 3.
+         */
+
+        /*
+         * RFC 3268
+         */
+        public const int TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F;
+        public const int TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x0030;
+        public const int TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031;
+        public const int TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032;
+        public const int TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033;
+        public const int TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034;
+        public const int TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035;
+        public const int TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x0036;
+        public const int TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037;
+        public const int TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038;
+        public const int TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039;
+        public const int TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A;
+
+        /*
+         * RFC 5932
+         */
+        public const int TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0041;
+        public const int TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0042;
+        public const int TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0043;
+        public const int TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0044;
+        public const int TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0045;
+        public const int TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA = 0x0046;
+
+        public const int TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0084;
+        public const int TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0085;
+        public const int TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0086;
+        public const int TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0087;
+        public const int TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0088;
+        public const int TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA = 0x0089;
+
+        public const int TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BA;
+        public const int TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BB;
+        public const int TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BC;
+        public const int TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BD;
+        public const int TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BE;
+        public const int TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BF;
+
+        public const int TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C0;
+        public const int TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C1;
+        public const int TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C2;
+        public const int TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C3;
+        public const int TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C4;
+        public const int TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C5;
+
+        /*
+         * RFC 4162
+         */
+        public const int TLS_RSA_WITH_SEED_CBC_SHA = 0x0096;
+        public const int TLS_DH_DSS_WITH_SEED_CBC_SHA = 0x0097;
+        public const int TLS_DH_RSA_WITH_SEED_CBC_SHA = 0x0098;
+        public const int TLS_DHE_DSS_WITH_SEED_CBC_SHA = 0x0099;
+        public const int TLS_DHE_RSA_WITH_SEED_CBC_SHA = 0x009A;
+        public const int TLS_DH_anon_WITH_SEED_CBC_SHA = 0x009B;
+
+        /*
+         * RFC 4279
+         */
+        public const int TLS_PSK_WITH_RC4_128_SHA = 0x008A;
+        public const int TLS_PSK_WITH_3DES_EDE_CBC_SHA = 0x008B;
+        public const int TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C;
+        public const int TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D;
+        public const int TLS_DHE_PSK_WITH_RC4_128_SHA = 0x008E;
+        public const int TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = 0x008F;
+        public const int TLS_DHE_PSK_WITH_AES_128_CBC_SHA = 0x0090;
+        public const int TLS_DHE_PSK_WITH_AES_256_CBC_SHA = 0x0091;
+        public const int TLS_RSA_PSK_WITH_RC4_128_SHA = 0x0092;
+        public const int TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = 0x0093;
+        public const int TLS_RSA_PSK_WITH_AES_128_CBC_SHA = 0x0094;
+        public const int TLS_RSA_PSK_WITH_AES_256_CBC_SHA = 0x0095;
+
+        /*
+         * RFC 4492
+         */
+        public const int TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001;
+        public const int TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002;
+        public const int TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003;
+        public const int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004;
+        public const int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005;
+        public const int TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006;
+        public const int TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007;
+        public const int TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A;
+        public const int TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B;
+        public const int TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C;
+        public const int TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D;
+        public const int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E;
+        public const int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F;
+        public const int TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010;
+        public const int TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011;
+        public const int TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012;
+        public const int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013;
+        public const int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014;
+        public const int TLS_ECDH_anon_WITH_NULL_SHA = 0xC015;
+        public const int TLS_ECDH_anon_WITH_RC4_128_SHA = 0xC016;
+        public const int TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017;
+        public const int TLS_ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018;
+        public const int TLS_ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019;
+
+        /*
+         * RFC 4785
+         */
+        public const int TLS_PSK_WITH_NULL_SHA = 0x002C;
+        public const int TLS_DHE_PSK_WITH_NULL_SHA = 0x002D;
+        public const int TLS_RSA_PSK_WITH_NULL_SHA = 0x002E;
+
+        /*
+         * RFC 5054
+         */
+        public const int TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A;
+        public const int TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B;
+        public const int TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C;
+        public const int TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D;
+        public const int TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E;
+        public const int TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F;
+        public const int TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020;
+        public const int TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021;
+        public const int TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022;
+
+        /*
+         * RFC 5246
+         */
+        public const int TLS_RSA_WITH_NULL_SHA256 = 0x003B;
+        public const int TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C;
+        public const int TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D;
+        public const int TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x003E;
+        public const int TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003F;
+        public const int TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040;
+        public const int TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067;
+        public const int TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x0068;
+        public const int TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069;
+        public const int TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A;
+        public const int TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B;
+        public const int TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C;
+        public const int TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D;
+
+        /*
+         * RFC 5288
+         */
+        public const int TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C;
+        public const int TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D;
+        public const int TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E;
+        public const int TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F;
+        public const int TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00A0;
+        public const int TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00A1;
+        public const int TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2;
+        public const int TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3;
+        public const int TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = 0x00A4;
+        public const int TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = 0x00A5;
+        public const int TLS_DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6;
+        public const int TLS_DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7;
+
+        /*
+         * RFC 5289
+         */
+        public const int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024;
+        public const int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025;
+        public const int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026;
+        public const int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027;
+        public const int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028;
+        public const int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029;
+        public const int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C;
+        public const int TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D;
+        public const int TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E;
+        public const int TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F;
+        public const int TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030;
+        public const int TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031;
+        public const int TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032;
+
+        /*
+         * RFC 5487
+         */
+        public const int TLS_PSK_WITH_AES_128_GCM_SHA256 = 0x00A8;
+        public const int TLS_PSK_WITH_AES_256_GCM_SHA384 = 0x00A9;
+        public const int TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = 0x00AA;
+        public const int TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = 0x00AB;
+        public const int TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = 0x00AC;
+        public const int TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = 0x00AD;
+        public const int TLS_PSK_WITH_AES_128_CBC_SHA256 = 0x00AE;
+        public const int TLS_PSK_WITH_AES_256_CBC_SHA384 = 0x00AF;
+        public const int TLS_PSK_WITH_NULL_SHA256 = 0x00B0;
+        public const int TLS_PSK_WITH_NULL_SHA384 = 0x00B1;
+        public const int TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 = 0x00B2;
+        public const int TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 = 0x00B3;
+        public const int TLS_DHE_PSK_WITH_NULL_SHA256 = 0x00B4;
+        public const int TLS_DHE_PSK_WITH_NULL_SHA384 = 0x00B5;
+        public const int TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 = 0x00B6;
+        public const int TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 = 0x00B7;
+        public const int TLS_RSA_PSK_WITH_NULL_SHA256 = 0x00B8;
+        public const int TLS_RSA_PSK_WITH_NULL_SHA384 = 0x00B9;
+
+        /*
+         * RFC 5489
+         */
+        public const int TLS_ECDHE_PSK_WITH_RC4_128_SHA = 0xC033;
+        public const int TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = 0xC034;
+        public const int TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = 0xC035;
+        public const int TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = 0xC036;
+        public const int TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = 0xC037;
+        public const int TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = 0xC038;
+        public const int TLS_ECDHE_PSK_WITH_NULL_SHA = 0xC039;
+        public const int TLS_ECDHE_PSK_WITH_NULL_SHA256 = 0xC03A;
+        public const int TLS_ECDHE_PSK_WITH_NULL_SHA384 = 0xC03B;
+
+        /*
+         * RFC 5746
+         */
+        public const int TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF;
+
+        /*
+         * RFC 6209
+         */
+        public const int TLS_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC03C;
+        public const int TLS_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC03D;
+        public const int TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 = 0xC03E;
+        public const int TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 = 0xC03F;
+        public const int TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC040;
+        public const int TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC041;
+        public const int TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 = 0xC042;
+        public const int TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 = 0xC043;
+        public const int TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC044;
+        public const int TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC045;
+        public const int TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 = 0xC046;
+        public const int TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 = 0xC047;
+
+        public const int TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 = 0xC048;
+        public const int TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 = 0xC049;
+        public const int TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 = 0xC04A;
+        public const int TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 = 0xC04B;
+        public const int TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC04C;
+        public const int TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC04D;
+        public const int TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 = 0xC04E;
+        public const int TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 = 0xC04F;
+
+        public const int TLS_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC050;
+        public const int TLS_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC051;
+        public const int TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC052;
+        public const int TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC053;
+        public const int TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC054;
+        public const int TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC055;
+        public const int TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 = 0xC056;
+        public const int TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 = 0xC057;
+        public const int TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 = 0xC058;
+        public const int TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 = 0xC059;
+        public const int TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 = 0xC05A;
+        public const int TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 = 0xC05B;
+
+        public const int TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 = 0xC05C;
+        public const int TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 = 0xC05D;
+        public const int TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 = 0xC05E;
+        public const int TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 = 0xC05F;
+        public const int TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC060;
+        public const int TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC061;
+        public const int TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 = 0xC062;
+        public const int TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 = 0xC063;
+
+        public const int TLS_PSK_WITH_ARIA_128_CBC_SHA256 = 0xC064;
+        public const int TLS_PSK_WITH_ARIA_256_CBC_SHA384 = 0xC065;
+        public const int TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 = 0xC066;
+        public const int TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 = 0xC067;
+        public const int TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 = 0xC068;
+        public const int TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 = 0xC069;
+        public const int TLS_PSK_WITH_ARIA_128_GCM_SHA256 = 0xC06A;
+        public const int TLS_PSK_WITH_ARIA_256_GCM_SHA384 = 0xC06B;
+        public const int TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 = 0xC06C;
+        public const int TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 = 0xC06D;
+        public const int TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 = 0xC06E;
+        public const int TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 = 0xC06F;
+        public const int TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 = 0xC070;
+        public const int TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 = 0xC071;
+
+        /*
+         * RFC 6367
+         */
+        public const int TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC072;
+        public const int TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC073;
+        public const int TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC074;
+        public const int TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC075;
+        public const int TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC076;
+        public const int TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC077;
+        public const int TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC078;
+        public const int TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC079;
+
+        public const int TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07A;
+        public const int TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07B;
+        public const int TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07C;
+        public const int TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07D;
+        public const int TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07E;
+        public const int TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07F;
+        public const int TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 = 0xC080;
+        public const int TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 = 0xC081;
+        public const int TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 = 0xC082;
+        public const int TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 = 0xC083;
+        public const int TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 = 0xC084;
+        public const int TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 = 0xC085;
+        public const int TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC086;
+        public const int TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC087;
+        public const int TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC088;
+        public const int TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC089;
+        public const int TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08A;
+        public const int TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08B;
+        public const int TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08C;
+        public const int TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08D;
+
+        public const int TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08E;
+        public const int TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08F;
+        public const int TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC090;
+        public const int TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC091;
+        public const int TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC092;
+        public const int TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC093;
+        public const int TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC094;
+        public const int TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC095;
+        public const int TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC096;
+        public const int TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC097;
+        public const int TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC098;
+        public const int TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC099;
+        public const int TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC09A;
+        public const int TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC09B;
+
+        /*
+         * RFC 6655
+         */
+        public const int TLS_RSA_WITH_AES_128_CCM = 0xC09C;
+        public const int TLS_RSA_WITH_AES_256_CCM = 0xC09D;
+        public const int TLS_DHE_RSA_WITH_AES_128_CCM = 0xC09E;
+        public const int TLS_DHE_RSA_WITH_AES_256_CCM = 0xC09F;
+        public const int TLS_RSA_WITH_AES_128_CCM_8 = 0xC0A0;
+        public const int TLS_RSA_WITH_AES_256_CCM_8 = 0xC0A1;
+        public const int TLS_DHE_RSA_WITH_AES_128_CCM_8 = 0xC0A2;
+        public const int TLS_DHE_RSA_WITH_AES_256_CCM_8 = 0xC0A3;
+        public const int TLS_PSK_WITH_AES_128_CCM = 0xC0A4;
+        public const int TLS_PSK_WITH_AES_256_CCM = 0xC0A5;
+        public const int TLS_DHE_PSK_WITH_AES_128_CCM = 0xC0A6;
+        public const int TLS_DHE_PSK_WITH_AES_256_CCM = 0xC0A7;
+        public const int TLS_PSK_WITH_AES_128_CCM_8 = 0xC0A8;
+        public const int TLS_PSK_WITH_AES_256_CCM_8 = 0xC0A9;
+        public const int TLS_PSK_DHE_WITH_AES_128_CCM_8 = 0xC0AA;
+        public const int TLS_PSK_DHE_WITH_AES_256_CCM_8 = 0xC0AB;
+
+        /*
+         * RFC 7251
+         */
+        public const int TLS_ECDHE_ECDSA_WITH_AES_128_CCM = 0xC0AC;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_256_CCM = 0xC0AD;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 = 0xC0AE;
+        public const int TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 = 0xC0AF;
+
+        /*
+         * RFC 7507
+         */
+        public const int TLS_FALLBACK_SCSV = 0x5600;
+
+        /*
+         * RFC 7905
+         */
+        public const int TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA8;
+        public const int TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCA9;
+        public const int TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAA;
+        public const int TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAB;
+        public const int TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAC;
+        public const int TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAD;
+        public const int TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 = 0xCCAE;
+
+        /*
+         * RFC 8442
+         */
+        public const int TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 = 0xD001;
+        public const int TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 = 0xD002;
+        public const int TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256 = 0xD003;
+        public const int TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256 = 0xD005;
+
+        /*
+         * TLS 1.3 Section
+         * 
+         * Although TLS 1.3 uses the same cipher suite space as previous versions of TLS, TLS 1.3 cipher
+         * suites are defined differently, only specifying the symmetric ciphers, and cannot be used for
+         * TLS 1.2. Similarly, cipher suites for TLS 1.2 and lower cannot be used with TLS 1.3.
+         */
+
+        /*
+         * RFC 8446
+         */
+        public const int TLS_AES_128_GCM_SHA256 = 0x1301;
+        public const int TLS_AES_256_GCM_SHA384 = 0x1302;
+        public const int TLS_CHACHA20_POLY1305_SHA256 = 0x1303;
+        public const int TLS_AES_128_CCM_SHA256 = 0x1304;
+        public const int TLS_AES_128_CCM_8_SHA256 = 0x1305;
+
+        /*
+         * RFC 8998
+         */
+        public const int TLS_SM4_GCM_SM3 = 0x00C6;
+        public const int TLS_SM4_CCM_SM3 = 0x00C7;
+
+        /*
+         * draft-smyshlyaev-tls12-gost-suites-10
+         */
+        public const int TLS_GOSTR341112_256_WITH_KUZNYECHIK_CTR_OMAC = 0xC100;
+        public const int TLS_GOSTR341112_256_WITH_MAGMA_CTR_OMAC = 0xC101;
+        public const int TLS_GOSTR341112_256_WITH_28147_CNT_IMIT = 0xC102;
+    }
+}
diff --git a/crypto/src/tls/CipherType.cs b/crypto/src/tls/CipherType.cs
new file mode 100644
index 000000000..3525960ff
--- /dev/null
+++ b/crypto/src/tls/CipherType.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 2246</summary>
+    /// <remarks>
+    /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the
+    /// particular values (e.g. serialization).
+    /// </remarks>
+    public abstract class CipherType
+    {
+        public const int stream = 0;
+        public const int block = 1;
+
+        /*
+         * RFC 5246
+         */
+        public const int aead = 2;
+    }
+}
diff --git a/crypto/src/tls/ClientAuthenticationType.cs b/crypto/src/tls/ClientAuthenticationType.cs
new file mode 100644
index 000000000..2166e973e
--- /dev/null
+++ b/crypto/src/tls/ClientAuthenticationType.cs
@@ -0,0 +1,14 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class ClientAuthenticationType
+    {
+        /*
+         * RFC 5077 4
+         */
+        public const short anonymous = 0;
+        public const short certificate_based = 1;
+        public const short psk = 2;
+    }
+}
diff --git a/crypto/src/tls/ClientCertificateType.cs b/crypto/src/tls/ClientCertificateType.cs
new file mode 100644
index 000000000..f3a4a08a6
--- /dev/null
+++ b/crypto/src/tls/ClientCertificateType.cs
@@ -0,0 +1,69 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class ClientCertificateType
+    {
+        /*
+         *  RFC 4346 7.4.4
+         */
+        public const short rsa_sign = 1;
+        public const short dss_sign = 2;
+        public const short rsa_fixed_dh = 3;
+        public const short dss_fixed_dh = 4;
+        public const short rsa_ephemeral_dh_RESERVED = 5;
+        public const short dss_ephemeral_dh_RESERVED = 6;
+        public const short fortezza_dms_RESERVED = 20;
+
+        /*
+         * RFC 4492 5.5
+         */
+        public const short ecdsa_sign = 64;
+        public const short rsa_fixed_ecdh = 65;
+        public const short ecdsa_fixed_ecdh = 66;
+
+        /*
+         * draft-smyshlyaev-tls12-gost-suites-10
+         */
+        public const short gost_sign256 = 67;
+        public const short gost_sign512 = 68;
+
+        public static string GetName(short clientCertificateType)
+        {
+            switch (clientCertificateType)
+            {
+            case rsa_sign:
+                return "rsa_sign";
+            case dss_sign:
+                return "dss_sign";
+            case rsa_fixed_dh:
+                return "rsa_fixed_dh";
+            case dss_fixed_dh:
+                return "dss_fixed_dh";
+            case rsa_ephemeral_dh_RESERVED:
+                return "rsa_ephemeral_dh_RESERVED";
+            case dss_ephemeral_dh_RESERVED:
+                return "dss_ephemeral_dh_RESERVED";
+            case fortezza_dms_RESERVED:
+                return "fortezza_dms_RESERVED";
+            case ecdsa_sign:
+                return "ecdsa_sign";
+            case rsa_fixed_ecdh:
+                return "rsa_fixed_ecdh";
+            case ecdsa_fixed_ecdh:
+                return "ecdsa_fixed_ecdh";
+            case gost_sign256:
+                return "gost_sign256";
+            case gost_sign512:
+                return "gost_sign512";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short clientCertificateType)
+        {
+            return GetName(clientCertificateType) + "(" + clientCertificateType + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/ClientHello.cs b/crypto/src/tls/ClientHello.cs
new file mode 100644
index 000000000..50a33ac39
--- /dev/null
+++ b/crypto/src/tls/ClientHello.cs
@@ -0,0 +1,167 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class ClientHello
+    {
+        private readonly ProtocolVersion m_version;
+        private readonly byte[] m_random;
+        private readonly byte[] m_sessionID;
+        private readonly byte[] m_cookie;
+        private readonly int[] m_cipherSuites;
+        private readonly IDictionary m_extensions;
+
+        public ClientHello(ProtocolVersion version, byte[] random, byte[] sessionID, byte[] cookie,
+            int[] cipherSuites, IDictionary extensions)
+        {
+            this.m_version = version;
+            this.m_random = random;
+            this.m_sessionID = sessionID;
+            this.m_cookie = cookie;
+            this.m_cipherSuites = cipherSuites;
+            this.m_extensions = extensions;
+        }
+
+        public int[] CipherSuites
+        {
+            get { return m_cipherSuites; }
+        }
+
+        public byte[] Cookie
+        {
+            get { return m_cookie; }
+        }
+
+        public IDictionary Extensions
+        {
+            get { return m_extensions; }
+        }
+
+        public byte[] Random
+        {
+            get { return m_random; }
+        }
+
+        public byte[] SessionID
+        {
+            get { return m_sessionID; }
+        }
+
+        public ProtocolVersion Version
+        {
+            get { return m_version; }
+        }
+
+        /// <summary>Encode this <see cref="ClientHello"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(TlsContext context, Stream output)
+        {
+            TlsUtilities.WriteVersion(m_version, output);
+
+            output.Write(m_random, 0, m_random.Length);
+
+            TlsUtilities.WriteOpaque8(m_sessionID, output);
+
+            if (null != m_cookie)
+            {
+                TlsUtilities.WriteOpaque8(m_cookie, output);
+            }
+
+            TlsUtilities.WriteUint16ArrayWithUint16Length(m_cipherSuites, output);
+
+            TlsUtilities.WriteUint8ArrayWithUint8Length(new short[]{ CompressionMethod.cls_null }, output);
+
+            TlsProtocol.WriteExtensions(output, m_extensions);
+        }
+
+        /// <summary>Parse a <see cref="ClientHello"/> from a <see cref="MemoryStream"/>.</summary>
+        /// <param name="messageInput">the <see cref="MemoryStream"/> to parse from.</param>
+        /// <param name="dtlsOutput">for DTLS this should be non-null; the input is copied to this
+        /// <see cref="Stream"/>, minus the cookie field.</param>
+        /// <returns>a <see cref="ClientHello"/> object.</returns>
+        /// <exception cref="TlsFatalAlert"/>
+        public static ClientHello Parse(MemoryStream messageInput, Stream dtlsOutput)
+        {
+            try
+            {
+                return ImplParse(messageInput, dtlsOutput);
+            }
+            catch (TlsFatalAlert e)
+            {
+                throw e;
+            }
+            catch (IOException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.decode_error, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        private static ClientHello ImplParse(MemoryStream messageInput, Stream dtlsOutput)
+        {
+            Stream input = messageInput;
+            if (null != dtlsOutput)
+            {
+                input = new TeeInputStream(input, dtlsOutput);
+            }
+
+            ProtocolVersion clientVersion = TlsUtilities.ReadVersion(input);
+
+            byte[] random = TlsUtilities.ReadFully(32, input);
+
+            byte[] sessionID = TlsUtilities.ReadOpaque8(input, 0, 32);
+
+            byte[] cookie = null;
+            if (null != dtlsOutput)
+            {
+                /*
+                 * RFC 6347 This specification increases the cookie size limit to 255 bytes for greater
+                 * future flexibility. The limit remains 32 for previous versions of DTLS.
+                 */
+                int maxCookieLength = ProtocolVersion.DTLSv12.IsEqualOrEarlierVersionOf(clientVersion) ? 255 : 32;
+
+                cookie = TlsUtilities.ReadOpaque8(messageInput, 0, maxCookieLength);
+            }
+
+            int cipher_suites_length = TlsUtilities.ReadUint16(input);
+            if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0
+                || (int)(messageInput.Length - messageInput.Position) < cipher_suites_length)
+            {
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+
+            /*
+             * NOTE: "If the session_id field is not empty (implying a session resumption request) this
+             * vector must include at least the cipher_suite from that session."
+             */
+            int[] cipherSuites = TlsUtilities.ReadUint16Array(cipher_suites_length / 2, input);
+
+            short[] compressionMethods = TlsUtilities.ReadUint8ArrayWithUint8Length(input, 1);
+            if (!Arrays.Contains(compressionMethods, CompressionMethod.cls_null))
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+            /*
+             * NOTE: Can't use TlsProtocol.ReadExtensions directly because TeeInputStream a) won't have
+             * 'Length' or 'Position' properties in the FIPS provider, b) isn't a MemoryStream.
+             */
+            IDictionary extensions = null;
+            if (messageInput.Position < messageInput.Length)
+            {
+                byte[] extBytes = TlsUtilities.ReadOpaque16(input);
+
+                TlsProtocol.AssertEmpty(messageInput);
+
+                extensions = TlsProtocol.ReadExtensionsDataClientHello(extBytes);
+            }
+
+            return new ClientHello(clientVersion, random, sessionID, cookie, cipherSuites, extensions);
+        }
+    }
+}
diff --git a/crypto/src/tls/CombinedHash.cs b/crypto/src/tls/CombinedHash.cs
new file mode 100644
index 000000000..71151d2a5
--- /dev/null
+++ b/crypto/src/tls/CombinedHash.cs
@@ -0,0 +1,67 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>A combined hash, which implements md5(m) || sha1(m).</summary>
+    public class CombinedHash
+        : TlsHash
+    {
+        protected readonly TlsContext m_context;
+        protected readonly TlsCrypto m_crypto;
+        protected readonly TlsHash m_md5;
+        protected readonly TlsHash m_sha1;
+
+        internal CombinedHash(TlsContext context, TlsHash md5, TlsHash sha1)
+        {
+            this.m_context = context;
+            this.m_crypto = context.Crypto;
+            this.m_md5 = md5;
+            this.m_sha1 = sha1;
+        }
+
+        public CombinedHash(TlsCrypto crypto)
+        {
+            this.m_crypto = crypto;
+            this.m_md5 = crypto.CreateHash(CryptoHashAlgorithm.md5);
+            this.m_sha1 = crypto.CreateHash(CryptoHashAlgorithm.sha1);
+        }
+
+        public CombinedHash(CombinedHash t)
+        {
+            this.m_context = t.m_context;
+            this.m_crypto = t.m_crypto;
+            this.m_md5 = t.m_md5.CloneHash();
+            this.m_sha1 = t.m_sha1.CloneHash();
+        }
+
+        public virtual void Update(byte[] input, int inOff, int len)
+        {
+            m_md5.Update(input, inOff, len);
+            m_sha1.Update(input, inOff, len);
+        }
+
+        public virtual byte[] CalculateHash()
+        {
+            if (null != m_context && TlsUtilities.IsSsl(m_context))
+            {
+                Ssl3Utilities.CompleteCombinedHash(m_context, m_md5, m_sha1);
+            }
+
+            return Arrays.Concatenate(m_md5.CalculateHash(), m_sha1.CalculateHash());
+        }
+
+        public virtual TlsHash CloneHash()
+        {
+            return new CombinedHash(this);
+        }
+
+        public virtual void Reset()
+        {
+            m_md5.Reset();
+            m_sha1.Reset();
+        }
+    }
+}
diff --git a/crypto/src/tls/CompressionMethod.cs b/crypto/src/tls/CompressionMethod.cs
new file mode 100644
index 000000000..21b43d477
--- /dev/null
+++ b/crypto/src/tls/CompressionMethod.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 2246 6.1</summary>
+    public abstract class CompressionMethod
+    {
+        public const short cls_null = 0;
+
+        /*
+         * RFC 3749 2
+         */
+        public const short DEFLATE = 1;
+
+        /*
+         * Values from 224 decimal (0xE0) through 255 decimal (0xFF)
+         * inclusive are reserved for private use.
+         */
+    }
+}
diff --git a/crypto/src/tls/ConnectionEnd.cs b/crypto/src/tls/ConnectionEnd.cs
new file mode 100644
index 000000000..01ceb1b25
--- /dev/null
+++ b/crypto/src/tls/ConnectionEnd.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 2246</summary>
+    /// <remarks>
+    /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the
+    /// particular values(e.g.serialization).
+    /// </remarks>
+    public abstract class ConnectionEnd
+    {
+        public const int server = 0;
+        public const int client = 1;
+    }
+}
diff --git a/crypto/src/tls/ContentType.cs b/crypto/src/tls/ContentType.cs
new file mode 100644
index 000000000..cf89539c7
--- /dev/null
+++ b/crypto/src/tls/ContentType.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 2246 6.2.1</summary>
+    public abstract class ContentType
+    {
+        public const short change_cipher_spec = 20;
+        public const short alert = 21;
+        public const short handshake = 22;
+        public const short application_data = 23;
+        public const short heartbeat = 24;
+
+        public static string GetName(short contentType)
+        {
+            switch (contentType)
+            {
+            case alert:
+                return "alert";
+            case application_data:
+                return "application_data";
+            case change_cipher_spec:
+                return "change_cipher_spec";
+            case handshake:
+                return "handshake";
+            case heartbeat:
+                return "heartbeat";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short contentType)
+        {
+            return GetName(contentType) + "(" + contentType + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/DatagramReceiver.cs b/crypto/src/tls/DatagramReceiver.cs
new file mode 100644
index 000000000..5ab605ac4
--- /dev/null
+++ b/crypto/src/tls/DatagramReceiver.cs
@@ -0,0 +1,14 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public interface DatagramReceiver
+    {
+        /// <exception cref="IOException"/>
+        int GetReceiveLimit();
+
+        /// <exception cref="IOException"/>
+        int Receive(byte[] buf, int off, int len, int waitMillis);
+    }
+}
diff --git a/crypto/src/tls/DatagramSender.cs b/crypto/src/tls/DatagramSender.cs
new file mode 100644
index 000000000..bf14c18fe
--- /dev/null
+++ b/crypto/src/tls/DatagramSender.cs
@@ -0,0 +1,14 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public interface DatagramSender
+    {
+        /// <exception cref="IOException"/>
+        int GetSendLimit();
+
+        /// <exception cref="IOException"/>
+        void Send(byte[] buf, int off, int len);
+    }
+}
diff --git a/crypto/src/tls/DatagramTransport.cs b/crypto/src/tls/DatagramTransport.cs
new file mode 100644
index 000000000..4aecd0515
--- /dev/null
+++ b/crypto/src/tls/DatagramTransport.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for an object sending and receiving DTLS data.</summary>
+    public interface DatagramTransport
+        : DatagramReceiver, DatagramSender, TlsCloseable
+    {
+    }
+}
diff --git a/crypto/src/tls/DefaultTlsClient.cs b/crypto/src/tls/DefaultTlsClient.cs
new file mode 100644
index 000000000..a2a742633
--- /dev/null
+++ b/crypto/src/tls/DefaultTlsClient.cs
@@ -0,0 +1,48 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class DefaultTlsClient
+        : AbstractTlsClient
+    {
+        private static readonly int[] DefaultCipherSuites = new int[]
+        {
+            /*
+             * TODO[tls13] TLS 1.3
+             */
+            //CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
+            //CipherSuite.TLS_AES_128_GCM_SHA256,
+
+            /*
+             * pre-TLS 1.3
+             */
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+        };
+
+        public DefaultTlsClient(TlsCrypto crypto)
+            : base(crypto)
+        {
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, DefaultCipherSuites);
+        }
+    }
+}
diff --git a/crypto/src/tls/DefaultTlsCredentialedSigner.cs b/crypto/src/tls/DefaultTlsCredentialedSigner.cs
new file mode 100644
index 000000000..64bc30a8e
--- /dev/null
+++ b/crypto/src/tls/DefaultTlsCredentialedSigner.cs
@@ -0,0 +1,66 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Container class for generating signatures that carries the signature type, parameters, public key
+    /// certificate and public key's associated signer object.</summary>
+    public class DefaultTlsCredentialedSigner
+        : TlsCredentialedSigner
+    {
+        protected readonly TlsCryptoParameters m_cryptoParams;
+        protected readonly Certificate m_certificate;
+        protected readonly SignatureAndHashAlgorithm m_signatureAndHashAlgorithm;
+        protected readonly TlsSigner m_signer;
+
+        public DefaultTlsCredentialedSigner(TlsCryptoParameters cryptoParams, TlsSigner signer,
+            Certificate certificate, SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            if (certificate == null)
+                throw new ArgumentNullException("certificate");
+            if (certificate.IsEmpty)
+                throw new ArgumentException("cannot be empty", "certificate");
+            if (signer == null)
+                throw new ArgumentNullException("signer");
+
+            this.m_cryptoParams = cryptoParams;
+            this.m_certificate = certificate;
+            this.m_signatureAndHashAlgorithm = signatureAndHashAlgorithm;
+            this.m_signer = signer;
+        }
+
+        public virtual Certificate Certificate
+        {
+            get { return m_certificate; }
+        }
+
+        public virtual byte[] GenerateRawSignature(byte[] hash)
+        {
+            return m_signer.GenerateRawSignature(GetEffectiveAlgorithm(), hash);
+        }
+
+        public virtual SignatureAndHashAlgorithm SignatureAndHashAlgorithm
+        {
+            get { return m_signatureAndHashAlgorithm; }
+        }
+
+        public virtual TlsStreamSigner GetStreamSigner()
+        {
+            return m_signer.GetStreamSigner(GetEffectiveAlgorithm());
+        }
+
+        protected virtual SignatureAndHashAlgorithm GetEffectiveAlgorithm()
+        {
+            SignatureAndHashAlgorithm algorithm = null;
+            if (TlsImplUtilities.IsTlsV12(m_cryptoParams))
+            {
+                algorithm = SignatureAndHashAlgorithm;
+                if (algorithm == null)
+                    throw new InvalidOperationException("'signatureAndHashAlgorithm' cannot be null for (D)TLS 1.2+");
+            }
+            return algorithm;
+        }
+    }
+}
diff --git a/crypto/src/tls/DefaultTlsDHGroupVerifier.cs b/crypto/src/tls/DefaultTlsDHGroupVerifier.cs
new file mode 100644
index 000000000..8b9cf2e0f
--- /dev/null
+++ b/crypto/src/tls/DefaultTlsDHGroupVerifier.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class DefaultTlsDHGroupVerifier
+        : TlsDHGroupVerifier
+    {
+        public static readonly int DefaultMinimumPrimeBits = 2048;
+
+        private static readonly IList DefaultGroups = Platform.CreateArrayList();
+
+        private static void AddDefaultGroup(DHGroup dhGroup)
+        {
+            DefaultGroups.Add(dhGroup);
+        }
+
+        static DefaultTlsDHGroupVerifier()
+        {
+            /*
+             * These 10 standard groups are those specified in NIST SP 800-56A Rev. 3 Appendix D. Make
+             * sure to consider the impact on BCJSSE's FIPS mode and/or usage with the BCFIPS provider
+             * before modifying this list.
+             */
+
+            AddDefaultGroup(DHStandardGroups.rfc3526_2048);
+            AddDefaultGroup(DHStandardGroups.rfc3526_3072);
+            AddDefaultGroup(DHStandardGroups.rfc3526_4096);
+            AddDefaultGroup(DHStandardGroups.rfc3526_6144);
+            AddDefaultGroup(DHStandardGroups.rfc3526_8192);
+
+            AddDefaultGroup(DHStandardGroups.rfc7919_ffdhe2048);
+            AddDefaultGroup(DHStandardGroups.rfc7919_ffdhe3072);
+            AddDefaultGroup(DHStandardGroups.rfc7919_ffdhe4096);
+            AddDefaultGroup(DHStandardGroups.rfc7919_ffdhe6144);
+            AddDefaultGroup(DHStandardGroups.rfc7919_ffdhe8192);
+        }
+
+        // IList is (DHGroup)
+        protected readonly IList m_groups;
+        protected readonly int m_minimumPrimeBits;
+
+        /// <summary>Accept named groups and various standard DH groups with 'P' at least
+        /// <see cref="DefaultMinimumPrimeBits"/> bits.</summary>
+        public DefaultTlsDHGroupVerifier()
+            : this(DefaultMinimumPrimeBits)
+        {
+        }
+
+        /// <summary>Accept named groups and various standard DH groups with 'P' at least the specified number of bits.
+        /// </summary>
+        /// <param name="minimumPrimeBits">the minimum bitlength of 'P'.</param>
+        public DefaultTlsDHGroupVerifier(int minimumPrimeBits)
+            : this(DefaultGroups, minimumPrimeBits)
+        {
+        }
+
+        /// <summary>Accept named groups and a custom set of group parameters, subject to a minimum bitlength for 'P'.
+        /// </summary>
+        /// <param name="groups">a <see cref="IList">list</see> of acceptable <see cref="DHGroup"/>s.</param>
+        /// <param name="minimumPrimeBits">the minimum bitlength of 'P'.</param>
+        public DefaultTlsDHGroupVerifier(IList groups, int minimumPrimeBits)
+        {
+            this.m_groups = Platform.CreateArrayList(groups);
+            this.m_minimumPrimeBits = minimumPrimeBits;
+        }
+
+        public virtual bool Accept(DHGroup dhGroup)
+        {
+            return CheckMinimumPrimeBits(dhGroup) && CheckGroup(dhGroup);
+        }
+
+        public virtual int MinimumPrimeBits
+        {
+            get { return m_minimumPrimeBits; }
+        }
+
+        protected virtual bool AreGroupsEqual(DHGroup a, DHGroup b)
+        {
+            return a == b || (AreParametersEqual(a.P, b.P) && AreParametersEqual(a.G, b.G));
+        }
+
+        protected virtual bool AreParametersEqual(BigInteger a, BigInteger b)
+        {
+            return a == b || a.Equals(b);
+        }
+
+        protected virtual bool CheckGroup(DHGroup dhGroup)
+        {
+            foreach (DHGroup group in m_groups)
+            {
+                if (AreGroupsEqual(dhGroup, group))
+                    return true;
+            }
+            return false;
+        }
+
+        protected virtual bool CheckMinimumPrimeBits(DHGroup dhGroup)
+        {
+            return dhGroup.P.BitLength >= MinimumPrimeBits;
+        }
+    }
+}
diff --git a/crypto/src/tls/DefaultTlsHeartbeat.cs b/crypto/src/tls/DefaultTlsHeartbeat.cs
new file mode 100644
index 000000000..9090d2cc0
--- /dev/null
+++ b/crypto/src/tls/DefaultTlsHeartbeat.cs
@@ -0,0 +1,44 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class DefaultTlsHeartbeat
+        : TlsHeartbeat
+    {
+        private readonly int idleMillis, timeoutMillis;
+
+        private uint counter = 0U;
+
+        public DefaultTlsHeartbeat(int idleMillis, int timeoutMillis)
+        {
+            if (idleMillis <= 0)
+                throw new ArgumentException("must be > 0", "idleMillis");
+            if (timeoutMillis <= 0)
+                throw new ArgumentException("must be > 0", "timeoutMillis");
+
+            this.idleMillis = idleMillis;
+            this.timeoutMillis = timeoutMillis;
+        }
+
+        public virtual byte[] GeneratePayload()
+        {
+            lock (this)
+            {
+                // NOTE: The counter naturally wraps back to 0
+                return Pack.UInt32_To_BE(++counter);
+            }
+        }
+
+        public virtual int IdleMillis
+        {
+            get { return idleMillis; }
+        }
+
+        public virtual int TimeoutMillis
+        {
+            get { return timeoutMillis; }
+        }
+    }
+}
diff --git a/crypto/src/tls/DefaultTlsKeyExchangeFactory.cs b/crypto/src/tls/DefaultTlsKeyExchangeFactory.cs
new file mode 100644
index 000000000..c8d6ff130
--- /dev/null
+++ b/crypto/src/tls/DefaultTlsKeyExchangeFactory.cs
@@ -0,0 +1,89 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class DefaultTlsKeyExchangeFactory
+        : AbstractTlsKeyExchangeFactory
+    {
+        public override TlsKeyExchange CreateDHKeyExchange(int keyExchange)
+        {
+            return new TlsDHKeyExchange(keyExchange);
+        }
+
+        public override TlsKeyExchange CreateDHanonKeyExchangeClient(int keyExchange,
+            TlsDHGroupVerifier dhGroupVerifier)
+        {
+            return new TlsDHanonKeyExchange(keyExchange, dhGroupVerifier);
+        }
+
+        public override TlsKeyExchange CreateDHanonKeyExchangeServer(int keyExchange, TlsDHConfig dhConfig)
+        {
+            return new TlsDHanonKeyExchange(keyExchange, dhConfig);
+        }
+
+        public override TlsKeyExchange CreateDheKeyExchangeClient(int keyExchange, TlsDHGroupVerifier dhGroupVerifier)
+        {
+            return new TlsDheKeyExchange(keyExchange, dhGroupVerifier);
+        }
+
+        public override TlsKeyExchange CreateDheKeyExchangeServer(int keyExchange, TlsDHConfig dhConfig)
+        {
+            return new TlsDheKeyExchange(keyExchange, dhConfig);
+        }
+
+        public override TlsKeyExchange CreateECDHKeyExchange(int keyExchange)
+        {
+            return new TlsECDHKeyExchange(keyExchange);
+        }
+
+        public override TlsKeyExchange CreateECDHanonKeyExchangeClient(int keyExchange)
+        {
+            return new TlsECDHanonKeyExchange(keyExchange);
+        }
+
+        public override TlsKeyExchange CreateECDHanonKeyExchangeServer(int keyExchange, TlsECConfig ecConfig)
+        {
+            return new TlsECDHanonKeyExchange(keyExchange, ecConfig);
+        }
+
+        public override TlsKeyExchange CreateECDheKeyExchangeClient(int keyExchange)
+        {
+            return new TlsECDheKeyExchange(keyExchange);
+        }
+
+        public override TlsKeyExchange CreateECDheKeyExchangeServer(int keyExchange, TlsECConfig ecConfig)
+        {
+            return new TlsECDheKeyExchange(keyExchange, ecConfig);
+        }
+
+        public override TlsKeyExchange CreatePskKeyExchangeClient(int keyExchange, TlsPskIdentity pskIdentity,
+            TlsDHGroupVerifier dhGroupVerifier)
+        {
+            return new TlsPskKeyExchange(keyExchange, pskIdentity, dhGroupVerifier);
+        }
+
+        public override TlsKeyExchange CreatePskKeyExchangeServer(int keyExchange,
+            TlsPskIdentityManager pskIdentityManager, TlsDHConfig dhConfig, TlsECConfig ecConfig)
+        {
+            return new TlsPskKeyExchange(keyExchange, pskIdentityManager, dhConfig, ecConfig);
+        }
+
+        public override TlsKeyExchange CreateRsaKeyExchange(int keyExchange)
+        {
+            return new TlsRsaKeyExchange(keyExchange);
+        }
+
+        public override TlsKeyExchange CreateSrpKeyExchangeClient(int keyExchange, TlsSrpIdentity srpIdentity,
+            TlsSrpConfigVerifier srpConfigVerifier)
+        {
+            return new TlsSrpKeyExchange(keyExchange, srpIdentity, srpConfigVerifier);
+        }
+
+        public override TlsKeyExchange CreateSrpKeyExchangeServer(int keyExchange, TlsSrpLoginParameters loginParameters)
+        {
+            return new TlsSrpKeyExchange(keyExchange, loginParameters);
+        }
+    }
+}
diff --git a/crypto/src/tls/DefaultTlsServer.cs b/crypto/src/tls/DefaultTlsServer.cs
new file mode 100644
index 000000000..de8a3f4a0
--- /dev/null
+++ b/crypto/src/tls/DefaultTlsServer.cs
@@ -0,0 +1,108 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class DefaultTlsServer
+        : AbstractTlsServer
+    {
+        private static readonly int[] DefaultCipherSuites = new int[]
+        {
+            /*
+             * TODO[tls13] TLS 1.3
+             */
+            //CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
+            //CipherSuite.TLS_AES_256_GCM_SHA384,
+            //CipherSuite.TLS_AES_128_GCM_SHA256,
+
+            /*
+             * pre-TLS 1.3
+             */
+            CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
+            CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+        };
+
+        public DefaultTlsServer(TlsCrypto crypto)
+            : base(crypto)
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual TlsCredentialedSigner GetDsaSignerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual TlsCredentialedSigner GetECDsaSignerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, DefaultCipherSuites);
+        }
+
+        public override TlsCredentials GetCredentials()
+        {
+            int keyExchangeAlgorithm = m_context.SecurityParameters.KeyExchangeAlgorithm;
+
+            switch (keyExchangeAlgorithm)
+            {
+            case KeyExchangeAlgorithm.DHE_DSS:
+                return GetDsaSignerCredentials();
+
+            case KeyExchangeAlgorithm.DH_anon:
+            case KeyExchangeAlgorithm.ECDH_anon:
+                return null;
+
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+                return GetECDsaSignerCredentials();
+
+            case KeyExchangeAlgorithm.DHE_RSA:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+                return GetRsaSignerCredentials();
+
+            case KeyExchangeAlgorithm.RSA:
+                return GetRsaEncryptionCredentials();
+
+            default:
+                // Note: internal error here; selected a key exchange we don't implement!
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/DefaultTlsSrpConfigVerifier.cs b/crypto/src/tls/DefaultTlsSrpConfigVerifier.cs
new file mode 100644
index 000000000..781249829
--- /dev/null
+++ b/crypto/src/tls/DefaultTlsSrpConfigVerifier.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class DefaultTlsSrpConfigVerifier
+        : TlsSrpConfigVerifier
+    {
+        private static readonly IList DefaultGroups = Platform.CreateArrayList();
+
+        static DefaultTlsSrpConfigVerifier()
+        {
+            DefaultGroups.Add(Srp6StandardGroups.rfc5054_1024);
+            DefaultGroups.Add(Srp6StandardGroups.rfc5054_1536);
+            DefaultGroups.Add(Srp6StandardGroups.rfc5054_2048);
+            DefaultGroups.Add(Srp6StandardGroups.rfc5054_3072);
+            DefaultGroups.Add(Srp6StandardGroups.rfc5054_4096);
+            DefaultGroups.Add(Srp6StandardGroups.rfc5054_6144);
+            DefaultGroups.Add(Srp6StandardGroups.rfc5054_8192);
+        }
+
+        // IList is (SRP6Group)
+        protected readonly IList m_groups;
+
+        /// <summary>Accept only the group parameters specified in RFC 5054 Appendix A.</summary>
+        public DefaultTlsSrpConfigVerifier()
+            : this(DefaultGroups)
+        {
+        }
+
+        /// <summary>Specify a custom set of acceptable group parameters.</summary>
+        /// <param name="groups">an <see cref="IList"/> of acceptable <see cref="Srp6Group"/>.</param>
+        public DefaultTlsSrpConfigVerifier(IList groups)
+        {
+            this.m_groups = Platform.CreateArrayList(groups);
+        }
+
+        public virtual bool Accept(TlsSrpConfig srpConfig)
+        {
+            foreach (Srp6Group group in m_groups)
+            {
+                if (AreGroupsEqual(srpConfig, group))
+                    return true;
+            }
+            return false;
+        }
+
+        protected virtual bool AreGroupsEqual(TlsSrpConfig a, Srp6Group b)
+        {
+            BigInteger[] ng = a.GetExplicitNG();
+            return AreParametersEqual(ng[0], b.N) && AreParametersEqual(ng[1], b.G);
+        }
+
+        protected virtual bool AreParametersEqual(BigInteger a, BigInteger b)
+        {
+            return a == b || a.Equals(b);
+        }
+    }
+}
diff --git a/crypto/src/tls/DeferredHash.cs b/crypto/src/tls/DeferredHash.cs
new file mode 100644
index 000000000..43d60d07c
--- /dev/null
+++ b/crypto/src/tls/DeferredHash.cs
@@ -0,0 +1,249 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Buffers input until the hash algorithm is determined.</summary>
+    internal sealed class DeferredHash
+        : TlsHandshakeHash
+    {
+        private const int BufferingHashLimit = 4;
+
+        private readonly TlsContext m_context;
+
+        private DigestInputBuffer m_buf;
+        private readonly IDictionary m_hashes;
+        private bool m_forceBuffering;
+        private bool m_sealed;
+
+        internal DeferredHash(TlsContext context)
+        {
+            this.m_context = context;
+            this.m_buf = new DigestInputBuffer();
+            this.m_hashes = Platform.CreateHashtable();
+            this.m_forceBuffering = false;
+            this.m_sealed = false;
+        }
+
+        private DeferredHash(TlsContext context, IDictionary hashes)
+        {
+            this.m_context = context;
+            this.m_buf = null;
+            this.m_hashes = hashes;
+            this.m_forceBuffering = false;
+            this.m_sealed = true;
+        }
+
+        /// <exception cref="IOException"/>
+        public void CopyBufferTo(Stream output)
+        {
+            if (m_buf == null)
+            {
+                // If you see this, you need to call forceBuffering() before SealHashAlgorithms()
+                throw new InvalidOperationException("Not buffering");
+            }
+
+            m_buf.CopyTo(output);
+        }
+
+        public void ForceBuffering()
+        {
+            if (m_sealed)
+                throw new InvalidOperationException("Too late to force buffering");
+
+            this.m_forceBuffering = true;
+        }
+
+        public void NotifyPrfDetermined()
+        {
+            SecurityParameters securityParameters = m_context.SecurityParameters;
+
+            switch (securityParameters.PrfAlgorithm)
+            {
+            case PrfAlgorithm.ssl_prf_legacy:
+            case PrfAlgorithm.tls_prf_legacy:
+            {
+                CheckTrackingHash(CryptoHashAlgorithm.md5);
+                CheckTrackingHash(CryptoHashAlgorithm.sha1);
+                break;
+            }
+            default:
+            {
+                CheckTrackingHash(securityParameters.PrfHashAlgorithm);
+                if (TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
+                {
+                    SealHashAlgorithms();
+                }
+                break;
+            }
+            }
+        }
+
+        public void TrackHashAlgorithm(int cryptoHashAlgorithm)
+        {
+            if (m_sealed)
+                throw new InvalidOperationException("Too late to track more hash algorithms");
+
+            CheckTrackingHash(cryptoHashAlgorithm);
+        }
+
+        public void SealHashAlgorithms()
+        {
+            if (m_sealed)
+                throw new InvalidOperationException("Already sealed");
+
+            this.m_sealed = true;
+            CheckStopBuffering();
+        }
+
+        public TlsHandshakeHash StopTracking()
+        {
+            SecurityParameters securityParameters = m_context.SecurityParameters;
+
+            IDictionary newHashes = Platform.CreateHashtable();
+            switch (securityParameters.PrfAlgorithm)
+            {
+            case PrfAlgorithm.ssl_prf_legacy:
+            case PrfAlgorithm.tls_prf_legacy:
+            {
+                CloneHash(newHashes, HashAlgorithm.md5);
+                CloneHash(newHashes, HashAlgorithm.sha1);
+                break;
+            }
+            default:
+            {
+                CloneHash(newHashes, securityParameters.PrfHashAlgorithm);
+                break;
+            }
+            }
+            return new DeferredHash(m_context, newHashes);
+        }
+
+        public TlsHash ForkPrfHash()
+        {
+            CheckStopBuffering();
+
+            SecurityParameters securityParameters = m_context.SecurityParameters;
+
+            TlsHash prfHash;
+            switch (securityParameters.PrfAlgorithm)
+            {
+            case PrfAlgorithm.ssl_prf_legacy:
+            case PrfAlgorithm.tls_prf_legacy:
+            {
+                prfHash = new CombinedHash(m_context, CloneHash(HashAlgorithm.md5), CloneHash(HashAlgorithm.sha1));
+                break;
+            }
+            default:
+            {
+                prfHash = CloneHash(securityParameters.PrfHashAlgorithm);
+                break;
+            }
+            }
+
+            if (m_buf != null)
+            {
+                m_buf.UpdateDigest(prfHash);
+            }
+
+            return prfHash;
+        }
+
+        public byte[] GetFinalHash(int cryptoHashAlgorithm)
+        {
+            TlsHash d = (TlsHash)m_hashes[cryptoHashAlgorithm];
+            if (d == null)
+                throw new InvalidOperationException("CryptoHashAlgorithm." + cryptoHashAlgorithm
+                    + " is not being tracked");
+
+            CheckStopBuffering();
+
+            d = d.CloneHash();
+            if (m_buf != null)
+            {
+                m_buf.UpdateDigest(d);
+            }
+
+            return d.CalculateHash();
+        }
+
+        public void Update(byte[] input, int inOff, int len)
+        {
+            if (m_buf != null)
+            {
+                m_buf.Write(input, inOff, len);
+                return;
+            }
+
+            foreach (TlsHash hash in m_hashes.Values)
+            {
+                hash.Update(input, inOff, len);
+            }
+        }
+
+        public byte[] CalculateHash()
+        {
+            throw new InvalidOperationException("Use 'ForkPrfHash' to get a definite hash");
+        }
+
+        public TlsHash CloneHash()
+        {
+            throw new InvalidOperationException("attempt to clone a DeferredHash");
+        }
+
+        public void Reset()
+        {
+            if (m_buf != null)
+            {
+                m_buf.SetLength(0);
+                return;
+            }
+
+            foreach (TlsHash hash in m_hashes.Values)
+            {
+                hash.Reset();
+            }
+        }
+
+        private void CheckStopBuffering()
+        {
+            if (!m_forceBuffering && m_sealed && m_buf != null && m_hashes.Count <= BufferingHashLimit)
+            {
+                foreach (TlsHash hash in m_hashes.Values)
+                {
+                    m_buf.UpdateDigest(hash);
+                }
+
+                this.m_buf = null;
+            }
+        }
+
+        private void CheckTrackingHash(int cryptoHashAlgorithm)
+        {
+            if (!m_hashes.Contains(cryptoHashAlgorithm))
+            {
+                TlsHash hash = m_context.Crypto.CreateHash(cryptoHashAlgorithm);
+                m_hashes[cryptoHashAlgorithm] = hash;
+            }
+        }
+
+        private TlsHash CloneHash(int cryptoHashAlgorithm)
+        {
+            return ((TlsHash)m_hashes[cryptoHashAlgorithm]).CloneHash();
+        }
+
+        private void CloneHash(IDictionary newHashes, int cryptoHashAlgorithm)
+        {
+            TlsHash hash = CloneHash(cryptoHashAlgorithm);
+            if (m_buf != null)
+            {
+                m_buf.UpdateDigest(hash);
+            }
+            newHashes[cryptoHashAlgorithm] = hash;
+        }
+    }
+}
diff --git a/crypto/src/tls/DigestInputBuffer.cs b/crypto/src/tls/DigestInputBuffer.cs
new file mode 100644
index 000000000..7dd525f88
--- /dev/null
+++ b/crypto/src/tls/DigestInputBuffer.cs
@@ -0,0 +1,26 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal class DigestInputBuffer
+        : MemoryStream
+    {
+        internal void UpdateDigest(TlsHash hash)
+        {
+            Streams.WriteBufTo(this, new TlsHashSink(hash));
+        }
+
+        /// <exception cref="IOException"/>
+        internal void CopyTo(Stream output)
+        {
+            // TODO[tls-port]
+            // NOTE: Copy data since the output here may be under control of external code.
+            //Streams.PipeAll(new MemoryStream(buf, 0, count), output);
+            Streams.WriteBufTo(this, output);
+        }
+    }
+}
diff --git a/crypto/src/tls/DigitallySigned.cs b/crypto/src/tls/DigitallySigned.cs
new file mode 100644
index 000000000..e977b350b
--- /dev/null
+++ b/crypto/src/tls/DigitallySigned.cs
@@ -0,0 +1,62 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class DigitallySigned
+    {
+        private readonly SignatureAndHashAlgorithm algorithm;
+        private readonly byte[] signature;
+
+        public DigitallySigned(SignatureAndHashAlgorithm algorithm, byte[] signature)
+        {
+            if (signature == null)
+                throw new ArgumentNullException("signature");
+
+            this.algorithm = algorithm;
+            this.signature = signature;
+        }
+
+        /// <returns>a <see cref="SignatureAndHashAlgorithm"/> (or null before TLS 1.2).</returns>
+        public SignatureAndHashAlgorithm Algorithm
+        {
+            get { return algorithm; }
+        }
+
+        public byte[] Signature
+        {
+            get { return signature; }
+        }
+
+        /// <summary>Encode this <see cref="DigitallySigned"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            if (algorithm != null)
+            {
+                algorithm.Encode(output);
+            }
+            TlsUtilities.WriteOpaque16(signature, output);
+        }
+
+        /// <summary>Parse a <see cref="DigitallySigned"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="DigitallySigned"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static DigitallySigned Parse(TlsContext context, Stream input)
+        {
+            SignatureAndHashAlgorithm algorithm = null;
+            if (TlsUtilities.IsTlsV12(context))
+            {
+                algorithm = SignatureAndHashAlgorithm.Parse(input);
+
+                if (SignatureAlgorithm.anonymous == algorithm.Signature)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+            byte[] signature = TlsUtilities.ReadOpaque16(input);
+            return new DigitallySigned(algorithm, signature);
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsClientProtocol.cs b/crypto/src/tls/DtlsClientProtocol.cs
new file mode 100644
index 000000000..dea35a28b
--- /dev/null
+++ b/crypto/src/tls/DtlsClientProtocol.cs
@@ -0,0 +1,976 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class DtlsClientProtocol
+        : DtlsProtocol
+    {
+        public DtlsClientProtocol()
+            : base()
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual DtlsTransport Connect(TlsClient client, DatagramTransport transport)
+        {
+            if (client == null)
+                throw new ArgumentNullException("client");
+            if (transport == null)
+                throw new ArgumentNullException("transport");
+
+            ClientHandshakeState state = new ClientHandshakeState();
+            state.client = client;
+            state.clientContext = new TlsClientContextImpl(client.Crypto);
+
+            client.Init(state.clientContext);
+            state.clientContext.HandshakeBeginning(client);
+
+            SecurityParameters securityParameters = state.clientContext.SecurityParameters;
+            securityParameters.m_extendedPadding = client.ShouldUseExtendedPadding();
+
+            TlsSession sessionToResume = state.client.GetSessionToResume();
+            if (sessionToResume != null && sessionToResume.IsResumable)
+            {
+                SessionParameters sessionParameters = sessionToResume.ExportSessionParameters();
+
+                /*
+                 * NOTE: If we ever enable session resumption without extended_master_secret, then
+                 * renegotiation MUST be disabled (see RFC 7627 5.4).
+                 */
+                if (sessionParameters != null
+                    && (sessionParameters.IsExtendedMasterSecret
+                        || (!state.client.RequiresExtendedMasterSecret() && state.client.AllowLegacyResumption())))
+                {
+                    TlsSecret masterSecret = sessionParameters.MasterSecret;
+                    lock (masterSecret)
+                    {
+                        if (masterSecret.IsAlive())
+                        {
+                            state.tlsSession = sessionToResume;
+                            state.sessionParameters = sessionParameters;
+                            state.sessionMasterSecret = state.clientContext.Crypto.AdoptSecret(masterSecret);
+                        }
+                    }
+                }
+            }
+
+            DtlsRecordLayer recordLayer = new DtlsRecordLayer(state.clientContext, state.client, transport);
+            client.NotifyCloseHandle(recordLayer);
+
+            try
+            {
+                return ClientHandshake(state, recordLayer);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                AbortClientHandshake(state, recordLayer, fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (IOException e)
+            {
+                AbortClientHandshake(state, recordLayer, AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                AbortClientHandshake(state, recordLayer, AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+            finally
+            {
+                securityParameters.Clear();
+            }
+        }
+
+        internal virtual void AbortClientHandshake(ClientHandshakeState state, DtlsRecordLayer recordLayer,
+            short alertDescription)
+        {
+            recordLayer.Fail(alertDescription);
+            InvalidateSession(state);
+        }
+
+        /// <exception cref="IOException"/>
+        internal virtual DtlsTransport ClientHandshake(ClientHandshakeState state, DtlsRecordLayer recordLayer)
+        {
+            SecurityParameters securityParameters = state.clientContext.SecurityParameters;
+
+            DtlsReliableHandshake handshake = new DtlsReliableHandshake(state.clientContext, recordLayer,
+                state.client.GetHandshakeTimeoutMillis(), null);
+
+            byte[] clientHelloBody = GenerateClientHello(state);
+
+            recordLayer.SetWriteVersion(ProtocolVersion.DTLSv10);
+
+            handshake.SendMessage(HandshakeType.client_hello, clientHelloBody);
+
+            DtlsReliableHandshake.Message serverMessage = handshake.ReceiveMessage();
+
+            // TODO Consider stricter HelloVerifyRequest protocol
+            //if (serverMessage.Type == HandshakeType.hello_verify_request)
+            while (serverMessage.Type == HandshakeType.hello_verify_request)
+            {
+                byte[] cookie = ProcessHelloVerifyRequest(state, serverMessage.Body);
+                byte[] patched = PatchClientHelloWithCookie(clientHelloBody, cookie);
+
+                handshake.ResetAfterHelloVerifyRequestClient();
+                handshake.SendMessage(HandshakeType.client_hello, patched);
+
+                serverMessage = handshake.ReceiveMessage();
+            }
+
+            if (serverMessage.Type == HandshakeType.server_hello)
+            {
+                ProtocolVersion recordLayerVersion = recordLayer.ReadVersion;
+                ReportServerVersion(state, recordLayerVersion);
+                recordLayer.SetWriteVersion(recordLayerVersion);
+
+                ProcessServerHello(state, serverMessage.Body);
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            handshake.HandshakeHash.NotifyPrfDetermined();
+
+            ApplyMaxFragmentLengthExtension(recordLayer, securityParameters.MaxFragmentLength);
+
+            if (state.resumedSession)
+            {
+                securityParameters.m_masterSecret = state.sessionMasterSecret;
+                recordLayer.InitPendingEpoch(TlsUtilities.InitCipher(state.clientContext));
+
+                // NOTE: Calculated exclusive of the actual Finished message from the server
+                securityParameters.m_peerVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext,
+                    handshake.HandshakeHash, true);
+                ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished),
+                    securityParameters.PeerVerifyData);
+
+                // NOTE: Calculated exclusive of the Finished message itself
+                securityParameters.m_localVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext,
+                    handshake.HandshakeHash, false);
+                handshake.SendMessage(HandshakeType.finished, securityParameters.LocalVerifyData);
+
+                handshake.Finish();
+
+                if (securityParameters.IsExtendedMasterSecret)
+                {
+                    securityParameters.m_tlsUnique = securityParameters.PeerVerifyData;
+                }
+
+                securityParameters.m_localCertificate = state.sessionParameters.LocalCertificate;
+                securityParameters.m_peerCertificate = state.sessionParameters.PeerCertificate;
+                securityParameters.m_pskIdentity = state.sessionParameters.PskIdentity;
+                securityParameters.m_srpIdentity = state.sessionParameters.SrpIdentity;
+
+                state.clientContext.HandshakeComplete(state.client, state.tlsSession);
+
+                recordLayer.InitHeartbeat(state.heartbeat,
+                    HeartbeatMode.peer_allowed_to_send == state.heartbeatPolicy);
+
+                return new DtlsTransport(recordLayer);
+            }
+
+            InvalidateSession(state);
+
+            state.tlsSession = TlsUtilities.ImportSession(securityParameters.SessionID, null);
+            state.sessionParameters = null;
+            state.sessionMasterSecret = null;
+
+            serverMessage = handshake.ReceiveMessage();
+
+            if (serverMessage.Type == HandshakeType.supplemental_data)
+            {
+                ProcessServerSupplementalData(state, serverMessage.Body);
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                state.client.ProcessServerSupplementalData(null);
+            }
+
+            state.keyExchange = TlsUtilities.InitKeyExchangeClient(state.clientContext, state.client);
+
+            if (serverMessage.Type == HandshakeType.certificate)
+            {
+                ProcessServerCertificate(state, serverMessage.Body);
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                // Okay, Certificate is optional
+                state.authentication = null;
+            }
+
+            if (serverMessage.Type == HandshakeType.certificate_status)
+            {
+                if (securityParameters.StatusRequestVersion < 1)
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                ProcessCertificateStatus(state, serverMessage.Body);
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                // Okay, CertificateStatus is optional
+            }
+
+            TlsUtilities.ProcessServerCertificate(state.clientContext, state.certificateStatus, state.keyExchange,
+                state.authentication, state.clientExtensions, state.serverExtensions);
+
+            if (serverMessage.Type == HandshakeType.server_key_exchange)
+            {
+                ProcessServerKeyExchange(state, serverMessage.Body);
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                // Okay, ServerKeyExchange is optional
+                state.keyExchange.SkipServerKeyExchange();
+            }
+
+            if (serverMessage.Type == HandshakeType.certificate_request)
+            {
+                ProcessCertificateRequest(state, serverMessage.Body);
+
+                TlsUtilities.EstablishServerSigAlgs(securityParameters, state.certificateRequest);
+
+                /*
+                 * TODO Give the client a chance to immediately select the CertificateVerify hash
+                 * algorithm here to avoid tracking the other hash algorithms unnecessarily?
+                 */
+                TlsUtilities.TrackHashAlgorithms(handshake.HandshakeHash, securityParameters.ServerSigAlgs);
+
+                serverMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                // Okay, CertificateRequest is optional
+            }
+
+            if (serverMessage.Type == HandshakeType.server_hello_done)
+            {
+                if (serverMessage.Body.Length != 0)
+                {
+                    throw new TlsFatalAlert(AlertDescription.decode_error);
+                }
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            IList clientSupplementalData = state.client.GetClientSupplementalData();
+            if (clientSupplementalData != null)
+            {
+                byte[] supplementalDataBody = GenerateSupplementalData(clientSupplementalData);
+                handshake.SendMessage(HandshakeType.supplemental_data, supplementalDataBody);
+            }
+
+            if (null != state.certificateRequest)
+            {
+                state.clientCredentials = TlsUtilities.EstablishClientCredentials(state.authentication,
+                    state.certificateRequest);
+
+                /*
+                 * RFC 5246 If no suitable certificate is available, the client MUST send a certificate
+                 * message containing no certificates.
+                 * 
+                 * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                 */
+
+                Certificate clientCertificate = null;
+                if (null != state.clientCredentials)
+                {
+                    clientCertificate = state.clientCredentials.Certificate;
+                }
+
+                SendCertificateMessage(state.clientContext, handshake, clientCertificate, null);
+            }
+
+            TlsCredentialedSigner credentialedSigner = null;
+            TlsStreamSigner streamSigner = null;
+
+            if (null != state.clientCredentials)
+            {
+                state.keyExchange.ProcessClientCredentials(state.clientCredentials);
+
+                if (state.clientCredentials is TlsCredentialedSigner)
+                {
+                    credentialedSigner = (TlsCredentialedSigner)state.clientCredentials;
+                    streamSigner = credentialedSigner.GetStreamSigner();
+                }
+            }
+            else
+            {
+                state.keyExchange.SkipClientCredentials();
+            }
+
+            bool forceBuffering = streamSigner != null;
+            TlsUtilities.SealHandshakeHash(state.clientContext, handshake.HandshakeHash, forceBuffering);
+
+            byte[] clientKeyExchangeBody = GenerateClientKeyExchange(state);
+            handshake.SendMessage(HandshakeType.client_key_exchange, clientKeyExchangeBody);
+
+            securityParameters.m_sessionHash = TlsUtilities.GetCurrentPrfHash(handshake.HandshakeHash);
+
+            TlsProtocol.EstablishMasterSecret(state.clientContext, state.keyExchange);
+            recordLayer.InitPendingEpoch(TlsUtilities.InitCipher(state.clientContext));
+
+            {
+                if (credentialedSigner != null)
+                {
+                    DigitallySigned certificateVerify = TlsUtilities.GenerateCertificateVerifyClient(
+                        state.clientContext, credentialedSigner, streamSigner, handshake.HandshakeHash);
+                    byte[] certificateVerifyBody = GenerateCertificateVerify(state, certificateVerify);
+                    handshake.SendMessage(HandshakeType.certificate_verify, certificateVerifyBody);
+                }
+
+                handshake.PrepareToFinish();
+            }
+
+            securityParameters.m_localVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext,
+                handshake.HandshakeHash, false);
+            handshake.SendMessage(HandshakeType.finished, securityParameters.LocalVerifyData);
+
+            if (state.expectSessionTicket)
+            {
+                serverMessage = handshake.ReceiveMessage();
+                if (serverMessage.Type == HandshakeType.new_session_ticket)
+                {
+                    ProcessNewSessionTicket(state, serverMessage.Body);
+                }
+                else
+                {
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+            }
+
+            // NOTE: Calculated exclusive of the actual Finished message from the server
+            securityParameters.m_peerVerifyData = TlsUtilities.CalculateVerifyData(state.clientContext,
+                handshake.HandshakeHash, true);
+            ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), securityParameters.PeerVerifyData);
+
+            handshake.Finish();
+
+            state.sessionMasterSecret = securityParameters.MasterSecret;
+
+            state.sessionParameters = new SessionParameters.Builder()
+                .SetCipherSuite(securityParameters.CipherSuite)
+                .SetExtendedMasterSecret(securityParameters.IsExtendedMasterSecret)
+                .SetLocalCertificate(securityParameters.LocalCertificate)
+                .SetMasterSecret(state.clientContext.Crypto.AdoptSecret(state.sessionMasterSecret))
+                .SetNegotiatedVersion(securityParameters.NegotiatedVersion)
+                .SetPeerCertificate(securityParameters.PeerCertificate)
+                .SetPskIdentity(securityParameters.PskIdentity)
+                .SetSrpIdentity(securityParameters.SrpIdentity)
+                // TODO Consider filtering extensions that aren't relevant to resumed sessions
+                .SetServerExtensions(state.serverExtensions)
+                .Build();
+
+            state.tlsSession = TlsUtilities.ImportSession(state.tlsSession.SessionID, state.sessionParameters);
+
+            securityParameters.m_tlsUnique = securityParameters.LocalVerifyData;
+
+            state.clientContext.HandshakeComplete(state.client, state.tlsSession);
+
+            recordLayer.InitHeartbeat(state.heartbeat, HeartbeatMode.peer_allowed_to_send == state.heartbeatPolicy);
+
+            return new DtlsTransport(recordLayer);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual byte[] GenerateCertificateVerify(ClientHandshakeState state,
+            DigitallySigned certificateVerify)
+        {
+            MemoryStream buf = new MemoryStream();
+            certificateVerify.Encode(buf);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual byte[] GenerateClientHello(ClientHandshakeState state)
+        {
+            TlsClientContextImpl context = state.clientContext;
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            context.SetClientSupportedVersions(state.client.GetProtocolVersions());
+
+            ProtocolVersion client_version = ProtocolVersion.GetLatestDtls(context.ClientSupportedVersions);
+            if (!ProtocolVersion.IsSupportedDtlsVersionClient(client_version))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            context.SetClientVersion(client_version);
+
+            byte[] session_id = TlsUtilities.GetSessionID(state.tlsSession);
+
+            bool fallback = state.client.IsFallback();
+
+            state.offeredCipherSuites = state.client.GetCipherSuites();
+
+            if (session_id.Length > 0 && state.sessionParameters != null)
+            {
+                if (!Arrays.Contains(state.offeredCipherSuites, state.sessionParameters.CipherSuite))
+                {
+                    session_id = TlsUtilities.EmptyBytes;
+                }
+            }
+
+            state.clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                state.client.GetClientExtensions());
+
+            ProtocolVersion legacy_version = client_version;
+            if (client_version.IsLaterVersionOf(ProtocolVersion.DTLSv12))
+            {
+                legacy_version = ProtocolVersion.DTLSv12;
+
+                TlsExtensionsUtilities.AddSupportedVersionsExtensionClient(state.clientExtensions,
+                    context.ClientSupportedVersions);
+            }
+
+            context.SetRsaPreMasterSecretVersion(legacy_version);
+
+            securityParameters.m_clientServerNames = TlsExtensionsUtilities.GetServerNameExtensionClient(
+                state.clientExtensions);
+
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(client_version))
+            {
+                TlsUtilities.EstablishClientSigAlgs(securityParameters, state.clientExtensions);
+            }
+
+            securityParameters.m_clientSupportedGroups = TlsExtensionsUtilities.GetSupportedGroupsExtension(
+                state.clientExtensions);
+
+            state.clientAgreements = TlsUtilities.AddEarlyKeySharesToClientHello(state.clientContext, state.client,
+                state.clientExtensions);
+
+            if (TlsUtilities.IsExtendedMasterSecretOptionalDtls(context.ClientSupportedVersions)
+                && state.client.ShouldUseExtendedMasterSecret())
+            {
+                TlsExtensionsUtilities.AddExtendedMasterSecretExtension(state.clientExtensions);
+            }
+            else if (!TlsUtilities.IsTlsV13(client_version)
+                && state.client.RequiresExtendedMasterSecret())
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            {
+                bool useGmtUnixTime = ProtocolVersion.DTLSv12.IsEqualOrLaterVersionOf(client_version)
+                    && state.client.ShouldUseGmtUnixTime();
+
+                securityParameters.m_clientRandom = TlsProtocol.CreateRandomBlock(useGmtUnixTime, state.clientContext);
+            }
+
+            // 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 = (null == TlsUtilities.GetExtensionData(state.clientExtensions,
+                    ExtensionType.renegotiation_info));
+                bool noRenegScsv = !Arrays.Contains(state.offeredCipherSuites,
+                    CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+
+                if (noRenegExt && noRenegScsv)
+                {
+                    state.offeredCipherSuites = Arrays.Append(state.offeredCipherSuites,
+                        CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+                }
+            }
+
+            /* (Fallback SCSV)
+             * RFC 7507 4. If a client sends a ClientHello.client_version containing a lower value
+             * than the latest (highest-valued) version supported by the client, it SHOULD include
+             * the TLS_FALLBACK_SCSV cipher suite value in ClientHello.cipher_suites [..]. (The
+             * client SHOULD put TLS_FALLBACK_SCSV after all cipher suites that it actually intends
+             * to negotiate.)
+             */
+            if (fallback && !Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV))
+            {
+                state.offeredCipherSuites = Arrays.Append(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV);
+            }
+
+            // Heartbeats
+            {
+                state.heartbeat = state.client.GetHeartbeat();
+                state.heartbeatPolicy = state.client.GetHeartbeatPolicy();
+
+                if (null != state.heartbeat || HeartbeatMode.peer_allowed_to_send == state.heartbeatPolicy)
+                {
+                    TlsExtensionsUtilities.AddHeartbeatExtension(state.clientExtensions,
+                        new HeartbeatExtension(state.heartbeatPolicy));
+                }
+            }
+
+
+
+            ClientHello clientHello = new ClientHello(legacy_version, securityParameters.ClientRandom, session_id,
+                TlsUtilities.EmptyBytes, state.offeredCipherSuites, state.clientExtensions);
+
+            MemoryStream buf = new MemoryStream();
+            clientHello.Encode(state.clientContext, buf);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual byte[] GenerateClientKeyExchange(ClientHandshakeState state)
+        {
+            MemoryStream buf = new MemoryStream();
+            state.keyExchange.GenerateClientKeyExchange(buf);
+            return buf.ToArray();
+        }
+
+        protected virtual void InvalidateSession(ClientHandshakeState state)
+        {
+            if (state.sessionMasterSecret != null)
+            {
+                state.sessionMasterSecret.Destroy();
+                state.sessionMasterSecret = null;
+            }
+
+            if (state.sessionParameters != null)
+            {
+                state.sessionParameters.Clear();
+                state.sessionParameters = null;
+            }
+
+            if (state.tlsSession != null)
+            {
+                state.tlsSession.Invalidate();
+                state.tlsSession = null;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessCertificateRequest(ClientHandshakeState state, byte[] body)
+        {
+            if (null == state.authentication)
+            {
+                /*
+                 * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server to
+                 * request client identification.
+                 */
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+            }
+
+            MemoryStream buf = new MemoryStream(body, false);
+
+            CertificateRequest certificateRequest = CertificateRequest.Parse(state.clientContext, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            state.certificateRequest = TlsUtilities.ValidateCertificateRequest(certificateRequest, state.keyExchange);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessCertificateStatus(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            // TODO[tls13] Ensure this cannot happen for (D)TLS1.3+
+            state.certificateStatus = CertificateStatus.Parse(state.clientContext, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual byte[] ProcessHelloVerifyRequest(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            ProtocolVersion server_version = TlsUtilities.ReadVersion(buf);
+
+            /*
+             * RFC 6347 This specification increases the cookie size limit to 255 bytes for greater
+             * future flexibility. The limit remains 32 for previous versions of DTLS.
+             */
+            int maxCookieLength = ProtocolVersion.DTLSv12.IsEqualOrEarlierVersionOf(server_version) ? 255 : 32;
+
+            byte[] cookie = TlsUtilities.ReadOpaque8(buf, 0, maxCookieLength);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            // TODO Seems this behaviour is not yet in line with OpenSSL for DTLS 1.2
+            //ReportServerVersion(state, server_version);
+            if (!server_version.IsEqualOrEarlierVersionOf(state.clientContext.ClientVersion))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return cookie;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessNewSessionTicket(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            state.client.NotifyNewSessionTicket(newSessionTicket);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessServerCertificate(ClientHandshakeState state, byte[] body)
+        {
+            state.authentication = TlsUtilities.ReceiveServerCertificate(state.clientContext, state.client,
+                new MemoryStream(body, false));
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessServerHello(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            ServerHello serverHello = ServerHello.Parse(buf);
+            ProtocolVersion server_version = serverHello.Version;
+
+            state.serverExtensions = serverHello.Extensions;
+
+
+
+            SecurityParameters securityParameters = state.clientContext.SecurityParameters;
+
+            // TODO[dtls13] Check supported_version extension for negotiated version
+
+            ReportServerVersion(state, server_version);
+
+            securityParameters.m_serverRandom = serverHello.Random;
+
+            if (!state.clientContext.ClientVersion.Equals(server_version))
+            {
+                TlsUtilities.CheckDowngradeMarker(server_version, securityParameters.ServerRandom);
+            }
+
+            {
+                byte[] selectedSessionID = serverHello.SessionID;
+                securityParameters.m_sessionID = selectedSessionID;
+                state.client.NotifySessionID(selectedSessionID);
+                state.resumedSession = selectedSessionID.Length > 0 && state.tlsSession != null
+                    && Arrays.AreEqual(selectedSessionID, state.tlsSession.SessionID);
+            }
+
+            /*
+             * Find out which CipherSuite the server has chosen and check that it was one of the offered
+             * ones, and is a valid selection for the negotiated version.
+             */
+            {
+                int cipherSuite = ValidateSelectedCipherSuite(serverHello.CipherSuite,
+                    AlertDescription.illegal_parameter);
+
+                if (!TlsUtilities.IsValidCipherSuiteSelection(state.offeredCipherSuites, cipherSuite) ||
+                    !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, securityParameters.NegotiatedVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+                state.client.NotifySelectedCipherSuite(cipherSuite);
+            }
+
+            /*
+             * 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.
+             */
+
+            /*
+             * RFC 7627 4. Clients and servers SHOULD NOT accept handshakes that do not use the extended
+             * master secret [..]. (and see 5.2, 5.3)
+             * 
+             * RFC 8446 Appendix D. Because TLS 1.3 always hashes in the transcript up to the server
+             * Finished, implementations which support both TLS 1.3 and earlier versions SHOULD indicate
+             * the use of the Extended Master Secret extension in their APIs whenever TLS 1.3 is used.
+             */
+            if (TlsUtilities.IsTlsV13(server_version))
+            {
+                securityParameters.m_extendedMasterSecret = true;
+            }
+            else
+            {
+                bool acceptedExtendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(
+                    state.serverExtensions);
+
+                if (acceptedExtendedMasterSecret)
+                {
+                    if (!state.resumedSession && !state.client.ShouldUseExtendedMasterSecret())
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+                else
+                {
+                    if (state.client.RequiresExtendedMasterSecret()
+                        || (state.resumedSession && !state.client.AllowLegacyResumption()))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+
+                securityParameters.m_extendedMasterSecret = acceptedExtendedMasterSecret;
+            }
+
+            /*
+             * 
+             * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an
+             * extended client hello message. However, see RFC 5746 exception below. We always include
+             * the SCSV, so an Extended Server Hello is always allowed.
+             */
+            if (state.serverExtensions != null)
+            {
+                foreach (int extType in state.serverExtensions.Keys)
+                {
+                    /*
+                     * RFC 5746 3.6. Note that sending a "renegotiation_info" extension in response to a
+                     * ClientHello containing only the SCSV is an explicit exception to the prohibition
+                     * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is
+                     * only allowed because the client is signaling its willingness to receive the
+                     * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                     */
+                    if (extType == ExtensionType.renegotiation_info)
+                        continue;
+
+                    /*
+                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the
+                     * same extension type appeared in the corresponding ClientHello. If a client
+                     * receives an extension type in ServerHello that it did not request in the
+                     * associated ClientHello, it MUST abort the handshake with an unsupported_extension
+                     * fatal alert.
+                     */
+                    if (null == TlsUtilities.GetExtensionData(state.clientExtensions, extType))
+                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+
+                    /*
+                     * 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[.]
+                     */
+                    if (state.resumedSession)
+                    {
+                        // TODO[compat-gnutls] GnuTLS test server sends server extensions e.g. ec_point_formats
+                        // TODO[compat-openssl] OpenSSL test server sends server extensions e.g. ec_point_formats
+                        // TODO[compat-polarssl] PolarSSL test server sends server extensions e.g. ec_point_formats
+                        //throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                    }
+                }
+            }
+
+            /*
+             * RFC 5746 3.4. Client Behavior: Initial Handshake
+             */
+            {
+                /*
+                 * When a ServerHello is received, the client MUST check if it includes the
+                 * "renegotiation_info" extension:
+                 */
+                byte[] renegExtData = TlsUtilities.GetExtensionData(state.serverExtensions,
+                    ExtensionType.renegotiation_info);
+                if (renegExtData != null)
+                {
+                    /*
+                     * 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).
+                     */
+                    securityParameters.m_secureRenegotiation = true;
+
+                    if (!Arrays.ConstantTimeAreEqual(renegExtData,
+                        TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+            }
+
+            // TODO[compat-gnutls] GnuTLS test server fails to send renegotiation_info extension when resuming
+            state.client.NotifySecureRenegotiation(securityParameters.IsSecureRenegotiation);
+
+            /*
+             * RFC 7301 3.1. When session resumption or session tickets [...] are used, the previous
+             * contents of this extension are irrelevant, and only the values in the new handshake
+             * messages are considered.
+             */
+            securityParameters.m_applicationProtocol = TlsExtensionsUtilities.GetAlpnExtensionServer(
+                state.serverExtensions);
+            securityParameters.m_applicationProtocolSet = true;
+
+            // Heartbeats
+            {
+                HeartbeatExtension heartbeatExtension = TlsExtensionsUtilities.GetHeartbeatExtension(
+                    state.serverExtensions);
+                if (null == heartbeatExtension)
+                {
+                    state.heartbeat = null;
+                    state.heartbeatPolicy = HeartbeatMode.peer_not_allowed_to_send;
+                }
+                else if (HeartbeatMode.peer_allowed_to_send != heartbeatExtension.Mode)
+                {
+                    state.heartbeat = null;
+                }
+            }
+
+
+
+            IDictionary sessionClientExtensions = state.clientExtensions,
+                sessionServerExtensions = state.serverExtensions;
+
+            if (state.resumedSession)
+            {
+                if (securityParameters.CipherSuite != state.sessionParameters.CipherSuite
+                    || !server_version.Equals(state.sessionParameters.NegotiatedVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                sessionClientExtensions = null;
+                sessionServerExtensions = state.sessionParameters.ReadServerExtensions();
+            }
+
+            if (sessionServerExtensions != null && sessionServerExtensions.Count > 0)
+            {
+                {
+                    /*
+                     * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
+                     * and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
+                     * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
+                     * client.
+                     */
+                    bool serverSentEncryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(
+                        sessionServerExtensions);
+                    if (serverSentEncryptThenMac && !TlsUtilities.IsBlockCipherSuite(securityParameters.CipherSuite))
+                        throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                    securityParameters.m_encryptThenMac = serverSentEncryptThenMac;
+                }
+
+                securityParameters.m_maxFragmentLength = EvaluateMaxFragmentLengthExtension(state.resumedSession,
+                    sessionClientExtensions, sessionServerExtensions, AlertDescription.illegal_parameter);
+
+                securityParameters.m_truncatedHmac = TlsExtensionsUtilities.HasTruncatedHmacExtension(
+                    sessionServerExtensions);
+
+                if (!state.resumedSession)
+                {
+                    // TODO[tls13] See RFC 8446 4.4.2.1
+                    if (TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions,
+                        ExtensionType.status_request_v2, AlertDescription.illegal_parameter))
+                    {
+                        securityParameters.m_statusRequestVersion = 2;
+                    }
+                    else if (TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions,
+                        ExtensionType.status_request, AlertDescription.illegal_parameter))
+                    {
+                        securityParameters.m_statusRequestVersion = 1;
+                    }
+                }
+
+                state.expectSessionTicket = !state.resumedSession
+                    && TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions,
+                        ExtensionType.session_ticket, AlertDescription.illegal_parameter);
+            }
+
+            if (sessionClientExtensions != null)
+            {
+                state.client.ProcessServerExtensions(sessionServerExtensions);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessServerKeyExchange(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+            state.keyExchange.ProcessServerKeyExchange(buf);
+            TlsProtocol.AssertEmpty(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessServerSupplementalData(ClientHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+            IList serverSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf);
+            state.client.ProcessServerSupplementalData(serverSupplementalData);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReportServerVersion(ClientHandshakeState state, ProtocolVersion server_version)
+        {
+            TlsClientContextImpl context = state.clientContext;
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            ProtocolVersion currentServerVersion = securityParameters.NegotiatedVersion;
+            if (null != currentServerVersion)
+            {
+                if (!currentServerVersion.Equals(server_version))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                return;
+            }
+
+            if (!ProtocolVersion.Contains(context.ClientSupportedVersions, server_version))
+                throw new TlsFatalAlert(AlertDescription.protocol_version);
+
+            securityParameters.m_negotiatedVersion = server_version;
+
+            TlsUtilities.NegotiatedVersionDtlsClient(state.clientContext, state.client);
+        }
+
+        /// <exception cref="IOException"/>
+        protected static byte[] PatchClientHelloWithCookie(byte[] clientHelloBody, byte[] cookie)
+        {
+            int sessionIDPos = 34;
+            int sessionIDLength = TlsUtilities.ReadUint8(clientHelloBody, sessionIDPos);
+
+            int cookieLengthPos = sessionIDPos + 1 + sessionIDLength;
+            int cookiePos = cookieLengthPos + 1;
+
+            byte[] patched = new byte[clientHelloBody.Length + cookie.Length];
+            Array.Copy(clientHelloBody, 0, patched, 0, cookieLengthPos);
+            TlsUtilities.CheckUint8(cookie.Length);
+            TlsUtilities.WriteUint8(cookie.Length, patched, cookieLengthPos);
+            Array.Copy(cookie, 0, patched, cookiePos, cookie.Length);
+            Array.Copy(clientHelloBody, cookiePos, patched, cookiePos + cookie.Length,
+                clientHelloBody.Length - cookiePos);
+
+            return patched;
+        }
+
+        protected internal class ClientHandshakeState
+        {
+            internal TlsClient client = null;
+            internal TlsClientContextImpl clientContext = null;
+            internal TlsSession tlsSession = null;
+            internal SessionParameters sessionParameters = null;
+            internal TlsSecret sessionMasterSecret = null;
+            internal SessionParameters.Builder sessionParametersBuilder = null;
+            internal int[] offeredCipherSuites = null;
+            internal IDictionary clientExtensions = null;
+            internal IDictionary serverExtensions = null;
+            internal bool resumedSession = false;
+            internal bool expectSessionTicket = false;
+            internal IDictionary clientAgreements = null;
+            internal TlsKeyExchange keyExchange = null;
+            internal TlsAuthentication authentication = null;
+            internal CertificateStatus certificateStatus = null;
+            internal CertificateRequest certificateRequest = null;
+            internal TlsCredentials clientCredentials = null;
+            internal TlsHeartbeat heartbeat = null;
+            internal short heartbeatPolicy = HeartbeatMode.peer_not_allowed_to_send;
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsEpoch.cs b/crypto/src/tls/DtlsEpoch.cs
new file mode 100644
index 000000000..e4ce84948
--- /dev/null
+++ b/crypto/src/tls/DtlsEpoch.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal sealed class DtlsEpoch
+    {
+        private readonly DtlsReplayWindow m_replayWindow = new DtlsReplayWindow();
+
+        private readonly int m_epoch;
+        private readonly TlsCipher m_cipher;
+
+        private long m_sequenceNumber = 0;
+
+        internal DtlsEpoch(int epoch, TlsCipher cipher)
+        {
+            if (epoch < 0)
+                throw new ArgumentException("must be >= 0", "epoch");
+            if (cipher == null)
+                throw new ArgumentNullException("cipher");
+
+            this.m_epoch = epoch;
+            this.m_cipher = cipher;
+        }
+
+        /// <exception cref="IOException"/>
+        internal long AllocateSequenceNumber()
+        {
+            lock (this)
+            {
+                if (m_sequenceNumber >= (1L << 48))
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                return m_sequenceNumber++;
+            }
+        }
+
+        internal TlsCipher Cipher
+        {
+            get { return m_cipher; }
+        }
+
+        internal int Epoch
+        {
+            get { return m_epoch; }
+        }
+
+        internal DtlsReplayWindow ReplayWindow
+        {
+            get { return m_replayWindow; }
+        }
+
+        internal long SequenceNumber
+        {
+            get { lock (this) return m_sequenceNumber; }
+            set { lock (this) this.m_sequenceNumber = value; }
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsHandshakeRetransmit.cs b/crypto/src/tls/DtlsHandshakeRetransmit.cs
new file mode 100644
index 000000000..6e691b87c
--- /dev/null
+++ b/crypto/src/tls/DtlsHandshakeRetransmit.cs
@@ -0,0 +1,11 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal interface DtlsHandshakeRetransmit
+    {
+        /// <exception cref="IOException"/>
+        void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len);
+    }
+}
diff --git a/crypto/src/tls/DtlsProtocol.cs b/crypto/src/tls/DtlsProtocol.cs
new file mode 100644
index 000000000..f0f42f968
--- /dev/null
+++ b/crypto/src/tls/DtlsProtocol.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class DtlsProtocol
+    {
+        internal DtlsProtocol()
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        internal virtual void ProcessFinished(byte[] body, byte[] expected_verify_data)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            byte[] verify_data = TlsUtilities.ReadFully(expected_verify_data.Length, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            if (!Arrays.ConstantTimeAreEqual(expected_verify_data, verify_data))
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void ApplyMaxFragmentLengthExtension(DtlsRecordLayer recordLayer, short maxFragmentLength)
+        {
+            if (maxFragmentLength >= 0)
+            {
+                if (!MaxFragmentLength.IsValid(maxFragmentLength))
+                    throw new TlsFatalAlert(AlertDescription.internal_error); 
+
+                int plainTextLimit = 1 << (8 + maxFragmentLength);
+                recordLayer.SetPlaintextLimit(plainTextLimit);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        internal static short EvaluateMaxFragmentLengthExtension(bool resumedSession, IDictionary clientExtensions,
+            IDictionary serverExtensions, short alertDescription)
+        {
+            short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions);
+            if (maxFragmentLength >= 0)
+            {
+                if (!MaxFragmentLength.IsValid(maxFragmentLength)
+                    || (!resumedSession && maxFragmentLength != TlsExtensionsUtilities
+                        .GetMaxFragmentLengthExtension(clientExtensions)))
+                {
+                    throw new TlsFatalAlert(alertDescription);
+                }
+            }
+            return maxFragmentLength;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static byte[] GenerateCertificate(TlsContext context, Certificate certificate, Stream endPointHash)
+        {
+            MemoryStream buf = new MemoryStream();
+            certificate.Encode(context, buf, endPointHash);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        internal static byte[] GenerateSupplementalData(IList supplementalData)
+        {
+            MemoryStream buf = new MemoryStream();
+            TlsProtocol.WriteSupplementalData(buf, supplementalData);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void SendCertificateMessage(TlsContext context, DtlsReliableHandshake handshake,
+            Certificate certificate, Stream endPointHash)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            if (null != securityParameters.LocalCertificate)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (null == certificate)
+            {
+                certificate = Certificate.EmptyChain;
+            }
+
+            byte[] certificateBody = GenerateCertificate(context, certificate, endPointHash);
+            handshake.SendMessage(HandshakeType.certificate, certificateBody);
+
+            securityParameters.m_localCertificate = certificate;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static int ValidateSelectedCipherSuite(int selectedCipherSuite, short alertDescription)
+        {
+            switch (TlsUtilities.GetEncryptionAlgorithm(selectedCipherSuite))
+            {
+            case EncryptionAlgorithm.RC4_40:
+            case EncryptionAlgorithm.RC4_128:
+            case -1:
+                throw new TlsFatalAlert(alertDescription);
+            default:
+                return selectedCipherSuite;
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsReassembler.cs b/crypto/src/tls/DtlsReassembler.cs
new file mode 100644
index 000000000..964c8eb84
--- /dev/null
+++ b/crypto/src/tls/DtlsReassembler.cs
@@ -0,0 +1,120 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal sealed class DtlsReassembler
+    {
+        private readonly short m_msg_type;
+        private readonly byte[] m_body;
+
+        private readonly IList m_missing = Platform.CreateArrayList();
+
+        internal DtlsReassembler(short msg_type, int length)
+        {
+            this.m_msg_type = msg_type;
+            this.m_body = new byte[length];
+            this.m_missing.Add(new Range(0, length));
+        }
+
+        internal short MsgType
+        {
+            get { return m_msg_type; }
+        }
+
+        internal byte[] GetBodyIfComplete()
+        {
+            return m_missing.Count > 0 ? null : m_body;
+        }
+
+        internal void ContributeFragment(short msg_type, int length, byte[] buf, int off, int fragment_offset,
+            int fragment_length)
+        {
+            int fragment_end = fragment_offset + fragment_length;
+
+            if (m_msg_type != msg_type || m_body.Length != length || fragment_end > length)
+                return;
+
+            if (fragment_length == 0)
+            {
+                // NOTE: Empty messages still require an empty fragment to complete it
+                if (fragment_offset == 0 && m_missing.Count > 0)
+                {
+                    Range firstRange = (Range)m_missing[0];
+                    if (firstRange.End == 0)
+                    {
+                        m_missing.RemoveAt(0);
+                    }
+                }
+                return;
+            }
+
+            for (int i = 0; i < m_missing.Count; ++i)
+            {
+                Range range = (Range)m_missing[i];
+                if (range.Start >= fragment_end)
+                    break;
+
+                if (range.End > fragment_offset)
+                {
+                    int copyStart = System.Math.Max(range.Start, fragment_offset);
+                    int copyEnd = System.Math.Min(range.End, fragment_end);
+                    int copyLength = copyEnd - copyStart;
+
+                    Array.Copy(buf, off + copyStart - fragment_offset, m_body, copyStart, copyLength);
+
+                    if (copyStart == range.Start)
+                    {
+                        if (copyEnd == range.End)
+                        {
+                            m_missing.RemoveAt(i--);
+                        }
+                        else
+                        {
+                            range.Start = copyEnd;
+                        }
+                    }
+                    else
+                    {
+                        if (copyEnd != range.End)
+                        {
+                            m_missing.Insert(++i, new Range(copyEnd, range.End));
+                        }
+                        range.End = copyStart;
+                    }
+                }
+            }
+        }
+
+        internal void Reset()
+        {
+            m_missing.Clear();
+            m_missing.Add(new Range(0, m_body.Length));
+        }
+
+        private sealed class Range
+        {
+            private int m_start, m_end;
+
+            internal Range(int start, int end)
+            {
+                this.m_start = start;
+                this.m_end = end;
+            }
+
+            public int Start
+            {
+                get { return m_start; }
+                set { this.m_start = value; }
+            }
+
+            public int End
+            {
+                get { return m_end; }
+                set { this.m_end = value; }
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsRecordLayer.cs b/crypto/src/tls/DtlsRecordLayer.cs
new file mode 100644
index 000000000..ffc071967
--- /dev/null
+++ b/crypto/src/tls/DtlsRecordLayer.cs
@@ -0,0 +1,817 @@
+using System;
+using System.IO;
+#if !PORTABLE || DOTNET
+using System.Net.Sockets;
+#endif
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal class DtlsRecordLayer
+        : DatagramTransport
+    {
+        private const int RECORD_HEADER_LENGTH = 13;
+        private const int MAX_FRAGMENT_LENGTH = 1 << 14;
+        private const long TCP_MSL = 1000L * 60 * 2;
+        private const long RETRANSMIT_TIMEOUT = TCP_MSL * 2;
+
+        /// <exception cref="IOException"/>
+        internal static byte[] ReceiveClientHelloRecord(byte[] data, int dataOff, int dataLen)
+        {
+            if (dataLen < RECORD_HEADER_LENGTH)
+            {
+                return null;
+            }
+
+            short contentType = TlsUtilities.ReadUint8(data, dataOff + 0);
+            if (ContentType.handshake != contentType)
+                return null;
+
+            ProtocolVersion version = TlsUtilities.ReadVersion(data, dataOff + 1);
+            if (!ProtocolVersion.DTLSv10.IsEqualOrEarlierVersionOf(version))
+                return null;
+
+            int epoch = TlsUtilities.ReadUint16(data, dataOff + 3);
+            if (0 != epoch)
+                return null;
+
+            //long sequenceNumber = TlsUtilities.ReadUint48(data, dataOff + 5);
+
+            int length = TlsUtilities.ReadUint16(data, dataOff + 11);
+            if (dataLen < RECORD_HEADER_LENGTH + length)
+                return null;
+
+            if (length > MAX_FRAGMENT_LENGTH)
+                return null;
+
+            // NOTE: We ignore/drop any data after the first record 
+            return TlsUtilities.CopyOfRangeExact(data, dataOff + RECORD_HEADER_LENGTH,
+                dataOff + RECORD_HEADER_LENGTH + length);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void SendHelloVerifyRequestRecord(DatagramSender sender, long recordSeq, byte[] message)
+        {
+            TlsUtilities.CheckUint16(message.Length);
+
+            byte[] record = new byte[RECORD_HEADER_LENGTH + message.Length];
+            TlsUtilities.WriteUint8(ContentType.handshake, record, 0);
+            TlsUtilities.WriteVersion(ProtocolVersion.DTLSv10, record, 1);
+            TlsUtilities.WriteUint16(0, record, 3);
+            TlsUtilities.WriteUint48(recordSeq, record, 5);
+            TlsUtilities.WriteUint16(message.Length, record, 11);
+
+            Array.Copy(message, 0, record, RECORD_HEADER_LENGTH, message.Length);
+
+            SendDatagram(sender, record, 0, record.Length);
+        }
+
+        /// <exception cref="IOException"/>
+        private static void SendDatagram(DatagramSender sender, byte[] buf, int off, int len)
+        {
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //try
+            //{
+            //    sender.Send(buf, off, len);
+            //}
+            //catch (InterruptedIOException e)
+            //{
+            //    e.bytesTransferred = 0;
+            //    throw e;
+            //}
+
+            sender.Send(buf, off, len);
+        }
+
+        private readonly TlsContext m_context;
+        private readonly TlsPeer m_peer;
+        private readonly DatagramTransport m_transport;
+
+        private readonly ByteQueue m_recordQueue = new ByteQueue();
+        private readonly object m_writeLock = new object();
+
+        private volatile bool m_closed = false;
+        private volatile bool m_failed = false;
+        // TODO[dtls13] Review the draft/RFC (legacy_record_version) to see if readVersion can be removed
+        private volatile ProtocolVersion m_readVersion = null, m_writeVersion = null;
+        private volatile bool m_inConnection;
+        private volatile bool m_inHandshake;
+        private volatile int m_plaintextLimit;
+        private DtlsEpoch m_currentEpoch, m_pendingEpoch;
+        private DtlsEpoch m_readEpoch, m_writeEpoch;
+
+        private DtlsHandshakeRetransmit m_retransmit = null;
+        private DtlsEpoch m_retransmitEpoch = null;
+        private Timeout m_retransmitTimeout = null;
+
+        private TlsHeartbeat m_heartbeat = null;                // If non-null, controls the sending of heartbeat requests
+        private bool m_heartBeatResponder = false;              // Whether we should send heartbeat responses
+
+        private HeartbeatMessage m_heartbeatInFlight = null;    // The current in-flight heartbeat request, if any
+        private Timeout m_heartbeatTimeout = null;              // Idle timeout (if none in-flight), else expiry timeout for response
+
+        private int m_heartbeatResendMillis = -1;               // Delay before retransmit of current in-flight heartbeat request
+        private Timeout m_heartbeatResendTimeout = null;        // Timeout for next retransmit of the in-flight heartbeat request
+
+        internal DtlsRecordLayer(TlsContext context, TlsPeer peer, DatagramTransport transport)
+        {
+            this.m_context = context;
+            this.m_peer = peer;
+            this.m_transport = transport;
+
+            this.m_inHandshake = true;
+
+            this.m_currentEpoch = new DtlsEpoch(0, TlsNullNullCipher.Instance);
+            this.m_pendingEpoch = null;
+            this.m_readEpoch = m_currentEpoch;
+            this.m_writeEpoch = m_currentEpoch;
+
+            SetPlaintextLimit(MAX_FRAGMENT_LENGTH);
+        }
+
+        internal virtual bool IsClosed
+        {
+            get { return m_closed; }
+        }
+
+        internal virtual void ResetAfterHelloVerifyRequestServer(long recordSeq)
+        {
+            this.m_inConnection = true;
+
+            m_currentEpoch.SequenceNumber = recordSeq;
+            m_currentEpoch.ReplayWindow.Reset(recordSeq);
+        }
+
+        internal virtual void SetPlaintextLimit(int plaintextLimit)
+        {
+            this.m_plaintextLimit = plaintextLimit;
+        }
+
+        internal virtual int ReadEpoch
+        {
+            get { return m_readEpoch.Epoch; }
+        }
+
+        internal virtual ProtocolVersion ReadVersion
+        {
+            get { return m_readVersion; }
+            set { this.m_readVersion = value; }
+        }
+
+        internal virtual void SetWriteVersion(ProtocolVersion writeVersion)
+        {
+            this.m_writeVersion = writeVersion;
+        }
+
+        internal virtual void InitPendingEpoch(TlsCipher pendingCipher)
+        {
+            if (m_pendingEpoch != null)
+                throw new InvalidOperationException();
+
+            /*
+             * TODO "In order to ensure that any given sequence/epoch pair is unique, implementations
+             * MUST NOT allow the same epoch value to be reused within two times the TCP maximum segment
+             * lifetime."
+             */
+
+            // TODO Check for overflow
+            this.m_pendingEpoch = new DtlsEpoch(m_writeEpoch.Epoch + 1, pendingCipher);
+        }
+
+        internal virtual void HandshakeSuccessful(DtlsHandshakeRetransmit retransmit)
+        {
+            if (m_readEpoch == m_currentEpoch || m_writeEpoch == m_currentEpoch)
+            {
+                // TODO
+                throw new InvalidOperationException();
+            }
+
+            if (null != retransmit)
+            {
+                this.m_retransmit = retransmit;
+                this.m_retransmitEpoch = m_currentEpoch;
+                this.m_retransmitTimeout = new Timeout(RETRANSMIT_TIMEOUT);
+            }
+
+            this.m_inHandshake = false;
+            this.m_currentEpoch = m_pendingEpoch;
+            this.m_pendingEpoch = null;
+        }
+
+        internal virtual void InitHeartbeat(TlsHeartbeat heartbeat, bool heartbeatResponder)
+        {
+            if (m_inHandshake)
+                throw new InvalidOperationException();
+
+            this.m_heartbeat = heartbeat;
+            this.m_heartBeatResponder = heartbeatResponder;
+
+            if (null != heartbeat)
+            {
+                ResetHeartbeat();
+            }
+        }
+
+        internal virtual void ResetWriteEpoch()
+        {
+            if (null != m_retransmitEpoch)
+            {
+                this.m_writeEpoch = m_retransmitEpoch;
+            }
+            else
+            {
+                this.m_writeEpoch = m_currentEpoch;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual int GetReceiveLimit()
+        {
+            return System.Math.Min(m_plaintextLimit,
+                m_readEpoch.Cipher.GetPlaintextLimit(m_transport.GetReceiveLimit() - RECORD_HEADER_LENGTH));
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual int GetSendLimit()
+        {
+            return System.Math.Min(m_plaintextLimit,
+                m_writeEpoch.Cipher.GetPlaintextLimit(m_transport.GetSendLimit() - RECORD_HEADER_LENGTH));
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+        {
+            long currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
+
+            Timeout timeout = Timeout.ForWaitMillis(waitMillis, currentTimeMillis);
+            byte[] record = null;
+
+            while (waitMillis >= 0)
+            {
+                if (null != m_retransmitTimeout && m_retransmitTimeout.RemainingMillis(currentTimeMillis) < 1)
+                {
+                    m_retransmit = null;
+                    m_retransmitEpoch = null;
+                    m_retransmitTimeout = null;
+                }
+
+                if (Timeout.HasExpired(m_heartbeatTimeout, currentTimeMillis))
+                {
+                    if (null != m_heartbeatInFlight)
+                        throw new TlsTimeoutException("Heartbeat timed out");
+
+                    this.m_heartbeatInFlight = HeartbeatMessage.Create(m_context,
+                        HeartbeatMessageType.heartbeat_request, m_heartbeat.GeneratePayload());
+                    this.m_heartbeatTimeout = new Timeout(m_heartbeat.TimeoutMillis, currentTimeMillis);
+
+                    this.m_heartbeatResendMillis = DtlsReliableHandshake.INITIAL_RESEND_MILLIS;
+                    this.m_heartbeatResendTimeout = new Timeout(m_heartbeatResendMillis, currentTimeMillis);
+
+                    SendHeartbeatMessage(m_heartbeatInFlight);
+                }
+                else if (Timeout.HasExpired(m_heartbeatResendTimeout, currentTimeMillis))
+                {
+                    this.m_heartbeatResendMillis = DtlsReliableHandshake.BackOff(m_heartbeatResendMillis);
+                    this.m_heartbeatResendTimeout = new Timeout(m_heartbeatResendMillis, currentTimeMillis);
+
+                    SendHeartbeatMessage(m_heartbeatInFlight);
+                }
+
+                waitMillis = Timeout.ConstrainWaitMillis(waitMillis, m_heartbeatTimeout, currentTimeMillis);
+                waitMillis = Timeout.ConstrainWaitMillis(waitMillis, m_heartbeatResendTimeout, currentTimeMillis);
+
+                // NOTE: Guard against bad logic giving a negative value 
+                if (waitMillis < 0)
+                {
+                    waitMillis = 1;
+                }
+
+                int receiveLimit = System.Math.Min(len, GetReceiveLimit()) + RECORD_HEADER_LENGTH;
+                if (null == record || record.Length < receiveLimit)
+                {
+                    record = new byte[receiveLimit];
+                }
+
+                int received = ReceiveRecord(record, 0, receiveLimit, waitMillis);
+                int processed = ProcessRecord(received, record, buf, off);
+                if (processed >= 0)
+                {
+                    return processed;
+                }
+
+                currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
+                waitMillis = Timeout.GetWaitMillis(timeout, currentTimeMillis);
+            }
+
+            return -1;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void Send(byte[] buf, int off, int len)
+        {
+            short contentType = ContentType.application_data;
+
+            if (m_inHandshake || m_writeEpoch == m_retransmitEpoch)
+            {
+                contentType = ContentType.handshake;
+
+                short handshakeType = TlsUtilities.ReadUint8(buf, off);
+                if (handshakeType == HandshakeType.finished)
+                {
+                    DtlsEpoch nextEpoch = null;
+                    if (m_inHandshake)
+                    {
+                        nextEpoch = m_pendingEpoch;
+                    }
+                    else if (m_writeEpoch == m_retransmitEpoch)
+                    {
+                        nextEpoch = m_currentEpoch;
+                    }
+
+                    if (nextEpoch == null)
+                    {
+                        // TODO
+                        throw new InvalidOperationException();
+                    }
+
+                    // Implicitly send change_cipher_spec and change to pending cipher state
+
+                    // TODO Send change_cipher_spec and finished records in single datagram?
+                    byte[] data = new byte[]{ 1 };
+                    SendRecord(ContentType.change_cipher_spec, data, 0, data.Length);
+
+                    this.m_writeEpoch = nextEpoch;
+                }
+            }
+
+            SendRecord(contentType, buf, off, len);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void Close()
+        {
+            if (!m_closed)
+            {
+                if (m_inHandshake && m_inConnection)
+                {
+                    Warn(AlertDescription.user_canceled, "User canceled handshake");
+                }
+                CloseTransport();
+            }
+        }
+
+        internal virtual void Fail(short alertDescription)
+        {
+            if (!m_closed)
+            {
+                if (m_inConnection)
+                {
+                    try
+                    {
+                        RaiseAlert(AlertLevel.fatal, alertDescription, null, null);
+                    }
+                    catch (Exception)
+                    {
+                        // Ignore
+                    }
+                }
+
+                this.m_failed = true;
+
+                CloseTransport();
+            }
+        }
+
+        internal virtual void Failed()
+        {
+            if (!m_closed)
+            {
+                this.m_failed = true;
+
+                CloseTransport();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        internal virtual void Warn(short alertDescription, String message)
+        {
+            RaiseAlert(AlertLevel.warning, alertDescription, message, null);
+        }
+
+        private void CloseTransport()
+        {
+            if (!m_closed)
+            {
+                /*
+                 * RFC 5246 7.2.1. Unless some other fatal alert has been transmitted, each party is
+                 * required to send a close_notify alert before closing the write side of the
+                 * connection. The other party MUST respond with a close_notify alert of its own and
+                 * close down the connection immediately, discarding any pending writes.
+                 */
+
+                try
+                {
+                    if (!m_failed)
+                    {
+                        Warn(AlertDescription.close_notify, null);
+                    }
+                    m_transport.Close();
+                }
+                catch (Exception)
+                {
+                    // Ignore
+                }
+
+                this.m_closed = true;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        private void RaiseAlert(short alertLevel, short alertDescription, string message, Exception cause)
+        {
+            m_peer.NotifyAlertRaised(alertLevel, alertDescription, message, cause);
+
+            byte[] error = new byte[2];
+            error[0] = (byte)alertLevel;
+            error[1] = (byte)alertDescription;
+
+            SendRecord(ContentType.alert, error, 0, 2);
+        }
+
+        /// <exception cref="IOException"/>
+        private int ReceiveDatagram(byte[] buf, int off, int len, int waitMillis)
+        {
+            try
+            {
+                return m_transport.Receive(buf, off, len, waitMillis);
+            }
+            catch (TlsTimeoutException)
+            {
+                return -1;
+            }
+#if !PORTABLE || DOTNET
+            catch (SocketException e)
+            {
+                if (TlsUtilities.IsTimeout(e))
+                    return -1;
+
+                throw e;
+            }
+#endif
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //catch (InterruptedIOException e)
+            //{
+            //    e.bytesTransferred = 0;
+            //    throw e;
+            //}
+        }
+
+        // TODO Include 'currentTimeMillis' as an argument, use with Timeout, resetHeartbeat
+        /// <exception cref="IOException"/>
+        private int ProcessRecord(int received, byte[] record, byte[] buf, int off)
+        {
+            // NOTE: received < 0 (timeout) is covered by this first case
+            if (received < RECORD_HEADER_LENGTH)
+                return -1;
+
+            int length = TlsUtilities.ReadUint16(record, 11);
+            if (received != (length + RECORD_HEADER_LENGTH))
+                return -1;
+
+            // TODO[dtls13] Deal with opaque record type for 1.3 AEAD ciphers
+            short recordType = TlsUtilities.ReadUint8(record, 0);
+
+            switch (recordType)
+            {
+            case ContentType.alert:
+            case ContentType.application_data:
+            case ContentType.change_cipher_spec:
+            case ContentType.handshake:
+            case ContentType.heartbeat:
+                break;
+            default:
+                return -1;
+            }
+
+            int epoch = TlsUtilities.ReadUint16(record, 3);
+
+            DtlsEpoch recordEpoch = null;
+            if (epoch == m_readEpoch.Epoch)
+            {
+                recordEpoch = m_readEpoch;
+            }
+            else if (recordType == ContentType.handshake && null != m_retransmitEpoch
+                && epoch == m_retransmitEpoch.Epoch)
+            {
+                recordEpoch = m_retransmitEpoch;
+            }
+
+            if (null == recordEpoch)
+                return -1;
+
+            long seq = TlsUtilities.ReadUint48(record, 5);
+            if (recordEpoch.ReplayWindow.ShouldDiscard(seq))
+                return -1;
+
+            ProtocolVersion recordVersion = TlsUtilities.ReadVersion(record, 1);
+            if (!recordVersion.IsDtls)
+                return -1;
+
+            if (null != m_readVersion && !m_readVersion.Equals(recordVersion))
+            {
+                /*
+                 * Special-case handling for retransmitted ClientHello records.
+                 * 
+                 * TODO Revisit how 'readVersion' works, since this is quite awkward.
+                 */
+                bool isClientHelloFragment =
+                        ReadEpoch == 0
+                    &&  length > 0
+                    &&  ContentType.handshake == recordType
+                    &&  HandshakeType.client_hello == TlsUtilities.ReadUint8(record, RECORD_HEADER_LENGTH);
+
+                if (!isClientHelloFragment)
+                    return -1;
+            }
+
+            long macSeqNo = GetMacSequenceNumber(recordEpoch.Epoch, seq);
+
+            TlsDecodeResult decoded = recordEpoch.Cipher.DecodeCiphertext(macSeqNo, recordType, recordVersion, record,
+                RECORD_HEADER_LENGTH, length);
+
+            recordEpoch.ReplayWindow.ReportAuthenticated(seq);
+
+            if (decoded.len > m_plaintextLimit)
+                return -1;
+
+            if (decoded.len < 1 && decoded.contentType != ContentType.application_data)
+                return -1;
+
+            if (null == m_readVersion)
+            {
+                bool isHelloVerifyRequest =
+                        ReadEpoch == 0
+                    &&  length > 0
+                    &&  ContentType.handshake == recordType
+                    &&  HandshakeType.hello_verify_request == TlsUtilities.ReadUint8(record, RECORD_HEADER_LENGTH);
+
+                if (isHelloVerifyRequest)
+                {
+                    /*
+                     * RFC 6347 4.2.1 DTLS 1.2 server implementations SHOULD use DTLS version 1.0
+                     * regardless of the version of TLS that is expected to be negotiated. DTLS 1.2 and
+                     * 1.0 clients MUST use the version solely to indicate packet formatting (which is
+                     * the same in both DTLS 1.2 and 1.0) and not as part of version negotiation.
+                     */
+                    if (!ProtocolVersion.DTLSv12.IsEqualOrLaterVersionOf(recordVersion))
+                        return -1;
+                }
+                else
+                {
+                    this.m_readVersion = recordVersion;
+                }
+            }
+
+            switch (decoded.contentType)
+            {
+            case ContentType.alert:
+            {
+                if (decoded.len == 2)
+                {
+                    short alertLevel = TlsUtilities.ReadUint8(decoded.buf, decoded.off);
+                    short alertDescription = TlsUtilities.ReadUint8(decoded.buf, decoded.off + 1);
+
+                    m_peer.NotifyAlertReceived(alertLevel, alertDescription);
+
+                    if (alertLevel == AlertLevel.fatal)
+                    {
+                        Failed();
+                        throw new TlsFatalAlert(alertDescription);
+                    }
+
+                    // TODO Can close_notify be a fatal alert?
+                    if (alertDescription == AlertDescription.close_notify)
+                    {
+                        CloseTransport();
+                    }
+                }
+
+                return -1;
+            }
+            case ContentType.application_data:
+            {
+                if (m_inHandshake)
+                {
+                    // TODO Consider buffering application data for new epoch that arrives
+                    // out-of-order with the Finished message
+                    return -1;
+                }
+                break;
+            }
+            case ContentType.change_cipher_spec:
+            {
+                // Implicitly receive change_cipher_spec and change to pending cipher state
+
+                for (int i = 0; i < decoded.len; ++i)
+                {
+                    short message = TlsUtilities.ReadUint8(decoded.buf, decoded.off + i);
+                    if (message != ChangeCipherSpec.change_cipher_spec)
+                        continue;
+
+                    if (m_pendingEpoch != null)
+                    {
+                        m_readEpoch = m_pendingEpoch;
+                    }
+                }
+
+                return -1;
+            }
+            case ContentType.handshake:
+            {
+                if (!m_inHandshake)
+                {
+                    if (null != m_retransmit)
+                    {
+                        m_retransmit.ReceivedHandshakeRecord(epoch, decoded.buf, decoded.off, decoded.len);
+                    }
+
+                    // TODO Consider support for HelloRequest
+                    return -1;
+                }
+                break;
+            }
+            case ContentType.heartbeat:
+            {
+                if (null != m_heartbeatInFlight || m_heartBeatResponder)
+                {
+                    try
+                    {
+                        MemoryStream input = new MemoryStream(decoded.buf, decoded.off, decoded.len, false);
+                        HeartbeatMessage heartbeatMessage = HeartbeatMessage.Parse(input);
+
+                        if (null != heartbeatMessage)
+                        {
+                            switch (heartbeatMessage.Type)
+                            {
+                            case HeartbeatMessageType.heartbeat_request:
+                            {
+                                if (m_heartBeatResponder)
+                                {
+                                    HeartbeatMessage response = HeartbeatMessage.Create(m_context,
+                                        HeartbeatMessageType.heartbeat_response, heartbeatMessage.Payload);
+
+                                    SendHeartbeatMessage(response);
+                                }
+                                break;
+                            }
+                            case HeartbeatMessageType.heartbeat_response:
+                            {
+                                if (null != m_heartbeatInFlight
+                                    && Arrays.AreEqual(heartbeatMessage.Payload, m_heartbeatInFlight.Payload))
+                                {
+                                    ResetHeartbeat();
+                                }
+                                break;
+                            }
+                            default:
+                                break;
+                            }
+                        }
+                    }
+                    catch (Exception)
+                    {
+                        // Ignore
+                    }
+                }
+
+                return -1;
+            }
+            default:
+                return -1;
+            }
+
+            /*
+             * NOTE: If we receive any non-handshake data in the new epoch implies the peer has
+             * received our final flight.
+             */
+            if (!m_inHandshake && null != m_retransmit)
+            {
+                this.m_retransmit = null;
+                this.m_retransmitEpoch = null;
+                this.m_retransmitTimeout = null;
+            }
+
+            Array.Copy(decoded.buf, decoded.off, buf, off, decoded.len);
+            return decoded.len;
+        }
+
+        /// <exception cref="IOException"/>
+        private int ReceiveRecord(byte[] buf, int off, int len, int waitMillis)
+        {
+            if (m_recordQueue.Available > 0)
+            {
+                int length = 0;
+                if (m_recordQueue.Available >= RECORD_HEADER_LENGTH)
+                {
+                    byte[] lengthBytes = new byte[2];
+                    m_recordQueue.Read(lengthBytes, 0, 2, 11);
+                    length = TlsUtilities.ReadUint16(lengthBytes, 0);
+                }
+
+                int received = System.Math.Min(m_recordQueue.Available, RECORD_HEADER_LENGTH + length);
+                m_recordQueue.RemoveData(buf, off, received, 0);
+                return received;
+            }
+
+            {
+                int received = ReceiveDatagram(buf, off, len, waitMillis);
+                if (received >= RECORD_HEADER_LENGTH)
+                {
+                    this.m_inConnection = true;
+
+                    int fragmentLength = TlsUtilities.ReadUint16(buf, off + 11);
+                    int recordLength = RECORD_HEADER_LENGTH + fragmentLength;
+                    if (received > recordLength)
+                    {
+                        m_recordQueue.AddData(buf, off + recordLength, received - recordLength);
+                        received = recordLength;
+                    }
+                }
+
+                return received;
+            }
+        }
+
+        private void ResetHeartbeat()
+        {
+            this.m_heartbeatInFlight = null;
+            this.m_heartbeatResendMillis = -1;
+            this.m_heartbeatResendTimeout = null;
+            this.m_heartbeatTimeout = new Timeout(m_heartbeat.IdleMillis);
+        }
+
+        /// <exception cref="IOException"/>
+        private void SendHeartbeatMessage(HeartbeatMessage heartbeatMessage)
+        {
+            MemoryStream output = new MemoryStream();
+            heartbeatMessage.Encode(output);
+            byte[] buf = output.ToArray();
+
+            SendRecord(ContentType.heartbeat, buf, 0, buf.Length);
+        }
+
+        /*
+         * Currently uses synchronization to ensure heartbeat sends and application data sends don't
+         * interfere with each other. It may be overly cautious; the sequence number allocation is
+         * atomic, and if we synchronize only on the datagram send instead, then the only effect should
+         * be possible reordering of records (which might surprise a reliable transport implementation).
+         */
+        /// <exception cref="IOException"/>
+        private void SendRecord(short contentType, byte[] buf, int off, int len)
+        {
+            // Never send anything until a valid ClientHello has been received
+            if (m_writeVersion == null)
+                return;
+
+            if (len > m_plaintextLimit)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            /*
+             * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+             * or ChangeCipherSpec content types.
+             */
+            if (len < 1 && contentType != ContentType.application_data)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            lock (m_writeLock)
+            {
+                int recordEpoch = m_writeEpoch.Epoch;
+                long recordSequenceNumber = m_writeEpoch.AllocateSequenceNumber();
+                long macSequenceNumber = GetMacSequenceNumber(recordEpoch, recordSequenceNumber);
+                ProtocolVersion recordVersion = m_writeVersion;
+
+                TlsEncodeResult encoded = m_writeEpoch.Cipher.EncodePlaintext(macSequenceNumber, contentType,
+                    recordVersion, RECORD_HEADER_LENGTH, buf, off, len);
+
+                int ciphertextLength = encoded.len - RECORD_HEADER_LENGTH;
+                TlsUtilities.CheckUint16(ciphertextLength);
+
+                TlsUtilities.WriteUint8(encoded.recordType, encoded.buf, encoded.off + 0);
+                TlsUtilities.WriteVersion(recordVersion, encoded.buf, encoded.off + 1);
+                TlsUtilities.WriteUint16(recordEpoch, encoded.buf, encoded.off + 3);
+                TlsUtilities.WriteUint48(recordSequenceNumber, encoded.buf, encoded.off + 5);
+                TlsUtilities.WriteUint16(ciphertextLength, encoded.buf, encoded.off + 11);
+
+                SendDatagram(m_transport, encoded.buf, encoded.off, encoded.len);
+            }
+        }
+
+        private static long GetMacSequenceNumber(int epoch, long sequence_number)
+        {
+            return ((epoch & 0xFFFFFFFFL) << 48) | sequence_number;
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsReliableHandshake.cs b/crypto/src/tls/DtlsReliableHandshake.cs
new file mode 100644
index 000000000..b2f8f130a
--- /dev/null
+++ b/crypto/src/tls/DtlsReliableHandshake.cs
@@ -0,0 +1,558 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal class DtlsReliableHandshake
+    {
+        private const int MAX_RECEIVE_AHEAD = 16;
+        private const int MESSAGE_HEADER_LENGTH = 12;
+
+        internal const int INITIAL_RESEND_MILLIS = 1000;
+        private const int MAX_RESEND_MILLIS = 60000;
+
+        /// <exception cref="IOException"/>
+        internal static DtlsRequest ReadClientRequest(byte[] data, int dataOff, int dataLen, Stream dtlsOutput)
+        {
+            // TODO Support the possibility of a fragmented ClientHello datagram
+
+            byte[] message = DtlsRecordLayer.ReceiveClientHelloRecord(data, dataOff, dataLen);
+            if (null == message || message.Length < MESSAGE_HEADER_LENGTH)
+                return null;
+
+            long recordSeq = TlsUtilities.ReadUint48(data, dataOff + 5);
+
+            short msgType = TlsUtilities.ReadUint8(message, 0);
+            if (HandshakeType.client_hello != msgType)
+                return null;
+
+            int length = TlsUtilities.ReadUint24(message, 1);
+            if (message.Length != MESSAGE_HEADER_LENGTH + length)
+                return null;
+
+            // TODO Consider stricter HelloVerifyRequest-related checks
+            //int messageSeq = TlsUtilities.ReadUint16(message, 4);
+            //if (messageSeq > 1)
+            //    return null;
+
+            int fragmentOffset = TlsUtilities.ReadUint24(message, 6);
+            if (0 != fragmentOffset)
+                return null;
+
+            int fragmentLength = TlsUtilities.ReadUint24(message, 9);
+            if (length != fragmentLength)
+                return null;
+
+            ClientHello clientHello = ClientHello.Parse(
+                new MemoryStream(message, MESSAGE_HEADER_LENGTH, length, false), dtlsOutput);
+
+            return new DtlsRequest(recordSeq, message, clientHello);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void SendHelloVerifyRequest(DatagramSender sender, long recordSeq, byte[] cookie)
+        {
+            TlsUtilities.CheckUint8(cookie.Length);
+
+            int length = 3 + cookie.Length;
+
+            byte[] message = new byte[MESSAGE_HEADER_LENGTH + length];
+            TlsUtilities.WriteUint8(HandshakeType.hello_verify_request, message, 0);
+            TlsUtilities.WriteUint24(length, message, 1);
+            //TlsUtilities.WriteUint16(0, message, 4);
+            //TlsUtilities.WriteUint24(0, message, 6);
+            TlsUtilities.WriteUint24(length, message, 9);
+
+            // HelloVerifyRequest fields
+            TlsUtilities.WriteVersion(ProtocolVersion.DTLSv10, message, MESSAGE_HEADER_LENGTH + 0);
+            TlsUtilities.WriteOpaque8(cookie, message, MESSAGE_HEADER_LENGTH + 2);
+
+            DtlsRecordLayer.SendHelloVerifyRequestRecord(sender, recordSeq, message);
+        }
+
+        /*
+         * No 'final' modifiers so that it works in earlier JDKs
+         */
+        private DtlsRecordLayer m_recordLayer;
+        private Timeout m_handshakeTimeout;
+
+        private TlsHandshakeHash m_handshakeHash;
+
+        private IDictionary m_currentInboundFlight = Platform.CreateHashtable();
+        private IDictionary m_previousInboundFlight = null;
+        private IList m_outboundFlight = Platform.CreateArrayList();
+
+        private int m_resendMillis = -1;
+        private Timeout m_resendTimeout = null;
+
+        private int m_next_send_seq = 0, m_next_receive_seq = 0;
+
+        internal DtlsReliableHandshake(TlsContext context, DtlsRecordLayer transport, int timeoutMillis,
+            DtlsRequest request)
+        {
+            this.m_recordLayer = transport;
+            this.m_handshakeHash = new DeferredHash(context);
+            this.m_handshakeTimeout = Timeout.ForWaitMillis(timeoutMillis);
+
+            if (null != request)
+            {
+                this.m_resendMillis = INITIAL_RESEND_MILLIS;
+                this.m_resendTimeout = new Timeout(m_resendMillis);
+
+                long recordSeq = request.RecordSeq;
+                int messageSeq = request.MessageSeq;
+                byte[] message = request.Message;
+
+                m_recordLayer.ResetAfterHelloVerifyRequestServer(recordSeq);
+
+                // Simulate a previous flight consisting of the request ClientHello
+                DtlsReassembler reassembler = new DtlsReassembler(HandshakeType.client_hello,
+                    message.Length - MESSAGE_HEADER_LENGTH);
+                m_currentInboundFlight[messageSeq] = reassembler;
+
+                // We sent HelloVerifyRequest with (message) sequence number 0
+                this.m_next_send_seq = 1;
+                this.m_next_receive_seq = messageSeq + 1;
+
+                m_handshakeHash.Update(message, 0, message.Length);
+            }
+        }
+
+        internal void ResetAfterHelloVerifyRequestClient()
+        {
+            this.m_currentInboundFlight = Platform.CreateHashtable();
+            this.m_previousInboundFlight = null;
+            this.m_outboundFlight = Platform.CreateArrayList();
+
+            this.m_resendMillis = -1;
+            this.m_resendTimeout = null;
+
+            // We're waiting for ServerHello, always with (message) sequence number 1
+            this.m_next_receive_seq = 1;
+
+            m_handshakeHash.Reset();
+        }
+
+        internal TlsHandshakeHash HandshakeHash
+        {
+            get { return m_handshakeHash; }
+        }
+
+        internal TlsHandshakeHash PrepareToFinish()
+        {
+            TlsHandshakeHash result = m_handshakeHash;
+            this.m_handshakeHash = m_handshakeHash.StopTracking();
+            return result;
+        }
+
+        /// <exception cref="IOException"/>
+        internal void SendMessage(short msg_type, byte[] body)
+        {
+            TlsUtilities.CheckUint24(body.Length);
+
+            if (null != m_resendTimeout)
+            {
+                CheckInboundFlight();
+
+                this.m_resendMillis = -1;
+                this.m_resendTimeout = null;
+
+                m_outboundFlight.Clear();
+            }
+
+            Message message = new Message(m_next_send_seq++, msg_type, body);
+
+            m_outboundFlight.Add(message);
+
+            WriteMessage(message);
+            UpdateHandshakeMessagesDigest(message);
+        }
+
+        /// <exception cref="IOException"/>
+        internal byte[] ReceiveMessageBody(short msg_type)
+        {
+            Message message = ReceiveMessage();
+            if (message.Type != msg_type)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            return message.Body;
+        }
+
+        /// <exception cref="IOException"/>
+        internal Message ReceiveMessage()
+        {
+            long currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
+
+            if (null == m_resendTimeout)
+            {
+                m_resendMillis = INITIAL_RESEND_MILLIS;
+                m_resendTimeout = new Timeout(m_resendMillis, currentTimeMillis);
+
+                PrepareInboundFlight(Platform.CreateHashtable());
+            }
+
+            byte[] buf = null;
+
+            for (;;)
+            {
+                if (m_recordLayer.IsClosed)
+                    throw new TlsFatalAlert(AlertDescription.user_canceled);
+
+                Message pending = GetPendingMessage();
+                if (pending != null)
+                    return pending;
+
+                if (Timeout.HasExpired(m_handshakeTimeout, currentTimeMillis))
+                    throw new TlsTimeoutException("Handshake timed out");
+
+                int waitMillis = Timeout.GetWaitMillis(m_handshakeTimeout, currentTimeMillis);
+                waitMillis = Timeout.ConstrainWaitMillis(waitMillis, m_resendTimeout, currentTimeMillis);
+
+                // NOTE: Ensure a finite wait, of at least 1ms
+                if (waitMillis < 1)
+                {
+                    waitMillis = 1;
+                }
+
+                int receiveLimit = m_recordLayer.GetReceiveLimit();
+                if (buf == null || buf.Length < receiveLimit)
+                {
+                    buf = new byte[receiveLimit];
+                }
+
+                int received = m_recordLayer.Receive(buf, 0, receiveLimit, waitMillis);
+                if (received < 0)
+                {
+                    ResendOutboundFlight();
+                }
+                else
+                {
+                    ProcessRecord(MAX_RECEIVE_AHEAD, m_recordLayer.ReadEpoch, buf, 0, received);
+                }
+
+                currentTimeMillis = DateTimeUtilities.CurrentUnixMs();
+            }
+        }
+
+        internal void Finish()
+        {
+            DtlsHandshakeRetransmit retransmit = null;
+            if (null != m_resendTimeout)
+            {
+                CheckInboundFlight();
+            }
+            else
+            {
+                PrepareInboundFlight(null);
+
+                if (m_previousInboundFlight != null)
+                {
+                    /*
+                     * RFC 6347 4.2.4. In addition, for at least twice the default MSL defined for [TCP],
+                     * when in the FINISHED state, the node that transmits the last flight (the server in an
+                     * ordinary handshake or the client in a resumed handshake) MUST respond to a retransmit
+                     * of the peer's last flight with a retransmit of the last flight.
+                     */
+                    retransmit = new Retransmit(this);
+                }
+            }
+
+            m_recordLayer.HandshakeSuccessful(retransmit);
+        }
+
+        internal static int BackOff(int timeoutMillis)
+        {
+            /*
+             * TODO[DTLS] implementations SHOULD back off handshake packet size during the
+             * retransmit backoff.
+             */
+            return System.Math.Min(timeoutMillis * 2, MAX_RESEND_MILLIS);
+        }
+
+        /**
+         * Check that there are no "extra" messages left in the current inbound flight
+         */
+        private void CheckInboundFlight()
+        {
+            foreach (int key in m_currentInboundFlight.Keys)
+            {
+                if (key >= m_next_receive_seq)
+                {
+                    // TODO Should this be considered an error?
+                }
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        private Message GetPendingMessage()
+        {
+            DtlsReassembler next = (DtlsReassembler)m_currentInboundFlight[m_next_receive_seq];
+            if (next != null)
+            {
+                byte[] body = next.GetBodyIfComplete();
+                if (body != null)
+                {
+                    m_previousInboundFlight = null;
+                    return UpdateHandshakeMessagesDigest(new Message(m_next_receive_seq++, next.MsgType, body));
+                }
+            }
+            return null;
+        }
+
+        private void PrepareInboundFlight(IDictionary nextFlight)
+        {
+            ResetAll(m_currentInboundFlight);
+            m_previousInboundFlight = m_currentInboundFlight;
+            m_currentInboundFlight = nextFlight;
+        }
+
+        /// <exception cref="IOException"/>
+        private void ProcessRecord(int windowSize, int epoch, byte[] buf, int off, int len)
+        {
+            bool checkPreviousFlight = false;
+
+            while (len >= MESSAGE_HEADER_LENGTH)
+            {
+                int fragment_length = TlsUtilities.ReadUint24(buf, off + 9);
+                int message_length = fragment_length + MESSAGE_HEADER_LENGTH;
+                if (len < message_length)
+                {
+                    // NOTE: Truncated message - ignore it
+                    break;
+                }
+
+                int length = TlsUtilities.ReadUint24(buf, off + 1);
+                int fragment_offset = TlsUtilities.ReadUint24(buf, off + 6);
+                if (fragment_offset + fragment_length > length)
+                {
+                    // NOTE: Malformed fragment - ignore it and the rest of the record
+                    break;
+                }
+
+                /*
+                 * NOTE: This very simple epoch check will only work until we want to support
+                 * renegotiation (and we're not likely to do that anyway).
+                 */
+                short msg_type = TlsUtilities.ReadUint8(buf, off + 0);
+                int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0;
+                if (epoch != expectedEpoch)
+                    break;
+
+                int message_seq = TlsUtilities.ReadUint16(buf, off + 4);
+                if (message_seq >= (m_next_receive_seq + windowSize))
+                {
+                    // NOTE: Too far ahead - ignore
+                }
+                else if (message_seq >= m_next_receive_seq)
+                {
+                    DtlsReassembler reassembler = (DtlsReassembler)m_currentInboundFlight[message_seq];
+                    if (reassembler == null)
+                    {
+                        reassembler = new DtlsReassembler(msg_type, length);
+                        m_currentInboundFlight[message_seq] = reassembler;
+                    }
+
+                    reassembler.ContributeFragment(msg_type, length, buf, off + MESSAGE_HEADER_LENGTH, fragment_offset,
+                        fragment_length);
+                }
+                else if (m_previousInboundFlight != null)
+                {
+                    /*
+                     * NOTE: If we receive the previous flight of incoming messages in full again,
+                     * retransmit our last flight
+                     */
+
+                    DtlsReassembler reassembler = (DtlsReassembler)m_previousInboundFlight[message_seq];
+                    if (reassembler != null)
+                    {
+                        reassembler.ContributeFragment(msg_type, length, buf, off + MESSAGE_HEADER_LENGTH,
+                            fragment_offset, fragment_length);
+                        checkPreviousFlight = true;
+                    }
+                }
+
+                off += message_length;
+                len -= message_length;
+            }
+
+            if (checkPreviousFlight && CheckAll(m_previousInboundFlight))
+            {
+                ResendOutboundFlight();
+                ResetAll(m_previousInboundFlight);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        private void ResendOutboundFlight()
+        {
+            m_recordLayer.ResetWriteEpoch();
+            foreach (Message message in m_outboundFlight)
+            {
+                WriteMessage(message);
+            }
+
+            m_resendMillis = BackOff(m_resendMillis);
+            m_resendTimeout = new Timeout(m_resendMillis);
+        }
+
+        /// <exception cref="IOException"/>
+        private Message UpdateHandshakeMessagesDigest(Message message)
+        {
+            short msg_type = message.Type;
+            switch (msg_type)
+            {
+            case HandshakeType.hello_request:
+            case HandshakeType.hello_verify_request:
+            case HandshakeType.key_update:
+            case HandshakeType.new_session_ticket:
+                break;
+
+            default:
+            {
+                byte[] body = message.Body;
+                byte[] buf = new byte[MESSAGE_HEADER_LENGTH];
+                TlsUtilities.WriteUint8(msg_type, buf, 0);
+                TlsUtilities.WriteUint24(body.Length, buf, 1);
+                TlsUtilities.WriteUint16(message.Seq, buf, 4);
+                TlsUtilities.WriteUint24(0, buf, 6);
+                TlsUtilities.WriteUint24(body.Length, buf, 9);
+                m_handshakeHash.Update(buf, 0, buf.Length);
+                m_handshakeHash.Update(body, 0, body.Length);
+                break;
+            }
+            }
+
+            return message;
+        }
+
+        /// <exception cref="IOException"/>
+        private void WriteMessage(Message message)
+        {
+            int sendLimit = m_recordLayer.GetSendLimit();
+            int fragmentLimit = sendLimit - MESSAGE_HEADER_LENGTH;
+
+            // TODO Support a higher minimum fragment size?
+            if (fragmentLimit < 1)
+            {
+                // TODO Should we be throwing an exception here?
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            int length = message.Body.Length;
+
+            // NOTE: Must still send a fragment if body is empty
+            int fragment_offset = 0;
+            do
+            {
+                int fragment_length = System.Math.Min(length - fragment_offset, fragmentLimit);
+                WriteHandshakeFragment(message, fragment_offset, fragment_length);
+                fragment_offset += fragment_length;
+            }
+            while (fragment_offset < length);
+        }
+
+        /// <exception cref="IOException"/>
+        private void WriteHandshakeFragment(Message message, int fragment_offset, int fragment_length)
+        {
+            RecordLayerBuffer fragment = new RecordLayerBuffer(MESSAGE_HEADER_LENGTH + fragment_length);
+            TlsUtilities.WriteUint8(message.Type, fragment);
+            TlsUtilities.WriteUint24(message.Body.Length, fragment);
+            TlsUtilities.WriteUint16(message.Seq, fragment);
+            TlsUtilities.WriteUint24(fragment_offset, fragment);
+            TlsUtilities.WriteUint24(fragment_length, fragment);
+            fragment.Write(message.Body, fragment_offset, fragment_length);
+
+            fragment.SendToRecordLayer(m_recordLayer);
+        }
+
+        private static bool CheckAll(IDictionary inboundFlight)
+        {
+            foreach (DtlsReassembler r in inboundFlight.Values)
+            {
+                if (r.GetBodyIfComplete() == null)
+                    return false;
+            }
+            return true;
+        }
+
+        private static void ResetAll(IDictionary inboundFlight)
+        {
+            foreach (DtlsReassembler r in inboundFlight.Values)
+            {
+                r.Reset();
+            }
+        }
+
+        internal class Message
+        {
+            private readonly int m_message_seq;
+            private readonly short m_msg_type;
+            private readonly byte[] m_body;
+
+            internal Message(int message_seq, short msg_type, byte[] body)
+            {
+                this.m_message_seq = message_seq;
+                this.m_msg_type = msg_type;
+                this.m_body = body;
+            }
+
+            public int Seq
+            {
+                get { return m_message_seq; }
+            }
+
+            public short Type
+            {
+                get { return m_msg_type; }
+            }
+
+            public byte[] Body
+            {
+                get { return m_body; }
+            }
+        }
+
+        internal class RecordLayerBuffer
+            : MemoryStream
+        {
+            internal RecordLayerBuffer(int size)
+                : base(size)
+            {
+            }
+
+            internal void SendToRecordLayer(DtlsRecordLayer recordLayer)
+            {
+#if PORTABLE
+                byte[] buf = ToArray();
+                int bufLen = buf.Length;
+#else
+                byte[] buf = GetBuffer();
+                int bufLen = (int)Length;
+#endif
+
+                recordLayer.Send(buf, 0, bufLen);
+                Platform.Dispose(this);
+            }
+        }
+
+        internal class Retransmit
+            : DtlsHandshakeRetransmit
+        {
+            private readonly DtlsReliableHandshake m_outer;
+
+            internal Retransmit(DtlsReliableHandshake outer)
+            {
+                this.m_outer = outer;
+            }
+
+            public void ReceivedHandshakeRecord(int epoch, byte[] buf, int off, int len)
+            {
+                m_outer.ProcessRecord(0, epoch, buf, off, len);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsReplayWindow.cs b/crypto/src/tls/DtlsReplayWindow.cs
new file mode 100644
index 000000000..a08114c2a
--- /dev/null
+++ b/crypto/src/tls/DtlsReplayWindow.cs
@@ -0,0 +1,84 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /**
+     * RFC 4347 4.1.2.5 Anti-replay
+     * <p>
+     * Support fast rejection of duplicate records by maintaining a sliding receive window
+     * </p>
+     */
+    internal sealed class DtlsReplayWindow
+    {
+        private const long ValidSeqMask = 0x0000FFFFFFFFFFFFL;
+
+        private const long WindowSize = 64L;
+
+        private long m_latestConfirmedSeq = -1;
+        private ulong m_bitmap = 0;
+
+        /// <summary>Check whether a received record with the given sequence number should be rejected as a duplicate.
+        /// </summary>
+        /// <param name="seq">the 48-bit DTLSPlainText.sequence_number field of a received record.</param>
+        /// <returns>true if the record should be discarded without further processing.</returns>
+        internal bool ShouldDiscard(long seq)
+        {
+            if ((seq & ValidSeqMask) != seq)
+                return true;
+
+            if (seq <= m_latestConfirmedSeq)
+            {
+                long diff = m_latestConfirmedSeq - seq;
+                if (diff >= WindowSize)
+                    return true;
+
+                if ((m_bitmap & (1UL << (int)diff)) != 0)
+                    return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>Report that a received record with the given sequence number passed authentication checks.
+        /// </summary>
+        /// <param name="seq">the 48-bit DTLSPlainText.sequence_number field of an authenticated record.</param>
+        internal void ReportAuthenticated(long seq)
+        {
+            if ((seq & ValidSeqMask) != seq)
+                throw new ArgumentException("out of range", "seq");
+
+            if (seq <= m_latestConfirmedSeq)
+            {
+                long diff = m_latestConfirmedSeq - seq;
+                if (diff < WindowSize)
+                {
+                    m_bitmap |= (1UL << (int)diff);
+                }
+            }
+            else
+            {
+                long diff = seq - m_latestConfirmedSeq;
+                if (diff >= WindowSize)
+                {
+                    m_bitmap = 1;
+                }
+                else
+                {
+                    m_bitmap <<= (int)diff;        // for earlier JDKs
+                    m_bitmap |= 1UL;
+                }
+                m_latestConfirmedSeq = seq;
+            }
+        }
+
+        internal void Reset(long seq)
+        {
+            if ((seq & ValidSeqMask) != seq)
+                throw new ArgumentException("out of range", "seq");
+
+            // Discard future records unless sequence number > 'seq'
+            m_latestConfirmedSeq = seq;
+            m_bitmap = ulong.MaxValue >> (int)System.Math.Max(0, 63 - seq);
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsRequest.cs b/crypto/src/tls/DtlsRequest.cs
new file mode 100644
index 000000000..126429e67
--- /dev/null
+++ b/crypto/src/tls/DtlsRequest.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class DtlsRequest
+    {
+        private readonly long m_recordSeq;
+        private readonly byte[] m_message;
+        private readonly ClientHello m_clientHello;
+
+        internal DtlsRequest(long recordSeq, byte[] message, ClientHello clientHello)
+        {
+            this.m_recordSeq = recordSeq;
+            this.m_message = message;
+            this.m_clientHello = clientHello;
+        }
+
+        internal ClientHello ClientHello
+        {
+            get { return m_clientHello; }
+        }
+
+        internal byte[] Message
+        {
+            get { return m_message; }
+        }
+
+        internal int MessageSeq
+        {
+            get { return TlsUtilities.ReadUint16(m_message, 4); }
+        }
+
+        internal long RecordSeq
+        {
+            get { return m_recordSeq; }
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsServerProtocol.cs b/crypto/src/tls/DtlsServerProtocol.cs
new file mode 100644
index 000000000..5637d4106
--- /dev/null
+++ b/crypto/src/tls/DtlsServerProtocol.cs
@@ -0,0 +1,835 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class DtlsServerProtocol
+        : DtlsProtocol
+    {
+        protected bool m_verifyRequests = true;
+
+        public DtlsServerProtocol()
+            : base()
+        {
+        }
+
+        public virtual bool VerifyRequests
+        {
+            get { return m_verifyRequests; }
+            set { this.m_verifyRequests = value; }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual DtlsTransport Accept(TlsServer server, DatagramTransport transport)
+        {
+            return Accept(server, transport, null);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual DtlsTransport Accept(TlsServer server, DatagramTransport transport, DtlsRequest request)
+        {
+            if (server == null)
+                throw new ArgumentNullException("server");
+            if (transport == null)
+                throw new ArgumentNullException("transport");
+
+            ServerHandshakeState state = new ServerHandshakeState();
+            state.server = server;
+            state.serverContext = new TlsServerContextImpl(server.Crypto);
+            server.Init(state.serverContext);
+            state.serverContext.HandshakeBeginning(server);
+
+            SecurityParameters securityParameters = state.serverContext.SecurityParameters;
+            securityParameters.m_extendedPadding = server.ShouldUseExtendedPadding();
+
+            DtlsRecordLayer recordLayer = new DtlsRecordLayer(state.serverContext, state.server, transport);
+            server.NotifyCloseHandle(recordLayer);
+
+            try
+            {
+                return ServerHandshake(state, recordLayer, request);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                AbortServerHandshake(state, recordLayer, fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (IOException e)
+            {
+                AbortServerHandshake(state, recordLayer, AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                AbortServerHandshake(state, recordLayer, AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+            finally
+            {
+                securityParameters.Clear();
+            }
+        }
+
+        internal virtual void AbortServerHandshake(ServerHandshakeState state, DtlsRecordLayer recordLayer,
+            short alertDescription)
+        {
+            recordLayer.Fail(alertDescription);
+            InvalidateSession(state);
+        }
+
+        /// <exception cref="IOException"/>
+        internal virtual DtlsTransport ServerHandshake(ServerHandshakeState state, DtlsRecordLayer recordLayer,
+            DtlsRequest request)
+        {
+            SecurityParameters securityParameters = state.serverContext.SecurityParameters;
+
+            DtlsReliableHandshake handshake = new DtlsReliableHandshake(state.serverContext, recordLayer,
+                state.server.GetHandshakeTimeoutMillis(), request);
+
+            DtlsReliableHandshake.Message clientMessage = null;
+
+            if (null == request)
+            {
+                clientMessage = handshake.ReceiveMessage();
+
+                // NOTE: DtlsRecordLayer requires any DTLS version, we don't otherwise constrain this
+                //ProtocolVersion recordLayerVersion = recordLayer.ReadVersion;
+
+                if (clientMessage.Type == HandshakeType.client_hello)
+                {
+                    ProcessClientHello(state, clientMessage.Body);
+                }
+                else
+                {
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+            }
+            else
+            {
+                ProcessClientHello(state, request.ClientHello);
+            }
+
+            /*
+             * NOTE: Currently no server support for session resumption
+             * 
+             * If adding support, ensure securityParameters.tlsUnique is set to the localVerifyData, but
+             * ONLY when extended_master_secret has been negotiated (otherwise NULL).
+             */
+            {
+                // TODO[resumption]
+
+                state.tlsSession = TlsUtilities.ImportSession(TlsUtilities.EmptyBytes, null);
+                state.sessionParameters = null;
+                state.sessionMasterSecret = null;
+            }
+
+            securityParameters.m_sessionID = state.tlsSession.SessionID;
+
+            state.server.NotifySession(state.tlsSession);
+
+            {
+                byte[] serverHelloBody = GenerateServerHello(state, recordLayer);
+
+                // TODO[dtls13] Ideally, move this into GenerateServerHello once legacy_record_version clarified
+                {
+                    ProtocolVersion recordLayerVersion = state.serverContext.ServerVersion;
+                    recordLayer.ReadVersion = recordLayerVersion;
+                    recordLayer.SetWriteVersion(recordLayerVersion);
+                }
+
+                handshake.SendMessage(HandshakeType.server_hello, serverHelloBody);
+            }
+
+            handshake.HandshakeHash.NotifyPrfDetermined();
+
+            IList serverSupplementalData = state.server.GetServerSupplementalData();
+            if (serverSupplementalData != null)
+            {
+                byte[] supplementalDataBody = GenerateSupplementalData(serverSupplementalData);
+                handshake.SendMessage(HandshakeType.supplemental_data, supplementalDataBody);
+            }
+
+            state.keyExchange = TlsUtilities.InitKeyExchangeServer(state.serverContext, state.server);
+            state.serverCredentials = TlsUtilities.EstablishServerCredentials(state.server);
+
+            // Server certificate
+            {
+                Certificate serverCertificate = null;
+
+                MemoryStream endPointHash = new MemoryStream();
+                if (state.serverCredentials == null)
+                {
+                    state.keyExchange.SkipServerCredentials();
+                }
+                else
+                {
+                    state.keyExchange.ProcessServerCredentials(state.serverCredentials);
+
+                    serverCertificate = state.serverCredentials.Certificate;
+
+                    SendCertificateMessage(state.serverContext, handshake, serverCertificate, endPointHash);
+                }
+                securityParameters.m_tlsServerEndPoint = endPointHash.ToArray();
+
+                // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+                if (serverCertificate == null || serverCertificate.IsEmpty)
+                {
+                    securityParameters.m_statusRequestVersion = 0;
+                }
+            }
+
+            if (securityParameters.StatusRequestVersion > 0)
+            {
+                CertificateStatus certificateStatus = state.server.GetCertificateStatus();
+                if (certificateStatus != null)
+                {
+                    byte[] certificateStatusBody = GenerateCertificateStatus(state, certificateStatus);
+                    handshake.SendMessage(HandshakeType.certificate_status, certificateStatusBody);
+                }
+            }
+
+            byte[] serverKeyExchange = state.keyExchange.GenerateServerKeyExchange();
+            if (serverKeyExchange != null)
+            {
+                handshake.SendMessage(HandshakeType.server_key_exchange, serverKeyExchange);
+            }
+
+            if (state.serverCredentials != null)
+            {
+                state.certificateRequest = state.server.GetCertificateRequest();
+
+                if (null == state.certificateRequest)
+                {
+                    /*
+                     * For static agreement key exchanges, CertificateRequest is required since
+                     * the client Certificate message is mandatory but can only be sent if the
+                     * server requests it.
+                     */
+                    if (!state.keyExchange.RequiresCertificateVerify)
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+                else
+                {
+                    if (TlsUtilities.IsTlsV12(state.serverContext)
+                        != (state.certificateRequest.SupportedSignatureAlgorithms != null))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+                    }
+
+                    state.certificateRequest = TlsUtilities.ValidateCertificateRequest(state.certificateRequest, state.keyExchange);
+
+                    TlsUtilities.EstablishServerSigAlgs(securityParameters, state.certificateRequest);
+
+                    TlsUtilities.TrackHashAlgorithms(handshake.HandshakeHash, securityParameters.ServerSigAlgs);
+
+                    byte[] certificateRequestBody = GenerateCertificateRequest(state, state.certificateRequest);
+                    handshake.SendMessage(HandshakeType.certificate_request, certificateRequestBody);
+                }
+            }
+
+            handshake.SendMessage(HandshakeType.server_hello_done, TlsUtilities.EmptyBytes);
+
+            bool forceBuffering = false;
+            TlsUtilities.SealHandshakeHash(state.serverContext, handshake.HandshakeHash, forceBuffering);
+
+            clientMessage = handshake.ReceiveMessage();
+
+            if (clientMessage.Type == HandshakeType.supplemental_data)
+            {
+                ProcessClientSupplementalData(state, clientMessage.Body);
+                clientMessage = handshake.ReceiveMessage();
+            }
+            else
+            {
+                state.server.ProcessClientSupplementalData(null);
+            }
+
+            if (state.certificateRequest == null)
+            {
+                state.keyExchange.SkipClientCredentials();
+            }
+            else
+            {
+                if (clientMessage.Type == HandshakeType.certificate)
+                {
+                    ProcessClientCertificate(state, clientMessage.Body);
+                    clientMessage = handshake.ReceiveMessage();
+                }
+                else
+                {
+                    if (TlsUtilities.IsTlsV12(state.serverContext))
+                    {
+                        /*
+                         * RFC 5246 If no suitable certificate is available, the client MUST send a
+                         * certificate message containing no certificates.
+                         * 
+                         * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                         */
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    NotifyClientCertificate(state, Certificate.EmptyChain);
+                }
+            }
+
+            if (clientMessage.Type == HandshakeType.client_key_exchange)
+            {
+                ProcessClientKeyExchange(state, clientMessage.Body);
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            securityParameters.m_sessionHash = TlsUtilities.GetCurrentPrfHash(handshake.HandshakeHash);
+
+            TlsProtocol.EstablishMasterSecret(state.serverContext, state.keyExchange);
+            recordLayer.InitPendingEpoch(TlsUtilities.InitCipher(state.serverContext));
+
+            /*
+             * RFC 5246 7.4.8 This message is only sent following a client certificate that has signing
+             * capability (i.e., all certificates except those containing fixed Diffie-Hellman
+             * parameters).
+             */
+            {
+                TlsHandshakeHash certificateVerifyHash = handshake.PrepareToFinish();
+
+                if (ExpectCertificateVerifyMessage(state))
+                {
+                    byte[] certificateVerifyBody = handshake.ReceiveMessageBody(HandshakeType.certificate_verify);
+                    ProcessCertificateVerify(state, certificateVerifyBody, certificateVerifyHash);
+                }
+            }
+
+            // NOTE: Calculated exclusive of the actual Finished message from the client
+            securityParameters.m_peerVerifyData = TlsUtilities.CalculateVerifyData(state.serverContext,
+                handshake.HandshakeHash, false);
+            ProcessFinished(handshake.ReceiveMessageBody(HandshakeType.finished), securityParameters.PeerVerifyData);
+
+            if (state.expectSessionTicket)
+            {
+                NewSessionTicket newSessionTicket = state.server.GetNewSessionTicket();
+                byte[] newSessionTicketBody = GenerateNewSessionTicket(state, newSessionTicket);
+                handshake.SendMessage(HandshakeType.new_session_ticket, newSessionTicketBody);
+            }
+
+            // NOTE: Calculated exclusive of the Finished message itself
+            securityParameters.m_localVerifyData = TlsUtilities.CalculateVerifyData(state.serverContext,
+                handshake.HandshakeHash, true);
+            handshake.SendMessage(HandshakeType.finished, securityParameters.LocalVerifyData);
+
+            handshake.Finish();
+
+            state.sessionMasterSecret = securityParameters.MasterSecret;
+
+            state.sessionParameters = new SessionParameters.Builder()
+                .SetCipherSuite(securityParameters.CipherSuite)
+                .SetExtendedMasterSecret(securityParameters.IsExtendedMasterSecret)
+                .SetLocalCertificate(securityParameters.LocalCertificate)
+                .SetMasterSecret(state.serverContext.Crypto.AdoptSecret(state.sessionMasterSecret))
+                .SetNegotiatedVersion(securityParameters.NegotiatedVersion)
+                .SetPeerCertificate(securityParameters.PeerCertificate)
+                .SetPskIdentity(securityParameters.PskIdentity)
+                .SetSrpIdentity(securityParameters.SrpIdentity)
+                // TODO Consider filtering extensions that aren't relevant to resumed sessions
+                .SetServerExtensions(state.serverExtensions)
+                .Build();
+
+            state.tlsSession = TlsUtilities.ImportSession(state.tlsSession.SessionID, state.sessionParameters);
+
+            securityParameters.m_tlsUnique = securityParameters.PeerVerifyData;
+
+            state.serverContext.HandshakeComplete(state.server, state.tlsSession);
+
+            recordLayer.InitHeartbeat(state.heartbeat, HeartbeatMode.peer_allowed_to_send == state.heartbeatPolicy);
+
+            return new DtlsTransport(recordLayer);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual byte[] GenerateCertificateRequest(ServerHandshakeState state,
+            CertificateRequest certificateRequest)
+        {
+            MemoryStream buf = new MemoryStream();
+            certificateRequest.Encode(state.serverContext, buf);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual byte[] GenerateCertificateStatus(ServerHandshakeState state,
+            CertificateStatus certificateStatus)
+        {
+            MemoryStream buf = new MemoryStream();
+            // TODO[tls13] Ensure this cannot happen for (D)TLS1.3+
+            certificateStatus.Encode(buf);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual byte[] GenerateNewSessionTicket(ServerHandshakeState state,
+            NewSessionTicket newSessionTicket)
+        {
+            MemoryStream buf = new MemoryStream();
+            newSessionTicket.Encode(buf);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        internal virtual byte[] GenerateServerHello(ServerHandshakeState state, DtlsRecordLayer recordLayer)
+        {
+            TlsServerContextImpl context = state.serverContext;
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            ProtocolVersion server_version = state.server.GetServerVersion();
+            {
+                if (!ProtocolVersion.Contains(context.ClientSupportedVersions, server_version))
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                // TODO[dtls13] Read draft/RFC for guidance on the legacy_record_version field
+                //ProtocolVersion legacy_record_version = server_version.IsLaterVersionOf(ProtocolVersion.DTLSv12)
+                //    ? ProtocolVersion.DTLSv12
+                //    : server_version;
+
+                //recordLayer.SetWriteVersion(legacy_record_version);
+                securityParameters.m_negotiatedVersion = server_version;
+
+                TlsUtilities.NegotiatedVersionDtlsServer(context);
+            }
+
+            {
+                bool useGmtUnixTime = ProtocolVersion.DTLSv12.IsEqualOrLaterVersionOf(server_version)
+                    && state.server.ShouldUseGmtUnixTime();
+
+                securityParameters.m_serverRandom = TlsProtocol.CreateRandomBlock(useGmtUnixTime, context);
+
+                if (!server_version.Equals(ProtocolVersion.GetLatestDtls(state.server.GetProtocolVersions())))
+                {
+                    TlsUtilities.WriteDowngradeMarker(server_version, securityParameters.ServerRandom);
+                }
+            }
+
+            {
+                int cipherSuite = ValidateSelectedCipherSuite(state.server.GetSelectedCipherSuite(),
+                    AlertDescription.internal_error);
+
+                if (!TlsUtilities.IsValidCipherSuiteSelection(state.offeredCipherSuites, cipherSuite) ||
+                    !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, securityParameters.NegotiatedVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+            }
+
+            state.serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                state.server.GetServerExtensions());
+
+            state.server.GetServerExtensionsForConnection(state.serverExtensions);
+
+            ProtocolVersion legacy_version = server_version;
+            if (server_version.IsLaterVersionOf(ProtocolVersion.DTLSv12))
+            {
+                legacy_version = ProtocolVersion.DTLSv12;
+
+                TlsExtensionsUtilities.AddSupportedVersionsExtensionServer(state.serverExtensions, server_version);
+            }
+
+            /*
+             * RFC 5746 3.6. Server Behavior: Initial Handshake 
+             */
+            if (securityParameters.IsSecureRenegotiation)
+            {
+                byte[] renegExtData = TlsUtilities.GetExtensionData(state.serverExtensions,
+                    ExtensionType.renegotiation_info);
+                bool noRenegExt = (null == renegExtData);
+
+                if (noRenegExt)
+                {
+                    /*
+                     * Note that sending a "renegotiation_info" extension in response to a ClientHello
+                     * containing only the SCSV is an explicit exception to the prohibition in RFC 5246,
+                     * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed
+                     * because the client is signaling its willingness to receive the extension via the
+                     * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                     */
+
+                    /*
+                     * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty
+                     * "renegotiation_info" extension in the ServerHello message.
+                     */
+                    state.serverExtensions[ExtensionType.renegotiation_info] = TlsProtocol.CreateRenegotiationInfo(
+                        TlsUtilities.EmptyBytes);
+                }
+            }
+
+            /*
+             * RFC 7627 4. Clients and servers SHOULD NOT accept handshakes that do not use the extended
+             * master secret [..]. (and see 5.2, 5.3)
+             * 
+             * RFC 8446 Appendix D. Because TLS 1.3 always hashes in the transcript up to the server
+             * Finished, implementations which support both TLS 1.3 and earlier versions SHOULD indicate
+             * the use of the Extended Master Secret extension in their APIs whenever TLS 1.3 is used.
+             */
+            if (TlsUtilities.IsTlsV13(server_version))
+            {
+                securityParameters.m_extendedMasterSecret = true;
+            }
+            else
+            {
+                securityParameters.m_extendedMasterSecret = state.offeredExtendedMasterSecret
+                    && state.server.ShouldUseExtendedMasterSecret();
+
+                if (securityParameters.IsExtendedMasterSecret)
+                {
+                    TlsExtensionsUtilities.AddExtendedMasterSecretExtension(state.serverExtensions);
+                }
+                else if (state.server.RequiresExtendedMasterSecret())
+                {
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+                else if (state.resumedSession && !state.server.AllowLegacyResumption())
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+            }
+
+            // Heartbeats
+            if (null != state.heartbeat || HeartbeatMode.peer_allowed_to_send == state.heartbeatPolicy)
+            {
+                TlsExtensionsUtilities.AddHeartbeatExtension(state.serverExtensions,
+                    new HeartbeatExtension(state.heartbeatPolicy));
+            }
+
+
+
+            /*
+             * RFC 7301 3.1. When session resumption or session tickets [...] are used, the previous
+             * contents of this extension are irrelevant, and only the values in the new handshake
+             * messages are considered.
+             */
+            securityParameters.m_applicationProtocol = TlsExtensionsUtilities.GetAlpnExtensionServer(
+                state.serverExtensions);
+            securityParameters.m_applicationProtocolSet = true;
+
+            /*
+             * 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.
+             */
+            if (state.serverExtensions.Count > 0)
+            {
+                securityParameters.m_encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(
+                    state.serverExtensions);
+
+                securityParameters.m_maxFragmentLength = EvaluateMaxFragmentLengthExtension(state.resumedSession,
+                    state.clientExtensions, state.serverExtensions, AlertDescription.internal_error);
+
+                securityParameters.m_truncatedHmac = TlsExtensionsUtilities.HasTruncatedHmacExtension(state.serverExtensions);
+
+                /*
+                 * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in
+                 * a session resumption handshake.
+                 */
+                if (!state.resumedSession)
+                {
+                    // TODO[tls13] See RFC 8446 4.4.2.1
+                    if (TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions,
+                        ExtensionType.status_request_v2, AlertDescription.internal_error))
+                    {
+                        securityParameters.m_statusRequestVersion = 2;
+                    }
+                    else if (TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions,
+                        ExtensionType.status_request, AlertDescription.internal_error))
+                    {
+                        securityParameters.m_statusRequestVersion = 1;
+                    }
+                }
+
+                state.expectSessionTicket = !state.resumedSession
+                    && TlsUtilities.HasExpectedEmptyExtensionData(state.serverExtensions, ExtensionType.session_ticket,
+                        AlertDescription.internal_error);
+            }
+
+            ApplyMaxFragmentLengthExtension(recordLayer, securityParameters.MaxFragmentLength);
+
+
+
+            ServerHello serverHello = new ServerHello(legacy_version, securityParameters.ServerRandom,
+                state.tlsSession.SessionID, securityParameters.CipherSuite, state.serverExtensions);
+
+            MemoryStream buf = new MemoryStream();
+            serverHello.Encode(state.serverContext, buf);
+            return buf.ToArray();
+        }
+
+        protected virtual void InvalidateSession(ServerHandshakeState state)
+        {
+            if (state.sessionMasterSecret != null)
+            {
+                state.sessionMasterSecret.Destroy();
+                state.sessionMasterSecret = null;
+            }
+
+            if (state.sessionParameters != null)
+            {
+                state.sessionParameters.Clear();
+                state.sessionParameters = null;
+            }
+
+            if (state.tlsSession != null)
+            {
+                state.tlsSession.Invalidate();
+                state.tlsSession = null;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void NotifyClientCertificate(ServerHandshakeState state, Certificate clientCertificate)
+        {
+            if (null == state.certificateRequest)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            TlsUtilities.ProcessClientCertificate(state.serverContext, clientCertificate, state.keyExchange,
+                state.server);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessClientCertificate(ServerHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+
+            Certificate.ParseOptions options = new Certificate.ParseOptions()
+                .SetMaxChainLength(state.server.GetMaxCertificateChainLength());
+
+            Certificate clientCertificate = Certificate.Parse(options, state.serverContext, buf, null);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            NotifyClientCertificate(state, clientCertificate);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessCertificateVerify(ServerHandshakeState state, byte[] body,
+            TlsHandshakeHash handshakeHash)
+        {
+            if (state.certificateRequest == null)
+                throw new InvalidOperationException();
+
+            MemoryStream buf = new MemoryStream(body, false);
+
+            TlsServerContextImpl context = state.serverContext;
+            DigitallySigned certificateVerify = DigitallySigned.Parse(context, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            TlsUtilities.VerifyCertificateVerifyClient(context, state.certificateRequest, certificateVerify, handshakeHash);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessClientHello(ServerHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+            ClientHello clientHello = ClientHello.Parse(buf, new NullOutputStream());
+            ProcessClientHello(state, clientHello);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessClientHello(ServerHandshakeState state, ClientHello clientHello)
+        {
+            // TODO Read RFCs for guidance on the expected record layer version number
+            ProtocolVersion legacy_version = clientHello.Version;
+            state.offeredCipherSuites = clientHello.CipherSuites;
+
+            /*
+             * 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.
+             */
+            state.clientExtensions = clientHello.Extensions;
+
+
+
+            TlsServerContextImpl context = state.serverContext;
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            if (!legacy_version.IsDtls)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            context.SetRsaPreMasterSecretVersion(legacy_version);
+
+            context.SetClientSupportedVersions(
+                TlsExtensionsUtilities.GetSupportedVersionsExtensionClient(state.clientExtensions));
+
+            ProtocolVersion client_version = legacy_version;
+            if (null == context.ClientSupportedVersions)
+            {
+                if (client_version.IsLaterVersionOf(ProtocolVersion.DTLSv12))
+                {
+                    client_version = ProtocolVersion.DTLSv12;
+                }
+
+                context.SetClientSupportedVersions(client_version.DownTo(ProtocolVersion.DTLSv10));
+            }
+            else
+            {
+                client_version = ProtocolVersion.GetLatestDtls(context.ClientSupportedVersions);
+            }
+
+            if (!ProtocolVersion.SERVER_EARLIEST_SUPPORTED_DTLS.IsEqualOrEarlierVersionOf(client_version))
+                throw new TlsFatalAlert(AlertDescription.protocol_version);
+
+            context.SetClientVersion(client_version);
+
+            state.server.NotifyClientVersion(context.ClientVersion);
+
+            securityParameters.m_clientRandom = clientHello.Random;
+
+            state.server.NotifyFallback(Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV));
+
+            state.server.NotifyOfferedCipherSuites(state.offeredCipherSuites);
+
+            /*
+             * TODO[resumption] Check RFC 7627 5.4. for required behaviour 
+             */
+
+            /*
+             * RFC 5746 3.6. Server Behavior: Initial Handshake
+             */
+            {
+                /*
+                 * 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.
+                 */
+
+                /*
+                 * When a ClientHello is received, the server MUST check if it includes the
+                 * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag
+                 * to TRUE.
+                 */
+                if (Arrays.Contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV))
+                {
+                    securityParameters.m_secureRenegotiation = true;
+                }
+
+                /*
+                 * The server MUST check if the "renegotiation_info" extension is included in the
+                 * ClientHello.
+                 */
+                byte[] renegExtData = TlsUtilities.GetExtensionData(state.clientExtensions,
+                    ExtensionType.renegotiation_info);
+                if (renegExtData != null)
+                {
+                    /*
+                     * If the extension is present, set secure_renegotiation flag to TRUE. The
+                     * server MUST then verify that the length of the "renegotiated_connection"
+                     * field is zero, and if it is not, MUST abort the handshake.
+                     */
+                    securityParameters.m_secureRenegotiation = true;
+
+                    if (!Arrays.ConstantTimeAreEqual(renegExtData,
+                        TlsProtocol.CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+            }
+
+            state.server.NotifySecureRenegotiation(securityParameters.IsSecureRenegotiation);
+
+            state.offeredExtendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(
+                state.clientExtensions);
+
+            if (state.clientExtensions != null)
+            {
+                // NOTE: Validates the padding extension data, if present
+                TlsExtensionsUtilities.GetPaddingExtension(state.clientExtensions);
+
+                securityParameters.m_clientServerNames = TlsExtensionsUtilities.GetServerNameExtensionClient(
+                    state.clientExtensions);
+
+                /*
+                 * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior
+                 * to 1.2. Clients MUST NOT offer it if they are offering prior versions.
+                 */
+                if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(client_version))
+                {
+                    TlsUtilities.EstablishClientSigAlgs(securityParameters, state.clientExtensions);
+                }
+
+                securityParameters.m_clientSupportedGroups = TlsExtensionsUtilities.GetSupportedGroupsExtension(
+                    state.clientExtensions);
+
+                // Heartbeats
+                {
+                    HeartbeatExtension heartbeatExtension = TlsExtensionsUtilities.GetHeartbeatExtension(
+                        state.clientExtensions);
+                    if (null != heartbeatExtension)
+                    {
+                        if (HeartbeatMode.peer_allowed_to_send == heartbeatExtension.Mode)
+                        {
+                            state.heartbeat = state.server.GetHeartbeat();
+                        }
+
+                        state.heartbeatPolicy = state.server.GetHeartbeatPolicy();
+                    }
+                }
+
+                state.server.ProcessClientExtensions(state.clientExtensions);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessClientKeyExchange(ServerHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+            state.keyExchange.ProcessClientKeyExchange(buf);
+            TlsProtocol.AssertEmpty(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessClientSupplementalData(ServerHandshakeState state, byte[] body)
+        {
+            MemoryStream buf = new MemoryStream(body, false);
+            IList clientSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf);
+            state.server.ProcessClientSupplementalData(clientSupplementalData);
+        }
+
+        protected virtual bool ExpectCertificateVerifyMessage(ServerHandshakeState state)
+        {
+            if (null == state.certificateRequest)
+                return false;
+
+            Certificate clientCertificate = state.serverContext.SecurityParameters.PeerCertificate;
+
+            return null != clientCertificate && !clientCertificate.IsEmpty
+                && (null == state.keyExchange || state.keyExchange.RequiresCertificateVerify);
+        }
+
+        protected internal class ServerHandshakeState
+        {
+            internal TlsServer server = null;
+            internal TlsServerContextImpl serverContext = null;
+            internal TlsSession tlsSession = null;
+            internal SessionParameters sessionParameters = null;
+            internal TlsSecret sessionMasterSecret = null;
+            internal SessionParameters.Builder sessionParametersBuilder = null;
+            internal int[] offeredCipherSuites = null;
+            internal IDictionary clientExtensions = null;
+            internal IDictionary serverExtensions = null;
+            internal bool offeredExtendedMasterSecret = false;
+            internal bool resumedSession = false;
+            internal bool expectSessionTicket = false;
+            internal TlsKeyExchange keyExchange = null;
+            internal TlsCredentials serverCredentials = null;
+            internal CertificateRequest certificateRequest = null;
+            internal TlsHeartbeat heartbeat = null;
+            internal short heartbeatPolicy = HeartbeatMode.peer_not_allowed_to_send;
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsTransport.cs b/crypto/src/tls/DtlsTransport.cs
new file mode 100644
index 000000000..a41cb7866
--- /dev/null
+++ b/crypto/src/tls/DtlsTransport.cs
@@ -0,0 +1,139 @@
+using System;
+using System.IO;
+#if !PORTABLE || DOTNET
+using System.Net.Sockets;
+#endif
+
+namespace Org.BouncyCastle.Tls
+{
+    public class DtlsTransport
+        : DatagramTransport
+    {
+        private readonly DtlsRecordLayer m_recordLayer;
+
+        internal DtlsTransport(DtlsRecordLayer recordLayer)
+        {
+            this.m_recordLayer = recordLayer;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual int GetReceiveLimit()
+        {
+            return m_recordLayer.GetReceiveLimit();
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual int GetSendLimit()
+        {
+            return m_recordLayer.GetSendLimit();
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+        {
+            if (null == buf)
+                throw new ArgumentNullException("buf");
+            if (off < 0 || off >= buf.Length)
+                throw new ArgumentException("invalid offset: " + off, "off");
+            if (len < 0 || len > buf.Length - off)
+                throw new ArgumentException("invalid length: " + len, "len");
+            if (waitMillis < 0)
+                throw new ArgumentException("cannot be negative", "waitMillis");
+
+            try
+            {
+                return m_recordLayer.Receive(buf, off, len, waitMillis);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                m_recordLayer.Fail(fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (TlsTimeoutException e)
+            {
+                throw e;
+            }
+#if !PORTABLE || DOTNET
+            catch (SocketException e)
+            {
+                if (TlsUtilities.IsTimeout(e))
+                    throw e;
+
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+#endif
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //catch (InterruptedIOException e)
+            //{
+            //    throw e;
+            //}
+            catch (IOException e)
+            {
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void Send(byte[] buf, int off, int len)
+        {
+            if (null == buf)
+                throw new ArgumentNullException("buf");
+            if (off < 0 || off >= buf.Length)
+                throw new ArgumentException("invalid offset: " + off, "off");
+            if (len < 0 || len > buf.Length - off)
+                throw new ArgumentException("invalid length: " + len, "len");
+
+            try
+            {
+                m_recordLayer.Send(buf, off, len);
+            }
+            catch (TlsFatalAlert fatalAlert)
+            {
+                m_recordLayer.Fail(fatalAlert.AlertDescription);
+                throw fatalAlert;
+            }
+            catch (TlsTimeoutException e)
+            {
+                throw e;
+            }
+#if !PORTABLE || DOTNET
+            catch (SocketException e)
+            {
+                if (TlsUtilities.IsTimeout(e))
+                    throw e;
+
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+#endif
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //catch (InterruptedIOException e)
+            //{
+            //    throw e;
+            //}
+            catch (IOException e)
+            {
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                m_recordLayer.Fail(AlertDescription.internal_error);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void Close()
+        {
+            m_recordLayer.Close();
+        }
+    }
+}
diff --git a/crypto/src/tls/DtlsVerifier.cs b/crypto/src/tls/DtlsVerifier.cs
new file mode 100644
index 000000000..edadeae70
--- /dev/null
+++ b/crypto/src/tls/DtlsVerifier.cs
@@ -0,0 +1,89 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class DtlsVerifier
+    {
+        private static TlsMac CreateCookieMac(TlsCrypto crypto)
+        {
+            TlsMac mac = crypto.CreateHmac(MacAlgorithm.hmac_sha256);
+
+            byte[] secret = new byte[mac.MacLength];
+            crypto.SecureRandom.NextBytes(secret);
+
+            mac.SetKey(secret, 0, secret.Length);
+
+            return mac;
+        }
+
+        private readonly TlsMac m_cookieMac;
+        private readonly TlsMacSink m_cookieMacSink;
+
+        public DtlsVerifier(TlsCrypto crypto)
+        {
+            this.m_cookieMac = CreateCookieMac(crypto);
+            this.m_cookieMacSink = new TlsMacSink(m_cookieMac);
+        }
+
+        public virtual DtlsRequest VerifyRequest(byte[] clientID, byte[] data, int dataOff, int dataLen,
+            DatagramSender sender)
+        {
+            lock (this)
+            {
+                bool resetCookieMac = true;
+
+                try
+                {
+                    m_cookieMac.Update(clientID, 0, clientID.Length);
+
+                    DtlsRequest request = DtlsReliableHandshake.ReadClientRequest(data, dataOff, dataLen,
+                        m_cookieMacSink);
+                    if (null != request)
+                    {
+                        byte[] expectedCookie = m_cookieMac.CalculateMac();
+                        resetCookieMac = false;
+
+                        // TODO Consider stricter HelloVerifyRequest protocol
+                        //switch (request.MessageSeq)
+                        //{
+                        //case 0:
+                        //{
+                        //    DtlsReliableHandshake.SendHelloVerifyRequest(sender, request.RecordSeq, expectedCookie);
+                        //    break;
+                        //}
+                        //case 1:
+                        //{
+                        //    if (Arrays.ConstantTimeAreEqual(expectedCookie, request.ClientHello.Cookie))
+                        //        return request;
+
+                        //    break;
+                        //}
+                        //}
+
+                        if (Arrays.ConstantTimeAreEqual(expectedCookie, request.ClientHello.Cookie))
+                            return request;
+
+                        DtlsReliableHandshake.SendHelloVerifyRequest(sender, request.RecordSeq, expectedCookie);
+                    }
+                }
+                catch (IOException)
+                {
+                    // Ignore
+                }
+                finally
+                {
+                    if (resetCookieMac)
+                    {
+                        m_cookieMac.Reset();
+                    }
+                }
+
+                return null;
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/ECCurveType.cs b/crypto/src/tls/ECCurveType.cs
new file mode 100644
index 000000000..969d51b98
--- /dev/null
+++ b/crypto/src/tls/ECCurveType.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 4492 5.4</summary>
+    public abstract class ECCurveType
+    {
+        /**
+         * Indicates the elliptic curve domain parameters are conveyed verbosely, and the
+         * underlying finite field is a prime field.
+         */
+        public const short explicit_prime = 1;
+
+        /**
+         * Indicates the elliptic curve domain parameters are conveyed verbosely, and the
+         * underlying finite field is a characteristic-2 field.
+         */
+        public const short explicit_char2 = 2;
+
+        /**
+         * Indicates that a named curve is used. This option SHOULD be used when applicable.
+         */
+        public const short named_curve = 3;
+
+        /*
+         * Values 248 through 255 are reserved for private use.
+         */
+    }
+}
diff --git a/crypto/src/tls/ECPointFormat.cs b/crypto/src/tls/ECPointFormat.cs
new file mode 100644
index 000000000..d399cc604
--- /dev/null
+++ b/crypto/src/tls/ECPointFormat.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 4492 5.1.2</summary>
+    public abstract class ECPointFormat
+    {
+        public const short uncompressed = 0;
+        public const short ansiX962_compressed_prime = 1;
+        public const short ansiX962_compressed_char2 = 2;
+
+        /*
+         * reserved (248..255)
+         */
+    }
+}
diff --git a/crypto/src/tls/EncryptionAlgorithm.cs b/crypto/src/tls/EncryptionAlgorithm.cs
new file mode 100644
index 000000000..8064451ab
--- /dev/null
+++ b/crypto/src/tls/EncryptionAlgorithm.cs
@@ -0,0 +1,82 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 2246</summary>
+    /// <remarks>
+    /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the
+    /// particular values (e.g. serialization).
+    /// </remarks>
+    public abstract class EncryptionAlgorithm
+    {
+        public const int NULL = 0;
+        public const int RC4_40 = 1;
+        public const int RC4_128 = 2;
+        public const int RC2_CBC_40 = 3;
+        public const int IDEA_CBC = 4;
+        public const int DES40_CBC = 5;
+        public const int DES_CBC = 6;
+        public const int cls_3DES_EDE_CBC = 7;
+
+        /*
+         * RFC 3268
+         */
+        public const int AES_128_CBC = 8;
+        public const int AES_256_CBC = 9;
+
+        /*
+         * RFC 5289
+         */
+        public const int AES_128_GCM = 10;
+        public const int AES_256_GCM = 11;
+
+        /*
+         * RFC 5932
+         */
+        public const int CAMELLIA_128_CBC = 12;
+        public const int CAMELLIA_256_CBC = 13;
+
+        /*
+         * RFC 4162
+         */
+        public const int SEED_CBC = 14;
+
+        /*
+         * RFC 6655
+         */
+        public const int AES_128_CCM = 15;
+        public const int AES_128_CCM_8 = 16;
+        public const int AES_256_CCM = 17;
+        public const int AES_256_CCM_8 = 18;
+
+        /*
+         * RFC 6367
+         */
+        public const int CAMELLIA_128_GCM = 19;
+        public const int CAMELLIA_256_GCM = 20;
+
+        /*
+         * RFC 7905
+         */
+        public const int CHACHA20_POLY1305 = 21;
+
+        /*
+         * RFC 6209
+         */
+        public const int ARIA_128_CBC = 22;
+        public const int ARIA_256_CBC = 23;
+        public const int ARIA_128_GCM = 24;
+        public const int ARIA_256_GCM = 25;
+
+        /*
+         * RFC 8998
+         */
+        public const int SM4_CCM = 26;
+        public const int SM4_GCM = 27;
+
+        /*
+         * GMT 0024-2014
+         */
+        public const int SM4_CBC = 28;
+    }
+}
diff --git a/crypto/src/tls/ExporterLabel.cs b/crypto/src/tls/ExporterLabel.cs
new file mode 100644
index 000000000..481a3eed8
--- /dev/null
+++ b/crypto/src/tls/ExporterLabel.cs
@@ -0,0 +1,42 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5705</summary>
+    public abstract class ExporterLabel
+    {
+        /*
+         * RFC 5246
+         */
+        public const string client_finished = "client finished";
+        public const string server_finished = "server finished";
+        public const string master_secret = "master secret";
+        public const string key_expansion = "key expansion";
+
+        /*
+         * RFC 5216
+         */
+        public const string client_EAP_encryption = "client EAP encryption";
+
+        /*
+         * RFC 5281
+         */
+        public const string ttls_keying_material = "ttls keying material";
+        public const string ttls_challenge = "ttls challenge";
+
+        /*
+         * RFC 5764
+         */
+        public const string dtls_srtp = "EXTRACTOR-dtls_srtp";
+
+        /*
+         * RFC 7627
+         */
+        public const string extended_master_secret = "extended master secret";
+
+        /*
+         * draft-ietf-tokbind-protocol-16
+         */
+        public const string token_binding = "EXPORTER-Token-Binding";
+    }
+}
diff --git a/crypto/src/tls/ExtensionType.cs b/crypto/src/tls/ExtensionType.cs
new file mode 100644
index 000000000..87f6a7574
--- /dev/null
+++ b/crypto/src/tls/ExtensionType.cs
@@ -0,0 +1,279 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class ExtensionType
+    {
+        /*
+         * RFC 2546 2.3.
+         */
+        public const int server_name = 0;
+        public const int max_fragment_length = 1;
+        public const int client_certificate_url = 2;
+        public const int trusted_ca_keys = 3;
+        public const int truncated_hmac = 4;
+        public const int status_request = 5;
+
+        /*
+         * RFC 4681
+         */
+        public const int user_mapping = 6;
+
+        /*
+         * RFC 5878
+         */
+        public const int client_authz = 7;
+        public const int server_authz = 8;
+
+        /*
+         * RFC 6091
+         */
+        public const int cert_type = 9;
+
+        /*
+         * RFC 7919 (originally 'elliptic_curves' from RFC 4492)
+         */
+        public const int supported_groups = 10;
+
+        /*
+         * RFC 4492 5.1.
+         */
+        public const int ec_point_formats = 11;
+
+        /*
+         * RFC 5054 2.8.1.
+         */
+        public const int srp = 12;
+
+        /*
+         * RFC 5246 7.4.1.4.
+         */
+        public const int signature_algorithms = 13;
+
+        /*
+         * RFC 5764 9.
+         */
+        public const int use_srtp = 14;
+
+        /*
+         * RFC 6520 6.
+         */
+        public const int heartbeat = 15;
+
+        /*
+         * RFC 7301
+         */
+        public const int application_layer_protocol_negotiation = 16;
+
+        /*
+         * RFC 6961
+         */
+        public const int status_request_v2 = 17;
+
+        /*
+         * RFC 6962
+         */
+        public const int signed_certificate_timestamp = 18;
+
+        /*
+         * RFC 7250
+         */
+        public const int client_certificate_type = 19;
+        public const int server_certificate_type = 20;
+
+        /*
+         * RFC 7685
+         */
+        public const int padding = 21;
+
+        /*
+         * RFC 7366
+         */
+        public const int encrypt_then_mac = 22;
+
+        /*
+         * RFC 7627
+         */
+        public const int extended_master_secret = 23;
+
+        /*
+         * RFC 8472
+         */
+        public const int token_binding = 24;
+
+        /*
+         * RFC 7924
+         */
+        public const int cached_info = 25;
+
+        /*
+         * RFC 8449
+         */
+        public const int record_size_limit = 28;
+
+        /*
+         * RFC 5077 7.
+         */
+        public const int session_ticket = 35;
+
+        /*
+         * RFC 8446
+         */
+        public const int pre_shared_key = 41;
+        public const int early_data = 42;
+        public const int supported_versions = 43;
+        public const int cookie = 44;
+        public const int psk_key_exchange_modes = 45;
+        public const int certificate_authorities = 47;
+        public const int oid_filters = 48;
+        public const int post_handshake_auth = 49;
+        public const int signature_algorithms_cert = 50;
+        public const int key_share = 51;
+
+        /*
+         * RFC 5746 3.2.
+         */
+        public const int renegotiation_info = 0xff01;
+
+        public static string GetName(int extensionType)
+        {
+            switch (extensionType)
+            {
+            case server_name:
+                return "server_name";
+            case max_fragment_length:
+                return "max_fragment_length";
+            case client_certificate_url:
+                return "client_certificate_url";
+            case trusted_ca_keys:
+                return "trusted_ca_keys";
+            case truncated_hmac:
+                return "truncated_hmac";
+            case status_request:
+                return "status_request";
+            case user_mapping:
+                return "user_mapping";
+            case client_authz:
+                return "client_authz";
+            case server_authz:
+                return "server_authz";
+            case cert_type:
+                return "cert_type";
+            case supported_groups:
+                return "supported_groups";
+            case ec_point_formats:
+                return "ec_point_formats";
+            case srp:
+                return "srp";
+            case signature_algorithms:
+                return "signature_algorithms";
+            case use_srtp:
+                return "use_srtp";
+            case heartbeat:
+                return "heartbeat";
+            case application_layer_protocol_negotiation:
+                return "application_layer_protocol_negotiation";
+            case status_request_v2:
+                return "status_request_v2";
+            case signed_certificate_timestamp:
+                return "signed_certificate_timestamp";
+            case client_certificate_type:
+                return "client_certificate_type";
+            case server_certificate_type:
+                return "server_certificate_type";
+            case padding:
+                return "padding";
+            case encrypt_then_mac:
+                return "encrypt_then_mac";
+            case extended_master_secret:
+                return "extended_master_secret";
+            case token_binding:
+                return "token_binding";
+            case cached_info:
+                return "cached_info";
+            case record_size_limit:
+                return "record_size_limit";
+            case session_ticket:
+                return "session_ticket";
+            case pre_shared_key:
+                return "pre_shared_key";
+            case early_data:
+                return "early_data";
+            case supported_versions:
+                return "supported_versions";
+            case cookie:
+                return "cookie";
+            case psk_key_exchange_modes:
+                return "psk_key_exchange_modes";
+            case certificate_authorities:
+                return "certificate_authorities";
+            case oid_filters:
+                return "oid_filters";
+            case post_handshake_auth:
+                return "post_handshake_auth";
+            case signature_algorithms_cert:
+                return "signature_algorithms_cert";
+            case key_share:
+                return "key_share";
+            case renegotiation_info:
+                return "renegotiation_info";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(int extensionType)
+        {
+            return GetName(extensionType) + "(" + extensionType + ")";
+        }
+
+        public static bool IsRecognized(int extensionType)
+        {
+            switch (extensionType)
+            {
+            case server_name:
+            case max_fragment_length:
+            case client_certificate_url:
+            case trusted_ca_keys:
+            case truncated_hmac:
+            case status_request:
+            case user_mapping:
+            case client_authz:
+            case server_authz:
+            case cert_type:
+            case supported_groups:
+            case ec_point_formats:
+            case srp:
+            case signature_algorithms:
+            case use_srtp:
+            case heartbeat:
+            case application_layer_protocol_negotiation:
+            case status_request_v2:
+            case signed_certificate_timestamp:
+            case client_certificate_type:
+            case server_certificate_type:
+            case padding:
+            case encrypt_then_mac:
+            case extended_master_secret:
+            case token_binding:
+            case cached_info:
+            case record_size_limit:
+            case session_ticket:
+            case pre_shared_key:
+            case early_data:
+            case supported_versions:
+            case cookie:
+            case psk_key_exchange_modes:
+            case certificate_authorities:
+            case oid_filters:
+            case post_handshake_auth:
+            case signature_algorithms_cert:
+            case key_share:
+            case renegotiation_info:
+                return true;
+            default:
+                return false;
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/HandshakeMessageInput.cs b/crypto/src/tls/HandshakeMessageInput.cs
new file mode 100644
index 000000000..d7cd19994
--- /dev/null
+++ b/crypto/src/tls/HandshakeMessageInput.cs
@@ -0,0 +1,22 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class HandshakeMessageInput
+        : MemoryStream
+    {
+        internal HandshakeMessageInput(byte[] buf, int offset, int length)
+            : base(buf, offset, length, false)
+        {
+        }
+
+        public void UpdateHash(TlsHash hash)
+        {
+            Streams.WriteBufTo(this, new TlsHashSink(hash));
+        }
+    }
+}
diff --git a/crypto/src/tls/HandshakeMessageOutput.cs b/crypto/src/tls/HandshakeMessageOutput.cs
new file mode 100644
index 000000000..ae07b9682
--- /dev/null
+++ b/crypto/src/tls/HandshakeMessageOutput.cs
@@ -0,0 +1,62 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal sealed class HandshakeMessageOutput
+        : MemoryStream
+    {
+        internal static int GetLength(int bodyLength)
+        {
+            return 4 + bodyLength;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void Send(TlsProtocol protocol, short handshakeType, byte[] body)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(handshakeType, body.Length);
+            message.Write(body, 0, body.Length);
+            message.Send(protocol);
+        }
+
+        /// <exception cref="IOException"/>
+        internal HandshakeMessageOutput(short handshakeType)
+            : this(handshakeType, 60)
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        internal HandshakeMessageOutput(short handshakeType, int bodyLength)
+            : base(GetLength(bodyLength))
+        {
+            TlsUtilities.CheckUint8(handshakeType);
+            TlsUtilities.WriteUint8(handshakeType, this);
+            // Reserve space for length
+            Seek(3L, SeekOrigin.Current);
+        }
+
+        /// <exception cref="IOException"/>
+        internal void Send(TlsProtocol protocol)
+        {
+            // Patch actual length back in
+            int bodyLength = (int)Length - 4;
+            TlsUtilities.CheckUint24(bodyLength);
+
+            Seek(1L, SeekOrigin.Begin);
+            TlsUtilities.WriteUint24(bodyLength, this);
+
+#if PORTABLE
+            byte[] buf = ToArray();
+            int count = buf.Length;
+#else
+            byte[] buf = GetBuffer();
+            int count = (int)Length;
+#endif
+            protocol.WriteHandshakeMessage(buf, 0, count);
+
+            Platform.Dispose(this);
+        }
+    }
+}
diff --git a/crypto/src/tls/HandshakeType.cs b/crypto/src/tls/HandshakeType.cs
new file mode 100644
index 000000000..563cd1150
--- /dev/null
+++ b/crypto/src/tls/HandshakeType.cs
@@ -0,0 +1,131 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class HandshakeType
+    {
+        /*
+         * RFC 2246 7.4
+         */
+        public const short hello_request = 0;
+        public const short client_hello = 1;
+        public const short server_hello = 2;
+        public const short certificate = 11;
+        public const short server_key_exchange = 12;
+        public const short certificate_request = 13;
+        public const short server_hello_done = 14;
+        public const short certificate_verify = 15;
+        public const short client_key_exchange = 16;
+        public const short finished = 20;
+
+        /*
+         * RFC 3546 2.4
+         */
+        public const short certificate_url = 21;
+        public const short certificate_status = 22;
+
+        /*
+         * (DTLS) RFC 4347 4.3.2
+         */
+        public const short hello_verify_request = 3;
+
+        /*
+         * RFC 4680
+         */
+        public const short supplemental_data = 23;
+
+        /*
+         * RFC 8446
+         */
+        public const short new_session_ticket = 4;
+        public const short end_of_early_data = 5;
+        public const short hello_retry_request = 6;
+        public const short encrypted_extensions = 8;
+        public const short key_update = 24;
+        public const short message_hash = 254;
+
+        public static string GetName(short handshakeType)
+        {
+            switch (handshakeType)
+            {
+            case hello_request:
+                return "hello_request";
+            case client_hello:
+                return "client_hello";
+            case server_hello:
+                return "server_hello";
+            case certificate:
+                return "certificate";
+            case server_key_exchange:
+                return "server_key_exchange";
+            case certificate_request:
+                return "certificate_request";
+            case server_hello_done:
+                return "server_hello_done";
+            case certificate_verify:
+                return "certificate_verify";
+            case client_key_exchange:
+                return "client_key_exchange";
+            case finished:
+                return "finished";
+            case certificate_url:
+                return "certificate_url";
+            case certificate_status:
+                return "certificate_status";
+            case hello_verify_request:
+                return "hello_verify_request";
+            case supplemental_data:
+                return "supplemental_data";
+            case new_session_ticket:
+                return "new_session_ticket";
+            case end_of_early_data:
+                return "end_of_early_data";
+            case hello_retry_request:
+                return "hello_retry_request";
+            case encrypted_extensions:
+                return "encrypted_extensions";
+            case key_update:
+                return "key_update";
+            case message_hash:
+                return "message_hash";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short handshakeType)
+        {
+            return GetName(handshakeType) + "(" + handshakeType + ")";
+        }
+
+        public static bool IsRecognized(short handshakeType)
+        {
+            switch (handshakeType)
+            {
+            case hello_request:
+            case client_hello:
+            case server_hello:
+            case certificate:
+            case server_key_exchange:
+            case certificate_request:
+            case server_hello_done:
+            case certificate_verify:
+            case client_key_exchange:
+            case finished:
+            case certificate_url:
+            case certificate_status:
+            case hello_verify_request:
+            case supplemental_data:
+            case new_session_ticket:
+            case end_of_early_data:
+            case hello_retry_request:
+            case encrypted_extensions:
+            case key_update:
+            case message_hash:
+                return true;
+            default:
+                return false;
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/HashAlgorithm.cs b/crypto/src/tls/HashAlgorithm.cs
new file mode 100644
index 000000000..2c8ba4fff
--- /dev/null
+++ b/crypto/src/tls/HashAlgorithm.cs
@@ -0,0 +1,94 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5246 7.4.1.4.1</summary>
+    public abstract class HashAlgorithm
+    {
+        public const short none = 0;
+        public const short md5 = 1;
+        public const short sha1 = 2;
+        public const short sha224 = 3;
+        public const short sha256 = 4;
+        public const short sha384 = 5;
+        public const short sha512 = 6;
+
+        /*
+         * RFC 8422
+         */
+        public const short Intrinsic = 8;
+
+        public static string GetName(short hashAlgorithm)
+        {
+            switch (hashAlgorithm)
+            {
+            case none:
+                return "none";
+            case md5:
+                return "md5";
+            case sha1:
+                return "sha1";
+            case sha224:
+                return "sha224";
+            case sha256:
+                return "sha256";
+            case sha384:
+                return "sha384";
+            case sha512:
+                return "sha512";
+            case Intrinsic:
+                return "Intrinsic";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static int GetOutputSize(short hashAlgorithm)
+        {
+            switch (hashAlgorithm)
+            {
+            case md5:
+                return 16;
+            case sha1:
+                return 20;
+            case sha224:
+                return 28;
+            case sha256:
+                return 32;
+            case sha384:
+                return 48;
+            case sha512:
+                return 64;
+            default:
+                return -1;
+            }
+        }
+
+        public static string GetText(short hashAlgorithm)
+        {
+            return GetName(hashAlgorithm) + "(" + hashAlgorithm + ")";
+        }
+
+        public static bool IsPrivate(short hashAlgorithm)
+        {
+            return 224 <= hashAlgorithm && hashAlgorithm <= 255;
+        }
+
+        public static bool IsRecognized(short hashAlgorithm)
+        {
+            switch (hashAlgorithm)
+            {
+                case md5:
+                case sha1:
+                case sha224:
+                case sha256:
+                case sha384:
+                case sha512:
+                case Intrinsic:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/HeartbeatExtension.cs b/crypto/src/tls/HeartbeatExtension.cs
new file mode 100644
index 000000000..c44d84a67
--- /dev/null
+++ b/crypto/src/tls/HeartbeatExtension.cs
@@ -0,0 +1,44 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class HeartbeatExtension
+    {
+        private readonly short m_mode;
+
+        public HeartbeatExtension(short mode)
+        {
+            if (!HeartbeatMode.IsValid(mode))
+                throw new ArgumentException("not a valid HeartbeatMode value", "mode");
+
+            this.m_mode = mode;
+        }
+
+        public short Mode
+        {
+            get { return m_mode; }
+        }
+
+        /// <summary>Encode this <see cref="HeartbeatExtension"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(m_mode, output);
+        }
+
+        /// <summary>Parse a <see cref="HeartbeatExtension"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="HeartbeatExtension"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static HeartbeatExtension Parse(Stream input)
+        {
+            short mode = TlsUtilities.ReadUint8(input);
+            if (!HeartbeatMode.IsValid(mode))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return new HeartbeatExtension(mode);
+        }
+    }
+}
diff --git a/crypto/src/tls/HeartbeatMessage.cs b/crypto/src/tls/HeartbeatMessage.cs
new file mode 100644
index 000000000..9e5c7d14e
--- /dev/null
+++ b/crypto/src/tls/HeartbeatMessage.cs
@@ -0,0 +1,118 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class HeartbeatMessage
+    {
+        public static HeartbeatMessage Create(TlsContext context, short type, byte[] payload)
+        {
+            return Create(context, type, payload, 16);
+        }
+
+        public static HeartbeatMessage Create(TlsContext context, short type, byte[] payload, int paddingLength)
+        {
+            byte[] padding = context.NonceGenerator.GenerateNonce(paddingLength);
+
+            return new HeartbeatMessage(type, payload, padding);
+        }
+
+        private readonly short m_type;
+        private readonly byte[] m_payload;
+        private readonly byte[] m_padding;
+
+        public HeartbeatMessage(short type, byte[] payload, byte[] padding)
+        {
+            if (!HeartbeatMessageType.IsValid(type))
+                throw new ArgumentException("not a valid HeartbeatMessageType value", "type");
+            if (null == payload || payload.Length >= (1 << 16))
+                throw new ArgumentException("must have length < 2^16", "payload");
+            if (null == padding || padding.Length < 16)
+                throw new ArgumentException("must have length >= 16", "padding");
+
+            this.m_type = type;
+            this.m_payload = payload;
+            this.m_padding = padding;
+        }
+
+        public int PaddingLength
+        {
+            /*
+             * RFC 6520 4. The padding of a received HeartbeatMessage message MUST be ignored
+             */
+            get { return m_padding.Length; }
+        }
+
+        public byte[] Payload
+        {
+            get { return m_payload; }
+        }
+
+        public short Type
+        {
+            get { return m_type; }
+        }
+
+        /// <summary>Encode this <see cref="HeartbeatMessage"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(m_type, output);
+
+            TlsUtilities.CheckUint16(m_payload.Length);
+            TlsUtilities.WriteUint16(m_payload.Length, output);
+            output.Write(m_payload, 0, m_payload.Length);
+
+            output.Write(m_padding, 0, m_padding.Length);
+        }
+
+        /// <summary>Parse a <see cref="HeartbeatMessage"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="HeartbeatMessage"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static HeartbeatMessage Parse(Stream input)
+        {
+            short type = TlsUtilities.ReadUint8(input);
+            if (!HeartbeatMessageType.IsValid(type))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            int payload_length = TlsUtilities.ReadUint16(input);
+            byte[] payloadBuffer = Streams.ReadAll(input);
+
+            byte[] payload = GetPayload(payloadBuffer, payload_length);
+            if (null == payload)
+            {
+                /*
+                 * RFC 6520 4. If the payload_length of a received HeartbeatMessage is too large, the received
+                 * HeartbeatMessage MUST be discarded silently.
+                 */
+                return null;
+            }
+
+            byte[] padding = GetPadding(payloadBuffer, payload_length);
+
+            return new HeartbeatMessage(type, payload, padding);
+        }
+
+        private static byte[] GetPayload(byte[] payloadBuffer, int payloadLength)
+        {
+            /*
+             * RFC 6520 4. The padding_length MUST be at least 16.
+             */
+            int maxPayloadLength = payloadBuffer.Length - 16;
+            if (payloadLength > maxPayloadLength)
+                return null;
+
+            return Arrays.CopyOf(payloadBuffer, payloadLength);
+        }
+
+        private static byte[] GetPadding(byte[] payloadBuffer, int payloadLength)
+        {
+            return TlsUtilities.CopyOfRangeExact(payloadBuffer, payloadLength, payloadBuffer.Length);
+        }
+    }
+}
diff --git a/crypto/src/tls/HeartbeatMessageType.cs b/crypto/src/tls/HeartbeatMessageType.cs
new file mode 100644
index 000000000..18f86b1ab
--- /dev/null
+++ b/crypto/src/tls/HeartbeatMessageType.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 6520 3.</summary>
+    public abstract class HeartbeatMessageType
+    {
+        public const short heartbeat_request = 1;
+        public const short heartbeat_response = 2;
+
+        public static string GetName(short heartbeatMessageType)
+        {
+            switch (heartbeatMessageType)
+            {
+            case heartbeat_request:
+                return "heartbeat_request";
+            case heartbeat_response:
+                return "heartbeat_response";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short heartbeatMessageType)
+        {
+            return GetName(heartbeatMessageType) + "(" + heartbeatMessageType + ")";
+        }
+
+        public static bool IsValid(short heartbeatMessageType)
+        {
+            return heartbeatMessageType >= heartbeat_request && heartbeatMessageType <= heartbeat_response;
+        }
+    }
+}
diff --git a/crypto/src/tls/HeartbeatMode.cs b/crypto/src/tls/HeartbeatMode.cs
new file mode 100644
index 000000000..8e65d548f
--- /dev/null
+++ b/crypto/src/tls/HeartbeatMode.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /*
+     * RFC 6520
+     */
+    public abstract class HeartbeatMode
+    {
+        public const short peer_allowed_to_send = 1;
+        public const short peer_not_allowed_to_send = 2;
+
+        public static string GetName(short heartbeatMode)
+        {
+            switch (heartbeatMode)
+            {
+            case peer_allowed_to_send:
+                return "peer_allowed_to_send";
+            case peer_not_allowed_to_send:
+                return "peer_not_allowed_to_send";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short heartbeatMode)
+        {
+            return GetName(heartbeatMode) + "(" + heartbeatMode + ")";
+        }
+
+        public static bool IsValid(short heartbeatMode)
+        {
+            return heartbeatMode >= peer_allowed_to_send && heartbeatMode <= peer_not_allowed_to_send;
+        }
+    }
+}
diff --git a/crypto/src/tls/IdentifierType.cs b/crypto/src/tls/IdentifierType.cs
new file mode 100644
index 000000000..df62e82f5
--- /dev/null
+++ b/crypto/src/tls/IdentifierType.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 6066</summary>
+    public abstract class IdentifierType
+    {
+        public const short pre_agreed = 0;
+        public const short key_sha1_hash = 1;
+        public const short x509_name = 2;
+        public const short cert_sha1_hash = 3;
+
+        public static string GetName(short identifierType)
+        {
+            switch (identifierType)
+            {
+            case pre_agreed:
+                return "pre_agreed";
+            case key_sha1_hash:
+                return "key_sha1_hash";
+            case x509_name:
+                return "x509_name";
+            case cert_sha1_hash:
+                return "cert_sha1_hash";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short identifierType)
+        {
+            return GetName(identifierType) + "(" + identifierType + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/KeyExchangeAlgorithm.cs b/crypto/src/tls/KeyExchangeAlgorithm.cs
new file mode 100644
index 000000000..1dfa6db66
--- /dev/null
+++ b/crypto/src/tls/KeyExchangeAlgorithm.cs
@@ -0,0 +1,63 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 2246</summary>
+    /// <remarks>
+    /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the
+    /// particular values (e.g. serialization).
+    /// </remarks>
+    public abstract class KeyExchangeAlgorithm
+    {
+        /*
+         * NOTE: We interpret TLS 1.3 cipher suites as having a NULL key exchange
+         */
+        public const int NULL = 0;
+
+        public const int RSA = 1;
+        public const int RSA_EXPORT = 2;
+        public const int DHE_DSS = 3;
+        public const int DHE_DSS_EXPORT = 4;
+        public const int DHE_RSA = 5;
+        public const int DHE_RSA_EXPORT = 6;
+        public const int DH_DSS = 7;
+        public const int DH_DSS_EXPORT = 8;
+        public const int DH_RSA = 9;
+        public const int DH_RSA_EXPORT = 10;
+        public const int DH_anon = 11;
+        public const int DH_anon_EXPORT = 12;
+
+        /*
+         * RFC 4279
+         */
+        public const int PSK = 13;
+        public const int DHE_PSK = 14;
+        public const int RSA_PSK = 15;
+
+        /*
+         * RFC 4429
+         */
+        public const int ECDH_ECDSA = 16;
+        public const int ECDHE_ECDSA = 17;
+        public const int ECDH_RSA = 18;
+        public const int ECDHE_RSA = 19;
+        public const int ECDH_anon = 20;
+
+        /*
+         * RFC 5054
+         */
+        public const int SRP = 21;
+        public const int SRP_DSS = 22;
+        public const int SRP_RSA = 23;
+
+        /*
+         * RFC 5489
+         */
+        public const int ECDHE_PSK = 24;
+
+        /*
+         * GMT 0024-2014
+         */
+        public const int SM2 = 25;
+    }
+}
diff --git a/crypto/src/tls/KeyShareEntry.cs b/crypto/src/tls/KeyShareEntry.cs
new file mode 100644
index 000000000..c4be657c0
--- /dev/null
+++ b/crypto/src/tls/KeyShareEntry.cs
@@ -0,0 +1,62 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class KeyShareEntry
+    {
+        private static bool CheckKeyExchangeLength(int length)
+        {
+            return 0 < length && length < (1 << 16);
+        }
+
+        private readonly int m_namedGroup;
+        private readonly byte[] m_keyExchange;
+
+        /// <param name="namedGroup"><see cref="NamedGroup"/></param>
+        /// <param name="keyExchange"></param>
+        public KeyShareEntry(int namedGroup, byte[] keyExchange)
+        {
+            if (!TlsUtilities.IsValidUint16(namedGroup))
+                throw new ArgumentException("should be a uint16", "namedGroup");
+            if (null == keyExchange)
+                throw new ArgumentNullException("keyExchange");
+            if (!CheckKeyExchangeLength(keyExchange.Length))
+                throw new ArgumentException("must have length from 1 to (2^16 - 1)", "keyExchange");
+
+            this.m_namedGroup = namedGroup;
+            this.m_keyExchange = keyExchange;
+        }
+
+        /// <returns><see cref="NamedGroup"/></returns>
+        public int NamedGroup
+        {
+            get { return m_namedGroup; }
+        }
+
+        public byte[] KeyExchange
+        {
+            get { return m_keyExchange; }
+        }
+
+        /// <summary>Encode this <see cref="KeyShareEntry"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint16(NamedGroup, output);
+            TlsUtilities.WriteOpaque16(KeyExchange, output);
+        }
+
+        /// <summary>Parse a <see cref="KeyShareEntry"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="KeyShareEntry"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static KeyShareEntry Parse(Stream input)
+        {
+            int namedGroup = TlsUtilities.ReadUint16(input);
+            byte[] keyExchange = TlsUtilities.ReadOpaque16(input, 1);
+            return new KeyShareEntry(namedGroup, keyExchange);
+        }
+    }
+}
diff --git a/crypto/src/tls/KeyUpdateRequest.cs b/crypto/src/tls/KeyUpdateRequest.cs
new file mode 100644
index 000000000..2a784e6e7
--- /dev/null
+++ b/crypto/src/tls/KeyUpdateRequest.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 8446 4.6.3</summary>
+    public abstract class KeyUpdateRequest
+    {
+        public const short update_not_requested = 0;
+        public const short update_requested = 1;
+
+        public static string GetName(short keyUpdateRequest)
+        {
+            switch (keyUpdateRequest)
+            {
+            case update_not_requested:
+                return "update_not_requested";
+            case update_requested:
+                return "update_requested";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short keyUpdateRequest)
+        {
+            return GetName(keyUpdateRequest) + "(" + keyUpdateRequest + ")";
+        }
+
+        public static bool IsValid(short keyUpdateRequest)
+        {
+            return keyUpdateRequest >= update_not_requested && keyUpdateRequest <= update_requested;
+        }
+    }
+}
diff --git a/crypto/src/tls/MacAlgorithm.cs b/crypto/src/tls/MacAlgorithm.cs
new file mode 100644
index 000000000..1706de1bd
--- /dev/null
+++ b/crypto/src/tls/MacAlgorithm.cs
@@ -0,0 +1,66 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 2246</summary>
+    /// <remarks>
+    /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the
+    /// particular values (e.g. serialization).
+    /// </remarks>
+    public abstract class MacAlgorithm
+    {
+        public const int cls_null = 0;
+        public const int md5 = 1;
+        public const int sha = 2;
+
+        /*
+         * RFC 5246
+         */
+        public const int hmac_md5 = md5;
+        public const int hmac_sha1 = sha;
+        public const int hmac_sha256 = 3;
+        public const int hmac_sha384 = 4;
+        public const int hmac_sha512 = 5;
+
+        public static string GetName(int macAlgorithm)
+        {
+            switch (macAlgorithm)
+            {
+            case cls_null:
+                return "null";
+            case hmac_md5:
+                return "hmac_md5";
+            case hmac_sha1:
+                return "hmac_sha1";
+            case hmac_sha256:
+                return "hmac_sha256";
+            case hmac_sha384:
+                return "hmac_sha384";
+            case hmac_sha512:
+                return "hmac_sha512";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(int macAlgorithm)
+        {
+            return GetName(macAlgorithm) + "(" + macAlgorithm + ")";
+        }
+
+        public static bool IsHmac(int macAlgorithm)
+        {
+            switch (macAlgorithm)
+            {
+            case hmac_md5:
+            case hmac_sha1:
+            case hmac_sha256:
+            case hmac_sha384:
+            case hmac_sha512:
+                return true;
+            default:
+                return false;
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/MaxFragmentLength.cs b/crypto/src/tls/MaxFragmentLength.cs
new file mode 100644
index 000000000..d335de5c5
--- /dev/null
+++ b/crypto/src/tls/MaxFragmentLength.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class MaxFragmentLength
+    {
+        /*
+         * RFC 3546 3.2.
+         */
+        public const short pow2_9 = 1;
+        public const short pow2_10 = 2;
+        public const short pow2_11 = 3;
+        public const short pow2_12 = 4;
+
+        public static bool IsValid(short maxFragmentLength)
+        {
+            return maxFragmentLength >= pow2_9 && maxFragmentLength <= pow2_12;
+        }
+    }
+}
diff --git a/crypto/src/tls/NameType.cs b/crypto/src/tls/NameType.cs
new file mode 100644
index 000000000..f2e8ec6b6
--- /dev/null
+++ b/crypto/src/tls/NameType.cs
@@ -0,0 +1,38 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class NameType
+    {
+        /*
+         * RFC 3546 3.1.
+         */
+        public const short host_name = 0;
+
+        public static string GetName(short nameType)
+        {
+            switch (nameType)
+            {
+            case host_name:
+                return "host_name";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short nameType)
+        {
+            return GetName(nameType) + "(" + nameType + ")";
+        }
+
+        public static bool IsRecognized(short nameType)
+        {
+            return host_name == nameType;
+        }
+
+        public static bool IsValid(short nameType)
+        {
+            return TlsUtilities.IsValidUint8(nameType);
+        }
+    }
+}
diff --git a/crypto/src/tls/NamedGroup.cs b/crypto/src/tls/NamedGroup.cs
new file mode 100644
index 000000000..0035ef9f3
--- /dev/null
+++ b/crypto/src/tls/NamedGroup.cs
@@ -0,0 +1,416 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 7919</summary>
+    public abstract class NamedGroup
+    {
+        /*
+         * RFC 4492 5.1.1
+         * <p>
+         * 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).
+         */
+        public const int sect163k1 = 1;
+        public const int sect163r1 = 2;
+        public const int sect163r2 = 3;
+        public const int sect193r1 = 4;
+        public const int sect193r2 = 5;
+        public const int sect233k1 = 6;
+        public const int sect233r1 = 7;
+        public const int sect239k1 = 8;
+        public const int sect283k1 = 9;
+        public const int sect283r1 = 10;
+        public const int sect409k1 = 11;
+        public const int sect409r1 = 12;
+        public const int sect571k1 = 13;
+        public const int sect571r1 = 14;
+        public const int secp160k1 = 15;
+        public const int secp160r1 = 16;
+        public const int secp160r2 = 17;
+        public const int secp192k1 = 18;
+        public const int secp192r1 = 19;
+        public const int secp224k1 = 20;
+        public const int secp224r1 = 21;
+        public const int secp256k1 = 22;
+        public const int secp256r1 = 23;
+        public const int secp384r1 = 24;
+        public const int secp521r1 = 25;
+
+        /*
+         * RFC 7027
+         */
+        public const int brainpoolP256r1 = 26;
+        public const int brainpoolP384r1 = 27;
+        public const int brainpoolP512r1 = 28;
+
+        /*
+         * RFC 8422
+         */
+        public const int x25519 = 29;
+        public const int x448 = 30;
+
+        /*
+         * RFC 8734
+         */
+        public const int brainpoolP256r1tls13 = 31;
+        public const int brainpoolP384r1tls13 = 32;
+        public const int brainpoolP512r1tls13 = 33;
+
+        /*
+         * draft-smyshlyaev-tls12-gost-suites-10
+         */
+        public const int GC256A = 34;
+        public const int GC256B = 35;
+        public const int GC256C = 36;
+        public const int GC256D = 37;
+        public const int GC512A = 38;
+        public const int GC512B = 39;
+        public const int GC512C = 40;
+
+        /*
+         * RFC 8998
+         */
+        public const int curveSM2 = 41;
+
+        /*
+         * RFC 7919 2. Codepoints in the "Supported Groups Registry" with a high byte of 0x01 (that is,
+         * between 256 and 511, inclusive) are set aside for FFDHE groups, though only a small number of
+         * them are initially defined and we do not expect many other FFDHE groups to be added to this
+         * range. No codepoints outside of this range will be allocated to FFDHE groups.
+         */
+        public const int ffdhe2048 = 256;
+        public const int ffdhe3072 = 257;
+        public const int ffdhe4096 = 258;
+        public const int ffdhe6144 = 259;
+        public const int ffdhe8192 = 260;
+
+        /*
+         * RFC 8446 reserved ffdhe_private_use (0x01FC..0x01FF)
+         */
+
+        /*
+         * RFC 4492 reserved ecdhe_private_use (0xFE00..0xFEFF)
+         */
+
+        /*
+         * RFC 4492
+         */
+        public const int arbitrary_explicit_prime_curves = 0xFF01;
+        public const int arbitrary_explicit_char2_curves = 0xFF02;
+
+        /* Names of the actual underlying elliptic curves (not necessarily matching the NamedGroup names). */
+        private static readonly string[] CurveNames = new string[]{ "sect163k1", "sect163r1", "sect163r2", "sect193r1",
+            "sect193r2", "sect233k1", "sect233r1", "sect239k1", "sect283k1", "sect283r1", "sect409k1", "sect409r1",
+            "sect571k1", "sect571r1", "secp160k1", "secp160r1", "secp160r2", "secp192k1", "secp192r1", "secp224k1",
+            "secp224r1", "secp256k1", "secp256r1", "secp384r1", "secp521r1", "brainpoolP256r1", "brainpoolP384r1",
+            "brainpoolP512r1", "X25519", "X448", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1",
+            "Tc26-Gost-3410-12-256-paramSetA", "GostR3410-2001-CryptoPro-A", "GostR3410-2001-CryptoPro-B",
+            "GostR3410-2001-CryptoPro-C", "Tc26-Gost-3410-12-512-paramSetA", "Tc26-Gost-3410-12-512-paramSetB",
+            "Tc26-Gost-3410-12-512-paramSetC", "sm2p256v1" };
+
+        private static readonly string[] FiniteFieldNames = new string[]{ "ffdhe2048", "ffdhe3072", "ffdhe4096",
+            "ffdhe6144", "ffdhe8192" };
+
+        public static bool CanBeNegotiated(int namedGroup, ProtocolVersion version)
+        {
+            if (TlsUtilities.IsTlsV13(version))
+            {
+                if ((namedGroup >= sect163k1 && namedGroup <= secp256k1)
+                    || (namedGroup >= brainpoolP256r1 && namedGroup <= brainpoolP512r1)
+                    || (namedGroup >= GC256A && namedGroup <= GC512C)
+                    || (namedGroup >= arbitrary_explicit_prime_curves && namedGroup <= arbitrary_explicit_char2_curves))
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                if ((namedGroup >= brainpoolP256r1tls13 && namedGroup <= brainpoolP512r1tls13)
+                    || (namedGroup == curveSM2))
+                {
+                    return false;
+                }
+            }
+
+            return IsValid(namedGroup);
+        }
+
+        public static int GetCurveBits(int namedGroup)
+        {
+            switch (namedGroup)
+            {
+            case secp160k1:
+            case secp160r1:
+            case secp160r2:
+                return 160;
+
+            case sect163k1:
+            case sect163r1:
+            case sect163r2:
+                return 163;
+
+            case secp192k1:
+            case secp192r1:
+                return 192;
+
+            case sect193r1:
+            case sect193r2:
+                return 193;
+
+            case secp224k1:
+            case secp224r1:
+                return 224;
+
+            case sect233k1:
+            case sect233r1:
+                return 233;
+
+            case sect239k1:
+                return 239;
+
+            case x25519:
+                return 252;
+
+            case brainpoolP256r1:
+            case brainpoolP256r1tls13:
+            case curveSM2:
+            case GC256A:
+            case GC256B:
+            case GC256C:
+            case GC256D:
+            case secp256k1:
+            case secp256r1:
+                return 256;
+
+            case sect283k1:
+            case sect283r1:
+                return 283;
+
+            case brainpoolP384r1:
+            case brainpoolP384r1tls13:
+            case secp384r1:
+                return 384;
+
+            case sect409k1:
+            case sect409r1:
+                return 409;
+
+            case x448:
+                return 446;
+
+            case brainpoolP512r1:
+            case brainpoolP512r1tls13:
+            case GC512A:
+            case GC512B:
+            case GC512C:
+                return 512;
+
+            case secp521r1:
+                return 521;
+
+            case sect571k1:
+            case sect571r1:
+                return 571;
+
+            default:
+                return 0;
+            }
+        }
+
+        public static string GetCurveName(int namedGroup)
+        {
+            if (RefersToASpecificCurve(namedGroup))
+            {
+                return CurveNames[namedGroup - sect163k1];
+            }
+
+            return null;
+        }
+
+        public static int GetFiniteFieldBits(int namedGroup)
+        {
+            switch (namedGroup)
+            {
+            case ffdhe2048:
+                return 2048;
+            case ffdhe3072:
+                return 3072;
+            case ffdhe4096:
+                return 4096;
+            case ffdhe6144:
+                return 6144;
+            case ffdhe8192:
+                return 8192;
+            default:
+                return 0;
+            }
+        }
+
+        public static string GetFiniteFieldName(int namedGroup)
+        {
+            if (RefersToASpecificFiniteField(namedGroup))
+            {
+                return FiniteFieldNames[namedGroup - ffdhe2048];
+            }
+
+            return null;
+        }
+
+        public static int GetMaximumChar2CurveBits()
+        {
+            return 571;
+        }
+
+        public static int GetMaximumCurveBits()
+        {
+            return 571;
+        }
+
+        public static int GetMaximumFiniteFieldBits()
+        {
+            return 8192;
+        }
+
+        public static int GetMaximumPrimeCurveBits()
+        {
+            return 521;
+        }
+
+        public static string GetName(int namedGroup)
+        {
+            if (IsPrivate(namedGroup))
+            {
+                return "PRIVATE";
+            }
+
+            switch (namedGroup)
+            {
+            case x25519:
+                return "x25519";
+            case x448:
+                return "x448";
+            case brainpoolP256r1tls13:
+                return "brainpoolP256r1tls13";
+            case brainpoolP384r1tls13:
+                return "brainpoolP384r1tls13";
+            case brainpoolP512r1tls13:
+                return "brainpoolP512r1tls13";
+            case GC256A:
+                return "GC256A";
+            case GC256B:
+                return "GC256B";
+            case GC256C:
+                return "GC256C";
+            case GC256D:
+                return "GC256D";
+            case GC512A:
+                return "GC512A";
+            case GC512B:
+                return "GC512B";
+            case GC512C:
+                return "GC512C";
+            case curveSM2:
+                return "curveSM2";
+            case arbitrary_explicit_prime_curves:
+                return "arbitrary_explicit_prime_curves";
+            case arbitrary_explicit_char2_curves:
+                return "arbitrary_explicit_char2_curves";
+            }
+
+            string standardName = GetStandardName(namedGroup);
+            if (null != standardName)
+            {
+                return standardName;
+            }
+
+            return "UNKNOWN";
+        }
+
+        public static string GetStandardName(int namedGroup)
+        {
+            string curveName = GetCurveName(namedGroup);
+            if (null != curveName)
+            {
+                return curveName;
+            }
+
+            string finiteFieldName = GetFiniteFieldName(namedGroup);
+            if (null != finiteFieldName)
+            {
+                return finiteFieldName;
+            }
+
+            return null;
+        }
+
+        public static string GetText(int namedGroup)
+        {
+            return GetName(namedGroup) + "(" + namedGroup + ")";
+        }
+
+        public static bool IsChar2Curve(int namedGroup)
+        {
+            return (namedGroup >= sect163k1 && namedGroup <= sect571r1)
+                || (namedGroup == arbitrary_explicit_char2_curves);
+        }
+
+        public static bool IsPrimeCurve(int namedGroup)
+        {
+            return (namedGroup >= secp160k1 && namedGroup <= curveSM2)
+                || (namedGroup == arbitrary_explicit_prime_curves);
+        }
+
+        public static bool IsPrivate(int namedGroup)
+        {
+            return (namedGroup >> 2) == 0x7F || (namedGroup >> 8) == 0xFE;
+        }
+
+        public static bool IsValid(int namedGroup)
+        {
+            return RefersToASpecificGroup(namedGroup)
+                || IsPrivate(namedGroup)
+                || (namedGroup >= arbitrary_explicit_prime_curves && namedGroup <= arbitrary_explicit_char2_curves);
+        }
+
+        public static bool RefersToAnECDHCurve(int namedGroup)
+        {
+            return RefersToASpecificCurve(namedGroup);
+        }
+
+        public static bool RefersToAnECDSACurve(int namedGroup)
+        {
+            /*
+             * TODO[RFC 8998] Double-check whether this method is only being used to mean
+             * "signature-capable" or specifically ECDSA, and consider curveSM2 behaviour
+             * accordingly.
+             */
+            return RefersToASpecificCurve(namedGroup)
+                && !RefersToAnXDHCurve(namedGroup);
+        }
+
+        public static bool RefersToAnXDHCurve(int namedGroup)
+        {
+            return namedGroup >= x25519 && namedGroup <= x448;
+        }
+
+        public static bool RefersToASpecificCurve(int namedGroup)
+        {
+            return namedGroup >= sect163k1 && namedGroup <= curveSM2;
+        }
+
+        public static bool RefersToASpecificFiniteField(int namedGroup)
+        {
+            return namedGroup >= ffdhe2048 && namedGroup <= ffdhe8192;
+        }
+
+        public static bool RefersToASpecificGroup(int namedGroup)
+        {
+            return RefersToASpecificCurve(namedGroup)
+                || RefersToASpecificFiniteField(namedGroup);
+        }
+    }
+}
diff --git a/crypto/src/tls/NamedGroupRole.cs b/crypto/src/tls/NamedGroupRole.cs
new file mode 100644
index 000000000..d86e6ad61
--- /dev/null
+++ b/crypto/src/tls/NamedGroupRole.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <remarks>
+    /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the
+    /// particular values (e.g. serialization).
+    /// </remarks>
+    public abstract class NamedGroupRole
+    {
+        public const int dh = 1;
+        public const int ecdh = 2;
+        public const int ecdsa = 3;
+    }
+}
diff --git a/crypto/src/tls/NewSessionTicket.cs b/crypto/src/tls/NewSessionTicket.cs
new file mode 100644
index 000000000..6e2a01305
--- /dev/null
+++ b/crypto/src/tls/NewSessionTicket.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class NewSessionTicket
+    {
+        private readonly long m_ticketLifetimeHint;
+        private readonly byte[] m_ticket;
+
+        public NewSessionTicket(long ticketLifetimeHint, byte[] ticket)
+        {
+            this.m_ticketLifetimeHint = ticketLifetimeHint;
+            this.m_ticket = ticket;
+        }
+
+        public long TicketLifetimeHint
+        {
+            get { return m_ticketLifetimeHint; }
+        }
+
+        public byte[] Ticket
+        {
+            get { return m_ticket; }
+        }
+
+        /// <summary>Encode this <see cref="NewSessionTicket"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint32(TicketLifetimeHint, output);
+            TlsUtilities.WriteOpaque16(Ticket, output);
+        }
+
+        /// <summary>Parse a <see cref="NewSessionTicket"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="NewSessionTicket"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static NewSessionTicket Parse(Stream input)
+        {
+            long ticketLifetimeHint = TlsUtilities.ReadUint32(input);
+            byte[] ticket = TlsUtilities.ReadOpaque16(input);
+            return new NewSessionTicket(ticketLifetimeHint, ticket);
+        }
+    }
+}
diff --git a/crypto/src/tls/OcspStatusRequest.cs b/crypto/src/tls/OcspStatusRequest.cs
new file mode 100644
index 000000000..b52517e06
--- /dev/null
+++ b/crypto/src/tls/OcspStatusRequest.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Ocsp;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 3546 3.6</summary>
+    public sealed class OcspStatusRequest
+    {
+        private readonly IList m_responderIDList;
+        private readonly X509Extensions m_requestExtensions;
+
+        /// <param name="responderIDList">an <see cref="IList"/> of <see cref="ResponderID"/>, specifying the list of
+        /// trusted OCSP responders. An empty list has the special meaning that the responders are implicitly known to
+        /// the server - e.g., by prior arrangement.</param>
+        /// <param name="requestExtensions">OCSP request extensions. A null value means that there are no extensions.
+        /// </param>
+        public OcspStatusRequest(IList responderIDList, X509Extensions requestExtensions)
+        {
+            this.m_responderIDList = responderIDList;
+            this.m_requestExtensions = requestExtensions;
+        }
+
+        /// <returns>an <see cref="IList"/> of <see cref="ResponderID"/>.</returns>
+        public IList ResponderIDList
+        {
+            get { return m_responderIDList; }
+        }
+
+        /// <returns>OCSP request extensions.</returns>
+        public X509Extensions RequestExtensions
+        {
+            get { return m_requestExtensions; }
+        }
+
+        /// <summary>Encode this <see cref="OcspStatusRequest"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            if (m_responderIDList == null || m_responderIDList.Count < 1)
+            {
+                TlsUtilities.WriteUint16(0, output);
+            }
+            else
+            {
+                MemoryStream buf = new MemoryStream();
+                foreach (ResponderID responderID in m_responderIDList)
+                {
+                    byte[] derEncoding = responderID.GetEncoded(Asn1Encodable.Der);
+                    TlsUtilities.WriteOpaque16(derEncoding, buf);
+                }
+                TlsUtilities.CheckUint16(buf.Length);
+                TlsUtilities.WriteUint16((int)buf.Length, output);
+                Streams.WriteBufTo(buf, output);
+            }
+
+            if (m_requestExtensions == null)
+            {
+                TlsUtilities.WriteUint16(0, output);
+            }
+            else
+            {
+                byte[] derEncoding = m_requestExtensions.GetEncoded(Asn1Encodable.Der);
+                TlsUtilities.CheckUint16(derEncoding.Length);
+                TlsUtilities.WriteUint16(derEncoding.Length, output);
+                output.Write(derEncoding, 0, derEncoding.Length);
+            }
+        }
+
+        /// <summary>Parse an <see cref="OcspStatusRequest"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>an <see cref="OcspStatusRequest"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static OcspStatusRequest Parse(Stream input)
+        {
+            IList responderIDList = Platform.CreateArrayList();
+            {
+                byte[] data = TlsUtilities.ReadOpaque16(input);
+                if (data.Length > 0)
+                {
+                    MemoryStream buf = new MemoryStream(data, false);
+                    do
+                    {
+                        byte[] derEncoding = TlsUtilities.ReadOpaque16(buf, 1);
+                        ResponderID responderID = ResponderID.GetInstance(TlsUtilities.ReadDerObject(derEncoding));
+                        responderIDList.Add(responderID);
+                    }
+                    while (buf.Position < buf.Length);
+                }
+            }
+
+            X509Extensions requestExtensions = null;
+            {
+                byte[] derEncoding = TlsUtilities.ReadOpaque16(input);
+                if (derEncoding.Length > 0)
+                {
+                    requestExtensions = X509Extensions.GetInstance(TlsUtilities.ReadDerObject(derEncoding));
+                }
+            }
+
+            return new OcspStatusRequest(responderIDList, requestExtensions);
+        }
+    }
+}
diff --git a/crypto/src/tls/OfferedPsks.cs b/crypto/src/tls/OfferedPsks.cs
new file mode 100644
index 000000000..597ec195c
--- /dev/null
+++ b/crypto/src/tls/OfferedPsks.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class OfferedPsks
+    {
+        private readonly IList m_identities;
+        private readonly IList m_binders;
+
+        public OfferedPsks(IList identities, IList binders)
+        {
+            if (null == identities || identities.Count < 1)
+                throw new ArgumentException("cannot be null or empty", "identities");
+            if (null == binders || identities.Count != binders.Count)
+                throw new ArgumentException("must be non-null and the same length as 'identities'", "binders");
+
+            this.m_identities = identities;
+            this.m_binders = binders;
+        }
+
+        public IList Binders
+        {
+            get { return m_binders; }
+        }
+
+        public IList Identities
+        {
+            get { return m_identities; }
+        }
+
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            // identities
+            {
+                int totalLengthIdentities = 0;
+                foreach (PskIdentity identity in m_identities)
+                {
+                    totalLengthIdentities += 2 + identity.Identity.Length + 4;
+                }
+
+                TlsUtilities.CheckUint16(totalLengthIdentities);
+                TlsUtilities.WriteUint16(totalLengthIdentities, output);
+
+                foreach (PskIdentity identity in m_identities)
+                {
+                    identity.Encode(output);
+                }
+            }
+
+            // binders
+            {
+                int totalLengthBinders = 0;
+                foreach (byte[] binder in m_binders)
+                {
+                    totalLengthBinders += 1 + binder.Length;
+                }
+
+                TlsUtilities.CheckUint16(totalLengthBinders);
+                TlsUtilities.WriteUint16(totalLengthBinders, output);
+
+                foreach (byte[] binder in m_binders)
+                {
+                    TlsUtilities.WriteOpaque8(binder, output);
+                }
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public static OfferedPsks Parse(Stream input)
+        {
+            IList identities = Platform.CreateArrayList();
+            {
+                int totalLengthIdentities = TlsUtilities.ReadUint16(input);
+                if (totalLengthIdentities < 7)
+                    throw new TlsFatalAlert(AlertDescription.decode_error);
+
+                byte[] identitiesData = TlsUtilities.ReadFully(totalLengthIdentities, input);
+                MemoryStream buf = new MemoryStream(identitiesData, false);
+                do
+                {
+                    PskIdentity identity = PskIdentity.Parse(buf);
+                    identities.Add(identity);
+                }
+                while (buf.Position < buf.Length);
+            }
+
+            IList binders = Platform.CreateArrayList();
+            {
+                int totalLengthBinders = TlsUtilities.ReadUint16(input);
+                if (totalLengthBinders < 33)
+                    throw new TlsFatalAlert(AlertDescription.decode_error);
+
+                byte[] bindersData = TlsUtilities.ReadFully(totalLengthBinders, input);
+                MemoryStream buf = new MemoryStream(bindersData, false);
+                do
+                {
+                    byte[] binder = TlsUtilities.ReadOpaque8(input, 32);
+                    binders.Add(binder);
+                }
+                while (buf.Position < buf.Length);
+            }
+
+            return new OfferedPsks(identities, binders);
+        }
+    }
+}
diff --git a/crypto/src/tls/PrfAlgorithm.cs b/crypto/src/tls/PrfAlgorithm.cs
new file mode 100644
index 000000000..ec9c2f249
--- /dev/null
+++ b/crypto/src/tls/PrfAlgorithm.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5246</summary>
+    /// <remarks>
+    /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the
+    /// particular values (e.g. serialization).
+    /// </remarks>
+    public abstract class PrfAlgorithm
+    {
+        public const int ssl_prf_legacy = 0;
+        public const int tls_prf_legacy = 1;
+        public const int tls_prf_sha256 = 2;
+        public const int tls_prf_sha384 = 3;
+        public const int tls13_hkdf_sha256 = 4;
+        public const int tls13_hkdf_sha384 = 5;
+        //public const int tls13_hkdf_sha512 = 6;
+        public const int tls13_hkdf_sm3 = 7;
+
+        public static string GetName(int prfAlgorithm)
+        {
+            switch (prfAlgorithm)
+            {
+            case ssl_prf_legacy:
+                return "ssl_prf_legacy";
+            case tls_prf_legacy:
+                return "tls_prf_legacy";
+            case tls_prf_sha256:
+                return "tls_prf_sha256";
+            case tls_prf_sha384:
+                return "tls_prf_sha384";
+            case tls13_hkdf_sha256:
+                return "tls13_hkdf_sha256";
+            case tls13_hkdf_sha384:
+                return "tls13_hkdf_sha384";
+            case tls13_hkdf_sm3:
+                return "tls13_hkdf_sm3";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(int prfAlgorithm)
+        {
+            return GetName(prfAlgorithm) + "(" + prfAlgorithm + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/ProtocolName.cs b/crypto/src/tls/ProtocolName.cs
new file mode 100644
index 000000000..529e81b19
--- /dev/null
+++ b/crypto/src/tls/ProtocolName.cs
@@ -0,0 +1,88 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 7301 Represents a protocol name for use with ALPN.</summary>
+    public sealed class ProtocolName
+    {
+        public static ProtocolName AsRawBytes(byte[] bytes)
+        {
+            return new ProtocolName(Arrays.Clone(bytes));
+        }
+
+        public static ProtocolName AsUtf8Encoding(String name)
+        {
+            return new ProtocolName(Strings.ToUtf8ByteArray(name));
+        }
+
+        public static readonly ProtocolName Http_1_1 = AsUtf8Encoding("http/1.1");
+        public static readonly ProtocolName Spdy_1 = AsUtf8Encoding("spdy/1");
+        public static readonly ProtocolName Spdy_2 = AsUtf8Encoding("spdy/2");
+        public static readonly ProtocolName Spdy_3 = AsUtf8Encoding("spdy/3");
+        public static readonly ProtocolName Stun_Turn = AsUtf8Encoding("stun.turn");
+        public static readonly ProtocolName Stun_Nat_Discovery = AsUtf8Encoding("stun.nat-discovery");
+        public static readonly ProtocolName Http_2_Tls = AsUtf8Encoding("h2");
+        public static readonly ProtocolName Http_2_Tcp = AsUtf8Encoding("h2c");
+        public static readonly ProtocolName WebRtc = AsUtf8Encoding("webrtc");
+        public static readonly ProtocolName WebRtc_Confidential = AsUtf8Encoding("c-webrtc");
+        public static readonly ProtocolName Ftp = AsUtf8Encoding("ftp");
+        public static readonly ProtocolName Imap = AsUtf8Encoding("imap");
+        public static readonly ProtocolName Pop3 = AsUtf8Encoding("pop3");
+        public static readonly ProtocolName ManageSieve = AsUtf8Encoding("managesieve");
+        public static readonly ProtocolName Coap = AsUtf8Encoding("coap");
+        public static readonly ProtocolName Xmpp_Client = AsUtf8Encoding("xmpp-client");
+        public static readonly ProtocolName Xmpp_Server = AsUtf8Encoding("xmpp-server");
+
+        private readonly byte[] m_bytes;
+
+        private ProtocolName(byte[] bytes)
+        {
+            if (bytes == null)
+                throw new ArgumentNullException("bytes");
+            if (bytes.Length < 1 || bytes.Length > 255)
+                throw new ArgumentException("must have length from 1 to 255", "bytes");
+
+            this.m_bytes = bytes;
+        }
+
+        public byte[] GetBytes()
+        {
+            return Arrays.Clone(m_bytes);
+        }
+
+        public string GetUtf8Decoding()
+        {
+            return Strings.FromUtf8ByteArray(m_bytes);
+        }
+
+        /// <summary>Encode this <see cref="ProtocolName"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteOpaque8(m_bytes, output);
+        }
+
+        /// <summary>Parse a <see cref="ProtocolName"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="ProtocolName"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static ProtocolName Parse(Stream input)
+        {
+            return new ProtocolName(TlsUtilities.ReadOpaque8(input, 1));
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is ProtocolName && Arrays.AreEqual(m_bytes, ((ProtocolName)obj).m_bytes);
+        }
+
+        public override int GetHashCode()
+        {
+            return Arrays.GetHashCode(m_bytes);
+        }
+    }
+}
diff --git a/crypto/src/tls/ProtocolVersion.cs b/crypto/src/tls/ProtocolVersion.cs
new file mode 100644
index 000000000..f37ce382d
--- /dev/null
+++ b/crypto/src/tls/ProtocolVersion.cs
@@ -0,0 +1,406 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class ProtocolVersion
+    {
+        public static readonly ProtocolVersion SSLv3 = new ProtocolVersion(0x0300, "SSL 3.0");
+        public static readonly ProtocolVersion TLSv10 = new ProtocolVersion(0x0301, "TLS 1.0");
+        public static readonly ProtocolVersion TLSv11 = new ProtocolVersion(0x0302, "TLS 1.1");
+        public static readonly ProtocolVersion TLSv12 = new ProtocolVersion(0x0303, "TLS 1.2");
+        public static readonly ProtocolVersion TLSv13 = new ProtocolVersion(0x0304, "TLS 1.3");
+        public static readonly ProtocolVersion DTLSv10 = new ProtocolVersion(0xFEFF, "DTLS 1.0");
+        public static readonly ProtocolVersion DTLSv12 = new ProtocolVersion(0xFEFD, "DTLS 1.2");
+
+        internal static readonly ProtocolVersion CLIENT_EARLIEST_SUPPORTED_DTLS = DTLSv10;
+        internal static readonly ProtocolVersion CLIENT_EARLIEST_SUPPORTED_TLS = SSLv3;
+        internal static readonly ProtocolVersion CLIENT_LATEST_SUPPORTED_DTLS = DTLSv12;
+        internal static readonly ProtocolVersion CLIENT_LATEST_SUPPORTED_TLS = TLSv13;
+
+        internal static readonly ProtocolVersion SERVER_EARLIEST_SUPPORTED_DTLS = DTLSv10;
+        internal static readonly ProtocolVersion SERVER_EARLIEST_SUPPORTED_TLS = SSLv3;
+        internal static readonly ProtocolVersion SERVER_LATEST_SUPPORTED_DTLS = DTLSv12;
+        internal static readonly ProtocolVersion SERVER_LATEST_SUPPORTED_TLS = TLSv13;
+
+        public static bool Contains(ProtocolVersion[] versions, ProtocolVersion version)
+        {
+            if (versions != null && version != null)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    if (version.Equals(versions[i]))
+                        return true;
+                }
+            }
+            return false;
+        }
+
+        public static ProtocolVersion GetEarliestDtls(ProtocolVersion[] versions)
+        {
+            ProtocolVersion earliest = null;
+            if (null != versions)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    ProtocolVersion next = versions[i];
+                    if (null != next && next.IsDtls)
+                    {
+                        if (null == earliest || next.MinorVersion > earliest.MinorVersion)
+                        {
+                            earliest = next;
+                        }
+                    }
+                }
+            }
+            return earliest;
+        }
+
+        public static ProtocolVersion GetEarliestTls(ProtocolVersion[] versions)
+        {
+            ProtocolVersion earliest = null;
+            if (null != versions)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    ProtocolVersion next = versions[i];
+                    if (null != next && next.IsTls)
+                    {
+                        if (null == earliest || next.MinorVersion < earliest.MinorVersion)
+                        {
+                            earliest = next;
+                        }
+                    }
+                }
+            }
+            return earliest;
+        }
+
+        public static ProtocolVersion GetLatestDtls(ProtocolVersion[] versions)
+        {
+            ProtocolVersion latest = null;
+            if (null != versions)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    ProtocolVersion next = versions[i];
+                    if (null != next && next.IsDtls)
+                    {
+                        if (null == latest || next.MinorVersion < latest.MinorVersion)
+                        {
+                            latest = next;
+                        }
+                    }
+                }
+            }
+            return latest;
+        }
+
+        public static ProtocolVersion GetLatestTls(ProtocolVersion[] versions)
+        {
+            ProtocolVersion latest = null;
+            if (null != versions)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    ProtocolVersion next = versions[i];
+                    if (null != next && next.IsTls)
+                    {
+                        if (null == latest || next.MinorVersion > latest.MinorVersion)
+                        {
+                            latest = next;
+                        }
+                    }
+                }
+            }
+            return latest;
+        }
+
+        internal static bool IsSupportedDtlsVersionClient(ProtocolVersion version)
+        {
+            return null != version
+                && version.IsEqualOrLaterVersionOf(CLIENT_EARLIEST_SUPPORTED_DTLS)
+                && version.IsEqualOrEarlierVersionOf(CLIENT_LATEST_SUPPORTED_DTLS);
+        }
+
+        internal static bool IsSupportedDtlsVersionServer(ProtocolVersion version)
+        {
+            return null != version
+                && version.IsEqualOrLaterVersionOf(SERVER_EARLIEST_SUPPORTED_DTLS)
+                && version.IsEqualOrEarlierVersionOf(SERVER_LATEST_SUPPORTED_DTLS);
+        }
+
+        internal static bool IsSupportedTlsVersionClient(ProtocolVersion version)
+        {
+            if (null == version)
+                return false;
+
+            int fullVersion = version.FullVersion;
+
+            return fullVersion >= CLIENT_EARLIEST_SUPPORTED_TLS.FullVersion
+                && fullVersion <= CLIENT_LATEST_SUPPORTED_TLS.FullVersion;
+        }
+
+        internal static bool IsSupportedTlsVersionServer(ProtocolVersion version)
+        {
+            if (null == version)
+                return false;
+
+            int fullVersion = version.FullVersion;
+
+            return fullVersion >= SERVER_EARLIEST_SUPPORTED_TLS.FullVersion
+                && fullVersion <= SERVER_LATEST_SUPPORTED_TLS.FullVersion;
+        }
+
+        private readonly int version;
+        private readonly string name;
+
+        private ProtocolVersion(int v, string name)
+        {
+            this.version = v & 0xFFFF;
+            this.name = name;
+        }
+
+        public ProtocolVersion[] DownTo(ProtocolVersion min)
+        {
+            if (!IsEqualOrLaterVersionOf(min))
+                throw new ArgumentException("must be an equal or earlier version of this one", "min");
+
+            IList result = Platform.CreateArrayList();
+            result.Add(this);
+
+            ProtocolVersion current = this;
+            while (!current.Equals(min))
+            {
+                current = current.GetPreviousVersion();
+                result.Add(current);
+            }
+
+            ProtocolVersion[] versions = new ProtocolVersion[result.Count];
+            for (int i = 0; i < result.Count; ++i)
+            {
+                versions[i] = (ProtocolVersion)result[i];
+            }
+            return versions;
+        }
+
+        public int FullVersion
+        {
+            get { return version; }
+        }
+
+        public int MajorVersion
+        {
+            get { return version >> 8; }
+        }
+
+        public int MinorVersion
+        {
+            get { return version & 0xFF; }
+        }
+
+        public string Name
+        {
+            get { return name; }
+        }
+
+        public bool IsDtls
+        {
+            get { return MajorVersion == 0xFE; }
+        }
+
+        public bool IsSsl
+        {
+            get { return this == SSLv3; }
+        }
+
+        public bool IsTls
+        {
+            get { return MajorVersion == 0x03; }
+        }
+
+        public ProtocolVersion GetEquivalentTlsVersion()
+        {
+            switch (MajorVersion)
+            {
+                case 0x03:
+                    return this;
+                case 0xFE:
+                    switch (MinorVersion)
+                    {
+                        case 0xFF: return TLSv11;
+                        case 0xFD: return TLSv12;
+                        default: return null;
+                    }
+                default:
+                    return null;
+            }
+        }
+
+        public ProtocolVersion GetNextVersion()
+        {
+            int major = MajorVersion, minor = MinorVersion;
+            switch (major)
+            {
+            case 0x03:
+                switch (minor)
+                {
+                    case 0xFF: return null;
+                    default: return Get(major, minor + 1);
+                }
+            case 0xFE:
+                switch (minor)
+                {
+                    case 0x00: return null;
+                    case 0xFF: return DTLSv12;
+                    default: return Get(major, minor - 1);
+                }
+            default:
+                return null;
+            }
+        }
+
+        public ProtocolVersion GetPreviousVersion()
+        {
+            int major = MajorVersion, minor = MinorVersion;
+            switch (major)
+            {
+                case 0x03:
+                    switch (minor)
+                    {
+                        case 0x00: return null;
+                        default: return Get(major, minor - 1);
+                    }
+                case 0xFE:
+                    switch (minor)
+                    {
+                        case 0xFF: return null;
+                        case 0xFD: return DTLSv10;
+                        default: return Get(major, minor + 1);
+                    }
+                default:
+                    return null;
+            }
+        }
+
+        public bool IsEarlierVersionOf(ProtocolVersion version)
+        {
+            if (null == version || MajorVersion != version.MajorVersion)
+                return false;
+
+            int diffMinorVersion = MinorVersion - version.MinorVersion;
+            return IsDtls ? diffMinorVersion > 0 : diffMinorVersion < 0;
+        }
+
+        public bool IsEqualOrEarlierVersionOf(ProtocolVersion version)
+        {
+            if (null == version || MajorVersion != version.MajorVersion)
+                return false;
+
+            int diffMinorVersion = MinorVersion - version.MinorVersion;
+            return IsDtls ? diffMinorVersion >= 0 : diffMinorVersion <= 0;
+        }
+
+        public bool IsEqualOrLaterVersionOf(ProtocolVersion version)
+        {
+            if (null == version || MajorVersion != version.MajorVersion)
+                return false;
+
+            int diffMinorVersion = MinorVersion - version.MinorVersion;
+            return IsDtls ? diffMinorVersion <= 0 : diffMinorVersion >= 0;
+        }
+
+        public bool IsLaterVersionOf(ProtocolVersion version)
+        {
+            if (null == version || MajorVersion != version.MajorVersion)
+                return false;
+
+            int diffMinorVersion = MinorVersion - version.MinorVersion;
+            return IsDtls ? diffMinorVersion < 0 : diffMinorVersion > 0;
+        }
+
+        public override bool Equals(object other)
+        {
+            return this == other || (other is ProtocolVersion && Equals((ProtocolVersion)other));
+        }
+
+        public bool Equals(ProtocolVersion other)
+        {
+            return other != null && this.version == other.version;
+        }
+
+        public override int GetHashCode()
+        {
+            return version;
+        }
+
+        public static ProtocolVersion Get(int major, int minor)
+        {
+            switch (major)
+            {
+            case 0x03:
+            {
+                switch (minor)
+                {
+                case 0x00:
+                    return SSLv3;
+                case 0x01:
+                    return TLSv10;
+                case 0x02:
+                    return TLSv11;
+                case 0x03:
+                    return TLSv12;
+                case 0x04:
+                    return TLSv13;
+                }
+                return GetUnknownVersion(major, minor, "TLS");
+            }
+            case 0xFE:
+            {
+                switch (minor)
+                {
+                case 0xFF:
+                    return DTLSv10;
+                case 0xFE:
+                    throw new ArgumentException("{0xFE, 0xFE} is a reserved protocol version");
+                case 0xFD:
+                    return DTLSv12;
+                }
+                return GetUnknownVersion(major, minor, "DTLS");
+            }
+            default:
+            {
+                return GetUnknownVersion(major, minor, "UNKNOWN");
+            }
+            }
+        }
+
+        public ProtocolVersion[] Only()
+        {
+            return new ProtocolVersion[]{ this };
+        }
+
+        public override string ToString()
+        {
+            return name;
+        }
+
+        private static void CheckUint8(int versionOctet)
+        {
+            if (!TlsUtilities.IsValidUint8(versionOctet))
+                throw new ArgumentException("not a valid octet", "versionOctet");
+        }
+
+        private static ProtocolVersion GetUnknownVersion(int major, int minor, string prefix)
+        {
+            CheckUint8(major);
+            CheckUint8(minor);
+
+            int v = (major << 8) | minor;
+            string hex = Platform.ToUpperInvariant(Convert.ToString(0x10000 | v, 16).Substring(1));
+            return new ProtocolVersion(v, prefix + " 0x" + hex);
+        }
+    }
+}
diff --git a/crypto/src/tls/PskIdentity.cs b/crypto/src/tls/PskIdentity.cs
new file mode 100644
index 000000000..9b24527bb
--- /dev/null
+++ b/crypto/src/tls/PskIdentity.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class PskIdentity
+    {
+        private readonly byte[] m_identity;
+        private readonly long m_obfuscatedTicketAge;
+
+        public PskIdentity(byte[] identity, long obfuscatedTicketAge)
+        {
+            if (null == identity)
+                throw new ArgumentNullException("identity");
+            if (identity.Length < 1 || !TlsUtilities.IsValidUint16(identity.Length))
+                throw new ArgumentException("should have length from 1 to 65535", "identity");
+            if (!TlsUtilities.IsValidUint32(obfuscatedTicketAge))
+                throw new ArgumentException("should be a uint32", "obfuscatedTicketAge");
+
+            this.m_identity = identity;
+            this.m_obfuscatedTicketAge = obfuscatedTicketAge;
+        }
+
+        public byte[] Identity
+        {
+            get { return m_identity; }
+        }
+
+        public long ObfuscatedTicketAge
+        {
+            get { return m_obfuscatedTicketAge; }
+        }
+
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteOpaque16(Identity, output);
+            TlsUtilities.WriteUint32(ObfuscatedTicketAge, output);
+        }
+
+        public static PskIdentity Parse(Stream input)
+        {
+            byte[] identity = TlsUtilities.ReadOpaque16(input, 1);
+            long obfuscatedTicketAge = TlsUtilities.ReadUint32(input);
+            return new PskIdentity(identity, obfuscatedTicketAge);
+        }
+    }
+}
diff --git a/crypto/src/tls/PskKeyExchangeMode.cs b/crypto/src/tls/PskKeyExchangeMode.cs
new file mode 100644
index 000000000..565745b67
--- /dev/null
+++ b/crypto/src/tls/PskKeyExchangeMode.cs
@@ -0,0 +1,32 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class PskKeyExchangeMode
+    {
+        /*
+         * RFC 8446
+         */
+
+        public const short psk_ke = 0;
+        public const short psk_dhe_ke = 1;
+
+        public static string GetName(short pskKeyExchangeMode)
+        {
+            switch (pskKeyExchangeMode)
+            {
+            case psk_ke:
+                return "psk_ke";
+            case psk_dhe_ke:
+                return "psk_dhe_ke";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short pskKeyExchangeMode)
+        {
+            return GetName(pskKeyExchangeMode) + "(" + pskKeyExchangeMode + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/PskTlsClient.cs b/crypto/src/tls/PskTlsClient.cs
new file mode 100644
index 000000000..3e9a00390
--- /dev/null
+++ b/crypto/src/tls/PskTlsClient.cs
@@ -0,0 +1,60 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class PskTlsClient
+        : AbstractTlsClient
+    {
+        private static readonly int[] DefaultCipherSuites = new int[]
+        {
+            CipherSuite.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA
+        };
+
+        protected readonly TlsPskIdentity m_pskIdentity;
+
+        public PskTlsClient(TlsCrypto crypto, byte[] identity, byte[] psk)
+            : this(crypto, new BasicTlsPskIdentity(identity, psk))
+        {
+        }
+
+        public PskTlsClient(TlsCrypto crypto, TlsPskIdentity pskIdentity)
+            : base(crypto)
+        {
+            this.m_pskIdentity = pskIdentity;
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.DownTo(ProtocolVersion.TLSv10);
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, DefaultCipherSuites);
+        }
+
+        public override TlsPskIdentity GetPskIdentity()
+        {
+            return m_pskIdentity;
+        }
+
+        /// <exception cref="IOException"/>
+        public override TlsAuthentication GetAuthentication()
+        {
+            /*
+             * Note: This method is not called unless a server certificate is sent, which may be the
+             * case e.g. for RSA_PSK key exchange.
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+}
diff --git a/crypto/src/tls/PskTlsServer.cs b/crypto/src/tls/PskTlsServer.cs
new file mode 100644
index 000000000..7197b6ad8
--- /dev/null
+++ b/crypto/src/tls/PskTlsServer.cs
@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class PskTlsServer
+        : AbstractTlsServer
+    {
+        private static readonly int[] DefaultCipherSuites = new int[]
+        {
+            CipherSuite.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
+            CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA
+        };
+
+        protected readonly TlsPskIdentityManager m_pskIdentityManager;
+
+        public PskTlsServer(TlsCrypto crypto, TlsPskIdentityManager pskIdentityManager)
+            : base(crypto)
+        {
+            this.m_pskIdentityManager = pskIdentityManager;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.DownTo(ProtocolVersion.TLSv10);
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, DefaultCipherSuites);
+        }
+
+        public override TlsCredentials GetCredentials()
+        {
+            int keyExchangeAlgorithm = m_context.SecurityParameters.KeyExchangeAlgorithm;
+
+            switch (keyExchangeAlgorithm)
+            {
+            case KeyExchangeAlgorithm.DHE_PSK:
+            case KeyExchangeAlgorithm.ECDHE_PSK:
+            case KeyExchangeAlgorithm.PSK:
+                return null;
+
+            case KeyExchangeAlgorithm.RSA_PSK:
+                return GetRsaEncryptionCredentials();
+
+            default:
+                // Note: internal error here; selected a key exchange we don't implement!
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        public override TlsPskIdentityManager GetPskIdentityManager()
+        {
+            return m_pskIdentityManager;
+        }
+    }
+}
diff --git a/crypto/src/tls/RecordFormat.cs b/crypto/src/tls/RecordFormat.cs
new file mode 100644
index 000000000..3bc046902
--- /dev/null
+++ b/crypto/src/tls/RecordFormat.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class RecordFormat
+    {
+        public const int TypeOffset = 0;
+        public const int VersionOffset = 1;
+        public const int LengthOffset = 3;
+        public const int FragmentOffset = 5;
+    }
+}
diff --git a/crypto/src/tls/RecordPreview.cs b/crypto/src/tls/RecordPreview.cs
new file mode 100644
index 000000000..a0abb0fe3
--- /dev/null
+++ b/crypto/src/tls/RecordPreview.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class RecordPreview
+    {
+        private readonly int recordSize;
+        private readonly int contentLimit;
+
+        internal static RecordPreview CombineAppData(RecordPreview a, RecordPreview b)
+        {
+            return new RecordPreview(a.RecordSize + b.RecordSize, a.ContentLimit + b.ContentLimit);
+        }
+
+        internal static RecordPreview ExtendRecordSize(RecordPreview a, int recordSize)
+        {
+            return new RecordPreview(a.RecordSize + recordSize, a.ContentLimit);
+        }
+
+        internal RecordPreview(int recordSize, int contentLimit)
+        {
+            this.recordSize = recordSize;
+            this.contentLimit = contentLimit;
+        }
+
+        public int ContentLimit
+        {
+            get { return contentLimit; }
+        }
+
+        public int RecordSize
+        {
+            get { return recordSize; }
+        }
+    }
+}
diff --git a/crypto/src/tls/RecordStream.cs b/crypto/src/tls/RecordStream.cs
new file mode 100644
index 000000000..a97d34698
--- /dev/null
+++ b/crypto/src/tls/RecordStream.cs
@@ -0,0 +1,533 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>An implementation of the TLS 1.0/1.1/1.2 record layer.</summary>
+    internal sealed class RecordStream
+    {
+        private const int DefaultPlaintextLimit = (1 << 14);
+
+        private readonly Record m_inputRecord = new Record();
+        private readonly SequenceNumber m_readSeqNo = new SequenceNumber(), m_writeSeqNo = new SequenceNumber();
+
+        private readonly TlsProtocol m_handler;
+        private readonly Stream m_input;
+        private readonly Stream m_output;
+
+        private TlsCipher m_pendingCipher = null;
+        private TlsCipher m_readCipher = TlsNullNullCipher.Instance;
+        private TlsCipher m_readCipherDeferred = null;
+        private TlsCipher m_writeCipher = TlsNullNullCipher.Instance;
+
+        private ProtocolVersion m_writeVersion = null;
+
+        private int m_plaintextLimit = DefaultPlaintextLimit;
+        private int m_ciphertextLimit = DefaultPlaintextLimit;
+        private bool m_ignoreChangeCipherSpec = false;
+
+        internal RecordStream(TlsProtocol handler, Stream input, Stream output)
+        {
+            this.m_handler = handler;
+            this.m_input = input;
+            this.m_output = output;
+        }
+
+        internal int PlaintextLimit
+        {
+            get { return m_plaintextLimit; }
+        }
+
+        internal void SetPlaintextLimit(int plaintextLimit)
+        {
+            this.m_plaintextLimit = plaintextLimit;
+            this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(plaintextLimit);
+        }
+
+        internal void SetWriteVersion(ProtocolVersion writeVersion)
+        {
+            this.m_writeVersion = writeVersion;
+        }
+
+        internal void SetIgnoreChangeCipherSpec(bool ignoreChangeCipherSpec)
+        {
+            this.m_ignoreChangeCipherSpec = ignoreChangeCipherSpec;
+        }
+
+        internal void SetPendingCipher(TlsCipher tlsCipher)
+        {
+            this.m_pendingCipher = tlsCipher;
+        }
+
+        /// <exception cref="IOException"/>
+        internal void NotifyChangeCipherSpecReceived()
+        {
+            if (m_pendingCipher == null)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message, "No pending cipher");
+
+            EnablePendingCipherRead(false);
+        }
+
+        /// <exception cref="IOException"/>
+        internal void EnablePendingCipherRead(bool deferred)
+        {
+            if (m_pendingCipher == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (m_readCipherDeferred != null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (deferred)
+            {
+                this.m_readCipherDeferred = m_pendingCipher;
+            }
+            else
+            {
+                this.m_readCipher = m_pendingCipher;
+                this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(m_plaintextLimit);
+                m_readSeqNo.Reset();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        internal void EnablePendingCipherWrite()
+        {
+            if (m_pendingCipher == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_writeCipher = this.m_pendingCipher;
+            m_writeSeqNo.Reset();
+        }
+
+        /// <exception cref="IOException"/>
+        internal void FinaliseHandshake()
+        {
+            if (m_readCipher != m_pendingCipher || m_writeCipher != m_pendingCipher)
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+            this.m_pendingCipher = null;
+        }
+
+        internal bool NeedsKeyUpdate()
+        {
+            return m_writeSeqNo.CurrentValue >= (1L << 20);
+        }
+
+        /// <exception cref="IOException"/>
+        internal void NotifyKeyUpdateReceived()
+        {
+            m_readCipher.RekeyDecoder();
+            m_readSeqNo.Reset();
+        }
+
+        /// <exception cref="IOException"/>
+        internal void NotifyKeyUpdateSent()
+        {
+            m_writeCipher.RekeyEncoder();
+            m_writeSeqNo.Reset();
+        }
+
+        /// <exception cref="IOException"/>
+        internal RecordPreview PreviewRecordHeader(byte[] recordHeader)
+        {
+            short recordType = CheckRecordType(recordHeader, RecordFormat.TypeOffset);
+
+            //ProtocolVersion recordVersion = TlsUtilities.ReadVersion(recordHeader, RecordFormat.VersionOffset);
+
+            int length = TlsUtilities.ReadUint16(recordHeader, RecordFormat.LengthOffset);
+
+            CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
+
+            int recordSize = RecordFormat.FragmentOffset + length;
+            int applicationDataLimit = 0;
+
+            // NOTE: For TLS 1.3, this only MIGHT be application data
+            if (ContentType.application_data == recordType && m_handler.IsApplicationDataReady)
+            {
+                applicationDataLimit = System.Math.Max(0, System.Math.Min(m_plaintextLimit,
+                    m_readCipher.GetPlaintextLimit(length)));
+            }
+
+            return new RecordPreview(recordSize, applicationDataLimit);
+        }
+
+        internal RecordPreview PreviewOutputRecord(int contentLength)
+        {
+            int contentLimit = System.Math.Max(0, System.Math.Min(m_plaintextLimit, contentLength));
+            int recordSize = PreviewOutputRecordSize(contentLimit);
+            return new RecordPreview(recordSize, contentLimit);
+        }
+
+        internal int PreviewOutputRecordSize(int contentLength)
+        {
+            Debug.Assert(contentLength <= m_plaintextLimit);
+
+            return RecordFormat.FragmentOffset + m_writeCipher.GetCiphertextEncodeLimit(contentLength, m_plaintextLimit);
+        }
+
+        /// <exception cref="IOException"/>
+        internal bool ReadFullRecord(byte[] input, int inputOff, int inputLen)
+        {
+            if (inputLen < RecordFormat.FragmentOffset)
+                return false;
+
+            int length = TlsUtilities.ReadUint16(input, inputOff + RecordFormat.LengthOffset);
+            if (inputLen != (RecordFormat.FragmentOffset + length))
+                return false;
+
+            short recordType = CheckRecordType(input, inputOff + RecordFormat.TypeOffset);
+
+            ProtocolVersion recordVersion = TlsUtilities.ReadVersion(input, inputOff + RecordFormat.VersionOffset);
+
+            CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
+
+            if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
+            {
+                CheckChangeCipherSpec(input, inputOff + RecordFormat.FragmentOffset, length);
+                return true;
+            }
+
+            TlsDecodeResult decoded = DecodeAndVerify(recordType, recordVersion, input,
+                inputOff + RecordFormat.FragmentOffset, length);
+
+            m_handler.ProcessRecord(decoded.contentType, decoded.buf, decoded.off, decoded.len);
+            return true;
+        }
+
+        /// <exception cref="IOException"/>
+        internal bool ReadRecord()
+        {
+            if (!m_inputRecord.ReadHeader(m_input))
+                return false;
+
+            short recordType = CheckRecordType(m_inputRecord.m_buf, RecordFormat.TypeOffset);
+
+            ProtocolVersion recordVersion = TlsUtilities.ReadVersion(m_inputRecord.m_buf, RecordFormat.VersionOffset);
+
+            int length = TlsUtilities.ReadUint16(m_inputRecord.m_buf, RecordFormat.LengthOffset);
+
+            CheckLength(length, m_ciphertextLimit, AlertDescription.record_overflow);
+
+            m_inputRecord.ReadFragment(m_input, length);
+
+            TlsDecodeResult decoded;
+            try
+            {
+                if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
+                {
+                    CheckChangeCipherSpec(m_inputRecord.m_buf, RecordFormat.FragmentOffset, length);
+                    return true;
+                }
+
+                decoded = DecodeAndVerify(recordType, recordVersion, m_inputRecord.m_buf, RecordFormat.FragmentOffset,
+                    length);
+            }
+            finally
+            {
+                m_inputRecord.Reset();
+            }
+
+            m_handler.ProcessRecord(decoded.contentType, decoded.buf, decoded.off, decoded.len);
+            return true;
+        }
+
+        /// <exception cref="IOException"/>
+        internal TlsDecodeResult DecodeAndVerify(short recordType, ProtocolVersion recordVersion, byte[] ciphertext,
+            int off, int len)
+        {
+            long seqNo = m_readSeqNo.NextValue(AlertDescription.unexpected_message);
+            TlsDecodeResult decoded = m_readCipher.DecodeCiphertext(seqNo, recordType, recordVersion, ciphertext, off,
+                len);
+
+            CheckLength(decoded.len, m_plaintextLimit, AlertDescription.record_overflow);
+
+            /*
+             * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+             * or ChangeCipherSpec content types.
+             */
+            if (decoded.len < 1 && decoded.contentType != ContentType.application_data)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return decoded;
+        }
+
+        /// <exception cref="IOException"/>
+        internal void WriteRecord(short contentType, byte[] plaintext, int plaintextOffset, int plaintextLength)
+        {
+            // Never send anything until a valid ClientHello has been received
+            if (m_writeVersion == null)
+                return;
+
+            /*
+             * RFC 5246 6.2.1 The length should not exceed 2^14.
+             */
+            CheckLength(plaintextLength, m_plaintextLimit, AlertDescription.internal_error);
+
+            /*
+             * RFC 5246 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+             * or ChangeCipherSpec content types.
+             */
+            if (plaintextLength < 1 && contentType != ContentType.application_data)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            long seqNo = m_writeSeqNo.NextValue(AlertDescription.internal_error);
+            ProtocolVersion recordVersion = m_writeVersion;
+
+            TlsEncodeResult encoded = m_writeCipher.EncodePlaintext(seqNo, contentType, recordVersion,
+                RecordFormat.FragmentOffset, plaintext, plaintextOffset, plaintextLength);
+
+            int ciphertextLength = encoded.len - RecordFormat.FragmentOffset;
+            TlsUtilities.CheckUint16(ciphertextLength);
+
+            TlsUtilities.WriteUint8(encoded.recordType, encoded.buf, encoded.off + RecordFormat.TypeOffset);
+            TlsUtilities.WriteVersion(recordVersion, encoded.buf, encoded.off + RecordFormat.VersionOffset);
+            TlsUtilities.WriteUint16(ciphertextLength, encoded.buf, encoded.off + RecordFormat.LengthOffset);
+
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //try
+            //{
+                m_output.Write(encoded.buf, encoded.off, encoded.len);
+            //}
+            //catch (InterruptedIOException e)
+            //{
+            //    throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            //}
+
+            m_output.Flush();
+        }
+
+        /// <exception cref="IOException"/>
+        internal void Close()
+        {
+            m_inputRecord.Reset();
+
+            IOException io = null;
+            try
+            {
+                Platform.Dispose(m_input);
+            }
+            catch (IOException e)
+            {
+                io = e;
+            }
+
+            try
+            {
+                Platform.Dispose(m_output);
+            }
+            catch (IOException e)
+            {
+                if (io == null)
+                {
+                    io = e;
+                }
+                else
+                {
+                    // TODO[tls] Available from JDK 7
+                    //io.addSuppressed(e);
+                }
+            }
+
+            if (io != null)
+                throw io;
+        }
+
+        /// <exception cref="IOException"/>
+        private void CheckChangeCipherSpec(byte[] buf, int off, int len)
+        {
+            if (1 != len || (byte)ChangeCipherSpec.change_cipher_spec != buf[off])
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message,
+                    "Malformed " + ContentType.GetText(ContentType.change_cipher_spec));
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        private short CheckRecordType(byte[] buf, int off)
+        {
+            short recordType = TlsUtilities.ReadUint8(buf, off);
+
+            if (null != m_readCipherDeferred && recordType == ContentType.application_data)
+            {
+                this.m_readCipher = m_readCipherDeferred;
+                this.m_readCipherDeferred = null;
+                this.m_ciphertextLimit = m_readCipher.GetCiphertextDecodeLimit(m_plaintextLimit);
+                m_readSeqNo.Reset();
+            }
+            else if (m_readCipher.UsesOpaqueRecordType)
+            {
+                if (ContentType.application_data != recordType)
+                {
+                    if (m_ignoreChangeCipherSpec && ContentType.change_cipher_spec == recordType)
+                    {
+                        // See RFC 8446 D.4.
+                    }
+                    else
+                    {
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message,
+                            "Opaque " + ContentType.GetText(recordType));
+                    }
+                }
+            }
+            else
+            {
+                switch (recordType)
+                {
+                case ContentType.application_data:
+                {
+                    if (!m_handler.IsApplicationDataReady)
+                    {
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message,
+                            "Not ready for " + ContentType.GetText(ContentType.application_data));
+                    }
+                    break;
+                }
+                case ContentType.alert:
+                case ContentType.change_cipher_spec:
+                case ContentType.handshake:
+        //        case ContentType.heartbeat:
+                    break;
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message,
+                        "Unsupported " + ContentType.GetText(recordType));
+                }
+            }
+
+            return recordType;
+        }
+
+        /// <exception cref="IOException"/>
+        private static void CheckLength(int length, int limit, short alertDescription)
+        {
+            if (length > limit)
+                throw new TlsFatalAlert(alertDescription);
+        }
+
+        private sealed class Record
+        {
+            private readonly byte[] m_header = new byte[RecordFormat.FragmentOffset];
+
+            internal volatile byte[] m_buf;
+            internal volatile int m_pos;
+
+            internal Record()
+            {
+                this.m_buf = m_header;
+                this.m_pos = 0;
+            }
+
+            /// <exception cref="IOException"/>
+            internal void FillTo(Stream input, int length)
+            {
+                while (m_pos < length)
+                {
+                    // TODO[tls-port] Can we support interrupted IO on .NET?
+                    //try
+                    //{
+                        int numRead = input.Read(m_buf, m_pos, length - m_pos);
+                        if (numRead < 1)
+                            break;
+
+                        m_pos += numRead;
+                    //}
+                    //catch (InterruptedIOException e)
+                    //{
+                    //    /*
+                    //     * Although modifying the bytesTransferred doesn't seem ideal, it's the simplest
+                    //     * way to make sure we don't break client code that depends on the exact type,
+                    //     * e.g. in Apache's httpcomponents-core-4.4.9, BHttpConnectionBase.isStale
+                    //     * depends on the exception type being SocketTimeoutException (or a subclass).
+                    //     *
+                    //     * We can set to 0 here because the only relevant callstack (via
+                    //     * TlsProtocol.readApplicationData) only ever processes one non-empty record (so
+                    //     * interruption after partial output cannot occur).
+                    //     */
+                    //    m_pos += e.bytesTransferred;
+                    //    e.bytesTransferred = 0;
+                    //    throw e;
+                    //}
+                }
+            }
+
+            /// <exception cref="IOException"/>
+            internal void ReadFragment(Stream input, int fragmentLength)
+            {
+                int recordLength = RecordFormat.FragmentOffset + fragmentLength;
+                Resize(recordLength);
+                FillTo(input, recordLength);
+                if (m_pos < recordLength)
+                    throw new EndOfStreamException();
+            }
+
+            /// <exception cref="IOException"/>
+            internal bool ReadHeader(Stream input)
+            {
+                FillTo(input, RecordFormat.FragmentOffset);
+                if (m_pos == 0)
+                    return false;
+
+                if (m_pos < RecordFormat.FragmentOffset)
+                    throw new EndOfStreamException();
+
+                return true;
+            }
+
+            internal void Reset()
+            {
+                m_buf = m_header;
+                m_pos = 0;
+            }
+
+            private void Resize(int length)
+            {
+                if (m_buf.Length < length)
+                {
+                    byte[] tmp = new byte[length];
+                    Array.Copy(m_buf, 0, tmp, 0, m_pos);
+                    m_buf = tmp;
+                }
+            }
+        }
+
+        private sealed class SequenceNumber
+        {
+            private long m_value = 0L;
+            private bool m_exhausted = false;
+
+            internal long CurrentValue
+            {
+                get { lock (this) return m_value; }
+            }
+
+            /// <exception cref="TlsFatalAlert"/>
+            internal long NextValue(short alertDescription)
+            {
+                lock (this)
+                {
+                    if (m_exhausted)
+                        throw new TlsFatalAlert(alertDescription, "Sequence numbers exhausted");
+
+                    long result = m_value;
+                    if (++m_value == 0L)
+                    {
+                        this.m_exhausted = true;
+                    }
+                    return result;
+                }
+            }
+
+            internal void Reset()
+            {
+                lock (this)
+                {
+                    this.m_value = 0L;
+                    this.m_exhausted = false;
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/SecurityParameters.cs b/crypto/src/tls/SecurityParameters.cs
new file mode 100644
index 000000000..84c3b9f11
--- /dev/null
+++ b/crypto/src/tls/SecurityParameters.cs
@@ -0,0 +1,334 @@
+using System;
+using System.Collections;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class SecurityParameters
+    {
+        internal int m_entity = -1;
+        internal bool m_secureRenegotiation = false;
+        internal int m_cipherSuite = Tls.CipherSuite.TLS_NULL_WITH_NULL_NULL;
+        internal short m_maxFragmentLength = -1;
+        internal int m_prfAlgorithm = -1;
+        internal short m_prfHashAlgorithm = -1;
+        internal int m_prfHashLength = -1;
+        internal int m_verifyDataLength = -1;
+        internal TlsSecret m_baseKeyClient = null;
+        internal TlsSecret m_baseKeyServer = null;
+        internal TlsSecret m_earlyExporterMasterSecret = null;
+        internal TlsSecret m_earlySecret = null;
+        internal TlsSecret m_exporterMasterSecret = null;
+        internal TlsSecret m_handshakeSecret = null;
+        internal TlsSecret m_masterSecret = null;
+        internal TlsSecret m_sharedSecret = null;
+        internal TlsSecret m_trafficSecretClient = null;
+        internal TlsSecret m_trafficSecretServer = null;
+        internal byte[] m_clientRandom = null;
+        internal byte[] m_serverRandom = null;
+        internal byte[] m_sessionHash = null;
+        internal byte[] m_sessionID = null;
+        internal byte[] m_psk = null;
+        internal byte[] m_pskIdentity = null;
+        internal byte[] m_srpIdentity = null;
+        internal byte[] m_tlsServerEndPoint = null;
+        internal byte[] m_tlsUnique = null;
+        internal bool m_encryptThenMac = false;
+        internal bool m_extendedMasterSecret = false;
+        internal bool m_extendedPadding = false;
+        internal bool m_truncatedHmac = false;
+        internal ProtocolName m_applicationProtocol = null;
+        internal bool m_applicationProtocolSet = false;
+        internal short[] m_clientCertTypes = null;
+        internal IList m_clientServerNames = null;
+        internal IList m_clientSigAlgs = null;
+        internal IList m_clientSigAlgsCert = null;
+        internal int[] m_clientSupportedGroups = null;
+        internal IList m_serverSigAlgs = null;
+        internal IList m_serverSigAlgsCert = null;
+        internal int[] m_serverSupportedGroups = null;
+        internal int m_keyExchangeAlgorithm = -1;
+        internal Certificate m_localCertificate = null;
+        internal Certificate m_peerCertificate = null;
+        internal ProtocolVersion m_negotiatedVersion = null;
+        internal int m_statusRequestVersion = 0;
+
+        // TODO[tls-ops] Investigate whether we can handle verify data using TlsSecret
+        internal byte[] m_localVerifyData = null;
+        internal byte[] m_peerVerifyData = null;
+
+        internal void Clear()
+        {
+            this.m_sessionHash = null;
+            this.m_sessionID = null;
+            this.m_clientCertTypes = null;
+            this.m_clientServerNames = null;
+            this.m_clientSigAlgs = null;
+            this.m_clientSigAlgsCert = null;
+            this.m_clientSupportedGroups = null;
+            this.m_serverSigAlgs = null;
+            this.m_serverSigAlgsCert = null;
+            this.m_serverSupportedGroups = null;
+            this.m_statusRequestVersion = 0;
+
+            this.m_baseKeyClient = ClearSecret(m_baseKeyClient);
+            this.m_baseKeyServer = ClearSecret(m_baseKeyServer);
+            this.m_earlyExporterMasterSecret = ClearSecret(m_earlyExporterMasterSecret);
+            this.m_earlySecret = ClearSecret(m_earlySecret);
+            this.m_exporterMasterSecret = ClearSecret(m_exporterMasterSecret);
+            this.m_handshakeSecret = ClearSecret(m_handshakeSecret);
+            this.m_masterSecret = ClearSecret(m_masterSecret);
+            this.m_sharedSecret = ClearSecret(m_sharedSecret);
+        }
+
+        public ProtocolName ApplicationProtocol
+        {
+            get { return m_applicationProtocol; }
+        }
+
+        public TlsSecret BaseKeyClient
+        {
+            get { return m_baseKeyClient; }
+        }
+
+        public TlsSecret BaseKeyServer
+        {
+            get { return m_baseKeyServer; }
+        }
+
+        public int CipherSuite
+        {
+            get { return m_cipherSuite; }
+        }
+
+        public short[] ClientCertTypes
+        {
+            get { return m_clientCertTypes; }
+        }
+
+        public byte[] ClientRandom
+        {
+            get { return m_clientRandom; }
+        }
+
+        public IList ClientServerNames
+        {
+            get { return m_clientServerNames; }
+        }
+
+        public IList ClientSigAlgs
+        {
+            get { return m_clientSigAlgs; }
+        }
+
+        public IList ClientSigAlgsCert
+        {
+            get { return m_clientSigAlgsCert; }
+        }
+
+        public int[] ClientSupportedGroups
+        {
+            get { return m_clientSupportedGroups; }
+        }
+
+        public TlsSecret EarlyExporterMasterSecret
+        {
+            get { return m_earlyExporterMasterSecret; }
+        }
+
+        public TlsSecret EarlySecret
+        {
+            get { return m_earlySecret; }
+        }
+
+        public TlsSecret ExporterMasterSecret
+        {
+            get { return m_exporterMasterSecret; }
+        }
+
+        public int Entity
+        {
+            get { return m_entity; }
+        }
+
+        public TlsSecret HandshakeSecret
+        {
+            get { return m_handshakeSecret; }
+        }
+
+        public bool IsApplicationProtocolSet
+        {
+            get { return m_applicationProtocolSet; }
+        }
+
+        public bool IsEncryptThenMac
+        {
+            get { return m_encryptThenMac; }
+        }
+
+        public bool IsExtendedMasterSecret
+        {
+            get { return m_extendedMasterSecret; }
+        }
+
+        public bool IsExtendedPadding
+        {
+            get { return m_extendedPadding; }
+        }
+
+        public bool IsSecureRenegotiation
+        {
+            get { return m_secureRenegotiation; }
+        }
+
+        public bool IsTruncatedHmac
+        {
+            get { return m_truncatedHmac; }
+        }
+
+        public int KeyExchangeAlgorithm
+        {
+            get { return m_keyExchangeAlgorithm; }
+        }
+
+        public Certificate LocalCertificate
+        {
+            get { return m_localCertificate; }
+        }
+
+        public byte[] LocalVerifyData
+        {
+            get { return m_localVerifyData; }
+        }
+
+        public TlsSecret MasterSecret
+        {
+            get { return m_masterSecret; }
+        }
+
+        public short MaxFragmentLength
+        {
+            get { return m_maxFragmentLength; }
+        }
+
+        public ProtocolVersion NegotiatedVersion
+        {
+            get { return m_negotiatedVersion; }
+        }
+
+        public Certificate PeerCertificate
+        {
+            get { return m_peerCertificate; }
+        }
+
+        public byte[] PeerVerifyData
+        {
+            get { return m_peerVerifyData; }
+        }
+
+        public int PrfAlgorithm
+        {
+            get { return m_prfAlgorithm; }
+        }
+
+        public short PrfHashAlgorithm
+        {
+            get { return m_prfHashAlgorithm; }
+        }
+
+        public int PrfHashLength
+        {
+            get { return m_prfHashLength; }
+        }
+
+        public byte[] Psk
+        {
+            get { return m_psk; }
+        }
+
+        public byte[] PskIdentity
+        {
+            get { return m_pskIdentity; }
+        }
+
+        public byte[] ServerRandom
+        {
+            get { return m_serverRandom; }
+        }
+
+        public IList ServerSigAlgs
+        {
+            get { return m_serverSigAlgs; }
+        }
+
+        public IList ServerSigAlgsCert
+        {
+            get { return m_serverSigAlgsCert; }
+        }
+
+        public int[] ServerSupportedGroups
+        {
+            get { return m_serverSupportedGroups; }
+        }
+
+        public byte[] SessionHash
+        {
+            get { return m_sessionHash; }
+        }
+
+        public byte[] SessionID
+        {
+            get { return m_sessionID; }
+        }
+
+        public TlsSecret SharedSecret
+        {
+            get { return m_sharedSecret; }
+        }
+
+        public byte[] SrpIdentity
+        {
+            get { return m_srpIdentity; }
+        }
+
+        public int StatusRequestVersion
+        {
+            get { return m_statusRequestVersion; }
+        }
+
+        public byte[] TlsServerEndPoint
+        {
+            get { return m_tlsServerEndPoint; }
+        }
+
+        public byte[] TlsUnique
+        {
+            get { return m_tlsUnique; }
+        }
+
+        public TlsSecret TrafficSecretClient
+        {
+            get { return m_trafficSecretClient; }
+        }
+
+        public TlsSecret TrafficSecretServer
+        {
+            get { return m_trafficSecretServer; }
+        }
+
+        public int VerifyDataLength
+        {
+            get { return m_verifyDataLength; }
+        }
+
+        private static TlsSecret ClearSecret(TlsSecret secret)
+        {
+            if (null != secret)
+            {
+                secret.Destroy();
+            }
+            return null;
+        }
+    }
+}
diff --git a/crypto/src/tls/ServerHello.cs b/crypto/src/tls/ServerHello.cs
new file mode 100644
index 000000000..15cc09032
--- /dev/null
+++ b/crypto/src/tls/ServerHello.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class ServerHello
+    {
+        private static readonly byte[] HelloRetryRequestMagic = { 0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE,
+            0x1D, 0x8C, 0x02, 0x1E, 0x65, 0xB8, 0x91, 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB, 0x8C, 0x5E, 0x07, 0x9E, 0x09,
+            0xE2, 0xC8, 0xA8, 0x33, 0x9C };
+
+        private readonly ProtocolVersion m_version;
+        private readonly byte[] m_random;
+        private readonly byte[] m_sessionID;
+        private readonly int m_cipherSuite;
+        private readonly IDictionary m_extensions;
+
+        public ServerHello(byte[] sessionID, int cipherSuite, IDictionary extensions)
+            : this(ProtocolVersion.TLSv12, Arrays.Clone(HelloRetryRequestMagic), sessionID, cipherSuite, extensions)
+        {
+        }
+
+        public ServerHello(ProtocolVersion version, byte[] random, byte[] sessionID, int cipherSuite,
+            IDictionary extensions)
+        {
+            this.m_version = version;
+            this.m_random = random;
+            this.m_sessionID = sessionID;
+            this.m_cipherSuite = cipherSuite;
+            this.m_extensions = extensions;
+        }
+
+        public int CipherSuite
+        {
+            get { return m_cipherSuite; }
+        }
+
+        public IDictionary Extensions
+        {
+            get { return m_extensions; }
+        }
+
+        public byte[] Random
+        {
+            get { return m_random; }
+        }
+
+        public byte[] SessionID
+        {
+            get { return m_sessionID; }
+        }
+
+        public ProtocolVersion Version
+        {
+            get { return m_version; }
+        }
+
+        public bool IsHelloRetryRequest()
+        {
+            return Arrays.AreEqual(HelloRetryRequestMagic, m_random);
+        }
+
+        /// <summary>Encode this <see cref="ServerHello"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(TlsContext context, Stream output)
+        {
+            TlsUtilities.WriteVersion(m_version, output);
+
+            output.Write(m_random, 0, m_random.Length);
+
+            TlsUtilities.WriteOpaque8(m_sessionID, output);
+
+            TlsUtilities.WriteUint16(m_cipherSuite, output);
+
+            TlsUtilities.WriteUint8(CompressionMethod.cls_null, output);
+
+            TlsProtocol.WriteExtensions(output, m_extensions);
+        }
+
+        /// <summary>Parse a <see cref="ServerHello"/> from a <see cref="MemoryStream"/>.</summary>
+        /// <param name="input">the <see cref="MemoryStream"/> to parse from.</param>
+        /// <returns>a <see cref="ServerHello"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static ServerHello Parse(MemoryStream input)
+        {
+            ProtocolVersion version = TlsUtilities.ReadVersion(input);
+
+            byte[] random = TlsUtilities.ReadFully(32, input);
+
+            byte[] sessionID = TlsUtilities.ReadOpaque8(input, 0, 32);
+
+            int cipherSuite = TlsUtilities.ReadUint16(input);
+
+            short compressionMethod = TlsUtilities.ReadUint8(input);
+            if (CompressionMethod.cls_null != compressionMethod)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            IDictionary extensions = TlsProtocol.ReadExtensions(input);
+
+            return new ServerHello(version, random, sessionID, cipherSuite, extensions);
+        }
+    }
+}
diff --git a/crypto/src/tls/ServerName.cs b/crypto/src/tls/ServerName.cs
new file mode 100644
index 000000000..da40c2c8a
--- /dev/null
+++ b/crypto/src/tls/ServerName.cs
@@ -0,0 +1,63 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 6066 3. Server Name Indication</summary>
+    /// <remarks>
+    /// Current implementation uses this guidance: "For backward compatibility, all future data structures associated
+    /// with new NameTypes MUST begin with a 16-bit length field. TLS MAY treat provided server names as opaque data
+    /// and pass the names and types to the application.". RFC 6066 specifies ASCII encoding for host_name (possibly
+    /// using A-labels for IDNs), but note that the previous version (RFC 4366) specified UTF-8 encoding (see RFC 6066
+    /// Appendix A). For maximum compatibility, it is recommended that client code tolerate receiving UTF-8 from the
+    /// peer, but only generate ASCII itself.
+    /// </remarks>
+    public sealed class ServerName
+    {
+        private readonly short nameType;
+        private readonly byte[] nameData;
+
+        public ServerName(short nameType, byte[] nameData)
+        {
+            if (!TlsUtilities.IsValidUint8(nameType))
+                throw new ArgumentException("must be from 0 to 255", "nameType");
+            if (null == nameData)
+                throw new ArgumentNullException("nameData");
+            if (nameData.Length < 1 || !TlsUtilities.IsValidUint16(nameData.Length))
+                throw new ArgumentException("must have length from 1 to 65535", "nameData");
+
+            this.nameType = nameType;
+            this.nameData = nameData;
+        }
+
+        public byte[] NameData
+        {
+            get { return nameData; }
+        }
+
+        public short NameType
+        {
+            get { return nameType; }
+        }
+
+        /// <summary>Encode this <see cref="ServerName"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(nameType, output);
+            TlsUtilities.WriteOpaque16(nameData, output);
+        }
+
+        /// <summary>Parse a <see cref="ServerName"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="ServerName"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static ServerName Parse(Stream input)
+        {
+            short name_type = TlsUtilities.ReadUint8(input);
+            byte[] nameData = TlsUtilities.ReadOpaque16(input, 1);
+            return new ServerName(name_type, nameData);
+        }
+    }
+}
diff --git a/crypto/src/tls/ServerNameList.cs b/crypto/src/tls/ServerNameList.cs
new file mode 100644
index 000000000..915e94390
--- /dev/null
+++ b/crypto/src/tls/ServerNameList.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class ServerNameList
+    {
+        private readonly IList m_serverNameList;
+
+        /// <param name="serverNameList">an <see cref="IList"/> of <see cref="ServerName"/>.</param>
+        public ServerNameList(IList serverNameList)
+        {
+            if (null == serverNameList)
+                throw new ArgumentNullException("serverNameList");
+
+            this.m_serverNameList = serverNameList;
+        }
+
+        /// <returns>an <see cref="IList"/> of <see cref="ServerName"/>.</returns>
+        public IList ServerNames
+        {
+            get { return m_serverNameList; }
+        }
+
+        /// <summary>Encode this <see cref="ServerNameList"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to .</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            MemoryStream buf = new MemoryStream();
+
+            short[] nameTypesSeen = TlsUtilities.EmptyShorts;
+            foreach (ServerName entry in ServerNames)
+            {
+                nameTypesSeen = CheckNameType(nameTypesSeen, entry.NameType);
+                if (null == nameTypesSeen)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                entry.Encode(buf);
+            }
+
+            int length = (int)buf.Length;
+            TlsUtilities.CheckUint16(length);
+            TlsUtilities.WriteUint16(length, output);
+            Streams.WriteBufTo(buf, output);
+        }
+
+        /// <summary>Parse a <see cref="ServerNameList"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="ServerNameList"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static ServerNameList Parse(Stream input)
+        {
+            byte[] data = TlsUtilities.ReadOpaque16(input, 1);
+
+            MemoryStream buf = new MemoryStream(data, false);
+
+            short[] nameTypesSeen = TlsUtilities.EmptyShorts;
+            IList server_name_list = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                ServerName entry = ServerName.Parse(buf);
+
+                nameTypesSeen = CheckNameType(nameTypesSeen, entry.NameType);
+                if (null == nameTypesSeen)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                server_name_list.Add(entry);
+            }
+
+            return new ServerNameList(server_name_list);
+        }
+
+        private static short[] CheckNameType(short[] nameTypesSeen, short nameType)
+        {
+             // RFC 6066 3. The ServerNameList MUST NOT contain more than one name of the same NameType.
+            if (Arrays.Contains(nameTypesSeen, nameType))
+                return null;
+
+            return Arrays.Append(nameTypesSeen, nameType);
+        }
+    }
+}
diff --git a/crypto/src/tls/ServerOnlyTlsAuthentication.cs b/crypto/src/tls/ServerOnlyTlsAuthentication.cs
new file mode 100644
index 000000000..5a8d59904
--- /dev/null
+++ b/crypto/src/tls/ServerOnlyTlsAuthentication.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class ServerOnlyTlsAuthentication
+        : TlsAuthentication
+    {
+        public abstract void NotifyServerCertificate(TlsServerCertificate serverCertificate);
+
+        public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
+        {
+            return null;
+        }
+    }
+}
diff --git a/crypto/src/tls/ServerSrpParams.cs b/crypto/src/tls/ServerSrpParams.cs
new file mode 100644
index 000000000..9aca882f6
--- /dev/null
+++ b/crypto/src/tls/ServerSrpParams.cs
@@ -0,0 +1,67 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class ServerSrpParams
+    {
+        private BigInteger m_N, m_g, m_B;
+        private byte[] m_s;
+
+        public ServerSrpParams(BigInteger N, BigInteger g, byte[] s, BigInteger B)
+        {
+            this.m_N = N;
+            this.m_g = g;
+            this.m_s = Arrays.Clone(s);
+            this.m_B = B;
+        }
+
+        public BigInteger B
+        {
+            get { return m_B; }
+        }
+
+        public BigInteger G
+        {
+            get { return m_g; }
+        }
+
+        public BigInteger N
+        {
+            get { return m_N; }
+        }
+
+        public byte[] S
+        {
+            get { return m_s; }
+        }
+
+        /// <summary>Encode this <see cref="ServerSrpParams"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsSrpUtilities.WriteSrpParameter(m_N, output);
+            TlsSrpUtilities.WriteSrpParameter(m_g, output);
+            TlsUtilities.WriteOpaque8(m_s, output);
+            TlsSrpUtilities.WriteSrpParameter(m_B, output);
+        }
+
+        /// <summary>Parse a <see cref="ServerSrpParams"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="ServerSrpParams"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static ServerSrpParams Parse(Stream input)
+        {
+            BigInteger N = TlsSrpUtilities.ReadSrpParameter(input);
+            BigInteger g = TlsSrpUtilities.ReadSrpParameter(input);
+            byte[] s = TlsUtilities.ReadOpaque8(input, 1);
+            BigInteger B = TlsSrpUtilities.ReadSrpParameter(input);
+
+            return new ServerSrpParams(N, g, s, B);
+        }
+    }
+}
diff --git a/crypto/src/tls/SessionParameters.cs b/crypto/src/tls/SessionParameters.cs
new file mode 100644
index 000000000..9a62e351c
--- /dev/null
+++ b/crypto/src/tls/SessionParameters.cs
@@ -0,0 +1,195 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class SessionParameters
+    {
+        public sealed class Builder
+        {
+            private int m_cipherSuite = -1;
+            private Certificate m_localCertificate = null;
+            private TlsSecret m_masterSecret = null;
+            private ProtocolVersion m_negotiatedVersion;
+            private Certificate m_peerCertificate = null;
+            private byte[] m_pskIdentity = null;
+            private byte[] m_srpIdentity = null;
+            private byte[] m_encodedServerExtensions = null;
+            private bool m_extendedMasterSecret = false;
+
+            public Builder()
+            {
+            }
+
+            public SessionParameters Build()
+            {
+                Validate(m_cipherSuite >= 0, "cipherSuite");
+                Validate(m_masterSecret != null, "masterSecret");
+                return new SessionParameters(m_cipherSuite, m_localCertificate, m_masterSecret, m_negotiatedVersion,
+                    m_peerCertificate, m_pskIdentity, m_srpIdentity, m_encodedServerExtensions, m_extendedMasterSecret);
+            }
+
+            public Builder SetCipherSuite(int cipherSuite)
+            {
+                this.m_cipherSuite = cipherSuite;
+                return this;
+            }
+
+            public Builder SetExtendedMasterSecret(bool extendedMasterSecret)
+            {
+                this.m_extendedMasterSecret = extendedMasterSecret;
+                return this;
+            }
+
+            public Builder SetLocalCertificate(Certificate localCertificate)
+            {
+                this.m_localCertificate = localCertificate;
+                return this;
+            }
+
+            public Builder SetMasterSecret(TlsSecret masterSecret)
+            {
+                this.m_masterSecret = masterSecret;
+                return this;
+            }
+
+            public Builder SetNegotiatedVersion(ProtocolVersion negotiatedVersion)
+            {
+                this.m_negotiatedVersion = negotiatedVersion;
+                return this;
+            }
+
+            public Builder SetPeerCertificate(Certificate peerCertificate)
+            {
+                this.m_peerCertificate = peerCertificate;
+                return this;
+            }
+
+            public Builder SetPskIdentity(byte[] pskIdentity)
+            {
+                this.m_pskIdentity = pskIdentity;
+                return this;
+            }
+
+            public Builder SetSrpIdentity(byte[] srpIdentity)
+            {
+                this.m_srpIdentity = srpIdentity;
+                return this;
+            }
+
+            /// <exception cref="IOException"/>
+            public Builder SetServerExtensions(IDictionary serverExtensions)
+            {
+                if (serverExtensions == null || serverExtensions.Count < 1)
+                {
+                    this.m_encodedServerExtensions = null;
+                }
+                else
+                {
+                    MemoryStream buf = new MemoryStream();
+                    TlsProtocol.WriteExtensions(buf, serverExtensions);
+                    this.m_encodedServerExtensions = buf.ToArray();
+                }
+                return this;
+            }
+
+            private void Validate(bool condition, string parameter)
+            {
+                if (!condition)
+                    throw new InvalidOperationException("Required session parameter '" + parameter + "' not configured");
+            }
+        }
+
+        private readonly int m_cipherSuite;
+        private readonly Certificate m_localCertificate;
+        private readonly TlsSecret m_masterSecret;
+        private readonly ProtocolVersion m_negotiatedVersion;
+        private readonly Certificate m_peerCertificate;
+        private readonly byte[] m_pskIdentity;
+        private readonly byte[] m_srpIdentity;
+        private readonly byte[] m_encodedServerExtensions;
+        private readonly bool m_extendedMasterSecret;
+
+        private SessionParameters(int cipherSuite, Certificate localCertificate, TlsSecret masterSecret,
+            ProtocolVersion negotiatedVersion, Certificate peerCertificate, byte[] pskIdentity, byte[] srpIdentity,
+            byte[] encodedServerExtensions, bool extendedMasterSecret)
+        {
+            this.m_cipherSuite = cipherSuite;
+            this.m_localCertificate = localCertificate;
+            this.m_masterSecret = masterSecret;
+            this.m_negotiatedVersion = negotiatedVersion;
+            this.m_peerCertificate = peerCertificate;
+            this.m_pskIdentity = Arrays.Clone(pskIdentity);
+            this.m_srpIdentity = Arrays.Clone(srpIdentity);
+            this.m_encodedServerExtensions = encodedServerExtensions;
+            this.m_extendedMasterSecret = extendedMasterSecret;
+        }
+
+        public int CipherSuite
+        {
+            get { return m_cipherSuite; }
+        }
+
+        public void Clear()
+        {
+            if (m_masterSecret != null)
+            {
+                m_masterSecret.Destroy();
+            }
+        }
+
+        public SessionParameters Copy()
+        {
+            return new SessionParameters(m_cipherSuite, m_localCertificate, m_masterSecret, m_negotiatedVersion,
+                m_peerCertificate, m_pskIdentity, m_srpIdentity, m_encodedServerExtensions, m_extendedMasterSecret);
+        }
+
+        public bool IsExtendedMasterSecret
+        {
+            get { return m_extendedMasterSecret; }
+        }
+
+        public Certificate LocalCertificate
+        {
+            get { return m_localCertificate; }
+        }
+
+        public TlsSecret MasterSecret
+        {
+            get { return m_masterSecret; }
+        }
+
+        public ProtocolVersion NegotiatedVersion
+        {
+            get { return m_negotiatedVersion; }
+        }
+
+        public Certificate PeerCertificate
+        {
+            get { return m_peerCertificate; }
+        }
+
+        public byte[] PskIdentity
+        {
+            get { return m_pskIdentity; }
+        }
+
+        /// <exception cref="IOException"/>
+        public IDictionary ReadServerExtensions()
+        {
+            if (m_encodedServerExtensions == null)
+                return null;
+
+            return TlsProtocol.ReadExtensions(new MemoryStream(m_encodedServerExtensions, false));
+        }
+
+        public byte[] SrpIdentity
+        {
+            get { return m_srpIdentity; }
+        }
+    }
+}
diff --git a/crypto/src/tls/SignatureAlgorithm.cs b/crypto/src/tls/SignatureAlgorithm.cs
new file mode 100644
index 000000000..726504c52
--- /dev/null
+++ b/crypto/src/tls/SignatureAlgorithm.cs
@@ -0,0 +1,125 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /**
+     * RFC 5246 7.4.1.4.1 (in RFC 2246, there were no specific values assigned)
+     */
+    public class SignatureAlgorithm
+    {
+        public const short anonymous = 0;
+        public const short rsa = 1;
+        public const short dsa = 2;
+        public const short ecdsa = 3;
+
+        /*
+         * RFC 8422
+         */
+        public const short ed25519 = 7;
+        public const short ed448 = 8;
+
+        /*
+         * RFC 8446 (implied from SignatureScheme values)
+         * RFC 8447 reserved these values without allocating the implied names
+         */
+        public const short rsa_pss_rsae_sha256 = 4;
+        public const short rsa_pss_rsae_sha384 = 5;
+        public const short rsa_pss_rsae_sha512 = 6;
+        public const short rsa_pss_pss_sha256 = 9;
+        public const short rsa_pss_pss_sha384 = 10;
+        public const short rsa_pss_pss_sha512 = 11;
+
+        /*
+         * RFC 8734 (implied from SignatureScheme values)
+         */
+        public const short ecdsa_brainpoolP256r1tls13_sha256 = 26;
+        public const short ecdsa_brainpoolP384r1tls13_sha384 = 27;
+        public const short ecdsa_brainpoolP512r1tls13_sha512 = 28;
+
+        /*
+         * draft-smyshlyaev-tls12-gost-suites-10
+         */
+        public const short gostr34102012_256 = 64;
+        public const short gostr34102012_512 = 65;
+
+        public static short GetClientCertificateType(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return ClientCertificateType.rsa_sign;
+
+            case SignatureAlgorithm.dsa:
+                return ClientCertificateType.dss_sign;
+
+            case SignatureAlgorithm.ecdsa:
+            case SignatureAlgorithm.ed25519:
+            case SignatureAlgorithm.ed448:
+                return ClientCertificateType.ecdsa_sign;
+
+            case SignatureAlgorithm.gostr34102012_256:
+                return ClientCertificateType.gost_sign256;
+
+            case SignatureAlgorithm.gostr34102012_512:
+                return ClientCertificateType.gost_sign512;
+
+            default:
+                return -1;
+            }
+        }
+
+        public static string GetName(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case anonymous:
+                return "anonymous";
+            case rsa:
+                return "rsa";
+            case dsa:
+                return "dsa";
+            case ecdsa:
+                return "ecdsa";
+            case ed25519:
+                return "ed25519";
+            case ed448:
+                return "ed448";
+            case gostr34102012_256:
+                return "gostr34102012_256";
+            case gostr34102012_512:
+                return "gostr34102012_512";
+            case rsa_pss_rsae_sha256:
+                return "rsa_pss_rsae_sha256";
+            case rsa_pss_rsae_sha384:
+                return "rsa_pss_rsae_sha384";
+            case rsa_pss_rsae_sha512:
+                return "rsa_pss_rsae_sha512";
+            case rsa_pss_pss_sha256:
+                return "rsa_pss_pss_sha256";
+            case rsa_pss_pss_sha384:
+                return "rsa_pss_pss_sha384";
+            case rsa_pss_pss_sha512:
+                return "rsa_pss_pss_sha512";
+            case ecdsa_brainpoolP256r1tls13_sha256:
+                return "ecdsa_brainpoolP256r1tls13_sha256";
+            case ecdsa_brainpoolP384r1tls13_sha384:
+                return "ecdsa_brainpoolP384r1tls13_sha384";
+            case ecdsa_brainpoolP512r1tls13_sha512:
+                return "ecdsa_brainpoolP512r1tls13_sha512";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        public static string GetText(short signatureAlgorithm)
+        {
+            return GetName(signatureAlgorithm) + "(" + signatureAlgorithm + ")";
+        }
+    }
+}
diff --git a/crypto/src/tls/SignatureAndHashAlgorithm.cs b/crypto/src/tls/SignatureAndHashAlgorithm.cs
new file mode 100644
index 000000000..9de2a42e4
--- /dev/null
+++ b/crypto/src/tls/SignatureAndHashAlgorithm.cs
@@ -0,0 +1,171 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5246 7.4.1.4.1</summary>
+    public sealed class SignatureAndHashAlgorithm
+    {
+        public static readonly SignatureAndHashAlgorithm ecdsa_brainpoolP256r1tls13_sha256 =
+            Create(SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256);
+        public static readonly SignatureAndHashAlgorithm ecdsa_brainpoolP384r1tls13_sha384 =
+            Create(SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384);
+        public static readonly SignatureAndHashAlgorithm ecdsa_brainpoolP512r1tls13_sha512 =
+            Create(SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512);
+        public static readonly SignatureAndHashAlgorithm ed25519 =
+            Create(SignatureScheme.ed25519);
+        public static readonly SignatureAndHashAlgorithm ed448 =
+            Create(SignatureScheme.ed448);
+        public static readonly SignatureAndHashAlgorithm gostr34102012_256 =
+            Create(HashAlgorithm.Intrinsic, SignatureAlgorithm.gostr34102012_256);
+        public static readonly SignatureAndHashAlgorithm gostr34102012_512 =
+            Create(HashAlgorithm.Intrinsic, SignatureAlgorithm.gostr34102012_512);
+        public static readonly SignatureAndHashAlgorithm rsa_pss_rsae_sha256 =
+            Create(SignatureScheme.rsa_pss_rsae_sha256);
+        public static readonly SignatureAndHashAlgorithm rsa_pss_rsae_sha384 =
+            Create(SignatureScheme.rsa_pss_rsae_sha384);
+        public static readonly SignatureAndHashAlgorithm rsa_pss_rsae_sha512 =
+            Create(SignatureScheme.rsa_pss_rsae_sha512);
+        public static readonly SignatureAndHashAlgorithm rsa_pss_pss_sha256 =
+            Create(SignatureScheme.rsa_pss_pss_sha256);
+        public static readonly SignatureAndHashAlgorithm rsa_pss_pss_sha384 =
+            Create(SignatureScheme.rsa_pss_pss_sha384);
+        public static readonly SignatureAndHashAlgorithm rsa_pss_pss_sha512 =
+            Create(SignatureScheme.rsa_pss_pss_sha512);
+
+        public static SignatureAndHashAlgorithm GetInstance(short hashAlgorithm, short signatureAlgorithm)
+        {
+            switch (hashAlgorithm)
+            {
+            case HashAlgorithm.Intrinsic:
+                return GetInstanceIntrinsic(signatureAlgorithm);
+            default:
+                return Create(hashAlgorithm, signatureAlgorithm);
+            }
+        }
+
+        private static SignatureAndHashAlgorithm GetInstanceIntrinsic(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.ed25519:
+                return ed25519;
+            case SignatureAlgorithm.ed448:
+                return ed448;
+            case SignatureAlgorithm.gostr34102012_256:
+                return gostr34102012_256;
+            case SignatureAlgorithm.gostr34102012_512:
+                return gostr34102012_512;
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+                return rsa_pss_rsae_sha256;
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+                return rsa_pss_rsae_sha384;
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                return rsa_pss_rsae_sha512;
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+                return rsa_pss_pss_sha256;
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+                return rsa_pss_pss_sha384;
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return rsa_pss_pss_sha512;
+            case SignatureAlgorithm.ecdsa_brainpoolP256r1tls13_sha256:
+                return ecdsa_brainpoolP256r1tls13_sha256;
+            case SignatureAlgorithm.ecdsa_brainpoolP384r1tls13_sha384:
+                return ecdsa_brainpoolP384r1tls13_sha384;
+            case SignatureAlgorithm.ecdsa_brainpoolP512r1tls13_sha512:
+                return ecdsa_brainpoolP512r1tls13_sha512;
+            default:
+                return Create(HashAlgorithm.Intrinsic, signatureAlgorithm);
+            }
+        }
+
+        private static SignatureAndHashAlgorithm Create(int signatureScheme)
+        {
+            short hashAlgorithm = SignatureScheme.GetHashAlgorithm(signatureScheme);
+            short signatureAlgorithm = SignatureScheme.GetSignatureAlgorithm(signatureScheme);
+            return Create(hashAlgorithm, signatureAlgorithm);
+        }
+
+        private static SignatureAndHashAlgorithm Create(short hashAlgorithm, short signatureAlgorithm)
+        {
+            return new SignatureAndHashAlgorithm(hashAlgorithm, signatureAlgorithm);
+        }
+
+        private readonly short m_hash;
+        private readonly short m_signature;
+
+        /// <param name="hash"><see cref="HashAlgorithm"/></param>
+        /// <param name="signature"><see cref="SignatureAlgorithm"/></param>
+        public SignatureAndHashAlgorithm(short hash, short signature)
+        {
+            /*
+             * TODO]tls] The TlsUtils methods are inlined here to avoid circular static initialization
+             * b/w these classes. We should refactor parts of TlsUtils into separate classes. e.g. the
+             * TLS low-level encoding methods, and/or the SigAndHash registry and methods.
+             */
+
+            //if (!TlsUtilities.IsValidUint8(hash))
+            if ((hash & 0xFF) != hash)
+                throw new ArgumentException("should be a uint8", "hash");
+
+            //if (!TlsUtilities.IsValidUint8(signature))
+            if ((signature & 0xFF) != signature)
+                throw new ArgumentException("should be a uint8", "signature");
+
+            this.m_hash = hash;
+            this.m_signature = signature;
+        }
+
+        /// <returns><see cref="HashAlgorithm"/></returns>
+        public short Hash
+        {
+            get { return m_hash; }
+        }
+
+        /// <returns><see cref="SignatureAlgorithm"/></returns>
+        public short Signature
+        {
+            get { return m_signature; }
+        }
+
+        /// <summary>Encode this <see cref="SignatureAndHashAlgorithm"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(Hash, output);
+            TlsUtilities.WriteUint8(Signature, output);
+        }
+
+        /// <summary>Parse a <see cref="SignatureAndHashAlgorithm"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="SignatureAndHashAlgorithm"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static SignatureAndHashAlgorithm Parse(Stream input)
+        {
+            short hash = TlsUtilities.ReadUint8(input);
+            short signature = TlsUtilities.ReadUint8(input);
+
+            return GetInstance(hash, signature);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is SignatureAndHashAlgorithm))
+                return false;
+
+            SignatureAndHashAlgorithm other = (SignatureAndHashAlgorithm)obj;
+            return other.Hash == Hash && other.Signature == Signature;
+        }
+
+        public override int GetHashCode()
+        {
+            return ((int)Hash << 16) | (int)Signature;
+        }
+
+        public override string ToString()
+        {
+            return "{" + HashAlgorithm.GetText(Hash) + "," + SignatureAlgorithm.GetText(Signature) + "}";
+        }
+    }
+}
diff --git a/crypto/src/tls/SignatureScheme.cs b/crypto/src/tls/SignatureScheme.cs
new file mode 100644
index 000000000..4b934133d
--- /dev/null
+++ b/crypto/src/tls/SignatureScheme.cs
@@ -0,0 +1,235 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class SignatureScheme
+    {
+        /*
+         * RFC 8446
+         */
+
+        public const int rsa_pkcs1_sha1 = 0x0201;
+        public const int ecdsa_sha1 = 0x0203;
+
+        public const int rsa_pkcs1_sha256 = 0x0401;
+        public const int rsa_pkcs1_sha384 = 0x0501;
+        public const int rsa_pkcs1_sha512 = 0x0601;
+
+        public const int ecdsa_secp256r1_sha256 = 0x0403;
+        public const int ecdsa_secp384r1_sha384 = 0x0503;
+        public const int ecdsa_secp521r1_sha512 = 0x0603;
+
+        public const int rsa_pss_rsae_sha256 = 0x0804;
+        public const int rsa_pss_rsae_sha384 = 0x0805;
+        public const int rsa_pss_rsae_sha512 = 0x0806;
+
+        public const int ed25519 = 0x0807;
+        public const int ed448 = 0x0808;
+
+        public const int rsa_pss_pss_sha256 = 0x0809;
+        public const int rsa_pss_pss_sha384 = 0x080A;
+        public const int rsa_pss_pss_sha512 = 0x080B;
+
+        /*
+         * RFC 8734
+         */
+
+        public const int ecdsa_brainpoolP256r1tls13_sha256 = 0x081A;
+        public const int ecdsa_brainpoolP384r1tls13_sha384 = 0x081B;
+        public const int ecdsa_brainpoolP512r1tls13_sha512 = 0x081C;
+
+        /*
+         * RFC 8998
+         */
+
+        public const int sm2sig_sm3 = 0x0708;
+
+        /*
+         * RFC 8446 reserved for private use (0xFE00..0xFFFF)
+         */
+
+        public static int From(SignatureAndHashAlgorithm sigAndHashAlg)
+        {
+            if (null == sigAndHashAlg)
+                throw new ArgumentNullException();
+
+            return From(sigAndHashAlg.Hash, sigAndHashAlg.Signature);
+        }
+
+        public static int From(short hashAlgorithm, short signatureAlgorithm)
+        {
+            return ((hashAlgorithm & 0xFF) << 8) | (signatureAlgorithm & 0xFF);
+        }
+
+        public static int GetCryptoHashAlgorithm(int signatureScheme)
+        {
+            switch (signatureScheme)
+            {
+            case ed25519:
+            case ed448:
+                return -1;
+            case ecdsa_brainpoolP256r1tls13_sha256:
+            case rsa_pss_pss_sha256:
+            case rsa_pss_rsae_sha256:
+                return CryptoHashAlgorithm.sha256;
+            case ecdsa_brainpoolP384r1tls13_sha384:
+            case rsa_pss_pss_sha384:
+            case rsa_pss_rsae_sha384:
+                return CryptoHashAlgorithm.sha384;
+            case ecdsa_brainpoolP512r1tls13_sha512:
+            case rsa_pss_pss_sha512:
+            case rsa_pss_rsae_sha512:
+                return CryptoHashAlgorithm.sha512;
+            case sm2sig_sm3:
+                return CryptoHashAlgorithm.sm3;
+            default:
+            {
+                short hashAlgorithm = GetHashAlgorithm(signatureScheme);
+                if (HashAlgorithm.Intrinsic == hashAlgorithm || !HashAlgorithm.IsRecognized(hashAlgorithm))
+                    return -1;
+
+                return TlsCryptoUtilities.GetHash(GetHashAlgorithm(signatureScheme));
+            }
+            }
+        }
+
+        public static string GetName(int signatureScheme)
+        {
+            switch (signatureScheme)
+            {
+            case rsa_pkcs1_sha1:
+                return "rsa_pkcs1_sha1";
+            case ecdsa_sha1:
+                return "ecdsa_sha1";
+            case rsa_pkcs1_sha256:
+                return "rsa_pkcs1_sha256";
+            case rsa_pkcs1_sha384:
+                return "rsa_pkcs1_sha384";
+            case rsa_pkcs1_sha512:
+                return "rsa_pkcs1_sha512";
+            case ecdsa_secp256r1_sha256:
+                return "ecdsa_secp256r1_sha256";
+            case ecdsa_secp384r1_sha384:
+                return "ecdsa_secp384r1_sha384";
+            case ecdsa_secp521r1_sha512:
+                return "ecdsa_secp521r1_sha512";
+            case rsa_pss_rsae_sha256:
+                return "rsa_pss_rsae_sha256";
+            case rsa_pss_rsae_sha384:
+                return "rsa_pss_rsae_sha384";
+            case rsa_pss_rsae_sha512:
+                return "rsa_pss_rsae_sha512";
+            case ed25519:
+                return "ed25519";
+            case ed448:
+                return "ed448";
+            case rsa_pss_pss_sha256:
+                return "rsa_pss_pss_sha256";
+            case rsa_pss_pss_sha384:
+                return "rsa_pss_pss_sha384";
+            case rsa_pss_pss_sha512:
+                return "rsa_pss_pss_sha512";
+            case ecdsa_brainpoolP256r1tls13_sha256:
+                return "ecdsa_brainpoolP256r1tls13_sha256";
+            case ecdsa_brainpoolP384r1tls13_sha384:
+                return "ecdsa_brainpoolP384r1tls13_sha384";
+            case ecdsa_brainpoolP512r1tls13_sha512:
+                return "ecdsa_brainpoolP512r1tls13_sha512";
+            case sm2sig_sm3:
+                return "sm2sig_sm3";
+            default:
+                return "UNKNOWN";
+            }
+        }
+
+        /**
+         * For TLS 1.3+ usage, some signature schemes are constrained to use a particular
+         * ({@link NamedGroup}. Not relevant for TLS 1.2 and below.
+         */
+        public static int GetNamedGroup(int signatureScheme)
+        {
+            switch (signatureScheme)
+            {
+            case ecdsa_brainpoolP256r1tls13_sha256:
+                return NamedGroup.brainpoolP256r1tls13;
+            case ecdsa_brainpoolP384r1tls13_sha384:
+                return NamedGroup.brainpoolP384r1tls13;
+            case ecdsa_brainpoolP512r1tls13_sha512:
+                return NamedGroup.brainpoolP512r1tls13;
+            case ecdsa_secp256r1_sha256:
+                return NamedGroup.secp256r1;
+            case ecdsa_secp384r1_sha384:
+                return NamedGroup.secp384r1;
+            case ecdsa_secp521r1_sha512:
+                return NamedGroup.secp521r1;
+            case sm2sig_sm3:
+                return NamedGroup.curveSM2;
+            default:
+                return -1;
+            }
+        }
+
+        public static short GetHashAlgorithm(int signatureScheme)
+        {
+            // TODO[RFC 8998] sm2sig_sm3
+            return (short)((signatureScheme >> 8) & 0xFF);
+        }
+
+        public static short GetSignatureAlgorithm(int signatureScheme)
+        {
+            // TODO[RFC 8998] sm2sig_sm3
+            return (short)(signatureScheme & 0xFF);
+        }
+
+        public static SignatureAndHashAlgorithm GetSignatureAndHashAlgorithm(int signatureScheme)
+        {
+            return SignatureAndHashAlgorithm.GetInstance(
+                GetHashAlgorithm(signatureScheme),
+                GetSignatureAlgorithm(signatureScheme));
+        }
+
+        public static string GetText(int signatureScheme)
+        {
+            string hex = Platform.ToUpperInvariant(Convert.ToString(signatureScheme, 16));
+            return GetName(signatureScheme) + "(0x" + hex + ")";
+        }
+
+        public static bool IsPrivate(int signatureScheme)
+        {
+            return (signatureScheme >> 9) == 0xFE;
+        }
+
+        public static bool IsECDsa(int signatureScheme)
+        {
+            switch (signatureScheme)
+            {
+            case ecdsa_brainpoolP256r1tls13_sha256:
+            case ecdsa_brainpoolP384r1tls13_sha384:
+            case ecdsa_brainpoolP512r1tls13_sha512:
+                return true;
+            default:
+                return SignatureAlgorithm.ecdsa == GetSignatureAlgorithm(signatureScheme);
+            }
+        }
+
+        public static bool IsRsaPss(int signatureScheme)
+        {
+            switch (signatureScheme)
+            {
+            case rsa_pss_rsae_sha256:
+            case rsa_pss_rsae_sha384:
+            case rsa_pss_rsae_sha512:
+            case rsa_pss_pss_sha256:
+            case rsa_pss_pss_sha384:
+            case rsa_pss_pss_sha512:
+                return true;
+            default:
+                return false;
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/SimulatedTlsSrpIdentityManager.cs b/crypto/src/tls/SimulatedTlsSrpIdentityManager.cs
new file mode 100644
index 000000000..904bdae6d
--- /dev/null
+++ b/crypto/src/tls/SimulatedTlsSrpIdentityManager.cs
@@ -0,0 +1,69 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>An implementation of <see cref="TlsSrpIdentityManager"/> that simulates the existence of "unknown"
+    /// identities to obscure the fact that there is no verifier for them.</summary>
+    public class SimulatedTlsSrpIdentityManager
+        : TlsSrpIdentityManager
+    {
+        private static readonly byte[] PrefixPassword = Strings.ToByteArray("password");
+        private static readonly byte[] PrefixSalt = Strings.ToByteArray("salt");
+
+        /// <summary>Create a <see cref="SimulatedTlsSrpIdentityManager"/> that implements the algorithm from RFC 5054
+        /// 2.5.1.3.</summary>
+        /// <param name="crypto"><see cref="TlsCrypto"/></param>
+        /// <param name="group">the <see cref="Srp6Group"/> defining the group that SRP is operating in.</param>
+        /// <param name="seedKey">the secret "seed key" referred to in RFC 5054 2.5.1.3.</param>
+        /// <returns>an instance of <see cref="SimulatedTlsSrpIdentityManager"/>.</returns>
+        /// <exception cref="IOException"/>
+        public static SimulatedTlsSrpIdentityManager GetRfc5054Default(TlsCrypto crypto, Srp6Group group, byte[] seedKey)
+        {
+            TlsMac mac = crypto.CreateHmac(MacAlgorithm.hmac_sha1);
+
+            mac.SetKey(seedKey, 0, seedKey.Length);
+
+            TlsSrpConfig srpConfig = new TlsSrpConfig();
+
+            srpConfig.SetExplicitNG(new BigInteger[]{ group.N, group.G });
+
+            return new SimulatedTlsSrpIdentityManager(group, crypto.CreateSrp6VerifierGenerator(srpConfig), mac);
+        }
+
+        protected readonly Srp6Group m_group;
+        protected readonly TlsSrp6VerifierGenerator m_verifierGenerator;
+        protected readonly TlsMac m_mac;
+
+        public SimulatedTlsSrpIdentityManager(Srp6Group group, TlsSrp6VerifierGenerator verifierGenerator, TlsMac mac)
+        {
+            this.m_group = group;
+            this.m_verifierGenerator = verifierGenerator;
+            this.m_mac = mac;
+        }
+
+        public virtual TlsSrpLoginParameters GetLoginParameters(byte[] identity)
+        {
+            m_mac.Update(PrefixSalt, 0, PrefixSalt.Length);
+            m_mac.Update(identity, 0, identity.Length);
+
+            byte[] salt = m_mac.CalculateMac();
+
+            m_mac.Update(PrefixPassword, 0, PrefixPassword.Length);
+            m_mac.Update(identity, 0, identity.Length);
+
+            byte[] password = m_mac.CalculateMac();
+
+            BigInteger verifier = m_verifierGenerator.GenerateVerifier(salt, identity, password);
+
+            TlsSrpConfig srpConfig = new TlsSrpConfig();
+            srpConfig.SetExplicitNG(new BigInteger[]{ m_group.N, m_group.G });
+
+            return new TlsSrpLoginParameters(identity, srpConfig, verifier, salt);
+        }
+    }
+}
diff --git a/crypto/src/tls/SrpTlsClient.cs b/crypto/src/tls/SrpTlsClient.cs
new file mode 100644
index 000000000..a2b0e9461
--- /dev/null
+++ b/crypto/src/tls/SrpTlsClient.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class SrpTlsClient
+        : AbstractTlsClient
+    {
+        private static readonly int[] DefaultCipherSuites = new int[]
+        {
+            CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA
+        };
+
+        protected readonly TlsSrpIdentity m_srpIdentity;
+
+        public SrpTlsClient(TlsCrypto crypto, byte[] identity, byte[] password)
+            : this(crypto, new BasicTlsSrpIdentity(identity, password))
+        {
+        }
+
+        public SrpTlsClient(TlsCrypto crypto, TlsSrpIdentity srpIdentity)
+            : base(crypto)
+        {
+            this.m_srpIdentity = srpIdentity;
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, DefaultCipherSuites);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.DownTo(ProtocolVersion.TLSv10);
+        }
+
+        protected virtual bool RequireSrpServerExtension
+        {
+            // No explicit guidance in RFC 5054; by default an (empty) extension from server is optional
+            get { return false; }
+        }
+
+        /// <exception cref="IOException"/>
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                base.GetClientExtensions());
+            TlsSrpUtilities.AddSrpExtension(clientExtensions, m_srpIdentity.GetSrpIdentity());
+            return clientExtensions;
+        }
+
+        /// <exception cref="IOException"/>
+        public override void ProcessServerExtensions(IDictionary serverExtensions)
+        {
+            if (!TlsUtilities.HasExpectedEmptyExtensionData(serverExtensions, ExtensionType.srp,
+                AlertDescription.illegal_parameter))
+            {
+                if (RequireSrpServerExtension)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            base.ProcessServerExtensions(serverExtensions);
+        }
+
+        public override TlsSrpIdentity GetSrpIdentity()
+        {
+            return m_srpIdentity;
+        }
+
+        /// <exception cref="IOException"/>
+        public override TlsAuthentication GetAuthentication()
+        {
+            /*
+             * Note: This method is not called unless a server certificate is sent, which may be the
+             * case e.g. for SRP_DSS or SRP_RSA key exchange.
+             */
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+}
diff --git a/crypto/src/tls/SrpTlsServer.cs b/crypto/src/tls/SrpTlsServer.cs
new file mode 100644
index 000000000..58f89ee22
--- /dev/null
+++ b/crypto/src/tls/SrpTlsServer.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class SrpTlsServer
+        : AbstractTlsServer
+    {
+        private static readonly int[] DefaultCipherSuites = new int[]
+        {
+            CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_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_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA
+        };
+
+        protected readonly TlsSrpIdentityManager m_srpIdentityManager;
+
+        protected byte[] m_srpIdentity = null;
+        protected TlsSrpLoginParameters m_srpLoginParameters = null;
+
+        public SrpTlsServer(TlsCrypto crypto, TlsSrpIdentityManager srpIdentityManager)
+            : base(crypto)
+        {
+            this.m_srpIdentityManager = srpIdentityManager;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual TlsCredentialedSigner GetDsaSignerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.DownTo(ProtocolVersion.TLSv10);
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, DefaultCipherSuites);
+        }
+
+        public override void ProcessClientExtensions(IDictionary clientExtensions)
+        {
+            base.ProcessClientExtensions(clientExtensions);
+
+            this.m_srpIdentity = TlsSrpUtilities.GetSrpExtension(clientExtensions);
+        }
+
+        public override int GetSelectedCipherSuite()
+        {
+            int cipherSuite = base.GetSelectedCipherSuite();
+
+            if (TlsSrpUtilities.IsSrpCipherSuite(cipherSuite))
+            {
+                if (m_srpIdentity != null)
+                {
+                    this.m_srpLoginParameters = m_srpIdentityManager.GetLoginParameters(m_srpIdentity);
+                }
+
+                if (m_srpLoginParameters == null)
+                    throw new TlsFatalAlert(AlertDescription.unknown_psk_identity);
+            }
+
+            return cipherSuite;
+        }
+
+        public override TlsCredentials GetCredentials()
+        {
+            int keyExchangeAlgorithm = m_context.SecurityParameters.KeyExchangeAlgorithm;
+
+            switch (keyExchangeAlgorithm)
+            {
+            case KeyExchangeAlgorithm.SRP:
+                return null;
+
+            case KeyExchangeAlgorithm.SRP_DSS:
+                return GetDsaSignerCredentials();
+
+            case KeyExchangeAlgorithm.SRP_RSA:
+                return GetRsaSignerCredentials();
+
+            default:
+                // Note: internal error here; selected a key exchange we don't implement!
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        public override TlsSrpLoginParameters GetSrpLoginParameters()
+        {
+            return m_srpLoginParameters;
+        }
+    }
+}
diff --git a/crypto/src/tls/SrtpProtectionProfile.cs b/crypto/src/tls/SrtpProtectionProfile.cs
new file mode 100644
index 000000000..e81988e41
--- /dev/null
+++ b/crypto/src/tls/SrtpProtectionProfile.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class SrtpProtectionProfile
+    {
+        /*
+         * RFC 5764 4.1.2.
+         */
+        public const int SRTP_AES128_CM_HMAC_SHA1_80 = 0x0001;
+        public const int SRTP_AES128_CM_HMAC_SHA1_32 = 0x0002;
+        public const int SRTP_NULL_HMAC_SHA1_80 = 0x0005;
+        public const int SRTP_NULL_HMAC_SHA1_32 = 0x0006;
+
+        /*
+         * RFC 7714 14.2.
+         */
+        public const int SRTP_AEAD_AES_128_GCM = 0x0007;
+        public const int SRTP_AEAD_AES_256_GCM = 0x0008;
+    }
+}
diff --git a/crypto/src/tls/Ssl3Utilities.cs b/crypto/src/tls/Ssl3Utilities.cs
new file mode 100644
index 000000000..594bdefbc
--- /dev/null
+++ b/crypto/src/tls/Ssl3Utilities.cs
@@ -0,0 +1,69 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal abstract class Ssl3Utilities
+    {
+        private static readonly byte[] SSL_CLIENT = {0x43, 0x4C, 0x4E, 0x54};
+        private static readonly byte[] SSL_SERVER = {0x53, 0x52, 0x56, 0x52};
+
+        private const byte IPAD_BYTE = (byte)0x36;
+        private const byte OPAD_BYTE = (byte)0x5C;
+
+        private static readonly byte[] IPAD = GenPad(IPAD_BYTE, 48);
+        private static readonly byte[] OPAD = GenPad(OPAD_BYTE, 48);
+
+        internal static byte[] CalculateVerifyData(TlsHandshakeHash handshakeHash, bool isServer)
+        {
+            TlsHash prf = handshakeHash.ForkPrfHash();
+            byte[] sslSender = isServer ? SSL_SERVER : SSL_CLIENT;
+            prf.Update(sslSender, 0, sslSender.Length);
+            return prf.CalculateHash();
+        }
+
+        internal static void CompleteCombinedHash(TlsContext context, TlsHash md5, TlsHash sha1)
+        {
+            TlsSecret masterSecret = context.SecurityParameters.MasterSecret;
+            byte[] master_secret = context.Crypto.AdoptSecret(masterSecret).Extract();
+
+            CompleteHash(master_secret, md5, 48);
+            CompleteHash(master_secret, sha1, 40);
+        }
+
+        private static void CompleteHash(byte[] master_secret, TlsHash hash, int padLength)
+        {
+            hash.Update(master_secret, 0, master_secret.Length);
+            hash.Update(IPAD, 0, padLength);
+
+            byte[] tmp = hash.CalculateHash();
+
+            hash.Update(master_secret, 0, master_secret.Length);
+            hash.Update(OPAD, 0, padLength);
+            hash.Update(tmp, 0, tmp.Length);
+        }
+
+        private static byte[] GenPad(byte b, int count)
+        {
+            byte[] padding = new byte[count];
+            Arrays.Fill(padding, b);
+            return padding;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static byte[] ReadEncryptedPms(Stream input)
+        {
+            return Streams.ReadAll(input);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void WriteEncryptedPms(byte[] encryptedPms, Stream output)
+        {
+            output.Write(encryptedPms, 0, encryptedPms.Length);
+        }
+    }
+}
diff --git a/crypto/src/tls/SupplementalDataEntry.cs b/crypto/src/tls/SupplementalDataEntry.cs
new file mode 100644
index 000000000..baf428968
--- /dev/null
+++ b/crypto/src/tls/SupplementalDataEntry.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class SupplementalDataEntry
+    {
+        private readonly int m_dataType;
+        private readonly byte[] m_data;
+
+        public SupplementalDataEntry(int dataType, byte[] data)
+        {
+            this.m_dataType = dataType;
+            this.m_data = data;
+        }
+
+        public int DataType
+        {
+            get { return m_dataType; }
+        }
+
+        public byte[] Data
+        {
+            get { return m_data; }
+        }
+    }
+}
diff --git a/crypto/src/tls/SupplementalDataType.cs b/crypto/src/tls/SupplementalDataType.cs
new file mode 100644
index 000000000..d1eebfdc1
--- /dev/null
+++ b/crypto/src/tls/SupplementalDataType.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 4680</summary>
+    public abstract class SupplementalDataType
+    {
+        /*
+         * RFC 4681
+         */
+        public const int user_mapping_data = 0;
+    }
+}
diff --git a/crypto/src/tls/Timeout.cs b/crypto/src/tls/Timeout.cs
new file mode 100644
index 000000000..e47fc5d9a
--- /dev/null
+++ b/crypto/src/tls/Timeout.cs
@@ -0,0 +1,119 @@
+using System;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal class Timeout
+    {
+        private long durationMillis;
+        private long startMillis;
+
+        internal Timeout(long durationMillis)
+            : this(durationMillis, DateTimeUtilities.CurrentUnixMs())
+        {
+        }
+
+        internal Timeout(long durationMillis, long currentTimeMillis)
+        {
+            this.durationMillis = System.Math.Max(0, durationMillis);
+            this.startMillis = System.Math.Max(0, currentTimeMillis);
+        }
+
+        //internal long RemainingMillis()
+        //{
+        //    return RemainingMillis(DateTimeUtilities.CurrentUnixMs());
+        //}
+
+        internal long RemainingMillis(long currentTimeMillis)
+        {
+            lock (this)
+            {
+                // If clock jumped backwards, reset start time 
+                if (startMillis > currentTimeMillis)
+                {
+                    startMillis = currentTimeMillis;
+                    return durationMillis;
+                }
+
+                long elapsed = currentTimeMillis - startMillis;
+                long remaining = durationMillis - elapsed;
+
+                // Once timeout reached, lock it in
+                if (remaining <= 0)
+                    return durationMillis = 0L;
+
+                return remaining;
+            }
+        }
+
+        //internal static int ConstrainWaitMillis(int waitMillis, Timeout timeout)
+        //{
+        //    return constrainWaitMillis(waitMillis, timeout, DateTimeUtilities.CurrentUnixMs());
+        //}
+
+        internal static int ConstrainWaitMillis(int waitMillis, Timeout timeout, long currentTimeMillis)
+        {
+            if (waitMillis < 0)
+                return -1;
+
+            int timeoutMillis = GetWaitMillis(timeout, currentTimeMillis);
+            if (timeoutMillis < 0)
+                return -1;
+
+            if (waitMillis == 0)
+                return timeoutMillis;
+
+            if (timeoutMillis == 0)
+                return waitMillis;
+
+            return System.Math.Min(waitMillis, timeoutMillis);
+        }
+
+        internal static Timeout ForWaitMillis(int waitMillis)
+        {
+            return ForWaitMillis(waitMillis, DateTimeUtilities.CurrentUnixMs());
+        }
+
+        internal static Timeout ForWaitMillis(int waitMillis, long currentTimeMillis)
+        {
+            if (waitMillis < 0)
+                throw new ArgumentException("cannot be negative", "waitMillis");
+
+            if (waitMillis > 0)
+                return new Timeout(waitMillis, currentTimeMillis);
+
+            return null;
+        }
+
+        //internal static int GetWaitMillis(Timeout timeout)
+        //{
+        //    return GetWaitMillis(timeout, DateTimeUtilities.CurrentUnixMs());
+        //}
+
+        internal static int GetWaitMillis(Timeout timeout, long currentTimeMillis)
+        {
+            if (null == timeout)
+                return 0;
+
+            long remainingMillis = timeout.RemainingMillis(currentTimeMillis);
+            if (remainingMillis < 1L)
+                return -1;
+
+            if (remainingMillis > int.MaxValue)
+                return int.MaxValue;
+
+            return (int)remainingMillis;
+        }
+
+        internal static bool HasExpired(Timeout timeout)
+        {
+            return HasExpired(timeout, DateTimeUtilities.CurrentUnixMs());
+        }
+
+        internal static bool HasExpired(Timeout timeout, long currentTimeMillis)
+        {
+            return null != timeout && timeout.RemainingMillis(currentTimeMillis) < 1L;
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsAuthentication.cs b/crypto/src/tls/TlsAuthentication.cs
new file mode 100644
index 000000000..32228ed64
--- /dev/null
+++ b/crypto/src/tls/TlsAuthentication.cs
@@ -0,0 +1,29 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface to provide TLS authentication credentials.</summary>
+    public interface TlsAuthentication
+    {
+        /// <summary>Called by the protocol handler to report the server certificate.</summary>
+        /// <remarks>
+        /// Note: this method is responsible for certificate verification and validation.
+        /// </remarks>
+        /// <param name="serverCertificate">the server certificate received.</param>
+        /// <exception cref="IOException"/>
+        void NotifyServerCertificate(TlsServerCertificate serverCertificate);
+
+        /// <summary>Return client credentials in response to server's certificate request.</summary>
+        /// <remarks>
+        /// The returned value may be null, or else it MUST implement <em>exactly one</em> of
+        /// <see cref="TlsCredentialedAgreement"/>, <see cref="TlsCredentialedDecryptor"/>, or
+        /// <see cref="TlsCredentialedSigner"/>, depending on the key exchange that was negotiated and the details of
+        /// the <see cref="CertificateRequest"/>.
+        /// </remarks>
+        /// <param name="certificateRequest">details of the certificate request.</param>
+        /// <returns>a <see cref="TlsCredentials"/> object or null for no client authentication.</returns>
+        /// <exception cref="IOException"/>
+        TlsCredentials GetClientCredentials(CertificateRequest certificateRequest);
+    }
+}
diff --git a/crypto/src/tls/TlsClient.cs b/crypto/src/tls/TlsClient.cs
new file mode 100644
index 000000000..4d2e15437
--- /dev/null
+++ b/crypto/src/tls/TlsClient.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public interface TlsClient
+        : TlsPeer
+    {
+        void Init(TlsClientContext context);
+
+        /// <summary>Return the session this client wants to resume, if any.</summary>
+        /// <remarks>
+        /// Note that the peer's certificate chain for the session (if any) may need to be periodically revalidated.
+        /// </remarks>
+        /// <returns>A <see cref="TlsSession"/> representing the resumable session to be used for this connection, or
+        /// null to use a new session.</returns>
+        /// <seealso cref="SessionParameters.PeerCertificate"/>
+        TlsSession GetSessionToResume();
+
+        bool IsFallback();
+
+        /// <returns>(Int32 -> byte[])</returns>
+        /// <exception cref="IOException"/>
+        IDictionary GetClientExtensions();
+
+        /// <summary>If this client is offering TLS 1.3 or higher, this method may be called to determine for which
+        /// groups a key share should be included in the initial ClientHello.</summary>
+        /// <remarks>
+        /// Groups that were not included in the supported_groups extension (by <see cref="GetClientExtensions"/> will
+        /// be ignored. The protocol will then add a suitable key_share extension to the ClientHello extensions.
+        /// </remarks>
+        /// <returns>an <see cref="IList"/> of <see cref="NamedGroup">named group</see> values, possibly empty or null.
+        /// </returns>
+        IList GetEarlyKeyShareGroups();
+
+        /// <exception cref="IOException"/>
+        void NotifyServerVersion(ProtocolVersion selectedVersion);
+
+        /// <summary>Notifies the client of the session that will be offered in ClientHello for resumption, if any.
+        /// </summary>
+        /// <remarks>
+        /// This will be either the session returned from {@link #getSessionToResume()} or null if that session was
+        /// unusable. NOTE: the actual negotiated session_id is notified by <see cref="NotifySessionID(byte[])"/>.
+        /// </remarks>
+        /// <param name="session">The <see cref="TlsSession"/> representing the resumable session to be offered for
+        /// this connection, or null if there is none.</param>
+        /// <seealso cref="NotifySessionID(byte[])"/>
+        void NotifySessionToResume(TlsSession session);
+
+        /// <summary>Notifies the client of the session_id sent in the ServerHello.</summary>
+        /// <param name="sessionID"/>
+        /// <seealso cref="TlsContext.Session"/>
+        void NotifySessionID(byte[] sessionID);
+
+        void NotifySelectedCipherSuite(int selectedCipherSuite);
+
+        /// <summary>The protocol implementation validates that any server extensions received correspond to client
+        /// extensions sent.</summary>
+        /// <remarks>
+        /// If further processing of the server extensions is needed, it can be done in this callback. NOTE: This is
+        /// not called for session resumption handshakes.
+        /// </remarks>
+        /// <param name="serverExtensions">(Int32 -> byte[])</param>
+        /// <exception cref="IOException"/>
+        void ProcessServerExtensions(IDictionary serverExtensions);
+
+        /// <param name="serverSupplementalData">(SupplementalDataEntry)</param>
+        /// <exception cref="IOException"/>
+        void ProcessServerSupplementalData(IList serverSupplementalData);
+
+        /// <exception cref="IOException"/>
+        TlsPskIdentity GetPskIdentity();
+
+        /// <exception cref="IOException"/>
+        TlsSrpIdentity GetSrpIdentity();
+
+        /// <exception cref="IOException"/>
+        TlsDHGroupVerifier GetDHGroupVerifier();
+
+        /// <exception cref="IOException"/>
+        TlsSrpConfigVerifier GetSrpConfigVerifier();
+
+        /// <exception cref="IOException"/>
+        TlsAuthentication GetAuthentication();
+
+        /// <returns>(SupplementalDataEntry)</returns>
+        /// <exception cref="IOException"/>
+        IList GetClientSupplementalData();
+
+        /// <summary>RFC 5077 3.3. NewSessionTicket Handshake Message</summary>
+        /// <remarks>
+        /// This method will be called (only) when a NewSessionTicket handshake message is received. The ticket is
+        /// opaque to the client and clients MUST NOT examine the ticket under the assumption that it complies with e.g.
+        /// RFC 5077 4. "Recommended Ticket Construction".
+        /// </remarks>
+        /// <param name="newSessionTicket">The ticket.</param>
+        /// <exception cref="IOException"/>
+        void NotifyNewSessionTicket(NewSessionTicket newSessionTicket);
+    }
+}
diff --git a/crypto/src/tls/TlsClientContext.cs b/crypto/src/tls/TlsClientContext.cs
new file mode 100644
index 000000000..f1ed06e04
--- /dev/null
+++ b/crypto/src/tls/TlsClientContext.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Marker interface to distinguish a TLS client context.</summary>
+    public interface TlsClientContext
+        : TlsContext
+    {
+    }
+}
diff --git a/crypto/src/tls/TlsClientContextImpl.cs b/crypto/src/tls/TlsClientContextImpl.cs
new file mode 100644
index 000000000..3ce4ab86b
--- /dev/null
+++ b/crypto/src/tls/TlsClientContextImpl.cs
@@ -0,0 +1,20 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal class TlsClientContextImpl
+        : AbstractTlsContext, TlsClientContext
+    {
+        internal TlsClientContextImpl(TlsCrypto crypto)
+            : base(crypto, ConnectionEnd.client)
+        {
+        }
+
+        public override bool IsServer
+        {
+            get { return false; }
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsClientProtocol.cs b/crypto/src/tls/TlsClientProtocol.cs
new file mode 100644
index 000000000..31ad9b956
--- /dev/null
+++ b/crypto/src/tls/TlsClientProtocol.cs
@@ -0,0 +1,1715 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsClientProtocol
+        : TlsProtocol
+    {
+        protected TlsClient m_tlsClient = null;
+        internal TlsClientContextImpl m_tlsClientContext = null;
+
+        protected IDictionary m_clientAgreements = null;
+        protected ClientHello m_clientHello = null;
+        protected TlsKeyExchange m_keyExchange = null;
+        protected TlsAuthentication m_authentication = null;
+
+        protected CertificateStatus m_certificateStatus = null;
+        protected CertificateRequest m_certificateRequest = null;
+
+        /// <summary>Constructor for non-blocking mode.</summary>
+        /// <remarks>
+        /// When data is received, use <see cref="TlsProtocol.OfferInput(byte[])"/> to provide the received ciphertext,
+        /// then use <see cref="TlsProtocol.ReadInput(byte[],int,int)"/> to read the corresponding cleartext.<br/><br/>
+        /// Similarly, when data needs to be sent, use <see cref="TlsProtocol.WriteApplicationData(byte[],int,int)"/>
+        /// to provide the cleartext, then use <see cref="TlsProtocol.ReadOutput(byte[],int,int)"/> to get the
+        /// corresponding ciphertext.
+        /// </remarks>
+        public TlsClientProtocol()
+            : base()
+        {
+        }
+
+        /// <summary>Constructor for blocking mode.</summary>
+        /// <param name="stream">The <see cref="Stream"/> of data to/from the server.</param>
+        public TlsClientProtocol(Stream stream)
+            : base(stream)
+        {
+        }
+
+        /// <summary>Constructor for blocking mode.</summary>
+        /// <param name="input">The <see cref="Stream"/> of data from the server.</param>
+        /// <param name="output">The <see cref="Stream"/> of data to the server.</param>
+        public TlsClientProtocol(Stream input, Stream output)
+            : base(input, output)
+        {
+        }
+
+        /// <summary>Initiates a TLS handshake in the role of client.</summary>
+        /// <remarks>
+        /// In blocking mode, this will not return until the handshake is complete. In non-blocking mode, use
+        /// <see cref="TlsPeer.NotifyHandshakeComplete"/> to receive a callback when the handshake is complete.
+        /// </remarks>
+        /// <param name="tlsClient">The <see cref="TlsClient"/> to use for the handshake.</param>
+        /// <exception cref="IOException">If in blocking mode and handshake was not successful.</exception>
+        public virtual void Connect(TlsClient tlsClient)
+        {
+            if (tlsClient == null)
+                throw new ArgumentNullException("tlsClient");
+            if (m_tlsClient != null)
+                throw new InvalidOperationException("'Connect' can only be called once");
+
+            this.m_tlsClient = tlsClient;
+            this.m_tlsClientContext = new TlsClientContextImpl(tlsClient.Crypto);
+
+            tlsClient.Init(m_tlsClientContext);
+            tlsClient.NotifyCloseHandle(this);
+
+            BeginHandshake();
+
+            if (m_blocking)
+            {
+                BlockForHandshake();
+            }
+        }
+
+        protected override void BeginHandshake()
+        {
+            base.BeginHandshake();
+
+            EstablishSession(m_tlsClient.GetSessionToResume());
+            m_tlsClient.NotifySessionToResume(m_tlsSession);
+
+            SendClientHello();
+            this.m_connectionState = CS_CLIENT_HELLO;
+        }
+
+        protected override void CleanupHandshake()
+        {
+            base.CleanupHandshake();
+
+            this.m_clientAgreements = null;
+            this.m_clientHello = null;
+            this.m_keyExchange = null;
+            this.m_authentication = null;
+
+            this.m_certificateStatus = null;
+            this.m_certificateRequest = null;
+        }
+
+        protected override TlsContext Context
+        {
+            get { return m_tlsClientContext; }
+        }
+
+        internal override AbstractTlsContext ContextAdmin
+        {
+            get { return m_tlsClientContext; }
+        }
+
+        protected override TlsPeer Peer
+        {
+            get { return m_tlsClient; }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Handle13HandshakeMessage(short type, HandshakeMessageInput buf)
+        {
+            if (!IsTlsV13ConnectionState())
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (m_resumedSession)
+            {
+                /*
+                 * TODO[tls13] Resumption/PSK
+                 * 
+                 * NOTE: No CertificateRequest, Certificate, CertificateVerify messages, but client
+                 * might now send EndOfEarlyData after receiving server Finished message.
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            switch (type)
+            {
+            case HandshakeType.certificate:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_ENCRYPTED_EXTENSIONS:
+                case CS_SERVER_CERTIFICATE_REQUEST:
+                {
+                    if (m_connectionState != CS_SERVER_CERTIFICATE_REQUEST)
+                    {
+                        Skip13CertificateRequest();
+                    }
+
+                    /*
+                     * TODO[tls13] For PSK-only key exchange, there's no Certificate message.
+                     */
+                    Receive13ServerCertificate(buf);
+                    this.m_connectionState = CS_SERVER_CERTIFICATE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate_request:
+            {
+                switch (m_connectionState)
+                {
+                case CS_END:
+                {
+                    // TODO[tls13] Permit post-handshake authentication if we sent post_handshake_auth extension
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                case CS_SERVER_ENCRYPTED_EXTENSIONS:
+                {
+                    Receive13CertificateRequest(buf, false);
+                    this.m_connectionState = CS_SERVER_CERTIFICATE_REQUEST;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate_verify:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_CERTIFICATE:
+                {
+                    Receive13ServerCertificateVerify(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_SERVER_CERTIFICATE_VERIFY;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.encrypted_extensions:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO:
+                {
+                    Receive13EncryptedExtensions(buf);
+                    this.m_connectionState = CS_SERVER_ENCRYPTED_EXTENSIONS;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.finished:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_ENCRYPTED_EXTENSIONS:
+                case CS_SERVER_CERTIFICATE_REQUEST:
+                case CS_SERVER_CERTIFICATE_VERIFY:
+                {
+                    if (m_connectionState == CS_SERVER_ENCRYPTED_EXTENSIONS)
+                    {
+                        Skip13CertificateRequest();
+                    }
+                    if (m_connectionState != CS_SERVER_CERTIFICATE_VERIFY)
+                    {
+                        Skip13ServerCertificate();
+                    }
+
+                    Receive13ServerFinished(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_SERVER_FINISHED;
+
+                    byte[] serverFinishedTranscriptHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+                    // See RFC 8446 D.4.
+                    m_recordStream.SetIgnoreChangeCipherSpec(false);
+
+                    if (null != m_certificateRequest)
+                    {
+                        TlsCredentialedSigner clientCredentials = TlsUtilities.Establish13ClientCredentials(
+                            m_authentication, m_certificateRequest);
+
+                        Certificate clientCertificate = null;
+                        if (null != clientCredentials)
+                        {
+                            clientCertificate = clientCredentials.Certificate;
+                        }
+
+                        if (null == clientCertificate)
+                        {
+                            // In this calling context, certificate_request_context is length 0
+                            clientCertificate = Certificate.EmptyChainTls13;
+                        }
+
+                        Send13CertificateMessage(clientCertificate);
+                        this.m_connectionState = CS_CLIENT_CERTIFICATE;
+
+                        if (null != clientCredentials)
+                        {
+                            DigitallySigned certificateVerify = TlsUtilities.Generate13CertificateVerify(
+                                m_tlsClientContext, clientCredentials, m_handshakeHash);
+                            Send13CertificateVerifyMessage(certificateVerify);
+                            this.m_connectionState = CS_CLIENT_CERTIFICATE_VERIFY;
+                        }
+                    }
+
+                    Send13FinishedMessage();
+                    this.m_connectionState = CS_CLIENT_FINISHED;
+
+                    TlsUtilities.Establish13PhaseApplication(m_tlsClientContext, serverFinishedTranscriptHash,
+                        m_recordStream);
+
+                    m_recordStream.EnablePendingCipherWrite();
+                    m_recordStream.EnablePendingCipherRead(false);
+
+                    CompleteHandshake();
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.key_update:
+            {
+                Receive13KeyUpdate(buf);
+                break;
+            }
+            case HandshakeType.new_session_ticket:
+            {
+                Receive13NewSessionTicket(buf);
+                break;
+            }
+            case HandshakeType.server_hello:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_HELLO:
+                {
+                    // NOTE: Legacy handler should be dispatching initial ServerHello/HelloRetryRequest.
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+                case CS_CLIENT_HELLO_RETRY:
+                {
+                    ServerHello serverHello = ReceiveServerHelloMessage(buf);
+                    if (serverHello.IsHelloRetryRequest())
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    Process13ServerHello(serverHello, true);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_SERVER_HELLO;
+
+                    Process13ServerHelloCoda(serverHello, true);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+
+            case HandshakeType.certificate_status:
+            case HandshakeType.certificate_url:
+            case HandshakeType.client_hello:
+            case HandshakeType.client_key_exchange:
+            case HandshakeType.end_of_early_data:
+            case HandshakeType.hello_request:
+            case HandshakeType.hello_verify_request:
+            case HandshakeType.message_hash:
+            case HandshakeType.server_hello_done:
+            case HandshakeType.server_key_exchange:
+            case HandshakeType.supplemental_data:
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        protected override void HandleHandshakeMessage(short type, HandshakeMessageInput buf)
+        {
+            SecurityParameters securityParameters = m_tlsClientContext.SecurityParameters;
+
+            if (m_connectionState > CS_CLIENT_HELLO
+                && TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
+            {
+                Handle13HandshakeMessage(type, buf);
+                return;
+            }
+
+            if (!IsLegacyConnectionState())
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (m_resumedSession)
+            {
+                if (type != HandshakeType.finished || m_connectionState != CS_SERVER_HELLO)
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                ProcessFinishedMessage(buf);
+                buf.UpdateHash(m_handshakeHash);
+                this.m_connectionState = CS_SERVER_FINISHED;
+
+                SendChangeCipherSpec();
+                SendFinishedMessage();
+                this.m_connectionState = CS_CLIENT_FINISHED;
+
+                CompleteHandshake();
+                return;
+            }
+
+            switch (type)
+            {
+            case HandshakeType.certificate:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO:
+                case CS_SERVER_SUPPLEMENTAL_DATA:
+                {
+                    if (m_connectionState != CS_SERVER_SUPPLEMENTAL_DATA)
+                    {
+                        HandleSupplementalData(null);
+                    }
+
+                    /*
+                     * NOTE: Certificate processing (including authentication) is delayed to allow for a
+                     * possible CertificateStatus message.
+                     */
+                    this.m_authentication = TlsUtilities.ReceiveServerCertificate(m_tlsClientContext, m_tlsClient,
+                        buf);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.m_connectionState = CS_SERVER_CERTIFICATE;
+                break;
+            }
+            case HandshakeType.certificate_status:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_CERTIFICATE:
+                {
+                    if (securityParameters.StatusRequestVersion < 1)
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    this.m_certificateStatus = CertificateStatus.Parse(m_tlsClientContext, buf);
+
+                    AssertEmpty(buf);
+
+                    this.m_connectionState = CS_SERVER_CERTIFICATE_STATUS;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.finished:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_FINISHED:
+                case CS_SERVER_SESSION_TICKET:
+                {
+                    if (m_connectionState != CS_SERVER_SESSION_TICKET)
+                    {
+                        /*
+                         * RFC 5077 3.3. This message MUST be sent if the server included a
+                         * SessionTicket extension in the ServerHello.
+                         */
+                        if (m_expectSessionTicket)
+                            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    ProcessFinishedMessage(buf);
+                    this.m_connectionState = CS_SERVER_FINISHED;
+
+                    CompleteHandshake();
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.server_hello:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_HELLO:
+                {
+                    ServerHello serverHello = ReceiveServerHelloMessage(buf);
+
+                    // TODO[tls13] Only treat as HRR if it's TLS 1.3??
+                    if (serverHello.IsHelloRetryRequest())
+                    {
+                        Process13HelloRetryRequest(serverHello);
+                        m_handshakeHash.NotifyPrfDetermined();
+                        TlsUtilities.AdjustTranscriptForRetry(m_handshakeHash);
+                        buf.UpdateHash(m_handshakeHash);
+                        this.m_connectionState = CS_SERVER_HELLO_RETRY_REQUEST;
+
+                        Send13ClientHelloRetry();
+                        this.m_connectionState = CS_CLIENT_HELLO_RETRY;
+                    }
+                    else
+                    {
+                        ProcessServerHello(serverHello);
+                        m_handshakeHash.NotifyPrfDetermined();
+                        buf.UpdateHash(m_handshakeHash);
+                        this.m_connectionState = CS_SERVER_HELLO;
+
+                        if (TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
+                        {
+                            Process13ServerHelloCoda(serverHello, false);
+                        }
+                    }
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.supplemental_data:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO:
+                {
+                    HandleSupplementalData(ReadSupplementalDataMessage(buf));
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.server_hello_done:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO:
+                case CS_SERVER_SUPPLEMENTAL_DATA:
+                case CS_SERVER_CERTIFICATE:
+                case CS_SERVER_CERTIFICATE_STATUS:
+                case CS_SERVER_KEY_EXCHANGE:
+                case CS_SERVER_CERTIFICATE_REQUEST:
+                {
+                    if (m_connectionState == CS_SERVER_HELLO)
+                    {
+                        HandleSupplementalData(null);
+                    }
+                    if (m_connectionState == CS_SERVER_HELLO ||
+                        m_connectionState == CS_SERVER_SUPPLEMENTAL_DATA)
+                    {
+                        this.m_authentication = null;
+                    }
+                    if (m_connectionState != CS_SERVER_KEY_EXCHANGE &&
+                        m_connectionState != CS_SERVER_CERTIFICATE_REQUEST)
+                    {
+                        HandleServerCertificate();
+
+                        // There was no server key exchange message; check it's OK
+                        m_keyExchange.SkipServerKeyExchange();
+                    }
+
+                    AssertEmpty(buf);
+
+                    this.m_connectionState = CS_SERVER_HELLO_DONE;
+
+                    IList clientSupplementalData = m_tlsClient.GetClientSupplementalData();
+                    if (clientSupplementalData != null)
+                    {
+                        SendSupplementalDataMessage(clientSupplementalData);
+                        this.m_connectionState = CS_CLIENT_SUPPLEMENTAL_DATA;
+                    }
+
+                    TlsCredentialedSigner credentialedSigner = null;
+                    TlsStreamSigner streamSigner = null;
+
+                    if (m_certificateRequest == null)
+                    {
+                        m_keyExchange.SkipClientCredentials();
+                    }
+                    else
+                    {
+                        Certificate clientCertificate = null;
+
+                        TlsCredentials clientCredentials = TlsUtilities.EstablishClientCredentials(m_authentication,
+                            m_certificateRequest);
+                        if (null == clientCredentials)
+                        {
+                            m_keyExchange.SkipClientCredentials();
+
+                            /*
+                             * RFC 5246 If no suitable certificate is available, the client MUST send a
+                             * certificate message containing no certificates.
+                             * 
+                             * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                             */
+                        }
+                        else
+                        {
+                            m_keyExchange.ProcessClientCredentials(clientCredentials);
+
+                            clientCertificate = clientCredentials.Certificate;
+
+                            if (clientCredentials is TlsCredentialedSigner)
+                            {
+                                credentialedSigner = (TlsCredentialedSigner)clientCredentials;
+                                streamSigner = credentialedSigner.GetStreamSigner();
+                            }
+                        }
+
+                        SendCertificateMessage(clientCertificate, null);
+                        this.m_connectionState = CS_CLIENT_CERTIFICATE;
+                    }
+
+                    bool forceBuffering = streamSigner != null;
+                    TlsUtilities.SealHandshakeHash(m_tlsClientContext, m_handshakeHash, forceBuffering);
+
+                    /*
+                     * Send the client key exchange message, depending on the key exchange we are using
+                     * in our CipherSuite.
+                     */
+                    SendClientKeyExchange();
+                    this.m_connectionState = CS_CLIENT_KEY_EXCHANGE;
+
+                    bool isSsl = TlsUtilities.IsSsl(m_tlsClientContext);
+                    if (isSsl)
+                    {
+                        // NOTE: For SSLv3 (only), master_secret needed to calculate session hash
+                        EstablishMasterSecret(m_tlsClientContext, m_keyExchange);
+                    }
+
+                    securityParameters.m_sessionHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+                    if (!isSsl)
+                    {
+                        // NOTE: For (D)TLS, session hash potentially needed for extended_master_secret
+                        EstablishMasterSecret(m_tlsClientContext, m_keyExchange);
+                    }
+
+                    m_recordStream.SetPendingCipher(TlsUtilities.InitCipher(m_tlsClientContext));
+
+                    if (credentialedSigner != null)
+                    {
+                        DigitallySigned certificateVerify = TlsUtilities.GenerateCertificateVerifyClient(
+                            m_tlsClientContext, credentialedSigner, streamSigner, m_handshakeHash);
+                        SendCertificateVerifyMessage(certificateVerify);
+                        this.m_connectionState = CS_CLIENT_CERTIFICATE_VERIFY;
+                    }
+
+                    this.m_handshakeHash = m_handshakeHash.StopTracking();
+
+                    SendChangeCipherSpec();
+                    SendFinishedMessage();
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.m_connectionState = CS_CLIENT_FINISHED;
+                break;
+            }
+            case HandshakeType.server_key_exchange:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO:
+                case CS_SERVER_SUPPLEMENTAL_DATA:
+                case CS_SERVER_CERTIFICATE:
+                case CS_SERVER_CERTIFICATE_STATUS:
+                {
+                    if (m_connectionState == CS_SERVER_HELLO)
+                    {
+                        HandleSupplementalData(null);
+                    }
+                    if (m_connectionState != CS_SERVER_CERTIFICATE &&
+                        m_connectionState != CS_SERVER_CERTIFICATE_STATUS)
+                    {
+                        this.m_authentication = null;
+                    }
+
+                    HandleServerCertificate();
+
+                    m_keyExchange.ProcessServerKeyExchange(buf);
+
+                    AssertEmpty(buf);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.m_connectionState = CS_SERVER_KEY_EXCHANGE;
+                break;
+            }
+            case HandshakeType.certificate_request:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_CERTIFICATE:
+                case CS_SERVER_CERTIFICATE_STATUS:
+                case CS_SERVER_KEY_EXCHANGE:
+                {
+                    if (m_connectionState != CS_SERVER_KEY_EXCHANGE)
+                    {
+                        HandleServerCertificate();
+
+                        // There was no server key exchange message; check it's OK
+                        m_keyExchange.SkipServerKeyExchange();
+                    }
+
+                    ReceiveCertificateRequest(buf);
+
+                    TlsUtilities.EstablishServerSigAlgs(securityParameters, m_certificateRequest);
+
+                    /*
+                     * TODO Give the client a chance to immediately select the CertificateVerify hash
+                     * algorithm here to avoid tracking the other hash algorithms unnecessarily?
+                     */
+                    TlsUtilities.TrackHashAlgorithms(m_handshakeHash, securityParameters.ServerSigAlgs);
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.m_connectionState = CS_SERVER_CERTIFICATE_REQUEST;
+                break;
+            }
+            case HandshakeType.new_session_ticket:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_FINISHED:
+                {
+                    if (!m_expectSessionTicket)
+                    {
+                        /*
+                         * RFC 5077 3.3. This message MUST NOT be sent if the server did not include a
+                         * SessionTicket extension in the ServerHello.
+                         */
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    /*
+                     * RFC 5077 3.4. If the client receives a session ticket from the server, then it
+                     * discards any Session ID that was sent in the ServerHello.
+                     */
+                    InvalidateSession();
+
+                    ReceiveNewSessionTicket(buf);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                this.m_connectionState = CS_SERVER_SESSION_TICKET;
+                break;
+            }
+            case HandshakeType.hello_request:
+            {
+                AssertEmpty(buf);
+
+                /*
+                 * 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 (IsApplicationDataReady)
+                {
+                    RefuseRenegotiation();
+                }
+                break;
+            }
+
+            case HandshakeType.certificate_url:
+            case HandshakeType.certificate_verify:
+            case HandshakeType.client_hello:
+            case HandshakeType.client_key_exchange:
+            case HandshakeType.encrypted_extensions:
+            case HandshakeType.end_of_early_data:
+            case HandshakeType.hello_verify_request:
+            case HandshakeType.key_update:
+            case HandshakeType.message_hash:
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void HandleServerCertificate()
+        {
+            TlsUtilities.ProcessServerCertificate(m_tlsClientContext, m_certificateStatus, m_keyExchange,
+                m_authentication, m_clientExtensions, m_serverExtensions);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void HandleSupplementalData(IList serverSupplementalData)
+        {
+            m_tlsClient.ProcessServerSupplementalData(serverSupplementalData);
+            this.m_connectionState = CS_SERVER_SUPPLEMENTAL_DATA;
+
+            this.m_keyExchange = TlsUtilities.InitKeyExchangeClient(m_tlsClientContext, m_tlsClient);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Process13HelloRetryRequest(ServerHello helloRetryRequest)
+        {
+            ProtocolVersion legacy_record_version = ProtocolVersion.TLSv12;
+            m_recordStream.SetWriteVersion(legacy_record_version);
+
+            SecurityParameters securityParameters = m_tlsClientContext.SecurityParameters;
+
+            /*
+             * RFC 8446 4.1.4. Upon receipt of a HelloRetryRequest, the client MUST check the
+             * legacy_version, legacy_session_id_echo, cipher_suite, and legacy_compression_method as
+             * specified in Section 4.1.3 and then process the extensions, starting with determining the
+             * version using "supported_versions".
+             */
+            ProtocolVersion legacy_version = helloRetryRequest.Version;
+            byte[] legacy_session_id_echo = helloRetryRequest.SessionID;
+            int cipherSuite = helloRetryRequest.CipherSuite;
+            // NOTE: legacy_compression_method checked during ServerHello parsing
+
+            if (!ProtocolVersion.TLSv12.Equals(legacy_version) ||
+                !Arrays.AreEqual(m_clientHello.SessionID, legacy_session_id_echo) ||
+                !TlsUtilities.IsValidCipherSuiteSelection(m_clientHello.CipherSuites, cipherSuite))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            IDictionary extensions = helloRetryRequest.Extensions;
+            if (null == extensions)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            TlsUtilities.CheckExtensionData13(extensions, HandshakeType.hello_retry_request,
+                AlertDescription.illegal_parameter);
+
+            {
+                /*
+                 * RFC 8446 4.2. Implementations MUST NOT send extension responses if the remote
+                 * endpoint did not send the corresponding extension requests, with the exception of the
+                 * "cookie" extension in the HelloRetryRequest. Upon receiving such an extension, an
+                 * endpoint MUST abort the handshake with an "unsupported_extension" alert.
+                 */
+                foreach (int extType in extensions.Keys)
+                {
+                    if (ExtensionType.cookie == extType)
+                        continue;
+
+                    if (null == TlsUtilities.GetExtensionData(m_clientExtensions, extType))
+                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+                }
+            }
+
+            ProtocolVersion server_version = TlsExtensionsUtilities.GetSupportedVersionsExtensionServer(extensions);
+            if (null == server_version)
+                throw new TlsFatalAlert(AlertDescription.missing_extension);
+
+            if (!ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(server_version) ||
+                !ProtocolVersion.Contains(m_tlsClientContext.ClientSupportedVersions, server_version) ||
+                !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, server_version))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            /*
+             * RFC 8446 4.2.8. Upon receipt of this [Key Share] extension in a HelloRetryRequest, the
+             * client MUST verify that (1) the selected_group field corresponds to a group which was
+             * provided in the "supported_groups" extension in the original ClientHello and (2) the
+             * selected_group field does not correspond to a group which was provided in the "key_share"
+             * extension in the original ClientHello. If either of these checks fails, then the client
+             * MUST abort the handshake with an "illegal_parameter" alert.
+             */
+            int selected_group = TlsExtensionsUtilities.GetKeyShareHelloRetryRequest(extensions);
+
+            if (!TlsUtilities.IsValidKeyShareSelection(server_version, securityParameters.ClientSupportedGroups,
+                m_clientAgreements, selected_group))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            byte[] cookie = TlsExtensionsUtilities.GetCookieExtension(extensions);
+
+
+
+            securityParameters.m_negotiatedVersion = server_version;
+            TlsUtilities.NegotiatedVersionTlsClient(m_tlsClientContext, m_tlsClient);
+
+            this.m_resumedSession = false;
+            securityParameters.m_sessionID = TlsUtilities.EmptyBytes;
+            m_tlsClient.NotifySessionID(TlsUtilities.EmptyBytes);
+
+            TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+            m_tlsClient.NotifySelectedCipherSuite(cipherSuite);
+
+            this.m_clientAgreements = null;
+            this.m_retryCookie = cookie;
+            this.m_retryGroup = selected_group;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Process13ServerHello(ServerHello serverHello, bool afterHelloRetryRequest)
+        {
+            SecurityParameters securityParameters = m_tlsClientContext.SecurityParameters;
+
+            ProtocolVersion legacy_version = serverHello.Version;
+            byte[] legacy_session_id_echo = serverHello.SessionID;
+            int cipherSuite = serverHello.CipherSuite;
+            // NOTE: legacy_compression_method checked during ServerHello parsing
+
+            if (!ProtocolVersion.TLSv12.Equals(legacy_version) ||
+                !Arrays.AreEqual(m_clientHello.SessionID, legacy_session_id_echo))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            IDictionary extensions = serverHello.Extensions;
+            if (null == extensions)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            TlsUtilities.CheckExtensionData13(extensions, HandshakeType.server_hello,
+                AlertDescription.illegal_parameter);
+
+            if (afterHelloRetryRequest)
+            {
+                ProtocolVersion server_version = TlsExtensionsUtilities.GetSupportedVersionsExtensionServer(extensions);
+                if (null == server_version)
+                    throw new TlsFatalAlert(AlertDescription.missing_extension);
+
+                if (!securityParameters.NegotiatedVersion.Equals(server_version) ||
+                    securityParameters.CipherSuite != cipherSuite)
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+            }
+            else
+            {
+                if (!TlsUtilities.IsValidCipherSuiteSelection(m_clientHello.CipherSuites, cipherSuite) ||
+                    !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, securityParameters.NegotiatedVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                this.m_resumedSession = false;
+                securityParameters.m_sessionID = TlsUtilities.EmptyBytes;
+                m_tlsClient.NotifySessionID(TlsUtilities.EmptyBytes);
+
+                TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+                m_tlsClient.NotifySelectedCipherSuite(cipherSuite);
+            }
+
+            this.m_clientHello = null;
+
+            // NOTE: Apparently downgrade marker mechanism not used for TLS 1.3+?
+            securityParameters.m_serverRandom = serverHello.Random;
+
+            securityParameters.m_secureRenegotiation = false;
+
+            /*
+             * RFC 8446 Appendix D. Because TLS 1.3 always hashes in the transcript up to the server
+             * Finished, implementations which support both TLS 1.3 and earlier versions SHOULD indicate
+             * the use of the Extended Master Secret extension in their APIs whenever TLS 1.3 is used.
+             */
+            securityParameters.m_extendedMasterSecret = true;
+
+            /*
+             * TODO[tls13] RFC 8446 4.4.2.1. OCSP Status and SCT Extensions.
+             * 
+             * OCSP information is carried in an extension for a CertificateEntry.
+             */
+            securityParameters.m_statusRequestVersion = m_clientExtensions.Contains(ExtensionType.status_request) ? 1 : 0;
+
+            {
+                KeyShareEntry keyShareEntry = TlsExtensionsUtilities.GetKeyShareServerHello(extensions);
+                if (null == keyShareEntry)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                if (!m_clientAgreements.Contains(keyShareEntry.NamedGroup))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                TlsAgreement agreement = (TlsAgreement)m_clientAgreements[keyShareEntry.NamedGroup];
+
+                this.m_clientAgreements = null;
+
+                agreement.ReceivePeerValue(keyShareEntry.KeyExchange);
+                securityParameters.m_sharedSecret = agreement.CalculateSecret();
+
+                TlsUtilities.Establish13PhaseSecrets(m_tlsClientContext);
+            }
+
+            {
+                InvalidateSession();
+
+                this.m_tlsSession = TlsUtilities.ImportSession(securityParameters.SessionID, null);
+                this.m_sessionParameters = null;
+                this.m_sessionMasterSecret = null;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Process13ServerHelloCoda(ServerHello serverHello, bool afterHelloRetryRequest)
+        {
+            byte[] serverHelloTranscriptHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+            TlsUtilities.Establish13PhaseHandshake(m_tlsClientContext, serverHelloTranscriptHash, m_recordStream);
+
+            // See RFC 8446 D.4.
+            if (!afterHelloRetryRequest)
+            {
+                m_recordStream.SetIgnoreChangeCipherSpec(true);
+
+                // TODO[tls13] If offering early data, the record is placed immediately after the first ClientHello.
+                /*
+                 * TODO[tls13] Ideally wait until just after Server Finished received, but then we'd need to defer
+                 * the enabling of the pending write cipher
+                 */
+                SendChangeCipherSpecMessage();
+            }
+
+            m_recordStream.EnablePendingCipherWrite();
+            m_recordStream.EnablePendingCipherRead(false);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessServerHello(ServerHello serverHello)
+        {
+            IDictionary serverHelloExtensions = serverHello.Extensions;
+
+            ProtocolVersion legacy_version = serverHello.Version;
+            ProtocolVersion supported_version = TlsExtensionsUtilities.GetSupportedVersionsExtensionServer(
+                serverHelloExtensions);
+
+            ProtocolVersion server_version;
+            if (null == supported_version)
+            {
+                server_version = legacy_version;
+            }
+            else
+            {
+                if (!ProtocolVersion.TLSv12.Equals(legacy_version) ||
+                    !ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(supported_version))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                server_version = supported_version;
+            }
+
+            SecurityParameters securityParameters = m_tlsClientContext.SecurityParameters;
+
+            // NOT renegotiating
+            {
+                if (!ProtocolVersion.Contains(m_tlsClientContext.ClientSupportedVersions, server_version))
+                    throw new TlsFatalAlert(AlertDescription.protocol_version);
+
+                ProtocolVersion legacy_record_version = server_version.IsLaterVersionOf(ProtocolVersion.TLSv12)
+                    ? ProtocolVersion.TLSv12
+                    : server_version;
+
+                m_recordStream.SetWriteVersion(legacy_record_version);
+                securityParameters.m_negotiatedVersion = server_version;
+            }
+
+            TlsUtilities.NegotiatedVersionTlsClient(m_tlsClientContext, m_tlsClient);
+
+            if (ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(server_version))
+            {
+                Process13ServerHello(serverHello, false);
+                return;
+            }
+
+            int[] offeredCipherSuites = m_clientHello.CipherSuites;
+
+            this.m_clientHello = null;
+            this.m_retryCookie = null;
+            this.m_retryGroup = -1;
+
+            securityParameters.m_serverRandom = serverHello.Random;
+
+            if (!m_tlsClientContext.ClientVersion.Equals(server_version))
+            {
+                TlsUtilities.CheckDowngradeMarker(server_version, securityParameters.ServerRandom);
+            }
+
+            {
+                byte[] selectedSessionID = serverHello.SessionID;
+                securityParameters.m_sessionID = selectedSessionID;
+                m_tlsClient.NotifySessionID(selectedSessionID);
+                this.m_resumedSession = selectedSessionID.Length > 0 && m_tlsSession != null
+                    && Arrays.AreEqual(selectedSessionID, m_tlsSession.SessionID);
+            }
+
+            /*
+             * Find out which CipherSuite the server has chosen and check that it was one of the offered
+             * ones, and is a valid selection for the negotiated version.
+             */
+            {
+                int cipherSuite = serverHello.CipherSuite;
+
+                if (!TlsUtilities.IsValidCipherSuiteSelection(offeredCipherSuites, cipherSuite) ||
+                    !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, securityParameters.NegotiatedVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+                m_tlsClient.NotifySelectedCipherSuite(cipherSuite);
+            }
+
+            /*
+             * RFC 3546 2.2 Note that the extended server hello message is only sent in response to an
+             * extended client hello message.
+             * 
+             * However, see RFC 5746 exception below. We always include the SCSV, so an Extended Server
+             * Hello is always allowed.
+             */
+            this.m_serverExtensions = serverHelloExtensions;
+            if (m_serverExtensions != null)
+            {
+                foreach (int extType in m_serverExtensions.Keys)
+                {
+                    /*
+                     * RFC 5746 3.6. Note that sending a "renegotiation_info" extension in response to a
+                     * ClientHello containing only the SCSV is an explicit exception to the prohibition
+                     * in RFC 5246, Section 7.4.1.4, on the server sending unsolicited extensions and is
+                     * only allowed because the client is signaling its willingness to receive the
+                     * extension via the TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                     */
+                    if (ExtensionType.renegotiation_info == extType)
+                        continue;
+
+                    /*
+                     * RFC 5246 7.4.1.4 An extension type MUST NOT appear in the ServerHello unless the
+                     * same extension type appeared in the corresponding ClientHello. If a client
+                     * receives an extension type in ServerHello that it did not request in the
+                     * associated ClientHello, it MUST abort the handshake with an unsupported_extension
+                     * fatal alert.
+                     */
+                    if (null == TlsUtilities.GetExtensionData(m_clientExtensions, extType))
+                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+
+                    /*
+                     * 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[.]
+                     */
+                    if (m_resumedSession)
+                    {
+                        // TODO[compat-gnutls] GnuTLS test server sends server extensions e.g. ec_point_formats
+                        // TODO[compat-openssl] OpenSSL test server sends server extensions e.g. ec_point_formats
+                        // TODO[compat-polarssl] PolarSSL test server sends server extensions e.g. ec_point_formats
+    //                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                    }
+                }
+            }
+
+            byte[] renegExtData = TlsUtilities.GetExtensionData(m_serverExtensions, ExtensionType.renegotiation_info);
+
+            // NOT renegotiating
+            {
+                /*
+                 * RFC 5746 3.4. Client Behavior: Initial Handshake (both full and session-resumption)
+                 */
+
+                /*
+                 * When a ServerHello is received, the client MUST check if it includes the
+                 * "renegotiation_info" extension:
+                 */
+                if (renegExtData == null)
+                {
+                    /*
+                     * 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.
+                     */
+                    securityParameters.m_secureRenegotiation = false;
+                }
+                else
+                {
+                    /*
+                     * 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).
+                     */
+                    securityParameters.m_secureRenegotiation = true;
+
+                    if (!Arrays.ConstantTimeAreEqual(renegExtData, CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+            }
+
+            // TODO[compat-gnutls] GnuTLS test server fails to send renegotiation_info extension when resuming
+            m_tlsClient.NotifySecureRenegotiation(securityParameters.IsSecureRenegotiation);
+
+            /*
+             * RFC 7627 4. Clients and servers SHOULD NOT accept handshakes that do not use the extended
+             * master secret [..]. (and see 5.2, 5.3)
+             * 
+             * RFC 8446 Appendix D. Because TLS 1.3 always hashes in the transcript up to the server
+             * Finished, implementations which support both TLS 1.3 and earlier versions SHOULD indicate
+             * the use of the Extended Master Secret extension in their APIs whenever TLS 1.3 is used.
+             */
+            {
+                bool acceptedExtendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(
+                    m_serverExtensions);
+
+                if (acceptedExtendedMasterSecret)
+                {
+                    if (server_version.IsSsl
+                        || (!m_resumedSession && !m_tlsClient.ShouldUseExtendedMasterSecret()))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+                else
+                {
+                    if (m_tlsClient.RequiresExtendedMasterSecret()
+                        || (m_resumedSession && !m_tlsClient.AllowLegacyResumption()))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+
+                securityParameters.m_extendedMasterSecret = acceptedExtendedMasterSecret;
+            }
+
+            /*
+             * RFC 7301 3.1. When session resumption or session tickets [...] are used, the previous
+             * contents of this extension are irrelevant, and only the values in the new handshake
+             * messages are considered.
+             */
+            securityParameters.m_applicationProtocol = TlsExtensionsUtilities.GetAlpnExtensionServer(
+                m_serverExtensions);
+            securityParameters.m_applicationProtocolSet = true;
+
+            IDictionary sessionClientExtensions = m_clientExtensions, sessionServerExtensions = m_serverExtensions;
+            if (m_resumedSession)
+            {
+                if (securityParameters.CipherSuite != m_sessionParameters.CipherSuite
+                    || !server_version.Equals(m_sessionParameters.NegotiatedVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                sessionClientExtensions = null;
+                sessionServerExtensions = m_sessionParameters.ReadServerExtensions();
+            }
+
+            if (sessionServerExtensions != null && sessionServerExtensions.Count > 0)
+            {
+                {
+                    /*
+                     * RFC 7366 3. If a server receives an encrypt-then-MAC request extension from a client
+                     * and then selects a stream or Authenticated Encryption with Associated Data (AEAD)
+                     * ciphersuite, it MUST NOT send an encrypt-then-MAC response extension back to the
+                     * client.
+                     */
+                    bool serverSentEncryptThenMAC = TlsExtensionsUtilities.HasEncryptThenMacExtension(
+                        sessionServerExtensions);
+                    if (serverSentEncryptThenMAC && !TlsUtilities.IsBlockCipherSuite(securityParameters.CipherSuite))
+                        throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                    securityParameters.m_encryptThenMac = serverSentEncryptThenMAC;
+                }
+
+                securityParameters.m_maxFragmentLength = ProcessMaxFragmentLengthExtension(sessionClientExtensions,
+                    sessionServerExtensions, AlertDescription.illegal_parameter);
+
+                securityParameters.m_truncatedHmac = TlsExtensionsUtilities.HasTruncatedHmacExtension(
+                    sessionServerExtensions);
+
+                /*
+                 * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in
+                 * a session resumption handshake.
+                 */
+                if (!m_resumedSession)
+                {
+                    // TODO[tls13] See RFC 8446 4.4.2.1
+                    if (TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions,
+                        ExtensionType.status_request_v2, AlertDescription.illegal_parameter))
+                    {
+                        securityParameters.m_statusRequestVersion = 2;
+                    }
+                    else if (TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions,
+                        ExtensionType.status_request, AlertDescription.illegal_parameter))
+                    {
+                        securityParameters.m_statusRequestVersion = 1;
+                    }
+
+                    this.m_expectSessionTicket = TlsUtilities.HasExpectedEmptyExtensionData(sessionServerExtensions,
+                        ExtensionType.session_ticket, AlertDescription.illegal_parameter);
+                }
+            }
+
+            if (sessionClientExtensions != null)
+            {
+                m_tlsClient.ProcessServerExtensions(sessionServerExtensions);
+            }
+
+            ApplyMaxFragmentLengthExtension(securityParameters.MaxFragmentLength);
+
+            if (m_resumedSession)
+            {
+                securityParameters.m_masterSecret = m_sessionMasterSecret;
+                m_recordStream.SetPendingCipher(TlsUtilities.InitCipher(m_tlsClientContext));
+            }
+            else
+            {
+                InvalidateSession();
+
+                this.m_tlsSession = TlsUtilities.ImportSession(securityParameters.SessionID, null);
+                this.m_sessionParameters = null;
+                this.m_sessionMasterSecret = null;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13CertificateRequest(MemoryStream buf, bool postHandshakeAuth)
+        {
+            /* 
+             * RFC 8446 4.3.2. A server which is authenticating with a certificate MAY optionally
+             * request a certificate from the client.
+             */
+
+            /*
+             * TODO[tls13] Currently all handshakes are certificate-authenticated. When PSK-only becomes an option,
+             * then check here that a certificate message is expected (else fatal unexpected_message alert).
+             */
+
+            CertificateRequest certificateRequest = CertificateRequest.Parse(m_tlsClientContext, buf);
+
+            AssertEmpty(buf);
+
+            if (postHandshakeAuth)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (!certificateRequest.HasCertificateRequestContext(TlsUtilities.EmptyBytes))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            this.m_certificateRequest = certificateRequest;
+
+            TlsUtilities.EstablishServerSigAlgs(m_tlsClientContext.SecurityParameters, certificateRequest);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13EncryptedExtensions(MemoryStream buf)
+        {
+            byte[] extBytes = TlsUtilities.ReadOpaque16(buf);
+
+            AssertEmpty(buf);
+
+
+            this.m_serverExtensions = ReadExtensionsData13(HandshakeType.encrypted_extensions, extBytes);
+
+            {
+                /*
+                 * RFC 8446 4.2. Implementations MUST NOT send extension responses if the remote
+                 * endpoint did not send the corresponding extension requests, with the exception of the
+                 * "cookie" extension in the HelloRetryRequest. Upon receiving such an extension, an
+                 * endpoint MUST abort the handshake with an "unsupported_extension" alert.
+                 */
+                foreach (int extType in m_serverExtensions.Keys)
+                {
+                    if (null == TlsUtilities.GetExtensionData(m_clientExtensions, extType))
+                        throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+                }
+            }
+
+
+            SecurityParameters securityParameters = m_tlsClientContext.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            securityParameters.m_applicationProtocol = TlsExtensionsUtilities.GetAlpnExtensionServer(
+                m_serverExtensions);
+            securityParameters.m_applicationProtocolSet = true;
+
+            IDictionary sessionClientExtensions = m_clientExtensions, sessionServerExtensions = m_serverExtensions;
+            if (m_resumedSession)
+            {
+                if (securityParameters.CipherSuite != m_sessionParameters.CipherSuite
+                    || !negotiatedVersion.Equals(m_sessionParameters.NegotiatedVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                sessionClientExtensions = null;
+                sessionServerExtensions = m_sessionParameters.ReadServerExtensions();
+            }
+
+            securityParameters.m_maxFragmentLength = ProcessMaxFragmentLengthExtension(sessionClientExtensions,
+                sessionServerExtensions, AlertDescription.illegal_parameter);
+
+            securityParameters.m_encryptThenMac = false;
+            securityParameters.m_truncatedHmac = false;
+
+            /*
+             * TODO[tls13] RFC 8446 4.4.2.1. OCSP Status and SCT Extensions.
+             * 
+             * OCSP information is carried in an extension for a CertificateEntry.
+             */
+            securityParameters.m_statusRequestVersion = m_clientExtensions.Contains(ExtensionType.status_request)
+                ? 1 : 0;
+
+            this.m_expectSessionTicket = false;
+
+            if (null != sessionClientExtensions)
+            {
+                m_tlsClient.ProcessServerExtensions(m_serverExtensions);
+            }
+
+            ApplyMaxFragmentLengthExtension(securityParameters.MaxFragmentLength);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13NewSessionTicket(MemoryStream buf)
+        {
+            if (!IsApplicationDataReady)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            // TODO[tls13] Do something more than just ignore them
+
+    //        struct {
+    //            uint32 ticket_lifetime;
+    //            uint32 ticket_age_add;
+    //            opaque ticket_nonce<0..255>;
+    //            opaque ticket<1..2^16-1>;
+    //            Extension extensions<0..2^16-2>;
+    //        } NewSessionTicket;
+
+            TlsUtilities.ReadUint32(buf);
+            TlsUtilities.ReadUint32(buf);
+            TlsUtilities.ReadOpaque8(buf);
+            TlsUtilities.ReadOpaque16(buf);
+            TlsUtilities.ReadOpaque16(buf);
+            AssertEmpty(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13ServerCertificate(MemoryStream buf)
+        {
+            this.m_authentication = TlsUtilities.Receive13ServerCertificate(m_tlsClientContext, m_tlsClient, buf);
+
+            // NOTE: In TLS 1.3 we don't have to wait for a possible CertificateStatus message.
+            HandleServerCertificate();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13ServerCertificateVerify(MemoryStream buf)
+        {
+            Certificate serverCertificate = m_tlsClientContext.SecurityParameters.PeerCertificate;
+            if (null == serverCertificate || serverCertificate.IsEmpty)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            // TODO[tls13] Actual structure is 'CertificateVerify' in RFC 8446, consider adding for clarity
+            DigitallySigned certificateVerify = DigitallySigned.Parse(m_tlsClientContext, buf);
+
+            AssertEmpty(buf);
+
+            TlsUtilities.Verify13CertificateVerifyServer(m_tlsClientContext, certificateVerify, m_handshakeHash);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13ServerFinished(MemoryStream buf)
+        {
+            Process13FinishedMessage(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReceiveCertificateRequest(MemoryStream buf)
+        {
+            if (null == m_authentication)
+            {
+                /*
+                 * RFC 2246 7.4.4. It is a fatal handshake_failure alert for an anonymous server to
+                 * request client identification.
+                 */
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+            }
+
+            CertificateRequest certificateRequest = CertificateRequest.Parse(m_tlsClientContext, buf);
+
+            AssertEmpty(buf);
+
+            this.m_certificateRequest = TlsUtilities.ValidateCertificateRequest(certificateRequest, m_keyExchange);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReceiveNewSessionTicket(MemoryStream buf)
+        {
+            NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf);
+
+            AssertEmpty(buf);
+
+            m_tlsClient.NotifyNewSessionTicket(newSessionTicket);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ServerHello ReceiveServerHelloMessage(MemoryStream buf)
+        {
+            return ServerHello.Parse(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13ClientHelloRetry()
+        {
+            IDictionary clientHelloExtensions = m_clientHello.Extensions;
+
+            clientHelloExtensions.Remove(ExtensionType.cookie);
+            clientHelloExtensions.Remove(ExtensionType.early_data);
+            clientHelloExtensions.Remove(ExtensionType.key_share);
+
+            /*
+             * RFC 4.2.2. When sending the new ClientHello, the client MUST copy the contents of the
+             * extension received in the HelloRetryRequest into a "cookie" extension in the new
+             * ClientHello.
+             */
+            if (null != m_retryCookie)
+            {
+                TlsExtensionsUtilities.AddCookieExtension(clientHelloExtensions, m_retryCookie);
+                this.m_retryCookie = null;
+            }
+
+            /*
+             * RFC 8446 4.2.8. [..] when sending the new ClientHello, the client MUST replace the
+             * original "key_share" extension with one containing only a new KeyShareEntry for the group
+             * indicated in the selected_group field of the triggering HelloRetryRequest.
+             */
+            if (m_retryGroup < 0)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_clientAgreements = TlsUtilities.AddKeyShareToClientHelloRetry(m_tlsClientContext,
+                clientHelloExtensions, m_retryGroup);
+
+            /*
+             * TODO[tls13] Updating the "pre_shared_key" extension if present by recomputing the
+             * "obfuscated_ticket_age" and binder values and (optionally) removing any PSKs which are
+             * incompatible with the server's indicated cipher suite.
+             */
+
+            /*
+             * TODO[tls13] Optionally adding, removing, or changing the length of the "padding"
+             * extension [RFC7685].
+             */
+
+            // See RFC 8446 D.4.
+            {
+                m_recordStream.SetIgnoreChangeCipherSpec(true);
+
+                // TODO[tls13] If offering early data, the record is placed immediately after the first ClientHello.
+                SendChangeCipherSpecMessage();
+            }
+
+            SendClientHelloMessage();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendCertificateVerifyMessage(DigitallySigned certificateVerify)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_verify);
+            certificateVerify.Encode(message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendClientHello()
+        {
+            SecurityParameters securityParameters = m_tlsClientContext.SecurityParameters;
+
+            ProtocolVersion client_version;
+
+            // NOT renegotiating
+            {
+                m_tlsClientContext.SetClientSupportedVersions(m_tlsClient.GetProtocolVersions());
+
+                if (ProtocolVersion.Contains(m_tlsClientContext.ClientSupportedVersions, ProtocolVersion.SSLv3))
+                {
+                    // TODO[tls13] Prevent offering SSLv3 AND TLSv13?
+                    m_recordStream.SetWriteVersion(ProtocolVersion.SSLv3);
+                }
+                else
+                {
+                    m_recordStream.SetWriteVersion(ProtocolVersion.TLSv10);
+                }
+
+                client_version = ProtocolVersion.GetLatestTls(m_tlsClientContext.ClientSupportedVersions);
+
+                if (!ProtocolVersion.IsSupportedTlsVersionClient(client_version))
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                m_tlsClientContext.SetClientVersion(client_version);
+            }
+
+            bool offeringTlsV13Plus = ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(client_version);
+
+            /*
+             * TODO RFC 5077 3.4. When presenting a ticket, the client MAY generate and include a
+             * Session ID in the TLS ClientHello.
+             */
+            byte[] legacy_session_id = TlsUtilities.GetSessionID(m_tlsSession);
+
+            bool fallback = m_tlsClient.IsFallback();
+
+            int[] offeredCipherSuites = m_tlsClient.GetCipherSuites();
+
+            if (legacy_session_id.Length > 0 && m_sessionParameters != null)
+            {
+                if (!Arrays.Contains(offeredCipherSuites, m_sessionParameters.CipherSuite))
+                {
+                    legacy_session_id = TlsUtilities.EmptyBytes;
+                }
+            }
+
+            this.m_clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                m_tlsClient.GetClientExtensions());
+
+            ProtocolVersion legacy_version = client_version;
+            if (offeringTlsV13Plus)
+            {
+                legacy_version = ProtocolVersion.TLSv12;
+
+                TlsExtensionsUtilities.AddSupportedVersionsExtensionClient(m_clientExtensions,
+                    m_tlsClientContext.ClientSupportedVersions);
+
+                /*
+                 * RFC 8446 4.2.1. In compatibility mode [..], this field MUST be non-empty, so a client
+                 * not offering a pre-TLS 1.3 session MUST generate a new 32-byte value.
+                 */
+                if (legacy_session_id.Length < 1)
+                {
+                    legacy_session_id = m_tlsClientContext.NonceGenerator.GenerateNonce(32);
+                }
+            }
+
+            m_tlsClientContext.SetRsaPreMasterSecretVersion(legacy_version);
+
+            securityParameters.m_clientServerNames = TlsExtensionsUtilities.GetServerNameExtensionClient(
+                m_clientExtensions);
+
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(client_version))
+            {
+                TlsUtilities.EstablishClientSigAlgs(securityParameters, m_clientExtensions);
+            }
+
+            securityParameters.m_clientSupportedGroups = TlsExtensionsUtilities.GetSupportedGroupsExtension(
+                m_clientExtensions);
+
+            this.m_clientAgreements = TlsUtilities.AddEarlyKeySharesToClientHello(m_tlsClientContext, m_tlsClient,
+                m_clientExtensions);
+
+            if (TlsUtilities.IsExtendedMasterSecretOptionalTls(m_tlsClientContext.ClientSupportedVersions)
+                && (m_tlsClient.ShouldUseExtendedMasterSecret() ||
+                    (null != m_sessionParameters && m_sessionParameters.IsExtendedMasterSecret)))
+            {
+                TlsExtensionsUtilities.AddExtendedMasterSecretExtension(m_clientExtensions);
+            }
+            else if (!offeringTlsV13Plus && m_tlsClient.RequiresExtendedMasterSecret())
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            {
+                bool useGmtUnixTime = !offeringTlsV13Plus && m_tlsClient.ShouldUseGmtUnixTime();
+
+                securityParameters.m_clientRandom = CreateRandomBlock(useGmtUnixTime, m_tlsClientContext);
+            }
+
+            // NOT renegotiating
+            {
+                /*
+                 * RFC 5746 3.4. Client Behavior: Initial Handshake (both full and session-resumption)
+                 */
+
+                /*
+                 * 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 = (null == TlsUtilities.GetExtensionData(m_clientExtensions,
+                    ExtensionType.renegotiation_info));
+                bool noRenegScsv = !Arrays.Contains(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+
+                if (noRenegExt && noRenegScsv)
+                {
+                    // TODO[tls13] Probably want to not add this if no pre-TLSv13 versions offered?
+                    offeredCipherSuites = Arrays.Append(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+                }
+            }
+
+            /*
+             * (Fallback SCSV)
+             * RFC 7507 4. If a client sends a ClientHello.client_version containing a lower value
+             * than the latest (highest-valued) version supported by the client, it SHOULD include
+             * the TLS_FALLBACK_SCSV cipher suite value in ClientHello.cipher_suites [..]. (The
+             * client SHOULD put TLS_FALLBACK_SCSV after all cipher suites that it actually intends
+             * to negotiate.)
+             */
+            if (fallback && !Arrays.Contains(offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV))
+            {
+                offeredCipherSuites = Arrays.Append(offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV);
+            }
+
+
+
+            this.m_clientHello = new ClientHello(legacy_version, securityParameters.ClientRandom, legacy_session_id,
+                null, offeredCipherSuites, m_clientExtensions);
+
+            SendClientHelloMessage();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendClientHelloMessage()
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.client_hello);
+            m_clientHello.Encode(m_tlsClientContext, message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendClientKeyExchange()
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.client_key_exchange);
+            m_keyExchange.GenerateClientKeyExchange(message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Skip13CertificateRequest()
+        {
+            this.m_certificateRequest = null;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Skip13ServerCertificate()
+        {
+            this.m_authentication = null;
+
+            // TODO[tls13] May be skipped for PSK handshakes?
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsCloseable.cs b/crypto/src/tls/TlsCloseable.cs
new file mode 100644
index 000000000..baf2ff444
--- /dev/null
+++ b/crypto/src/tls/TlsCloseable.cs
@@ -0,0 +1,11 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public interface TlsCloseable
+    {
+        /// <exception cref="IOException"/>
+        void Close();
+    }
+}
diff --git a/crypto/src/tls/TlsContext.cs b/crypto/src/tls/TlsContext.cs
new file mode 100644
index 000000000..5a2802f56
--- /dev/null
+++ b/crypto/src/tls/TlsContext.cs
@@ -0,0 +1,79 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for a TLS context implementation.</summary>
+    public interface TlsContext
+    {
+        TlsCrypto Crypto { get; }
+
+        TlsNonceGenerator NonceGenerator { get; }
+
+        SecurityParameters SecurityParameters { get; }
+
+        /// <summary>Return true if this context is for a server, false otherwise.</summary>
+        /// <returns>true for a server based context, false for a client based one.</returns>
+        bool IsServer { get; }
+
+        ProtocolVersion[] ClientSupportedVersions { get; }
+
+        ProtocolVersion ClientVersion { get; }
+
+        ProtocolVersion RsaPreMasterSecretVersion { get; }
+
+        ProtocolVersion ServerVersion { get; }
+
+        /// <summary>Used to get the resumable session, if any, used by this connection.</summary>
+        /// <remarks>
+        /// Only available after the handshake has successfully completed.
+        /// </remarks>
+        /// <returns>A <see cref="TlsSession"/> representing the resumable session used by this connection, or null if
+        /// no resumable session available.</returns>
+        /// <seealso cref="TlsPeer.NotifyHandshakeComplete"/>
+        TlsSession ResumableSession { get; }
+
+        /// <summary>Used to get the session information for this connection.</summary>
+        /// <remarks>
+        /// Only available after the handshake has successfully completed. Use <see cref="TlsSession.IsResumable"/>
+        /// to find out if the session is resumable.
+        /// </remarks>
+        /// <returns>A <see cref="TlsSession"/> representing the session used by this connection.</returns>
+        /// <seealso cref="TlsPeer.NotifyHandshakeComplete"/>
+        TlsSession Session { get; }
+
+        object UserObject { get; set; }
+
+        /// <summary>Export the value of the specified channel binding.</summary>
+        /// <remarks>
+        /// Only available after the handshake has successfully completed.
+        /// </remarks>
+        /// <param name="channelBinding">A <see cref="ChannelBinding"/> constant specifying the channel binding to
+        /// export.</param>
+        /// <returns>A copy of the channel binding data as a <c>byte[]</c>, or null if the binding could not be
+        /// determined.</returns>
+        byte[] ExportChannelBinding(int channelBinding);
+
+        /// <summary>Export (early data) keying material according to RFC 5705: "Keying Material Exporters for TLS", as
+        /// updated for TLS 1.3 (RFC 8446).</summary>
+        /// <remarks>
+        /// NOTE: for use in settings where an exporter is needed for 0-RTT data.
+        /// </remarks>
+        /// <param name="asciiLabel">indicates which application will use the exported keys.</param>
+        /// <param name="context_value">allows the application using the exporter to mix its own data with the TLS PRF
+        /// for the exporter output.</param>
+        /// <param name="length">the number of bytes to generate.</param>
+        /// <returns>a pseudorandom bit string of 'length' bytes generated from the (exporter_)master_secret.</returns>
+        byte[] ExportEarlyKeyingMaterial(string asciiLabel, byte[] context_value, int length);
+
+        /// <summary>Export keying material according to RFC 5705: "Keying Material Exporters for TLS", as updated for
+        /// TLS 1.3 (RFC 8446) when negotiated.</summary>
+        /// <param name="asciiLabel">indicates which application will use the exported keys.</param>
+        /// <param name="context_value">allows the application using the exporter to mix its own data with the TLS PRF
+        /// for the exporter output.</param>
+        /// <param name="length">the number of bytes to generate.</param>
+        /// <returns>a pseudorandom bit string of 'length' bytes generated from the (exporter_)master_secret.</returns>
+        byte[] ExportKeyingMaterial(string asciiLabel, byte[] context_value, int length);
+    }
+}
diff --git a/crypto/src/tls/TlsCredentialedAgreement.cs b/crypto/src/tls/TlsCredentialedAgreement.cs
new file mode 100644
index 000000000..354a17754
--- /dev/null
+++ b/crypto/src/tls/TlsCredentialedAgreement.cs
@@ -0,0 +1,19 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Support interface for generating a secret based on the credentials sent by a TLS peer.</summary>
+    public interface TlsCredentialedAgreement
+        : TlsCredentials
+    {
+        /// <summary>Calculate an agreed secret based on our credentials and the public key credentials of our peer.
+        /// </summary>
+        /// <param name="peerCertificate">public key certificate of our TLS peer.</param>
+        /// <returns>the agreed secret.</returns>
+        /// <exception cref="IOException">in case of an exception on generation of the secret.</exception>
+        TlsSecret GenerateAgreement(TlsCertificate peerCertificate);
+    }
+}
diff --git a/crypto/src/tls/TlsCredentialedDecryptor.cs b/crypto/src/tls/TlsCredentialedDecryptor.cs
new file mode 100644
index 000000000..5fa021d41
--- /dev/null
+++ b/crypto/src/tls/TlsCredentialedDecryptor.cs
@@ -0,0 +1,19 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for a class that decrypts TLS secrets.</summary>
+    public interface TlsCredentialedDecryptor
+        : TlsCredentials
+    {
+        /// <summary>Decrypt the passed in cipher text using the parameters available.</summary>
+        /// <param name="cryptoParams">the parameters to use for the decryption.</param>
+        /// <param name="ciphertext">the cipher text containing the secret.</param>
+        /// <returns>a TLS secret.</returns>
+        /// <exception cref="IOException">on a parsing or decryption error.</exception>
+        TlsSecret Decrypt(TlsCryptoParameters cryptoParams, byte[] ciphertext);
+    }
+}
diff --git a/crypto/src/tls/TlsCredentialedSigner.cs b/crypto/src/tls/TlsCredentialedSigner.cs
new file mode 100644
index 000000000..c6f5a8d7f
--- /dev/null
+++ b/crypto/src/tls/TlsCredentialedSigner.cs
@@ -0,0 +1,26 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Support interface for generating a signature based on our private credentials.</summary>
+    public interface TlsCredentialedSigner
+        : TlsCredentials
+    {
+        /// <summary>Generate a signature against the passed in hash.</summary>
+        /// <param name="hash">a message digest calculated across the message the signature is to apply to.</param>
+        /// <returns>an encoded signature.</returns>
+        /// <exception cref="IOException">if the hash cannot be processed, or there is an issue with the private
+        /// credentials.</exception>
+        byte[] GenerateRawSignature(byte[] hash);
+
+        /// <summary>Return the algorithm IDs for the signature algorithm and the associated hash it uses.</summary>
+        /// <returns>the full algorithm details for the signature.</returns>
+        SignatureAndHashAlgorithm SignatureAndHashAlgorithm { get; }
+
+        /// <exception cref="IOException"/>
+        TlsStreamSigner GetStreamSigner();
+    }
+}
diff --git a/crypto/src/tls/TlsCredentials.cs b/crypto/src/tls/TlsCredentials.cs
new file mode 100644
index 000000000..4a6084a07
--- /dev/null
+++ b/crypto/src/tls/TlsCredentials.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for interfaces/classes carrying TLS credentials.</summary>
+    public interface TlsCredentials
+    {
+        /// <summary>Return the certificate structure representing our identity.</summary>
+        /// <returns>our certificate structure.</returns>
+		Certificate Certificate { get; }
+    }
+}
diff --git a/crypto/src/tls/TlsDHGroupVerifier.cs b/crypto/src/tls/TlsDHGroupVerifier.cs
new file mode 100644
index 000000000..2fb208cc0
--- /dev/null
+++ b/crypto/src/tls/TlsDHGroupVerifier.cs
@@ -0,0 +1,15 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Interface for verifying explicit Diffie-Hellman group parameters.</summary>
+    public interface TlsDHGroupVerifier
+    {
+        /// <summary>Check whether the given DH group is acceptable for use.</summary>
+        /// <param name="dhGroup">the <see cref="DHGroup"/> to check.</param>
+        /// <returns>true if (and only if) the specified group is acceptable.</returns>
+        bool Accept(DHGroup dhGroup);
+    }
+}
diff --git a/crypto/src/tls/TlsDHKeyExchange.cs b/crypto/src/tls/TlsDHKeyExchange.cs
new file mode 100644
index 000000000..a00ed67b7
--- /dev/null
+++ b/crypto/src/tls/TlsDHKeyExchange.cs
@@ -0,0 +1,93 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>(D)TLS DH key exchange.</summary>
+    public class TlsDHKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.DH_DSS:
+            case KeyExchangeAlgorithm.DH_RSA:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsCredentialedAgreement m_agreementCredentials;
+        protected TlsCertificate m_dhPeerCertificate;
+
+        public TlsDHKeyExchange(int keyExchange)
+            : base(CheckKeyExchange(keyExchange))
+        {
+        }
+
+        public override void SkipServerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            this.m_agreementCredentials = TlsUtilities.RequireAgreementCredentials(serverCredentials);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            this.m_dhPeerCertificate = serverCertificate.GetCertificateAt(0).CheckUsageInRole(ConnectionEnd.server,
+                TlsCertificateRole.DH);
+        }
+
+        public override short[] GetClientCertificateTypes()
+        {
+            return new short[]{ ClientCertificateType.dss_fixed_dh, ClientCertificateType.rsa_fixed_dh };
+        }
+
+        public override void SkipClientCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            this.m_agreementCredentials = TlsUtilities.RequireAgreementCredentials(clientCredentials);
+        }
+
+        public override 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.
+             */
+        }
+
+        public override void ProcessClientCertificate(Certificate clientCertificate)
+        {
+            this.m_dhPeerCertificate = clientCertificate.GetCertificateAt(0).CheckUsageInRole(ConnectionEnd.client,
+                TlsCertificateRole.DH);
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            // For dss_fixed_dh and rsa_fixed_dh, the key arrived in the client certificate
+        }
+
+        public override bool RequiresCertificateVerify
+        {
+            get { return false; }
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            return m_agreementCredentials.GenerateAgreement(m_dhPeerCertificate);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsDHUtilities.cs b/crypto/src/tls/TlsDHUtilities.cs
new file mode 100644
index 000000000..7605b0055
--- /dev/null
+++ b/crypto/src/tls/TlsDHUtilities.cs
@@ -0,0 +1,159 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class TlsDHUtilities
+    {
+        public static TlsDHConfig CreateNamedDHConfig(TlsContext context, int namedGroup)
+        {
+            if (namedGroup < 0 || NamedGroup.GetFiniteFieldBits(namedGroup) < 1)
+                return null;
+
+            bool padded = TlsUtilities.IsTlsV13(context);
+            return new TlsDHConfig(namedGroup, padded);
+        }
+
+        public static DHGroup GetDHGroup(TlsDHConfig dhConfig)
+        {
+            int namedGroup = dhConfig.NamedGroup;
+            if (namedGroup >= 0)
+                return GetNamedDHGroup(namedGroup);
+
+            return dhConfig.ExplicitGroup;
+        }
+
+        public static DHGroup GetNamedDHGroup(int namedGroup)
+        {
+            switch (namedGroup)
+            {
+            case NamedGroup.ffdhe2048:
+                return DHStandardGroups.rfc7919_ffdhe2048;
+            case NamedGroup.ffdhe3072:
+                return DHStandardGroups.rfc7919_ffdhe3072;
+            case NamedGroup.ffdhe4096:
+                return DHStandardGroups.rfc7919_ffdhe4096;
+            case NamedGroup.ffdhe6144:
+                return DHStandardGroups.rfc7919_ffdhe6144;
+            case NamedGroup.ffdhe8192:
+                return DHStandardGroups.rfc7919_ffdhe8192;
+            default:
+                return null;
+            }
+        }
+
+        public static int GetMinimumFiniteFieldBits(int cipherSuite)
+        {
+            /*
+             * NOTE: An equivalent mechanism was added to support a minimum bit-size requirement for ECC
+             * mooted in early drafts of RFC 8442. This requirement was removed in later drafts, so that
+             * mechanism is currently somewhat trivial, and this similarly so.
+             */
+            return IsDHCipherSuite(cipherSuite) ? 1 : 0;
+        }
+
+        public static bool IsDHCipherSuite(int cipherSuite)
+        {
+            switch (TlsUtilities.GetKeyExchangeAlgorithm(cipherSuite))
+            {
+            case KeyExchangeAlgorithm.DH_anon:
+            case KeyExchangeAlgorithm.DH_DSS:
+            case KeyExchangeAlgorithm.DH_RSA:
+            case KeyExchangeAlgorithm.DHE_DSS:
+            case KeyExchangeAlgorithm.DHE_PSK:
+            case KeyExchangeAlgorithm.DHE_RSA:
+                return true;
+
+            default:
+                return false;
+            }
+        }
+
+        public static int GetNamedGroupForDHParameters(BigInteger p, BigInteger g)
+        {
+            int[] namedGroups = new int[]{ NamedGroup.ffdhe2048, NamedGroup.ffdhe3072, NamedGroup.ffdhe4096,
+                NamedGroup.ffdhe6144, NamedGroup.ffdhe8192 };
+
+            for (int i = 0; i < namedGroups.Length; ++i)
+            {
+                int namedGroup = namedGroups[i];
+                DHGroup dhGroup = GetNamedDHGroup(namedGroup);
+                if (dhGroup != null && dhGroup.P.Equals(p) && dhGroup.G.Equals(g))
+                    return namedGroup;
+            }
+
+            return -1;
+        }
+
+        public static DHGroup GetStandardGroupForDHParameters(BigInteger p, BigInteger g)
+        {
+            DHGroup[] standardGroups = new DHGroup[] { DHStandardGroups.rfc7919_ffdhe2048,
+                DHStandardGroups.rfc7919_ffdhe3072, DHStandardGroups.rfc7919_ffdhe4096, DHStandardGroups.rfc7919_ffdhe6144,
+                DHStandardGroups.rfc7919_ffdhe8192, DHStandardGroups.rfc3526_1536, DHStandardGroups.rfc3526_2048,
+                DHStandardGroups.rfc3526_3072, DHStandardGroups.rfc3526_4096, DHStandardGroups.rfc3526_6144,
+                DHStandardGroups.rfc3526_8192, DHStandardGroups.rfc5996_768, DHStandardGroups.rfc5996_1024 };
+
+            for (int i = 0; i < standardGroups.Length; ++i)
+            {
+                DHGroup dhGroup = standardGroups[i];
+                if (dhGroup != null && dhGroup.P.Equals(p) && dhGroup.G.Equals(g))
+                    return dhGroup;
+            }
+
+            return null;
+        }
+
+        /// <exception cref="IOException"/>
+        public static TlsDHConfig ReceiveDHConfig(TlsContext context, TlsDHGroupVerifier dhGroupVerifier,
+            Stream input)
+        {
+            BigInteger p = ReadDHParameter(input);
+            BigInteger g = ReadDHParameter(input);
+
+            int namedGroup = GetNamedGroupForDHParameters(p, g);
+            if (namedGroup< 0)
+            {
+                DHGroup dhGroup = GetStandardGroupForDHParameters(p, g);
+                if (null == dhGroup)
+                {
+                    dhGroup = new DHGroup(p, null, g, 0);
+                }
+
+                if (!dhGroupVerifier.Accept(dhGroup))
+                    throw new TlsFatalAlert(AlertDescription.insufficient_security);
+
+                return new TlsDHConfig(dhGroup);
+            }
+
+            int[] clientSupportedGroups = context.SecurityParameters.ClientSupportedGroups;
+            if (null == clientSupportedGroups || Arrays.Contains(clientSupportedGroups, namedGroup))
+                return new TlsDHConfig(namedGroup, false);
+
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        /// <exception cref="IOException"/>
+        public static BigInteger ReadDHParameter(Stream input)
+        {
+            return new BigInteger(1, TlsUtilities.ReadOpaque16(input, 1));
+        }
+
+        /// <exception cref="IOException"/>
+        public static void WriteDHConfig(TlsDHConfig dhConfig, Stream output)
+        {
+            DHGroup group = GetDHGroup(dhConfig);
+            WriteDHParameter(group.P, output);
+            WriteDHParameter(group.G, output);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void WriteDHParameter(BigInteger x, Stream output)
+        {
+            TlsUtilities.WriteOpaque16(BigIntegers.AsUnsignedByteArray(x), output);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsDHanonKeyExchange.cs b/crypto/src/tls/TlsDHanonKeyExchange.cs
new file mode 100644
index 000000000..dd03ce0f1
--- /dev/null
+++ b/crypto/src/tls/TlsDHanonKeyExchange.cs
@@ -0,0 +1,124 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>(D)TLS DH_anon key exchange.</summary>
+    public class TlsDHanonKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.DH_anon:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsDHGroupVerifier m_dhGroupVerifier;
+        protected TlsDHConfig m_dhConfig;
+
+        protected TlsAgreement m_agreement;
+
+        public TlsDHanonKeyExchange(int keyExchange, TlsDHGroupVerifier dhGroupVerifier)
+            : this(keyExchange, dhGroupVerifier, null)
+        {
+        }
+
+        public TlsDHanonKeyExchange(int keyExchange, TlsDHConfig dhConfig)
+            : this(keyExchange, null, dhConfig)
+        {
+        }
+
+        private TlsDHanonKeyExchange(int keyExchange, TlsDHGroupVerifier dhGroupVerifier, TlsDHConfig dhConfig)
+            : base(CheckKeyExchange(keyExchange))
+        {
+            this.m_dhGroupVerifier = dhGroupVerifier;
+            this.m_dhConfig = dhConfig;
+        }
+
+        public override void SkipServerCredentials()
+        {
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public override bool RequiresServerKeyExchange
+        {
+            get { return true; }
+        }
+
+        public override byte[] GenerateServerKeyExchange()
+        {
+            MemoryStream buf = new MemoryStream();
+
+            TlsDHUtilities.WriteDHConfig(m_dhConfig, buf);
+
+            this.m_agreement = m_context.Crypto.CreateDHDomain(m_dhConfig).CreateDH();
+
+            byte[] y = m_agreement.GenerateEphemeral();
+
+            TlsUtilities.WriteOpaque16(y, buf);
+
+            return buf. ToArray();
+        }
+
+        public override void ProcessServerKeyExchange(Stream input)
+        {
+            this.m_dhConfig = TlsDHUtilities.ReceiveDHConfig(m_context, m_dhGroupVerifier, input);
+
+            byte[] y = TlsUtilities.ReadOpaque16(input, 1);
+
+            this.m_agreement = m_context.Crypto.CreateDHDomain(m_dhConfig).CreateDH();
+
+            m_agreement.ReceivePeerValue(y);
+        }
+
+        public override short[] GetClientCertificateTypes()
+        {
+            return null;
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void GenerateClientKeyExchange(Stream output)
+        {
+            byte[] y = m_agreement.GenerateEphemeral();
+
+            TlsUtilities.WriteOpaque16(y, output);
+        }
+
+        public override void ProcessClientCertificate(Certificate clientCertificate)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            byte[] y = TlsUtilities.ReadOpaque16(input, 1);
+
+            m_agreement.ReceivePeerValue(y);
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            return m_agreement.CalculateSecret();
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsDheKeyExchange.cs b/crypto/src/tls/TlsDheKeyExchange.cs
new file mode 100644
index 000000000..dd41b1260
--- /dev/null
+++ b/crypto/src/tls/TlsDheKeyExchange.cs
@@ -0,0 +1,129 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsDheKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.DHE_DSS:
+            case KeyExchangeAlgorithm.DHE_RSA:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsDHGroupVerifier m_dhGroupVerifier;
+        protected TlsDHConfig m_dhConfig;
+
+        protected TlsCredentialedSigner m_serverCredentials = null;
+        protected TlsCertificate m_serverCertificate = null;
+        protected TlsAgreement m_agreement;
+
+        public TlsDheKeyExchange(int keyExchange, TlsDHGroupVerifier dhGroupVerifier)
+            : this(keyExchange, dhGroupVerifier, null)
+        {
+        }
+
+        public TlsDheKeyExchange(int keyExchange, TlsDHConfig dhConfig)
+            : this(keyExchange, null, dhConfig)
+        {
+        }
+
+        private TlsDheKeyExchange(int keyExchange, TlsDHGroupVerifier dhGroupVerifier, TlsDHConfig dhConfig)
+            : base(CheckKeyExchange(keyExchange))
+        {
+            this.m_dhGroupVerifier = dhGroupVerifier;
+            this.m_dhConfig = dhConfig;
+        }
+
+        public override void SkipServerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            this.m_serverCredentials = TlsUtilities.RequireSignerCredentials(serverCredentials);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            this.m_serverCertificate = serverCertificate.GetCertificateAt(0);
+        }
+
+        public override bool RequiresServerKeyExchange
+        {
+            get { return true; }
+        }
+
+        public override byte[] GenerateServerKeyExchange()
+        {
+            DigestInputBuffer digestBuffer = new DigestInputBuffer();
+
+            TlsDHUtilities.WriteDHConfig(m_dhConfig, digestBuffer);
+
+            this.m_agreement = m_context.Crypto.CreateDHDomain(m_dhConfig).CreateDH();
+
+            byte[] y = m_agreement.GenerateEphemeral();
+
+            TlsUtilities.WriteOpaque16(y, digestBuffer);
+
+            TlsUtilities.GenerateServerKeyExchangeSignature(m_context, m_serverCredentials, digestBuffer);
+
+            return digestBuffer.ToArray();
+        }
+
+        public override void ProcessServerKeyExchange(Stream input)
+        {
+            DigestInputBuffer digestBuffer = new DigestInputBuffer();
+            Stream teeIn = new TeeInputStream(input, digestBuffer);
+
+            this.m_dhConfig = TlsDHUtilities.ReceiveDHConfig(m_context, m_dhGroupVerifier, teeIn);
+
+            byte[] y = TlsUtilities.ReadOpaque16(teeIn, 1);
+
+            TlsUtilities.VerifyServerKeyExchangeSignature(m_context, input, m_serverCertificate, digestBuffer);
+
+            this.m_agreement = m_context.Crypto.CreateDHDomain(m_dhConfig).CreateDH();
+
+            m_agreement.ReceivePeerValue(y);
+        }
+
+        public override short[] GetClientCertificateTypes()
+        {
+            return new short[]{ ClientCertificateType.dss_sign, ClientCertificateType.ecdsa_sign,
+                ClientCertificateType.rsa_sign };
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            TlsUtilities.RequireSignerCredentials(clientCredentials);
+        }
+
+        public override void GenerateClientKeyExchange(Stream output)
+        {
+            byte[] y = m_agreement.GenerateEphemeral();
+
+            TlsUtilities.WriteOpaque16(y, output);
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            m_agreement.ReceivePeerValue(TlsUtilities.ReadOpaque16(input, 1));
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            return m_agreement.CalculateSecret();
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsECDHKeyExchange.cs b/crypto/src/tls/TlsECDHKeyExchange.cs
new file mode 100644
index 000000000..45e83f8ad
--- /dev/null
+++ b/crypto/src/tls/TlsECDHKeyExchange.cs
@@ -0,0 +1,95 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>(D)TLS ECDH key exchange (see RFC 4492).</summary>
+    public class TlsECDHKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.ECDH_ECDSA:
+            case KeyExchangeAlgorithm.ECDH_RSA:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsCredentialedAgreement m_agreementCredentials;
+        protected TlsCertificate m_ecdhPeerCertificate;
+
+        public TlsECDHKeyExchange(int keyExchange)
+            : base(CheckKeyExchange(keyExchange))
+        {
+        }
+
+        public override void SkipServerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            this.m_agreementCredentials = TlsUtilities.RequireAgreementCredentials(serverCredentials);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            this.m_ecdhPeerCertificate = serverCertificate.GetCertificateAt(0).CheckUsageInRole(ConnectionEnd.server,
+                TlsCertificateRole.ECDH);
+        }
+
+        public override short[] GetClientCertificateTypes()
+        {
+            /*
+             * 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.
+             */
+            return new short[]{ ClientCertificateType.ecdsa_fixed_ecdh, ClientCertificateType.rsa_fixed_ecdh };
+        }
+
+        public override void SkipClientCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            this.m_agreementCredentials = TlsUtilities.RequireAgreementCredentials(clientCredentials);
+        }
+
+        public override void GenerateClientKeyExchange(Stream output)
+        {
+            // In this case, the Client Key Exchange message will be sent, but will be empty.
+        }
+
+        public override void ProcessClientCertificate(Certificate clientCertificate)
+        {
+            this.m_ecdhPeerCertificate = clientCertificate.GetCertificateAt(0).CheckUsageInRole(ConnectionEnd.client,
+                TlsCertificateRole.ECDH);
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            // For ecdsa_fixed_ecdh and rsa_fixed_ecdh, the key arrived in the client certificate
+        }
+
+        public override bool RequiresCertificateVerify
+        {
+            get { return false; }
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            return m_agreementCredentials.GenerateAgreement(m_ecdhPeerCertificate);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsECDHanonKeyExchange.cs b/crypto/src/tls/TlsECDHanonKeyExchange.cs
new file mode 100644
index 000000000..66b0ec9fb
--- /dev/null
+++ b/crypto/src/tls/TlsECDHanonKeyExchange.cs
@@ -0,0 +1,127 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>(D)TLS ECDH_anon key exchange (see RFC 4492).</summary>
+    public class TlsECDHanonKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.ECDH_anon:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsECConfig m_ecConfig;
+
+        protected TlsAgreement m_agreement;
+
+        public TlsECDHanonKeyExchange(int keyExchange)
+            : this(keyExchange, null)
+        {
+        }
+
+        public TlsECDHanonKeyExchange(int keyExchange, TlsECConfig ecConfig)
+            : base(CheckKeyExchange(keyExchange))
+        {
+            this.m_ecConfig = ecConfig;
+        }
+
+        public override void SkipServerCredentials()
+        {
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public override bool RequiresServerKeyExchange
+        {
+            get { return true; }
+        }
+
+        public override byte[] GenerateServerKeyExchange()
+        {
+            MemoryStream buf = new MemoryStream();
+
+            TlsEccUtilities.WriteECConfig(m_ecConfig, buf);
+
+            this.m_agreement = m_context.Crypto.CreateECDomain(m_ecConfig).CreateECDH();
+
+            GenerateEphemeral(buf);
+
+            return buf.ToArray();
+        }
+
+        public override void ProcessServerKeyExchange(Stream input)
+        {
+            this.m_ecConfig = TlsEccUtilities.ReceiveECDHConfig(m_context, input);
+
+            byte[] point = TlsUtilities.ReadOpaque8(input, 1);
+
+            this.m_agreement = m_context.Crypto.CreateECDomain(m_ecConfig).CreateECDH();
+
+            ProcessEphemeral(point);
+        }
+
+        public override short[] GetClientCertificateTypes()
+        {
+            return null;
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void GenerateClientKeyExchange(Stream output)
+        {
+            GenerateEphemeral(output);
+        }
+
+        public override void ProcessClientCertificate(Certificate clientCertificate)
+        {
+            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            byte[] point = TlsUtilities.ReadOpaque8(input, 1);
+
+            ProcessEphemeral(point);
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            return m_agreement.CalculateSecret();
+        }
+
+        protected virtual void GenerateEphemeral(Stream output)
+        {
+            byte[] point = m_agreement.GenerateEphemeral();
+
+            TlsUtilities.WriteOpaque8(point, output);
+        }
+
+        protected virtual void ProcessEphemeral(byte[] point)
+        {
+            TlsEccUtilities.CheckPointEncoding(m_ecConfig.NamedGroup, point);
+
+            this.m_agreement.ReceivePeerValue(point);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsECDheKeyExchange.cs b/crypto/src/tls/TlsECDheKeyExchange.cs
new file mode 100644
index 000000000..ab83036d9
--- /dev/null
+++ b/crypto/src/tls/TlsECDheKeyExchange.cs
@@ -0,0 +1,141 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>(D)TLS ECDHE key exchange (see RFC 4492).</summary>
+    public class TlsECDheKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsECConfig m_ecConfig;
+
+        protected TlsCredentialedSigner m_serverCredentials = null;
+        protected TlsCertificate m_serverCertificate = null;
+        protected TlsAgreement m_agreement;
+
+        public TlsECDheKeyExchange(int keyExchange)
+            : this(keyExchange, null)
+        {
+        }
+
+        public TlsECDheKeyExchange(int keyExchange, TlsECConfig ecConfig)
+            : base(CheckKeyExchange(keyExchange))
+        {
+            this.m_ecConfig = ecConfig;
+        }
+
+        public override void SkipServerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            this.m_serverCredentials = TlsUtilities.RequireSignerCredentials(serverCredentials);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            this.m_serverCertificate = serverCertificate.GetCertificateAt(0);
+        }
+
+        public override bool RequiresServerKeyExchange
+        {
+            get { return true; }
+        }
+
+        public override byte[] GenerateServerKeyExchange()
+        {
+            DigestInputBuffer digestBuffer = new DigestInputBuffer();
+
+            TlsEccUtilities.WriteECConfig(m_ecConfig, digestBuffer);
+
+            this.m_agreement = m_context.Crypto.CreateECDomain(m_ecConfig).CreateECDH();
+
+            GenerateEphemeral(digestBuffer);
+
+            TlsUtilities.GenerateServerKeyExchangeSignature(m_context, m_serverCredentials, digestBuffer);
+
+            return digestBuffer.ToArray();
+        }
+
+        public override void ProcessServerKeyExchange(Stream input)
+        {
+            DigestInputBuffer digestBuffer = new DigestInputBuffer();
+            Stream teeIn = new TeeInputStream(input, digestBuffer);
+
+            this.m_ecConfig = TlsEccUtilities.ReceiveECDHConfig(m_context, teeIn);
+
+            byte[] point = TlsUtilities.ReadOpaque8(teeIn, 1);
+
+            TlsUtilities.VerifyServerKeyExchangeSignature(m_context, input, m_serverCertificate, digestBuffer);
+
+            this.m_agreement = m_context.Crypto.CreateECDomain(m_ecConfig).CreateECDH();
+
+            ProcessEphemeral(point);
+        }
+
+        public override short[] GetClientCertificateTypes()
+        {
+            /*
+             * 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.
+             */
+            return new short[]{ ClientCertificateType.dss_sign, ClientCertificateType.ecdsa_sign,
+                ClientCertificateType.rsa_sign };
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            TlsUtilities.RequireSignerCredentials(clientCredentials);
+        }
+
+        public override void GenerateClientKeyExchange(Stream output)
+        {
+            GenerateEphemeral(output);
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            byte[] point = TlsUtilities.ReadOpaque8(input, 1);
+
+            ProcessEphemeral(point);
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            return m_agreement.CalculateSecret();
+        }
+
+        protected virtual void GenerateEphemeral(Stream output)
+        {
+            byte[] point = m_agreement.GenerateEphemeral();
+
+            TlsUtilities.WriteOpaque8(point, output);
+        }
+
+        protected virtual void ProcessEphemeral(byte[] point)
+        {
+            TlsEccUtilities.CheckPointEncoding(m_ecConfig.NamedGroup, point);
+
+            this.m_agreement.ReceivePeerValue(point);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsEccUtilities.cs b/crypto/src/tls/TlsEccUtilities.cs
new file mode 100644
index 000000000..59320a717
--- /dev/null
+++ b/crypto/src/tls/TlsEccUtilities.cs
@@ -0,0 +1,117 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class TlsEccUtilities
+    {
+        /// <exception cref="IOException"/>
+        public static TlsECConfig CreateNamedECConfig(TlsContext context, int namedGroup)
+        {
+            if (NamedGroup.GetCurveBits(namedGroup) < 1)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return new TlsECConfig(namedGroup);
+        }
+
+        public static int GetMinimumCurveBits(int cipherSuite)
+        {
+            /*
+             * NOTE: This mechanism was added to support a minimum bit-size requirement mooted in early
+             * drafts of RFC 8442. This requirement was removed in later drafts, so this mechanism is
+             * currently somewhat trivial.
+             */
+            return IsEccCipherSuite(cipherSuite) ? 1 : 0;
+        }
+
+        public static bool IsEccCipherSuite(int cipherSuite)
+        {
+            switch (TlsUtilities.GetKeyExchangeAlgorithm(cipherSuite))
+            {
+            case KeyExchangeAlgorithm.ECDH_anon:
+            case KeyExchangeAlgorithm.ECDH_ECDSA:
+            case KeyExchangeAlgorithm.ECDH_RSA:
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+            case KeyExchangeAlgorithm.ECDHE_PSK:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+                return true;
+
+            default:
+                return false;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public static void CheckPointEncoding(int namedGroup, byte[] encoding)
+        {
+            if (TlsUtilities.IsNullOrEmpty(encoding))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            switch (namedGroup)
+            {
+            case NamedGroup.x25519:
+            case NamedGroup.x448:
+                return;
+            }
+
+            switch (encoding[0])
+            {
+            case 0x04: // uncompressed
+                return;
+
+            case 0x00: // infinity
+            case 0x02: // compressed
+            case 0x03: // compressed
+            case 0x06: // hybrid
+            case 0x07: // hybrid
+            default:
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public static TlsECConfig ReceiveECDHConfig(TlsContext context, Stream input)
+        {
+            short curveType = TlsUtilities.ReadUint8(input);
+            if (curveType != ECCurveType.named_curve)
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+            int namedGroup = TlsUtilities.ReadUint16(input);
+            if (NamedGroup.RefersToAnECDHCurve(namedGroup))
+            {
+                int[] clientSupportedGroups = context.SecurityParameters.ClientSupportedGroups;
+                if (null == clientSupportedGroups || Arrays.Contains(clientSupportedGroups, namedGroup))
+                    return new TlsECConfig(namedGroup);
+            }
+
+            throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void WriteECConfig(TlsECConfig ecConfig, Stream output)
+        {
+            WriteNamedECParameters(ecConfig.NamedGroup, output);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void WriteNamedECParameters(int namedGroup, Stream output)
+        {
+            if (!NamedGroup.RefersToASpecificCurve(namedGroup))
+            {
+                /*
+                 * RFC 4492 5.4. All those values of NamedCurve are allowed that refer to a specific
+                 * curve. Values of NamedCurve that indicate support for a class of explicitly defined
+                 * curves are not allowed here [...].
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            TlsUtilities.WriteUint8(ECCurveType.named_curve, output);
+            TlsUtilities.CheckUint16(namedGroup);
+            TlsUtilities.WriteUint16(namedGroup, output);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsException.cs b/crypto/src/tls/TlsException.cs
new file mode 100644
index 000000000..c6d7a1916
--- /dev/null
+++ b/crypto/src/tls/TlsException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsException
+        : IOException
+    {
+        public TlsException()
+            : base()
+        {
+        }
+
+        public TlsException(string message)
+            : base(message)
+        {
+        }
+
+        public TlsException(string message, Exception cause)
+            : base(message, cause)
+        {
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsExtensionsUtilities.cs b/crypto/src/tls/TlsExtensionsUtilities.cs
new file mode 100644
index 000000000..688fee3c7
--- /dev/null
+++ b/crypto/src/tls/TlsExtensionsUtilities.cs
@@ -0,0 +1,1377 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class TlsExtensionsUtilities
+    {
+        public static IDictionary EnsureExtensionsInitialised(IDictionary extensions)
+        {
+            return extensions == null ? Platform.CreateHashtable() : extensions;
+        }
+
+        /// <param name="extensions">(Int32 -> byte[])</param>
+        /// <param name="protocolNameList">an <see cref="IList"/> of <see cref="ProtocolName"/>.</param>
+        /// <exception cref="IOException"/>
+        public static void AddAlpnExtensionClient(IDictionary extensions, IList protocolNameList)
+        {
+            extensions[ExtensionType.application_layer_protocol_negotiation] = CreateAlpnExtensionClient(protocolNameList);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddAlpnExtensionServer(IDictionary extensions, ProtocolName protocolName)
+        {
+            extensions[ExtensionType.application_layer_protocol_negotiation] = CreateAlpnExtensionServer(protocolName);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddCertificateAuthoritiesExtension(IDictionary extensions, IList authorities)
+        {
+            extensions[ExtensionType.certificate_authorities] = CreateCertificateAuthoritiesExtension(authorities);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddClientCertificateTypeExtensionClient(IDictionary extensions, short[] certificateTypes)
+        {
+            extensions[ExtensionType.client_certificate_type] = CreateCertificateTypeExtensionClient(certificateTypes);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddClientCertificateTypeExtensionServer(IDictionary extensions, short certificateType)
+        {
+            extensions[ExtensionType.client_certificate_type] = CreateCertificateTypeExtensionServer(certificateType);
+        }
+
+        public static void AddClientCertificateUrlExtension(IDictionary extensions)
+        {
+            extensions[ExtensionType.client_certificate_url] = CreateClientCertificateUrlExtension();
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddCookieExtension(IDictionary extensions, byte[] cookie)
+        {
+            extensions[ExtensionType.cookie] = CreateCookieExtension(cookie);
+        }
+
+        public static void AddEarlyDataIndication(IDictionary extensions)
+        {
+            extensions[ExtensionType.early_data] = CreateEarlyDataIndication();
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddEarlyDataMaxSize(IDictionary extensions, long maxSize)
+        {
+            extensions[ExtensionType.early_data] = CreateEarlyDataMaxSize(maxSize);
+        }
+
+        public static void AddEmptyExtensionData(IDictionary extensions, Int32 extType)
+        {
+            extensions[extType] = CreateEmptyExtensionData();
+        }
+
+        public static void AddEncryptThenMacExtension(IDictionary extensions)
+        {
+            extensions[ExtensionType.encrypt_then_mac] = CreateEncryptThenMacExtension();
+        }
+
+        public static void AddExtendedMasterSecretExtension(IDictionary extensions)
+        {
+            extensions[ExtensionType.extended_master_secret] = CreateExtendedMasterSecretExtension();
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddHeartbeatExtension(IDictionary extensions, HeartbeatExtension heartbeatExtension)
+        {
+            extensions[ExtensionType.heartbeat] = CreateHeartbeatExtension(heartbeatExtension);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddKeyShareClientHello(IDictionary extensions, IList clientShares)
+        {
+            extensions[ExtensionType.key_share] = CreateKeyShareClientHello(clientShares);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddKeyShareHelloRetryRequest(IDictionary extensions, int namedGroup)
+        {
+            extensions[ExtensionType.key_share] = CreateKeyShareHelloRetryRequest(namedGroup);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddKeyShareServerHello(IDictionary extensions, KeyShareEntry serverShare)
+        {
+            extensions[ExtensionType.key_share] = CreateKeyShareServerHello(serverShare);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddMaxFragmentLengthExtension(IDictionary extensions, short maxFragmentLength)
+        {
+            extensions[ExtensionType.max_fragment_length] = CreateMaxFragmentLengthExtension(maxFragmentLength);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddOidFiltersExtension(IDictionary extensions, IDictionary filters)
+        {
+            extensions[ExtensionType.oid_filters] = CreateOidFiltersExtension(filters);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddPaddingExtension(IDictionary extensions, int dataLength)
+        {
+            extensions[ExtensionType.padding] = CreatePaddingExtension(dataLength);
+        }
+
+        public static void AddPostHandshakeAuthExtension(IDictionary extensions)
+        {
+            extensions[ExtensionType.post_handshake_auth] = CreatePostHandshakeAuthExtension();
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddPreSharedKeyClientHello(IDictionary extensions, OfferedPsks offeredPsks)
+        {
+            extensions[ExtensionType.pre_shared_key] = CreatePreSharedKeyClientHello(offeredPsks);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddPreSharedKeyServerHello(IDictionary extensions, int selectedIdentity)
+        {
+            extensions[ExtensionType.pre_shared_key] = CreatePreSharedKeyServerHello(selectedIdentity);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddPskKeyExchangeModesExtension(IDictionary extensions, short[] modes)
+        {
+            extensions[ExtensionType.psk_key_exchange_modes] = CreatePskKeyExchangeModesExtension(modes);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddRecordSizeLimitExtension(IDictionary extensions, int recordSizeLimit)
+        {
+            extensions[ExtensionType.record_size_limit] = CreateRecordSizeLimitExtension(recordSizeLimit);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddServerCertificateTypeExtensionClient(IDictionary extensions, short[] certificateTypes)
+        {
+            extensions[ExtensionType.server_certificate_type] = CreateCertificateTypeExtensionClient(certificateTypes);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddServerCertificateTypeExtensionServer(IDictionary extensions, short certificateType)
+        {
+            extensions[ExtensionType.server_certificate_type] = CreateCertificateTypeExtensionServer(certificateType);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddServerNameExtensionClient(IDictionary extensions, IList serverNameList)
+        {
+            extensions[ExtensionType.server_name] = CreateServerNameExtensionClient(serverNameList);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddServerNameExtensionServer(IDictionary extensions)
+        {
+            extensions[ExtensionType.server_name] = CreateServerNameExtensionServer();
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddSignatureAlgorithmsExtension(IDictionary extensions, IList supportedSignatureAlgorithms)
+        {
+            extensions[ExtensionType.signature_algorithms] = CreateSignatureAlgorithmsExtension(supportedSignatureAlgorithms);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddSignatureAlgorithmsCertExtension(IDictionary extensions, IList supportedSignatureAlgorithms)
+        {
+            extensions[ExtensionType.signature_algorithms_cert] = CreateSignatureAlgorithmsCertExtension(supportedSignatureAlgorithms);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddStatusRequestExtension(IDictionary extensions, CertificateStatusRequest statusRequest)
+        {
+            extensions[ExtensionType.status_request] = CreateStatusRequestExtension(statusRequest);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddStatusRequestV2Extension(IDictionary extensions, IList statusRequestV2)
+        {
+            extensions[ExtensionType.status_request_v2] = CreateStatusRequestV2Extension(statusRequestV2);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddSupportedGroupsExtension(IDictionary extensions, IList namedGroups)
+        {
+            extensions[ExtensionType.supported_groups] = CreateSupportedGroupsExtension(namedGroups);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddSupportedPointFormatsExtension(IDictionary extensions, short[] ecPointFormats)
+        {
+            extensions[ExtensionType.ec_point_formats] = CreateSupportedPointFormatsExtension(ecPointFormats);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddSupportedVersionsExtensionClient(IDictionary extensions, ProtocolVersion[] versions)
+        {
+            extensions[ExtensionType.supported_versions] = CreateSupportedVersionsExtensionClient(versions);
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddSupportedVersionsExtensionServer(IDictionary extensions, ProtocolVersion selectedVersion)
+        {
+            extensions[ExtensionType.supported_versions] = CreateSupportedVersionsExtensionServer(selectedVersion);
+        }
+
+        public static void AddTruncatedHmacExtension(IDictionary extensions)
+        {
+            extensions[ExtensionType.truncated_hmac] = CreateTruncatedHmacExtension();
+        }
+
+        /// <exception cref="IOException"/>
+        public static void AddTrustedCAKeysExtensionClient(IDictionary extensions, IList trustedAuthoritiesList)
+        {
+            extensions[ExtensionType.trusted_ca_keys] = CreateTrustedCAKeysExtensionClient(trustedAuthoritiesList);
+        }
+
+        public static void AddTrustedCAKeysExtensionServer(IDictionary extensions)
+        {
+            extensions[ExtensionType.trusted_ca_keys] = CreateTrustedCAKeysExtensionServer();
+        }
+
+        /// <returns>an <see cref="IList"/> of <see cref="ProtocolName"/>.</returns>
+        /// <exception cref="IOException"/>
+        public static IList GetAlpnExtensionClient(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.application_layer_protocol_negotiation);
+            return extensionData == null ? null : ReadAlpnExtensionClient(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static ProtocolName GetAlpnExtensionServer(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.application_layer_protocol_negotiation);
+            return extensionData == null ? null : ReadAlpnExtensionServer(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList GetCertificateAuthoritiesExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.certificate_authorities);
+            return extensionData == null ? null : ReadCertificateAuthoritiesExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static short[] GetClientCertificateTypeExtensionClient(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_type);
+            return extensionData == null ? null : ReadCertificateTypeExtensionClient(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static short GetClientCertificateTypeExtensionServer(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_type);
+            return extensionData == null ? (short)-1 : ReadCertificateTypeExtensionServer(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] GetCookieExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.cookie);
+            return extensionData == null ? null : ReadCookieExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static long GetEarlyDataMaxSize(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.early_data);
+            return extensionData == null ? -1L : ReadEarlyDataMaxSize(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static HeartbeatExtension GetHeartbeatExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.heartbeat);
+            return extensionData == null ? null : ReadHeartbeatExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList GetKeyShareClientHello(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.key_share);
+            return extensionData == null ? null : ReadKeyShareClientHello(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static int GetKeyShareHelloRetryRequest(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.key_share);
+            return extensionData == null ? -1 : ReadKeyShareHelloRetryRequest(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static KeyShareEntry GetKeyShareServerHello(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.key_share);
+            return extensionData == null ? null : ReadKeyShareServerHello(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static short GetMaxFragmentLengthExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.max_fragment_length);
+            return extensionData == null ? (short)-1 : ReadMaxFragmentLengthExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IDictionary GetOidFiltersExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.oid_filters);
+            return extensionData == null ? null : ReadOidFiltersExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static int GetPaddingExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.padding);
+            return extensionData == null ? -1 : ReadPaddingExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static OfferedPsks GetPreSharedKeyClientHello(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.pre_shared_key);
+            return extensionData == null ? null : ReadPreSharedKeyClientHello(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static int GetPreSharedKeyServerHello(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.pre_shared_key);
+            return extensionData == null ? -1 : ReadPreSharedKeyServerHello(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static short[] GetPskKeyExchangeModesExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.psk_key_exchange_modes);
+            return extensionData == null ? null : ReadPskKeyExchangeModesExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static int GetRecordSizeLimitExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.record_size_limit);
+            return extensionData == null ? -1 : ReadRecordSizeLimitExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static short[] GetServerCertificateTypeExtensionClient(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_certificate_type);
+            return extensionData == null ? null : ReadCertificateTypeExtensionClient(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static short GetServerCertificateTypeExtensionServer(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_certificate_type);
+            return extensionData == null ? (short)-1 : ReadCertificateTypeExtensionServer(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList GetServerNameExtensionClient(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_name);
+            return extensionData == null ? null : ReadServerNameExtensionClient(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList GetSignatureAlgorithmsExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.signature_algorithms);
+            return extensionData == null ? null : ReadSignatureAlgorithmsExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList GetSignatureAlgorithmsCertExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.signature_algorithms_cert);
+            return extensionData == null ? null : ReadSignatureAlgorithmsCertExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static CertificateStatusRequest GetStatusRequestExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.status_request);
+            return extensionData == null ? null : ReadStatusRequestExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList GetStatusRequestV2Extension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.status_request_v2);
+            return extensionData == null ? null : ReadStatusRequestV2Extension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static int[] GetSupportedGroupsExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.supported_groups);
+            return extensionData == null ? null : ReadSupportedGroupsExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static short[] GetSupportedPointFormatsExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.ec_point_formats);
+            return extensionData == null ? null : ReadSupportedPointFormatsExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static ProtocolVersion[] GetSupportedVersionsExtensionClient(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.supported_versions);
+            return extensionData == null ? null : ReadSupportedVersionsExtensionClient(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static ProtocolVersion GetSupportedVersionsExtensionServer(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.supported_versions);
+            return extensionData == null ? null : ReadSupportedVersionsExtensionServer(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList GetTrustedCAKeysExtensionClient(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.trusted_ca_keys);
+            return extensionData == null ? null : ReadTrustedCAKeysExtensionClient(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool HasClientCertificateUrlExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_url);
+            return extensionData == null ? false : ReadClientCertificateUrlExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool HasEarlyDataIndication(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.early_data);
+            return extensionData == null ? false : ReadEarlyDataIndication(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool HasEncryptThenMacExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.encrypt_then_mac);
+            return extensionData == null ? false : ReadEncryptThenMacExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool HasExtendedMasterSecretExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.extended_master_secret);
+            return extensionData == null ? false : ReadExtendedMasterSecretExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool HasServerNameExtensionServer(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_name);
+            return extensionData == null ? false : ReadServerNameExtensionServer(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool HasPostHandshakeAuthExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.post_handshake_auth);
+            return extensionData == null ? false : ReadPostHandshakeAuthExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool HasTruncatedHmacExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.truncated_hmac);
+            return extensionData == null ? false : ReadTruncatedHmacExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool HasTrustedCAKeysExtensionServer(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.trusted_ca_keys);
+            return extensionData == null ? false : ReadTrustedCAKeysExtensionServer(extensionData);
+        }
+
+        /// <param name="protocolNameList">an <see cref="IList"/> of <see cref="ProtocolName"/>.</param>
+        /// <exception cref="IOException"/>
+        public static byte[] CreateAlpnExtensionClient(IList protocolNameList)
+        {
+            if (protocolNameList == null || protocolNameList.Count < 1)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            MemoryStream buf = new MemoryStream();
+
+            // Placeholder for length
+            TlsUtilities.WriteUint16(0, buf);
+
+            foreach (ProtocolName protocolName in protocolNameList)
+            {
+                protocolName.Encode(buf);
+            }
+
+            return PatchOpaque16(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateAlpnExtensionServer(ProtocolName protocolName)
+        {
+            IList protocol_name_list = Platform.CreateArrayList();
+            protocol_name_list.Add(protocolName);
+
+            return CreateAlpnExtensionClient(protocol_name_list);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateCertificateAuthoritiesExtension(IList authorities)
+        {
+            if (null == authorities || authorities.Count < 1)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            MemoryStream buf = new MemoryStream();
+
+            // Placeholder for length
+            TlsUtilities.WriteUint16(0, buf);
+
+            foreach (X509Name authority in authorities)
+            {
+                byte[] derEncoding = authority.GetEncoded(Asn1Encodable.Der);
+                TlsUtilities.WriteOpaque16(derEncoding, buf);
+            }
+
+            return PatchOpaque16(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateCertificateTypeExtensionClient(short[] certificateTypes)
+        {
+            if (TlsUtilities.IsNullOrEmpty(certificateTypes) || certificateTypes.Length > 255)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return TlsUtilities.EncodeUint8ArrayWithUint8Length(certificateTypes);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateCertificateTypeExtensionServer(short certificateType)
+        {
+            return TlsUtilities.EncodeUint8(certificateType);
+        }
+
+        public static byte[] CreateClientCertificateUrlExtension()
+        {
+            return CreateEmptyExtensionData();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateCookieExtension(byte[] cookie)
+        {
+            if (TlsUtilities.IsNullOrEmpty(cookie) || cookie.Length >= (1 << 16))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return TlsUtilities.EncodeOpaque16(cookie);
+        }
+
+        public static byte[] CreateEarlyDataIndication()
+        {
+            return CreateEmptyExtensionData();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateEarlyDataMaxSize(long maxSize)
+        {
+            return TlsUtilities.EncodeUint32(maxSize);
+        }
+
+        public static byte[] CreateEmptyExtensionData()
+        {
+            return TlsUtilities.EmptyBytes;
+        }
+
+        public static byte[] CreateEncryptThenMacExtension()
+        {
+            return CreateEmptyExtensionData();
+        }
+
+        public static byte[] CreateExtendedMasterSecretExtension()
+        {
+            return CreateEmptyExtensionData();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateHeartbeatExtension(HeartbeatExtension heartbeatExtension)
+        {
+            if (heartbeatExtension == null)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            MemoryStream buf = new MemoryStream();
+
+            heartbeatExtension.Encode(buf);
+
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateKeyShareClientHello(IList clientShares)
+        {
+            if (clientShares == null || clientShares.Count < 1)
+                return TlsUtilities.EncodeUint16(0);
+
+            MemoryStream buf = new MemoryStream();
+
+            // Placeholder for length
+            TlsUtilities.WriteUint16(0, buf);
+
+            foreach (KeyShareEntry clientShare in clientShares)
+            {
+                clientShare.Encode(buf);
+            }
+
+            return PatchOpaque16(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateKeyShareHelloRetryRequest(int namedGroup)
+        {
+            return TlsUtilities.EncodeUint16(namedGroup);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateKeyShareServerHello(KeyShareEntry serverShare)
+        {
+            if (serverShare == null)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            MemoryStream buf = new MemoryStream();
+
+            serverShare.Encode(buf);
+
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateMaxFragmentLengthExtension(short maxFragmentLength)
+        {
+            return TlsUtilities.EncodeUint8(maxFragmentLength);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateOidFiltersExtension(IDictionary filters)
+        {
+            MemoryStream buf = new MemoryStream();
+
+            // Placeholder for length
+            TlsUtilities.WriteUint16(0, buf);
+
+            if (null != filters)
+            {
+                foreach (DerObjectIdentifier certificateExtensionOid in filters.Keys)
+                {
+                    byte[] certificateExtensionValues = (byte[])filters[certificateExtensionOid];
+
+                    if (null == certificateExtensionOid || null == certificateExtensionValues)
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                    byte[] derEncoding = certificateExtensionOid.GetEncoded(Asn1Encodable.Der);
+                    TlsUtilities.WriteOpaque8(derEncoding, buf);
+
+                    TlsUtilities.WriteOpaque16(certificateExtensionValues, buf);
+                }
+            }
+
+            return PatchOpaque16(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreatePaddingExtension(int dataLength)
+        {
+            TlsUtilities.CheckUint16(dataLength);
+            return new byte[dataLength];
+        }
+
+        public static byte[] CreatePostHandshakeAuthExtension()
+        {
+            return CreateEmptyExtensionData();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreatePreSharedKeyClientHello(OfferedPsks offeredPsks)
+        {
+            if (offeredPsks == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            MemoryStream buf = new MemoryStream();
+
+            offeredPsks.Encode(buf);
+
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreatePreSharedKeyServerHello(int selectedIdentity)
+        {
+            return TlsUtilities.EncodeUint16(selectedIdentity);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreatePskKeyExchangeModesExtension(short[] modes)
+        {
+            if (TlsUtilities.IsNullOrEmpty(modes) || modes.Length > 255)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return TlsUtilities.EncodeUint8ArrayWithUint8Length(modes);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateRecordSizeLimitExtension(int recordSizeLimit)
+        {
+            if (recordSizeLimit < 64)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return TlsUtilities.EncodeUint16(recordSizeLimit);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateServerNameExtensionClient(IList serverNameList)
+        {
+            if (serverNameList == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            MemoryStream buf = new MemoryStream();
+
+            new ServerNameList(serverNameList).Encode(buf);
+
+            return buf.ToArray();
+        }
+
+        public static byte[] CreateServerNameExtensionServer()
+        {
+            return CreateEmptyExtensionData();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateSignatureAlgorithmsExtension(IList supportedSignatureAlgorithms)
+        {
+            MemoryStream buf = new MemoryStream();
+
+            TlsUtilities.EncodeSupportedSignatureAlgorithms(supportedSignatureAlgorithms, buf);
+
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateSignatureAlgorithmsCertExtension(IList supportedSignatureAlgorithms)
+        {
+            return CreateSignatureAlgorithmsExtension(supportedSignatureAlgorithms);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateStatusRequestExtension(CertificateStatusRequest statusRequest)
+        {
+            if (statusRequest == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            MemoryStream buf = new MemoryStream();
+
+            statusRequest.Encode(buf);
+
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateStatusRequestV2Extension(IList statusRequestV2)
+        {
+            if (statusRequestV2 == null || statusRequestV2.Count < 1)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            MemoryStream buf = new MemoryStream();
+
+            // Placeholder for length
+            TlsUtilities.WriteUint16(0, buf);
+
+            foreach (CertificateStatusRequestItemV2 entry in statusRequestV2)
+            {
+                entry.Encode(buf);
+            }
+
+            return PatchOpaque16(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateSupportedGroupsExtension(IList namedGroups)
+        {
+            if (namedGroups == null || namedGroups.Count < 1)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            int count = namedGroups.Count;
+            int[] values = new int[count];
+            for (int i = 0; i < count; ++i)
+            {
+                values[i] = (Int32)namedGroups[i];
+            }
+
+            return TlsUtilities.EncodeUint16ArrayWithUint16Length(values);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateSupportedPointFormatsExtension(short[] ecPointFormats)
+        {
+            if (ecPointFormats == null || !Arrays.Contains(ecPointFormats, ECPointFormat.uncompressed))
+            {
+                /*
+                 * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST
+                 * contain the value 0 (uncompressed) as one of the items in the list of point formats.
+                 */
+
+                // NOTE: We add it at the start (highest preference)
+                ecPointFormats = Arrays.Prepend(ecPointFormats, ECPointFormat.uncompressed);
+            }
+
+            return TlsUtilities.EncodeUint8ArrayWithUint8Length(ecPointFormats);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateSupportedVersionsExtensionClient(ProtocolVersion[] versions)
+        {
+            if (TlsUtilities.IsNullOrEmpty(versions) || versions.Length > 127)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            int count = versions.Length;
+            byte[] data = new byte[1 + count * 2];
+            TlsUtilities.WriteUint8(count * 2, data, 0);
+            for (int i = 0; i < count; ++i)
+            {
+                TlsUtilities.WriteVersion((ProtocolVersion)versions[i], data, 1 + i * 2);
+            }
+            return data;
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateSupportedVersionsExtensionServer(ProtocolVersion selectedVersion)
+        {
+            return TlsUtilities.EncodeVersion(selectedVersion);
+        }
+
+        public static byte[] CreateTruncatedHmacExtension()
+        {
+            return CreateEmptyExtensionData();
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateTrustedCAKeysExtensionClient(IList trustedAuthoritiesList)
+        {
+            MemoryStream buf = new MemoryStream();
+
+            // Placeholder for length
+            TlsUtilities.WriteUint16(0, buf);
+
+            if (trustedAuthoritiesList != null)
+            {
+                foreach (TrustedAuthority entry in trustedAuthoritiesList)
+                {
+                    entry.Encode(buf);
+                }
+            }
+
+            return PatchOpaque16(buf);
+        }
+
+        public static byte[] CreateTrustedCAKeysExtensionServer()
+        {
+            return CreateEmptyExtensionData();
+        }
+
+        /// <exception cref="IOException"/>
+        private static bool ReadEmptyExtensionData(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            if (extensionData.Length != 0)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return true;
+        }
+
+        /// <returns>an <see cref="IList"/> of <see cref="ProtocolName"/>.</returns>
+        /// <exception cref="IOException"/>
+        public static IList ReadAlpnExtensionClient(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData);
+
+            int length = TlsUtilities.ReadUint16(buf);
+            if (length != (extensionData.Length - 2))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            IList protocol_name_list = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                ProtocolName protocolName = ProtocolName.Parse(buf);
+
+                protocol_name_list.Add(protocolName);
+            }
+            return protocol_name_list;
+        }
+
+        /// <exception cref="IOException"/>
+        public static ProtocolName ReadAlpnExtensionServer(byte[] extensionData)
+        {
+            IList protocol_name_list = ReadAlpnExtensionClient(extensionData);
+            if (protocol_name_list.Count != 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return (ProtocolName)protocol_name_list[0];
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList ReadCertificateAuthoritiesExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+            if (extensionData.Length < 5)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            MemoryStream buf = new MemoryStream(extensionData);
+
+            int length = TlsUtilities.ReadUint16(buf);
+            if (length != (extensionData.Length - 2))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            IList authorities = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                byte[] derEncoding = TlsUtilities.ReadOpaque16(buf, 1);
+                Asn1Object asn1 = TlsUtilities.ReadDerObject(derEncoding);
+                authorities.Add(X509Name.GetInstance(asn1));
+            }
+            return authorities;
+        }
+
+        /// <exception cref="IOException"/>
+        public static short[] ReadCertificateTypeExtensionClient(byte[] extensionData)
+        {
+            short[] certificateTypes = TlsUtilities.DecodeUint8ArrayWithUint8Length(extensionData);
+            if (certificateTypes.Length < 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return certificateTypes;
+        }
+
+        /// <exception cref="IOException"/>
+        public static short ReadCertificateTypeExtensionServer(byte[] extensionData)
+        {
+            return TlsUtilities.DecodeUint8(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ReadClientCertificateUrlExtension(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] ReadCookieExtension(byte[] extensionData)
+        {
+            return TlsUtilities.DecodeOpaque16(extensionData, 1);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ReadEarlyDataIndication(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static long ReadEarlyDataMaxSize(byte[] extensionData)
+        {
+            return TlsUtilities.DecodeUint32(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ReadEncryptThenMacExtension(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ReadExtendedMasterSecretExtension(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static HeartbeatExtension ReadHeartbeatExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            HeartbeatExtension heartbeatExtension = HeartbeatExtension.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return heartbeatExtension;
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList ReadKeyShareClientHello(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            /*
+             * TODO[tls13] Clients MUST NOT offer multiple KeyShareEntry values for the same group.
+             * Clients MUST NOT offer any KeyShareEntry values for groups not listed in the client's
+             * "supported_groups" extension. Servers MAY check for violations of these rules and abort
+             * the handshake with an "illegal_parameter" alert if one is violated.
+             */
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            int length = TlsUtilities.ReadUint16(buf);
+            if (length != (extensionData.Length - 2))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            IList clientShares = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                KeyShareEntry clientShare = KeyShareEntry.Parse(buf);
+
+                clientShares.Add(clientShare);
+            }
+            return clientShares;
+        }
+
+        /// <exception cref="IOException"/>
+        public static int ReadKeyShareHelloRetryRequest(byte[] extensionData)
+        {
+            return TlsUtilities.DecodeUint16(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static KeyShareEntry ReadKeyShareServerHello(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            KeyShareEntry serverShare = KeyShareEntry.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return serverShare;
+        }
+
+        /// <exception cref="IOException"/>
+        public static short ReadMaxFragmentLengthExtension(byte[] extensionData)
+        {
+            return TlsUtilities.DecodeUint8(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IDictionary ReadOidFiltersExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+            if (extensionData.Length < 2)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            int length = TlsUtilities.ReadUint16(buf);
+            if (length != (extensionData.Length - 2))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            IDictionary filters = Platform.CreateHashtable();
+            while (buf.Position < buf.Length)
+            {
+                byte[] derEncoding = TlsUtilities.ReadOpaque8(buf, 1);
+                Asn1Object asn1 = TlsUtilities.ReadDerObject(derEncoding);
+                DerObjectIdentifier certificateExtensionOid = DerObjectIdentifier.GetInstance(asn1);
+
+                if (filters.Contains(certificateExtensionOid))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                byte[] certificateExtensionValues = TlsUtilities.ReadOpaque16(buf);
+
+                filters[certificateExtensionOid] = certificateExtensionValues;
+            }
+            return filters;
+        }
+
+        /// <exception cref="IOException"/>
+        public static int ReadPaddingExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            if (!Arrays.AreAllZeroes(extensionData, 0, extensionData.Length))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return extensionData.Length;
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ReadPostHandshakeAuthExtension(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static OfferedPsks ReadPreSharedKeyClientHello(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            OfferedPsks offeredPsks = OfferedPsks.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return offeredPsks;
+        }
+
+        /// <exception cref="IOException"/>
+        public static int ReadPreSharedKeyServerHello(byte[] extensionData)
+        {
+            return TlsUtilities.DecodeUint16(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static short[] ReadPskKeyExchangeModesExtension(byte[] extensionData)
+        {
+            short[] modes = TlsUtilities.DecodeUint8ArrayWithUint8Length(extensionData);
+            if (modes.Length < 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return modes;
+        }
+
+        /// <exception cref="IOException"/>
+        public static int ReadRecordSizeLimitExtension(byte[] extensionData)
+        {
+            int recordSizeLimit = TlsUtilities.DecodeUint16(extensionData);
+            if (recordSizeLimit < 64)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return recordSizeLimit;
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList ReadServerNameExtensionClient(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            ServerNameList serverNameList = ServerNameList.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return serverNameList.ServerNames;
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ReadServerNameExtensionServer(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList ReadSignatureAlgorithmsExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            IList supported_signature_algorithms = TlsUtilities.ParseSupportedSignatureAlgorithms(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return supported_signature_algorithms;
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList ReadSignatureAlgorithmsCertExtension(byte[] extensionData)
+        {
+            return ReadSignatureAlgorithmsExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static CertificateStatusRequest ReadStatusRequestExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            CertificateStatusRequest statusRequest = CertificateStatusRequest.Parse(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return statusRequest;
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList ReadStatusRequestV2Extension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+            if (extensionData.Length < 3)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            int length = TlsUtilities.ReadUint16(buf);
+            if (length != (extensionData.Length - 2))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            IList statusRequestV2 = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                CertificateStatusRequestItemV2 entry = CertificateStatusRequestItemV2.Parse(buf);
+                statusRequestV2.Add(entry);
+            }
+            return statusRequestV2;
+        }
+
+        /// <exception cref="IOException"/>
+        public static int[] ReadSupportedGroupsExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            int length = TlsUtilities.ReadUint16(buf);
+            if (length < 2 || (length & 1) != 0)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int[] namedGroups = TlsUtilities.ReadUint16Array(length / 2, buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return namedGroups;
+        }
+
+        /// <exception cref="IOException"/>
+        public static short[] ReadSupportedPointFormatsExtension(byte[] extensionData)
+        {
+            short[] ecPointFormats = TlsUtilities.DecodeUint8ArrayWithUint8Length(extensionData);
+            if (!Arrays.Contains(ecPointFormats, ECPointFormat.uncompressed))
+            {
+                /*
+                 * RFC 4492 5.1. If the Supported Point Formats Extension is indeed sent, it MUST
+                 * contain the value 0 (uncompressed) as one of the items in the list of point formats.
+                 */
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+            return ecPointFormats;
+        }
+
+        /// <exception cref="IOException"/>
+        public static ProtocolVersion[] ReadSupportedVersionsExtensionClient(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+            if (extensionData.Length < 3 || extensionData.Length > 255 || (extensionData.Length & 1) == 0)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int length = TlsUtilities.ReadUint8(extensionData, 0);
+            if (length != (extensionData.Length - 1))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int count = length / 2;
+            ProtocolVersion[] versions = new ProtocolVersion[count];
+            for (int i = 0; i < count; ++i)
+            {
+                versions[i] = TlsUtilities.ReadVersion(extensionData, 1 + i * 2);
+            }
+            return versions;
+        }
+
+        /// <exception cref="IOException"/>
+        public static ProtocolVersion ReadSupportedVersionsExtensionServer(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+            if (extensionData.Length != 2)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return TlsUtilities.ReadVersion(extensionData, 0);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ReadTruncatedHmacExtension(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList ReadTrustedCAKeysExtensionClient(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+            if (extensionData.Length < 2)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            int length = TlsUtilities.ReadUint16(buf);
+            if (length != (extensionData.Length - 2))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            IList trusted_authorities_list = Platform.CreateArrayList();
+            while (buf.Position < buf.Length)
+            {
+                TrustedAuthority entry = TrustedAuthority.Parse(buf);
+                trusted_authorities_list.Add(entry);
+            }
+            return trusted_authorities_list;
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ReadTrustedCAKeysExtensionServer(byte[] extensionData)
+        {
+            return ReadEmptyExtensionData(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        private static byte[] PatchOpaque16(MemoryStream buf)
+        {
+            int length = (int)buf.Length - 2;
+            TlsUtilities.CheckUint16(length);
+            byte[] extensionData = buf.ToArray();
+            TlsUtilities.WriteUint16(length, extensionData, 0);
+            return extensionData;
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsFatalAlert.cs b/crypto/src/tls/TlsFatalAlert.cs
new file mode 100644
index 000000000..9f0ea8f30
--- /dev/null
+++ b/crypto/src/tls/TlsFatalAlert.cs
@@ -0,0 +1,46 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsFatalAlert
+        : TlsException
+    {
+        private static string GetMessage(short alertDescription, string detailMessage)
+        {
+            string msg = Tls.AlertDescription.GetText(alertDescription);
+            if (null != detailMessage)
+            {
+                msg += "; " + detailMessage;
+            }
+            return msg;
+        }
+
+        protected readonly short m_alertDescription;
+
+        public TlsFatalAlert(short alertDescription)
+            : this(alertDescription, (string)null)
+        {
+        }
+
+        public TlsFatalAlert(short alertDescription, string detailMessage)
+            : this(alertDescription, detailMessage, null)
+        {
+        }
+
+        public TlsFatalAlert(short alertDescription, Exception alertCause)
+            : this(alertDescription, null, alertCause)
+        {
+        }
+
+        public TlsFatalAlert(short alertDescription, string detailMessage, Exception alertCause)
+            : base(GetMessage(alertDescription, detailMessage), alertCause)
+        {
+            this.m_alertDescription = alertDescription;
+        }
+
+        public virtual short AlertDescription
+        {
+            get { return m_alertDescription; }
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsFatalAlertReceived.cs b/crypto/src/tls/TlsFatalAlertReceived.cs
new file mode 100644
index 000000000..0bf6cff38
--- /dev/null
+++ b/crypto/src/tls/TlsFatalAlertReceived.cs
@@ -0,0 +1,21 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsFatalAlertReceived
+        : TlsException
+    {
+        protected readonly short m_alertDescription;
+
+        public TlsFatalAlertReceived(short alertDescription)
+            : base(Tls.AlertDescription.GetText(alertDescription))
+        {
+            this.m_alertDescription = alertDescription;
+        }
+
+        public virtual short AlertDescription
+        {
+            get { return m_alertDescription; }
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsHandshakeHash.cs b/crypto/src/tls/TlsHandshakeHash.cs
new file mode 100644
index 000000000..aa33c680d
--- /dev/null
+++ b/crypto/src/tls/TlsHandshakeHash.cs
@@ -0,0 +1,29 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for an object that can calculate a handshake hash.</summary>
+    public interface TlsHandshakeHash
+        : TlsHash
+    {
+        /// <exception cref="IOException"/>
+        void CopyBufferTo(Stream output);
+
+        void ForceBuffering();
+
+        void NotifyPrfDetermined();
+
+        void TrackHashAlgorithm(int cryptoHashAlgorithm);
+
+        void SealHashAlgorithms();
+
+        TlsHandshakeHash StopTracking();
+
+        TlsHash ForkPrfHash();
+
+        byte[] GetFinalHash(int cryptoHashAlgorithm);
+    }
+}
diff --git a/crypto/src/tls/TlsHeartbeat.cs b/crypto/src/tls/TlsHeartbeat.cs
new file mode 100644
index 000000000..3f208a869
--- /dev/null
+++ b/crypto/src/tls/TlsHeartbeat.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    public interface TlsHeartbeat
+    {
+        byte[] GeneratePayload();
+
+        int IdleMillis { get; }
+
+        int TimeoutMillis { get; }
+    }
+}
diff --git a/crypto/src/tls/TlsKeyExchange.cs b/crypto/src/tls/TlsKeyExchange.cs
new file mode 100644
index 000000000..42c2aa4bc
--- /dev/null
+++ b/crypto/src/tls/TlsKeyExchange.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>A generic interface for key exchange implementations in (D)TLS.</summary>
+    public interface TlsKeyExchange
+    {
+        void Init(TlsContext context);
+
+        /// <exception cref="IOException"/>
+        void SkipServerCredentials();
+
+        /// <exception cref="IOException"/>
+        void ProcessServerCredentials(TlsCredentials serverCredentials);
+
+        /// <exception cref="IOException"/>
+        void ProcessServerCertificate(Certificate serverCertificate);
+
+        bool RequiresServerKeyExchange { get; }
+
+        /// <exception cref="IOException"/>
+        byte[] GenerateServerKeyExchange();
+
+        /// <exception cref="IOException"/>
+        void SkipServerKeyExchange();
+
+        /// <exception cref="IOException"/>
+        void ProcessServerKeyExchange(Stream input);
+
+        short[] GetClientCertificateTypes();
+
+        /// <exception cref="IOException"/>
+        void SkipClientCredentials();
+
+        /// <exception cref="IOException"/>
+        void ProcessClientCredentials(TlsCredentials clientCredentials);
+
+        /// <exception cref="IOException"/>
+        void ProcessClientCertificate(Certificate clientCertificate);
+
+        /// <exception cref="IOException"/>
+        void GenerateClientKeyExchange(Stream output);
+
+        /// <exception cref="IOException"/>
+        void ProcessClientKeyExchange(Stream input);
+
+        bool RequiresCertificateVerify { get; }
+
+        /// <exception cref="IOException"/>
+        TlsSecret GeneratePreMasterSecret();
+    }
+}
diff --git a/crypto/src/tls/TlsKeyExchangeFactory.cs b/crypto/src/tls/TlsKeyExchangeFactory.cs
new file mode 100644
index 000000000..59b5327ab
--- /dev/null
+++ b/crypto/src/tls/TlsKeyExchangeFactory.cs
@@ -0,0 +1,59 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Interface for a key exchange factory offering a variety of specific algorithms.</summary>
+    public interface TlsKeyExchangeFactory
+    {
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateDHKeyExchange(int keyExchange);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateDHanonKeyExchangeClient(int keyExchange, TlsDHGroupVerifier dhGroupVerifier);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateDHanonKeyExchangeServer(int keyExchange, TlsDHConfig dhConfig);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateDheKeyExchangeClient(int keyExchange, TlsDHGroupVerifier dhGroupVerifier);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateDheKeyExchangeServer(int keyExchange, TlsDHConfig dhConfig);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateECDHKeyExchange(int keyExchange);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateECDHanonKeyExchangeClient(int keyExchange);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateECDHanonKeyExchangeServer(int keyExchange, TlsECConfig ecConfig);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateECDheKeyExchangeClient(int keyExchange);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateECDheKeyExchangeServer(int keyExchange, TlsECConfig ecConfig);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreatePskKeyExchangeClient(int keyExchange, TlsPskIdentity pskIdentity,
+            TlsDHGroupVerifier dhGroupVerifier);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreatePskKeyExchangeServer(int keyExchange, TlsPskIdentityManager pskIdentityManager,
+            TlsDHConfig dhConfig, TlsECConfig ecConfig);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateRsaKeyExchange(int keyExchange);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateSrpKeyExchangeClient(int keyExchange, TlsSrpIdentity srpIdentity,
+            TlsSrpConfigVerifier srpConfigVerifier);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchange CreateSrpKeyExchangeServer(int keyExchange, TlsSrpLoginParameters loginParameters);
+    }
+}
diff --git a/crypto/src/tls/TlsNoCloseNotifyException.cs b/crypto/src/tls/TlsNoCloseNotifyException.cs
new file mode 100644
index 000000000..8fdfbbfc4
--- /dev/null
+++ b/crypto/src/tls/TlsNoCloseNotifyException.cs
@@ -0,0 +1,21 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>This exception will be thrown (only) when the connection is closed by the peer without sending a
+    /// <see cref="AlertDescription.close_notify">close_notify</see> warning alert.</summary>
+    /// <remarks>
+    /// If this happens, the TLS protocol cannot rule out truncation of the connection data (potentially
+    /// malicious). It may be possible to check for truncation via some property of a higher level protocol
+    /// built upon TLS, e.g.the Content-Length header for HTTPS.
+    /// </remarks>
+    public class TlsNoCloseNotifyException
+        : EndOfStreamException
+    {
+        public TlsNoCloseNotifyException()
+            : base("No close_notify alert received before connection closed")
+        {
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsObjectIdentifiers.cs b/crypto/src/tls/TlsObjectIdentifiers.cs
new file mode 100644
index 000000000..e4bba69f7
--- /dev/null
+++ b/crypto/src/tls/TlsObjectIdentifiers.cs
@@ -0,0 +1,14 @@
+using System;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.X509;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Object Identifiers associated with TLS extensions.</summary>
+    public abstract class TlsObjectIdentifiers
+    {
+        /// <summary>RFC 7633</summary>
+        public static readonly DerObjectIdentifier id_pe_tlsfeature = X509ObjectIdentifiers.IdPE.Branch("24");
+    }
+}
diff --git a/crypto/src/tls/TlsPeer.cs b/crypto/src/tls/TlsPeer.cs
new file mode 100644
index 000000000..29b4288e2
--- /dev/null
+++ b/crypto/src/tls/TlsPeer.cs
@@ -0,0 +1,121 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for a (D)TLS endpoint.</summary>
+    public interface TlsPeer
+    {
+        TlsCrypto Crypto { get; }
+
+        void NotifyCloseHandle(TlsCloseable closehandle);
+
+        /// <exception cref="IOException"/>
+        void Cancel();
+
+        ProtocolVersion[] GetProtocolVersions();
+
+        int[] GetCipherSuites();
+
+        /// <summary>Notifies the peer that a new handshake is about to begin.</summary>
+        /// <exception cref="IOException"/>
+        void NotifyHandshakeBeginning();
+
+        /// <summary>Specify the timeout, in milliseconds, to use for the complete handshake process.</summary>
+        /// <remarks>
+        /// NOTE: Currently only respected by DTLS protocols. Negative values are not allowed. A timeout of zero means
+        /// an infinite timeout (i.e.the handshake will never time out).
+        /// </remarks>
+        /// <returns>the handshake timeout, in milliseconds.</returns>
+        int GetHandshakeTimeoutMillis();
+
+        bool AllowLegacyResumption();
+
+        int GetMaxCertificateChainLength();
+
+        int GetMaxHandshakeMessageSize();
+
+        /// <remarks>
+        /// This option is provided as a last resort for interoperability with TLS peers that fail to correctly send a
+        /// close_notify alert at end of stream. Implementations SHOULD return true; caution is advised if returning
+        /// false without a full understanding of the implications.
+        /// </remarks>
+        bool RequiresCloseNotify();
+
+        /// <remarks>This implementation supports RFC 7627 and will always negotiate the extended_master_secret
+        /// extension where possible. When connecting to a peer that does not offer/accept this extension, it is
+        /// recommended to abort the handshake.This option is provided for interoperability with legacy peers, although
+        /// some TLS features will be disabled in that case (see RFC 7627 5.4).
+        /// </remarks>
+        /// <returns><c>true</c> if the handshake should be aborted when the peer does not negotiate the
+        /// extended_master_secret extension, or <c>false</c> to support legacy interoperability.</returns>
+        bool RequiresExtendedMasterSecret();
+
+        bool ShouldUseExtendedMasterSecret();
+
+        /// <summary>See RFC 5246 6.2.3.2. Controls whether block cipher encryption may randomly add extra padding
+        /// beyond the minimum.</summary>
+        /// <remarks>
+        /// Note that in configurations where this is known to be potential security risk this setting will be ignored
+        /// (and extended padding disabled). Extra padding is always supported when decrypting received records.
+        /// </remarks>
+        /// <returns><c>true</c> if random extra padding should be added during block cipher encryption, or
+        /// <c>false</c> to always use the minimum amount of required padding.</returns>
+        bool ShouldUseExtendedPadding();
+
+        /// <summary> draft-mathewson-no-gmtunixtime-00 2. "If existing users of a TLS implementation may rely on
+        /// gmt_unix_time containing the current time, we recommend that implementors MAY provide the ability to set
+        /// gmt_unix_time as an option only, off by default.".</summary>
+        /// <remarks>
+        /// NOTE: For a server that has negotiated TLS 1.3 (or later), or a client that has offered TLS 1.3 (or later),
+        /// this is not called and gmt_unix_time is not used.
+        /// </remarks>
+        /// <returns><c>true</c> if the current time should be used in the gmt_unix_time field of Random, or
+        /// <c>false</c> if gmt_unix_time should contain a cryptographically random value.</returns>
+        bool ShouldUseGmtUnixTime();
+
+        /// <summary>RFC 5746 3.4/3.6. In case this is false, peers may want to terminate the handshake instead of
+        /// continuing; see Section 4.1/4.3 for discussion.</summary>
+        /// <remarks>
+        /// NOTE: TLS 1.3 forbids renegotiation, so this is never called when TLS 1.3 (or later) was negotiated.
+        /// </remarks>
+        /// <exception cref="IOException"/>
+        void NotifySecureRenegotiation(bool secureRenegotiation);
+
+        /// <exception cref="IOException"/>
+        TlsKeyExchangeFactory GetKeyExchangeFactory();
+
+        /// <summary>This method will be called when an alert is raised by the protocol.</summary>
+        /// <param name="alertLevel"><see cref="AlertLevel"/></param>
+        /// <param name="alertDescription"><see cref="AlertDescription"/></param>
+        /// <param name="message">A human-readable message explaining what caused this alert. May be null.</param>
+        /// <param name="cause">The <see cref="Exception"/> that caused this alert to be raised. May be null.</param>
+        void NotifyAlertRaised(short alertLevel, short alertDescription, string message, Exception cause);
+
+        /// <summary>This method will be called when an alert is received from the remote peer.</summary>
+        /// <param name="alertLevel"><see cref="AlertLevel"/></param>
+        /// <param name="alertDescription"><see cref="AlertDescription"/></param>
+        void NotifyAlertReceived(short alertLevel, short alertDescription);
+
+        /// <summary>Notifies the peer that the handshake has been successfully completed.</summary>
+        /// <exception cref="IOException"/>
+        void NotifyHandshakeComplete();
+
+        /// <summary>Return a <see cref="TlsHeartbeat"/> instance that will control the generation of heartbeats
+        /// locally (if permitted by the remote peer), or null to not generate heartbeats. Heartbeats are described in
+        /// RFC 6520.</summary>
+        /// <returns>an instance of <see cref="TlsHeartbeat"/>.</returns>
+        /// <seealso cref="DefaultTlsHeartbeat"/>
+        TlsHeartbeat GetHeartbeat();
+
+        /// <summary>Return the heartbeat mode applicable to the remote peer. Heartbeats are described in RFC 6520.
+        /// </summary>
+        /// <remarks>
+        /// See enumeration class <see cref="HeartbeatMode"/> for appropriate return values.
+        /// </remarks>
+        /// <returns>the <see cref="HeartbeatMode"/> value.</returns>
+        short GetHeartbeatPolicy();
+    }
+}
diff --git a/crypto/src/tls/TlsProtocol.cs b/crypto/src/tls/TlsProtocol.cs
new file mode 100644
index 000000000..db30f6b40
--- /dev/null
+++ b/crypto/src/tls/TlsProtocol.cs
@@ -0,0 +1,1867 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class TlsProtocol
+        : TlsCloseable
+    {
+        /*
+         * Connection States.
+         * 
+         * NOTE: Redirection of handshake messages to TLS 1.3 handlers assumes CS_START, CS_CLIENT_HELLO
+         * are lower than any of the other values.
+         */
+        protected const short CS_START = 0;
+        protected const short CS_CLIENT_HELLO = 1;
+        protected const short CS_SERVER_HELLO_RETRY_REQUEST = 2;
+        protected const short CS_CLIENT_HELLO_RETRY = 3;
+        protected const short CS_SERVER_HELLO = 4;
+        protected const short CS_SERVER_ENCRYPTED_EXTENSIONS = 5;
+        protected const short CS_SERVER_SUPPLEMENTAL_DATA = 6;
+        protected const short CS_SERVER_CERTIFICATE = 7;
+        protected const short CS_SERVER_CERTIFICATE_STATUS = 8;
+        protected const short CS_SERVER_CERTIFICATE_VERIFY = 9;
+        protected const short CS_SERVER_KEY_EXCHANGE = 10;
+        protected const short CS_SERVER_CERTIFICATE_REQUEST = 11;
+        protected const short CS_SERVER_HELLO_DONE = 12;
+        protected const short CS_CLIENT_END_OF_EARLY_DATA = 13;
+        protected const short CS_CLIENT_SUPPLEMENTAL_DATA = 14;
+        protected const short CS_CLIENT_CERTIFICATE = 15;
+        protected const short CS_CLIENT_KEY_EXCHANGE = 16;
+        protected const short CS_CLIENT_CERTIFICATE_VERIFY = 17;
+        protected const short CS_CLIENT_FINISHED = 18;
+        protected const short CS_SERVER_SESSION_TICKET = 19;
+        protected const short CS_SERVER_FINISHED = 20;
+        protected const short CS_END = 21;
+
+        protected bool IsLegacyConnectionState()
+        {
+            switch (m_connectionState)
+            {
+            case CS_START:
+            case CS_CLIENT_HELLO:
+            case CS_SERVER_HELLO:
+            case CS_SERVER_SUPPLEMENTAL_DATA:
+            case CS_SERVER_CERTIFICATE:
+            case CS_SERVER_CERTIFICATE_STATUS:
+            case CS_SERVER_KEY_EXCHANGE:
+            case CS_SERVER_CERTIFICATE_REQUEST:
+            case CS_SERVER_HELLO_DONE:
+            case CS_CLIENT_SUPPLEMENTAL_DATA:
+            case CS_CLIENT_CERTIFICATE:
+            case CS_CLIENT_KEY_EXCHANGE:
+            case CS_CLIENT_CERTIFICATE_VERIFY:
+            case CS_CLIENT_FINISHED:
+            case CS_SERVER_SESSION_TICKET:
+            case CS_SERVER_FINISHED:
+            case CS_END:
+                return true;
+
+            case CS_SERVER_HELLO_RETRY_REQUEST:
+            case CS_CLIENT_HELLO_RETRY:
+            case CS_SERVER_ENCRYPTED_EXTENSIONS:
+            case CS_SERVER_CERTIFICATE_VERIFY:
+            case CS_CLIENT_END_OF_EARLY_DATA:
+            default:
+                return false;
+            }
+        }
+
+        protected bool IsTlsV13ConnectionState()
+        {
+            switch (m_connectionState)
+            {
+            case CS_START:
+            case CS_CLIENT_HELLO:
+            case CS_SERVER_HELLO_RETRY_REQUEST:
+            case CS_CLIENT_HELLO_RETRY:
+            case CS_SERVER_HELLO:
+            case CS_SERVER_ENCRYPTED_EXTENSIONS:
+            case CS_SERVER_CERTIFICATE_REQUEST:
+            case CS_SERVER_CERTIFICATE:
+            case CS_SERVER_CERTIFICATE_VERIFY:
+            case CS_SERVER_FINISHED:
+            case CS_CLIENT_END_OF_EARLY_DATA:
+            case CS_CLIENT_CERTIFICATE:
+            case CS_CLIENT_CERTIFICATE_VERIFY:
+            case CS_CLIENT_FINISHED:
+            case CS_END:
+                return true;
+
+            case CS_SERVER_SUPPLEMENTAL_DATA:
+            case CS_SERVER_CERTIFICATE_STATUS:
+            case CS_SERVER_KEY_EXCHANGE:
+            case CS_SERVER_HELLO_DONE:
+            case CS_CLIENT_SUPPLEMENTAL_DATA:
+            case CS_CLIENT_KEY_EXCHANGE:
+            case CS_SERVER_SESSION_TICKET:
+            default:
+                return false;
+            }
+        }
+
+        /*
+         * Different modes to handle the known IV weakness
+         */
+        protected const short ADS_MODE_1_Nsub1 = 0; // 1/n-1 record splitting
+        protected const short ADS_MODE_0_N = 1; // 0/n record splitting
+        protected const short ADS_MODE_0_N_FIRSTONLY = 2; // 0/n record splitting on first data fragment only
+
+        /*
+         * Queues for data from some protocols.
+         */
+        private readonly ByteQueue m_applicationDataQueue = new ByteQueue(0);
+        private readonly ByteQueue m_alertQueue = new ByteQueue(2);
+        private readonly ByteQueue m_handshakeQueue = new ByteQueue(0);
+        //private readonly ByteQueue m_heartbeatQueue = new ByteQueue(0);
+
+        internal readonly RecordStream m_recordStream;
+        internal readonly object m_recordWriteLock = new object();
+
+        private int m_maxHandshakeMessageSize = -1;
+
+        internal TlsHandshakeHash m_handshakeHash;
+
+        private TlsStream m_tlsStream = null;
+
+        private volatile bool m_closed = false;
+        private volatile bool m_failedWithError = false;
+        private volatile bool m_appDataReady = false;
+        private volatile bool m_appDataSplitEnabled = true;
+        private volatile bool m_keyUpdateEnabled = false;
+        //private volatile bool m_keyUpdatePendingReceive = false;
+        private volatile bool m_keyUpdatePendingSend = false;
+        private volatile bool m_resumableHandshake = false;
+        private volatile int m_appDataSplitMode = ADS_MODE_1_Nsub1;
+
+        protected TlsSession m_tlsSession = null;
+        protected SessionParameters m_sessionParameters = null;
+        protected TlsSecret m_sessionMasterSecret = null;
+
+        protected byte[] m_retryCookie = null;
+        protected int m_retryGroup = -1;
+        protected IDictionary m_clientExtensions = null;
+        protected IDictionary m_serverExtensions = null;
+
+        protected short m_connectionState = CS_START;
+        protected bool m_resumedSession = false;
+        protected bool m_receivedChangeCipherSpec = false;
+        protected bool m_expectSessionTicket = false;
+
+        protected readonly bool m_blocking;
+        protected readonly ByteQueueInputStream m_inputBuffers;
+        protected readonly ByteQueueOutputStream m_outputBuffer;
+
+        protected TlsProtocol()
+        {
+            this.m_blocking = false;
+            this.m_inputBuffers = new ByteQueueInputStream();
+            this.m_outputBuffer = new ByteQueueOutputStream();
+            this.m_recordStream = new RecordStream(this, m_inputBuffers, m_outputBuffer);
+        }
+
+        public TlsProtocol(Stream stream)
+            : this(stream, stream)
+        {
+        }
+
+        public TlsProtocol(Stream input, Stream output)
+        {
+            this.m_blocking = true;
+            this.m_inputBuffers = null;
+            this.m_outputBuffer = null;
+            this.m_recordStream = new RecordStream(this, input, output);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void ResumeHandshake()
+        {
+            if (!m_blocking)
+                throw new InvalidOperationException("Cannot use ResumeHandshake() in non-blocking mode!");
+            if (!IsHandshaking)
+                throw new InvalidOperationException("No handshake in progress");
+
+            BlockForHandshake();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void CloseConnection()
+        {
+            m_recordStream.Close();
+        }
+
+        protected abstract TlsContext Context { get; }
+
+        internal abstract AbstractTlsContext ContextAdmin { get; }
+
+        protected abstract TlsPeer Peer { get; }
+
+        /// <exception cref="IOException"/>
+        protected virtual void HandleAlertMessage(short alertLevel, short alertDescription)
+        {
+            Peer.NotifyAlertReceived(alertLevel, alertDescription);
+
+            if (alertLevel == AlertLevel.warning)
+            {
+                HandleAlertWarningMessage(alertDescription);
+            }
+            else
+            {
+                HandleFailure();
+
+                throw new TlsFatalAlertReceived(alertDescription);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void HandleAlertWarningMessage(short alertDescription)
+        {
+            switch (alertDescription)
+            {
+            /*
+             * RFC 5246 7.2.1. The other party MUST respond with a close_notify alert of its own
+             * and close down the connection immediately, discarding any pending writes.
+             */
+            case AlertDescription.close_notify:
+            {
+                if (!m_appDataReady)
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                HandleClose(false);
+                break;
+            }
+            case AlertDescription.no_certificate:
+            {
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+            case AlertDescription.no_renegotiation:
+            {
+                // TODO[reneg] Give peer the option to tolerate this
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+            }
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void HandleChangeCipherSpecMessage()
+        {
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void HandleClose(bool user_canceled)
+        {
+            if (!m_closed)
+            {
+                this.m_closed = true;
+
+                if (user_canceled && !m_appDataReady)
+                {
+                    RaiseAlertWarning(AlertDescription.user_canceled, "User canceled handshake");
+                }
+
+                RaiseAlertWarning(AlertDescription.close_notify, "Connection closed");
+
+                if (!m_appDataReady)
+                {
+                    CleanupHandshake();
+                }
+
+                CloseConnection();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void HandleException(short alertDescription, string message, Exception e)
+        {
+            // TODO[tls-port] Can we support interrupted IO on .NET?
+            //if ((m_appDataReady || IsResumableHandshake()) && (e is InterruptedIOException))
+            //    return;
+
+            if (!m_closed)
+            {
+                RaiseAlertFatal(alertDescription, message, e);
+
+                HandleFailure();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void HandleFailure()
+        {
+            this.m_closed = true;
+            this.m_failedWithError = true;
+
+            /*
+             * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated
+             * without proper close_notify messages with level equal to warning.
+             */
+            // TODO This isn't quite in the right place. Also, as of TLS 1.1 the above is obsolete.
+            InvalidateSession();
+
+            if (!m_appDataReady)
+            {
+                CleanupHandshake();
+            }
+
+            CloseConnection();
+        }
+
+        /// <exception cref="IOException"/>
+        protected abstract void HandleHandshakeMessage(short type, HandshakeMessageInput buf);
+
+        /// <exception cref="IOException"/>
+        protected virtual void ApplyMaxFragmentLengthExtension(short maxFragmentLength)
+        {
+            if (maxFragmentLength >= 0)
+            {
+                if (!MaxFragmentLength.IsValid(maxFragmentLength))
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                int plainTextLimit = 1 << (8 + maxFragmentLength);
+                m_recordStream.SetPlaintextLimit(plainTextLimit);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void CheckReceivedChangeCipherSpec(bool expected)
+        {
+            if (expected != m_receivedChangeCipherSpec)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void BlockForHandshake()
+        {
+            while (m_connectionState != CS_END)
+            {
+                if (IsClosed)
+                {
+                    // NOTE: Any close during the handshake should have raised an exception.
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                SafeReadRecord();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void BeginHandshake()
+        {
+            AbstractTlsContext context = ContextAdmin;
+            TlsPeer peer = Peer;
+
+            this.m_maxHandshakeMessageSize = System.Math.Max(1024, peer.GetMaxHandshakeMessageSize());
+
+            this.m_handshakeHash = new DeferredHash(context);
+            this.m_connectionState = CS_START;
+
+            context.HandshakeBeginning(peer);
+
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            securityParameters.m_extendedPadding = peer.ShouldUseExtendedPadding();
+        }
+
+        protected virtual void CleanupHandshake()
+        {
+            TlsContext context = Context;
+            if (null != context)
+            {
+                SecurityParameters securityParameters = context.SecurityParameters;
+                if (null != securityParameters)
+                {
+                    securityParameters.Clear();
+                }
+            }
+
+            this.m_tlsSession = null;
+            this.m_sessionParameters = null;
+            this.m_sessionMasterSecret = null;
+
+            this.m_retryCookie = null;
+            this.m_retryGroup = -1;
+            this.m_clientExtensions = null;
+            this.m_serverExtensions = null;
+
+            this.m_resumedSession = false;
+            this.m_receivedChangeCipherSpec = false;
+            this.m_expectSessionTicket = false;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void CompleteHandshake()
+        {
+            try
+            {
+                AbstractTlsContext context = ContextAdmin;
+                SecurityParameters securityParameters = context.SecurityParameters;
+
+                if (m_appDataReady ||
+                    null == securityParameters.LocalVerifyData ||
+                    null == securityParameters.PeerVerifyData)
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                m_recordStream.FinaliseHandshake();
+                this.m_connectionState = CS_END;
+
+                // TODO Prefer to set to null, but would need guards elsewhere
+                this.m_handshakeHash = new DeferredHash(context);
+
+                m_alertQueue.Shrink();
+                m_handshakeQueue.Shrink();
+
+                ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+                this.m_appDataSplitEnabled = !TlsUtilities.IsTlsV11(negotiatedVersion);
+                this.m_appDataReady = true;
+
+                this.m_keyUpdateEnabled = TlsUtilities.IsTlsV13(negotiatedVersion);
+
+                if (m_blocking)
+                {
+                    this.m_tlsStream = new TlsStream(this);
+                }
+
+                if (m_sessionParameters == null)
+                {
+                    this.m_sessionMasterSecret = securityParameters.MasterSecret;
+
+                    this.m_sessionParameters = new SessionParameters.Builder()
+                        .SetCipherSuite(securityParameters.CipherSuite)
+                        .SetExtendedMasterSecret(securityParameters.IsExtendedMasterSecret)
+                        .SetLocalCertificate(securityParameters.LocalCertificate)
+                        .SetMasterSecret(context.Crypto.AdoptSecret(m_sessionMasterSecret))
+                        .SetNegotiatedVersion(securityParameters.NegotiatedVersion)
+                        .SetPeerCertificate(securityParameters.PeerCertificate)
+                        .SetPskIdentity(securityParameters.PskIdentity)
+                        .SetSrpIdentity(securityParameters.SrpIdentity)
+                        // TODO Consider filtering extensions that aren't relevant to resumed sessions
+                        .SetServerExtensions(m_serverExtensions)
+                        .Build();
+
+                    this.m_tlsSession = TlsUtilities.ImportSession(m_tlsSession.SessionID, m_sessionParameters);
+                }
+                else
+                {
+                    securityParameters.m_localCertificate = m_sessionParameters.LocalCertificate;
+                    securityParameters.m_peerCertificate = m_sessionParameters.PeerCertificate;
+                    securityParameters.m_pskIdentity = m_sessionParameters.PskIdentity;
+                    securityParameters.m_srpIdentity = m_sessionParameters.SrpIdentity;
+                }
+
+                context.HandshakeComplete(Peer, m_tlsSession);
+            }
+            finally
+            {
+                CleanupHandshake();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        internal void ProcessRecord(short protocol, byte[] buf, int off, int len)
+        {
+            /*
+             * Have a look at the protocol type, and add it to the correct queue.
+             */
+            switch (protocol)
+            {
+            case ContentType.alert:
+            {
+                m_alertQueue.AddData(buf, off, len);
+                ProcessAlertQueue();
+                break;
+            }
+            case ContentType.application_data:
+            {
+                if (!m_appDataReady)
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                m_applicationDataQueue.AddData(buf, off, len);
+                ProcessApplicationDataQueue();
+                break;
+            }
+            case ContentType.change_cipher_spec:
+            {
+                ProcessChangeCipherSpec(buf, off, len);
+                break;
+            }
+            case ContentType.handshake:
+            {
+                if (m_handshakeQueue.Available > 0)
+                {
+                    m_handshakeQueue.AddData(buf, off, len);
+                    ProcessHandshakeQueue(m_handshakeQueue);
+                }
+                else
+                {
+                    ByteQueue tmpQueue = new ByteQueue(buf, off, len);
+                    ProcessHandshakeQueue(tmpQueue);
+                    int remaining = tmpQueue.Available;
+                    if (remaining > 0)
+                    {
+                        m_handshakeQueue.AddData(buf, off + len - remaining, remaining);
+                    }
+                }
+                break;
+            }
+            //case ContentType.heartbeat:
+            //{
+            //    if (!m_appDataReady)
+            //        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            //    // TODO[RFC 6520]
+            //    m_heartbeatQueue.addData(buf, off, len);
+            //    ProcessHeartbeatQueue();
+            //    break;
+            //}
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        private void ProcessHandshakeQueue(ByteQueue queue)
+        {
+            /*
+             * We need the first 4 bytes, they contain type and length of the message.
+             */
+            while (queue.Available >= 4)
+            {
+                int header = queue.ReadInt32();
+
+                short type = (short)((uint)header >> 24);
+                if (!HandshakeType.IsRecognized(type))
+                {
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message,
+                        "Handshake message of unrecognized type: " + type);
+                }
+
+                int length = header & 0x00FFFFFF;
+                if (length > m_maxHandshakeMessageSize)
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error,
+                        "Handshake message length exceeds the maximum: " + HandshakeType.GetText(type) + ", " + length
+                            + " > " + m_maxHandshakeMessageSize);
+                }
+
+                int totalLength = 4 + length;
+                if (queue.Available < totalLength)
+                {
+                    // Not enough bytes in the buffer to read the full message.
+                    break;
+                }
+
+                /*
+                 * Check ChangeCipherSpec status
+                 */
+                switch (type)
+                {
+                case HandshakeType.hello_request:
+                    break;
+
+                default:
+                {
+                    ProtocolVersion negotiatedVersion = Context.ServerVersion;
+                    if (null != negotiatedVersion && TlsUtilities.IsTlsV13(negotiatedVersion))
+                        break;
+
+                    CheckReceivedChangeCipherSpec(HandshakeType.finished == type);
+                    break;
+                }
+                }
+
+                HandshakeMessageInput buf = queue.ReadHandshakeMessage(totalLength);
+
+                switch (type)
+                {
+                /*
+                 * These message types aren't included in the transcript.
+                 */
+                case HandshakeType.hello_request:
+                case HandshakeType.key_update:
+                case HandshakeType.new_session_ticket:
+                    break;
+
+                /*
+                 * These message types are deferred to the handler to explicitly update the transcript.
+                 */
+                case HandshakeType.certificate_verify:
+                case HandshakeType.client_hello:
+                case HandshakeType.finished:
+                case HandshakeType.server_hello:
+                    break;
+
+                /*
+                 * For all others we automatically update the transcript immediately. 
+                 */
+                default:
+                {
+                    buf.UpdateHash(m_handshakeHash);
+                    break;
+                }
+                }
+
+                buf.Seek(4L, SeekOrigin.Current);
+
+                HandleHandshakeMessage(type, buf);
+            }
+        }
+
+        private void ProcessApplicationDataQueue()
+        {
+            /*
+             * There is nothing we need to do here.
+             * 
+             * This function could be used for callbacks when application data arrives in the future.
+             */
+        }
+
+        /// <exception cref="IOException"/>
+        private void ProcessAlertQueue()
+        {
+            while (m_alertQueue.Available >= 2)
+            {
+                /*
+                 * An alert is always 2 bytes. Read the alert.
+                 */
+                byte[] alert = m_alertQueue.RemoveData(2, 0);
+                short alertLevel = alert[0];
+                short alertDescription = alert[1];
+
+                HandleAlertMessage(alertLevel, alertDescription);
+            }
+        }
+
+        /// <summary>This method is called, when a change cipher spec message is received.</summary>
+        /// <exception cref="IOException">If the message has an invalid content or the handshake is not in the correct
+        /// state.</exception>
+        private void ProcessChangeCipherSpec(byte[] buf, int off, int len)
+        {
+            ProtocolVersion negotiatedVersion = Context.ServerVersion;
+            if (null == negotiatedVersion || TlsUtilities.IsTlsV13(negotiatedVersion))
+            {
+                // See RFC 8446 D.4.
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+
+            for (int i = 0; i < len; ++i)
+            {
+                short message = TlsUtilities.ReadUint8(buf, off + i);
+
+                if (message != ChangeCipherSpec.change_cipher_spec)
+                    throw new TlsFatalAlert(AlertDescription.decode_error);
+
+                if (this.m_receivedChangeCipherSpec
+                    || m_alertQueue.Available > 0
+                    || m_handshakeQueue.Available > 0)
+                {
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+
+                m_recordStream.NotifyChangeCipherSpecReceived();
+
+                this.m_receivedChangeCipherSpec = true;
+
+                HandleChangeCipherSpecMessage();
+            }
+        }
+
+        public virtual int ApplicationDataAvailable
+        {
+            get { return m_applicationDataQueue.Available; }
+        }
+
+        /// <summary>Read data from the network.</summary>
+        /// <remarks>
+        /// 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.
+        /// </remarks>
+        /// <param name="buf">The buffer where the data will be copied to.</param>
+        /// <param name="off">The position where the data will be placed in the buffer.</param>
+        /// <param name="len">The maximum number of bytes to read.</param>
+        /// <returns>The number of bytes read.</returns>
+        /// <exception cref="IOException">If something goes wrong during reading data.</exception>
+        public virtual int ReadApplicationData(byte[] buf, int off, int len)
+        {
+            if (len < 1)
+                return 0;
+
+            while (m_applicationDataQueue.Available == 0)
+            {
+                if (this.m_closed)
+                {
+                    if (this.m_failedWithError)
+                        throw new IOException("Cannot read application data on failed TLS connection");
+
+                    return -1;
+                }
+                if (!m_appDataReady)
+                    throw new InvalidOperationException("Cannot read application data until initial handshake completed.");
+
+                /*
+                 * NOTE: Only called more than once when empty records are received, so no special
+                 * InterruptedIOException handling is necessary.
+                 */
+                SafeReadRecord();
+            }
+
+            len = System.Math.Min(len, m_applicationDataQueue.Available);
+            m_applicationDataQueue.RemoveData(buf, off, len, 0);
+            return len;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual RecordPreview SafePreviewRecordHeader(byte[] recordHeader)
+        {
+            try
+            {
+                return m_recordStream.PreviewRecordHeader(recordHeader);
+            }
+            catch (TlsFatalAlert e)
+            {
+                HandleException(e.AlertDescription, "Failed to read record", e);
+                throw e;
+            }
+            catch (IOException e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to read record", e);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to read record", e);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SafeReadRecord()
+        {
+            try
+            {
+                if (m_recordStream.ReadRecord())
+                    return;
+
+                if (!m_appDataReady)
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                if (!Peer.RequiresCloseNotify())
+                {
+                    HandleClose(false);
+                    return;
+                }
+            }
+            catch (TlsFatalAlertReceived e)
+            {
+                // Connection failure already handled at source
+                throw e;
+            }
+            catch (TlsFatalAlert e)
+            {
+                HandleException(e.AlertDescription, "Failed to read record", e);
+                throw e;
+            }
+            catch (IOException e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to read record", e);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to read record", e);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+
+            HandleFailure();
+
+            throw new TlsNoCloseNotifyException();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual bool SafeReadFullRecord(byte[] input, int inputOff, int inputLen)
+        {
+            try
+            {
+                return m_recordStream.ReadFullRecord(input, inputOff, inputLen);
+            }
+            catch (TlsFatalAlert e)
+            {
+                HandleException(e.AlertDescription, "Failed to process record", e);
+                throw e;
+            }
+            catch (IOException e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to process record", e);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to process record", e);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SafeWriteRecord(short type, byte[] buf, int offset, int len)
+        {
+            try
+            {
+                m_recordStream.WriteRecord(type, buf, offset, len);
+            }
+            catch (TlsFatalAlert e)
+            {
+                HandleException(e.AlertDescription, "Failed to write record", e);
+                throw e;
+            }
+            catch (IOException e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to write record", e);
+                throw e;
+            }
+            catch (Exception e)
+            {
+                HandleException(AlertDescription.internal_error, "Failed to write record", e);
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+
+        /// <summary>Write some application data.</summary>
+        /// <remarks>
+        /// Fragmentation is handled internally. Usable in both blocking/non-blocking modes.<br/><br/>
+        /// In blocking mode, the output will be automatically sent via the underlying transport. In non-blocking mode,
+        /// call <see cref="ReadOutput(byte[], int, int)"/> to get the output bytes to send to the peer.<br/><br/>
+        /// This method must not be called until after the initial handshake is complete. Attempting to call it earlier
+        /// will result in an <see cref="InvalidOperationException"/>.
+        /// </remarks>
+        /// <param name="buf">The buffer containing application data to send.</param>
+        /// <param name="off">The offset at which the application data begins</param>
+        /// <param name="len">The number of bytes of application data.</param>
+        /// <exception cref="InvalidOperationException">If called before the initial handshake has completed.
+        /// </exception>
+        /// <exception cref="IOException">If connection is already closed, or for encryption or transport errors.
+        /// </exception>
+        public virtual void WriteApplicationData(byte[] buf, int off, int len)
+        {
+            if (!m_appDataReady)
+                throw new InvalidOperationException(
+                    "Cannot write application data until initial handshake completed.");
+
+            lock (m_recordWriteLock)
+            {
+                while (len > 0)
+                {
+                    if (m_closed)
+                        throw new IOException("Cannot write application data on closed/failed TLS connection");
+
+                    /*
+                     * RFC 5246 6.2.1. Zero-length fragments of Application data MAY be sent as they are
+                     * potentially useful as a traffic analysis countermeasure.
+                     * 
+                     * NOTE: Actually, implementations appear to have settled on 1/n-1 record splitting.
+                     */
+                    if (m_appDataSplitEnabled)
+                    {
+                        /*
+                         * Protect against known IV attack!
+                         * 
+                         * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
+                         */
+                        switch (m_appDataSplitMode)
+                        {
+                        case ADS_MODE_0_N_FIRSTONLY:
+                        {
+                            this.m_appDataSplitEnabled = false;
+                            SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
+                            break;
+                        }
+                        case ADS_MODE_0_N:
+                        {
+                            SafeWriteRecord(ContentType.application_data, TlsUtilities.EmptyBytes, 0, 0);
+                            break;
+                        }
+                        case ADS_MODE_1_Nsub1:
+                        default:
+                        {
+                            if (len > 1)
+                            {
+                                SafeWriteRecord(ContentType.application_data, buf, off, 1);
+                                ++off;
+                                --len;
+                            }
+                            break;
+                        }
+                        }
+                    }
+                    else if (m_keyUpdateEnabled)
+                    {
+                        if (m_keyUpdatePendingSend)
+                        {
+                            Send13KeyUpdate(false);
+                        }
+                        else if (m_recordStream.NeedsKeyUpdate())
+                        {
+                            Send13KeyUpdate(true);
+                        }
+                    }
+
+                    // Fragment data according to the current fragment limit.
+                    int toWrite = System.Math.Min(len, m_recordStream.PlaintextLimit);
+                    SafeWriteRecord(ContentType.application_data, buf, off, toWrite);
+                    off += toWrite;
+                    len -= toWrite;
+                }
+            }
+        }
+
+        public virtual int AppDataSplitMode
+        {
+            get { return m_appDataSplitMode; }
+            set
+            {
+                if (value < ADS_MODE_1_Nsub1 || value > ADS_MODE_0_N_FIRSTONLY)
+                    throw new InvalidOperationException("Illegal appDataSplitMode mode: " + value);
+
+                this.m_appDataSplitMode = value;
+            }
+        }
+
+        public virtual bool IsResumableHandshake
+        {
+            get { return m_resumableHandshake; }
+            set { this.m_resumableHandshake = value; }
+        }
+
+        /// <exception cref="IOException"/>
+        internal void WriteHandshakeMessage(byte[] buf, int off, int len)
+        {
+            if (len < 4)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            short type = TlsUtilities.ReadUint8(buf, off);
+            switch (type)
+            {
+            case HandshakeType.hello_request:
+            case HandshakeType.key_update:
+            case HandshakeType.new_session_ticket:
+                break;
+
+            default:
+            {
+                m_handshakeHash.Update(buf, off, len);
+                break;
+            }
+            }
+
+            int total = 0;
+            do
+            {
+                // Fragment data according to the current fragment limit.
+                int toWrite = System.Math.Min(len - total, m_recordStream.PlaintextLimit);
+                SafeWriteRecord(ContentType.handshake, buf, off + total, toWrite);
+                total += toWrite;
+            }
+            while (total < len);
+        }
+
+        /// <summary>The secure bidirectional stream for this connection</summary>
+        /// <remarks>Only allowed in blocking mode.</remarks>
+        public virtual Stream Stream
+        {
+            get
+            {
+                if (!m_blocking)
+                    throw new InvalidOperationException(
+                        "Cannot use Stream in non-blocking mode! Use OfferInput()/OfferOutput() instead.");
+
+                return this.m_tlsStream;
+            }
+        }
+
+        /// <summary>Should be called in non-blocking mode when the input data reaches EOF.</summary>
+        /// <exception cref="IOException"/>
+        public virtual void CloseInput()
+        {
+            if (m_blocking)
+                throw new InvalidOperationException("Cannot use CloseInput() in blocking mode!");
+
+            if (m_closed)
+                return;
+
+            if (m_inputBuffers.Available > 0)
+                throw new EndOfStreamException();
+
+            if (!m_appDataReady)
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+            if (!Peer.RequiresCloseNotify())
+            {
+                HandleClose(false);
+                return;
+            }
+
+            HandleFailure();
+
+            throw new TlsNoCloseNotifyException();
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual RecordPreview PreviewInputRecord(byte[] recordHeader)
+        {
+            if (m_blocking)
+                throw new InvalidOperationException("Cannot use PreviewInputRecord() in blocking mode!");
+            if (m_inputBuffers.Available != 0)
+                throw new InvalidOperationException("Can only use PreviewInputRecord() for record-aligned input.");
+            if (m_closed)
+                throw new IOException("Connection is closed, cannot accept any more input");
+
+            return SafePreviewRecordHeader(recordHeader);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual RecordPreview PreviewOutputRecord(int applicationDataSize)
+        {
+            if (!m_appDataReady)
+                throw new InvalidOperationException(
+                    "Cannot use PreviewOutputRecord() until initial handshake completed.");
+            if (m_blocking)
+                throw new InvalidOperationException("Cannot use PreviewOutputRecord() in blocking mode!");
+            if (m_outputBuffer.Buffer.Available != 0)
+                throw new InvalidOperationException("Can only use PreviewOutputRecord() for record-aligned output.");
+            if (m_closed)
+                throw new IOException("Connection is closed, cannot produce any more output");
+
+            if (applicationDataSize < 1)
+                return new RecordPreview(0, 0);
+
+            if (m_appDataSplitEnabled)
+            {
+                switch (m_appDataSplitMode)
+                {
+                case ADS_MODE_0_N_FIRSTONLY:
+                case ADS_MODE_0_N:
+                {
+                    RecordPreview a = m_recordStream.PreviewOutputRecord(0);
+                    RecordPreview b = m_recordStream.PreviewOutputRecord(applicationDataSize);
+                    return RecordPreview.CombineAppData(a, b);
+                }
+                case ADS_MODE_1_Nsub1:
+                default:
+                {
+                    RecordPreview a = m_recordStream.PreviewOutputRecord(1);
+                    if (applicationDataSize > 1)
+                    {
+                        RecordPreview b = m_recordStream.PreviewOutputRecord(applicationDataSize - 1);
+                        a = RecordPreview.CombineAppData(a, b);
+                    }
+                    return a;
+                }
+                }
+            }
+            else
+            {
+                RecordPreview a = m_recordStream.PreviewOutputRecord(applicationDataSize);
+                if (m_keyUpdateEnabled && (m_keyUpdatePendingSend || m_recordStream.NeedsKeyUpdate()))
+                {
+                    int keyUpdateLength = HandshakeMessageOutput.GetLength(1);
+                    int recordSize = m_recordStream.PreviewOutputRecordSize(keyUpdateLength);
+                    a = RecordPreview.ExtendRecordSize(a, recordSize);
+                }
+                return a;
+            }
+        }
+
+        /// <summary>Equivalent to <code>OfferInput(input, 0, input.Length)</code>.</summary>
+        /// <param name="input">The input buffer to offer.</param>
+        /// <exception cref="IOException"/>
+        /// <seealso cref="OfferInput(byte[], int, int)"/>
+        public virtual void OfferInput(byte[] input)
+        {
+            OfferInput(input, 0, input.Length);
+        }
+
+        /// <summary>Offer input from an arbitrary source.</summary>
+        /// <remarks>Only allowed in non-blocking mode.<br/><br/>
+        /// This method will decrypt and process all records that are fully available. If only part of a record is
+        /// available, the buffer will be retained until the remainder of the record is offered.<br/><br/>
+        /// If any records containing application data were processed, the decrypted data can be obtained using
+        /// <see cref="ReadInput(byte[], int, int)"/>. If any records containing protocol data were processed, a
+        /// response may have been generated. You should always check to see if there is any available output after
+        /// calling this method by calling <see cref="GetAvailableOutputBytes"/>.
+        /// </remarks>
+        /// <param name="input">The input buffer to offer.</param>
+        /// <param name="inputOff">The offset within the input buffer that input begins.</param>
+        /// <param name="inputLen">The number of bytes of input being offered.</param>
+        /// <exception cref="IOException">If an error occurs while decrypting or processing a record.</exception>
+        public virtual void OfferInput(byte[] input, int inputOff, int inputLen)
+        {
+            if (m_blocking)
+                throw new InvalidOperationException("Cannot use OfferInput() in blocking mode! Use Stream instead.");
+            if (m_closed)
+                throw new IOException("Connection is closed, cannot accept any more input");
+
+            // Fast path if the input is arriving one record at a time
+            if (m_inputBuffers.Available == 0 && SafeReadFullRecord(input, inputOff, inputLen))
+            {
+                if (m_closed)
+                {
+                    if (!m_appDataReady)
+                    {
+                        // NOTE: Any close during the handshake should have raised an exception.
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+                    }
+                }
+                return;
+            }
+
+            m_inputBuffers.AddBytes(input, inputOff, inputLen);
+
+            // loop while there are enough bytes to read the length of the next record
+            while (m_inputBuffers.Available >= RecordFormat.FragmentOffset)
+            {
+                byte[] recordHeader = new byte[RecordFormat.FragmentOffset];
+                if (RecordFormat.FragmentOffset != m_inputBuffers.Peek(recordHeader))
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                RecordPreview preview = SafePreviewRecordHeader(recordHeader);
+                if (m_inputBuffers.Available < preview.RecordSize)
+                {
+                    // not enough bytes to read a whole record
+                    break;
+                }
+
+                // NOTE: This is actually reading from inputBuffers, so InterruptedIOException shouldn't be possible
+                SafeReadRecord();
+
+                if (m_closed)
+                {
+                    if (!m_appDataReady)
+                    {
+                        // NOTE: Any close during the handshake should have raised an exception.
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+                    }
+                    break;
+                }
+            }
+        }
+
+        public virtual int ApplicationDataLimit
+        {
+            get { return m_recordStream.PlaintextLimit; }
+        }
+
+        /// <summary>Gets the amount of received application data.</summary>
+        /// <remarks>A call to <see cref="readInput(byte[], int, int)"/> is guaranteed to be able to return at least
+        /// this much data.<br/><br/>
+        /// Only allowed in non-blocking mode.
+        /// </remarks>
+        /// <returns>The number of bytes of available application data.</returns>
+        public virtual int GetAvailableInputBytes()
+        {
+            if (m_blocking)
+                throw new InvalidOperationException("Cannot use GetAvailableInputBytes() in blocking mode!");
+
+            return ApplicationDataAvailable;
+        }
+
+        /// <summary>Retrieves received application data.</summary>
+        /// <remarks>
+        /// Use <see cref="GetAvailableInputBytes"/> to check how much application data is currently available. This
+        /// method functions similarly to <see cref="Stream.Read(byte[], int, int)"/>, except that it never blocks. If
+        /// no data is available, nothing will be copied and zero will be returned.<br/><br/>
+        /// Only allowed in non-blocking mode.
+        /// </remarks>
+        /// <param name="buf">The buffer to hold the application data.</param>
+        /// <param name="off">The start offset in the buffer at which the data is written.</param>
+        /// <param name="len">The maximum number of bytes to read.</param>
+        /// <returns>The total number of bytes copied to the buffer. May be less than the length specified if the
+        /// length was greater than the amount of available data.</returns>
+        public virtual int ReadInput(byte[] buf, int off, int len)
+        {
+            if (m_blocking)
+                throw new InvalidOperationException("Cannot use ReadInput() in blocking mode! Use Stream instead.");
+
+            len = System.Math.Min(len, ApplicationDataAvailable);
+            if (len < 1)
+                return 0;
+
+            m_applicationDataQueue.RemoveData(buf, off, len, 0);
+            return len;
+        }
+
+        /// <summary>Gets the amount of encrypted data available to be sent.</summary>
+        /// <remarks>
+        /// A call to <see cref="ReadOutput(byte[], int, int)"/> is guaranteed to be able to return at least this much
+        /// data. Only allowed in non-blocking mode.
+        /// </remarks>
+        /// <returns>The number of bytes of available encrypted data.</returns>
+        public virtual int GetAvailableOutputBytes()
+        {
+            if (m_blocking)
+                throw new InvalidOperationException("Cannot use GetAvailableOutputBytes() in blocking mode! Use Stream instead.");
+
+            return m_outputBuffer.Buffer.Available;
+        }
+
+        /// <summary>Retrieves encrypted data to be sent.</summary>
+        /// <remarks>
+        /// Use <see cref="GetAvailableOutputBytes"/> to check how much encrypted data is currently available. This
+        /// method functions similarly to <see cref="Stream.Read(byte[], int, int)"/>, except that it never blocks. If
+        /// no data is available, nothing will be copied and zero will be returned. Only allowed in non-blocking mode.
+        /// </remarks>
+        /// <param name="buffer">The buffer to hold the encrypted data.</param>
+        /// <param name="offset">The start offset in the buffer at which the data is written.</param>
+        /// <param name="length">The maximum number of bytes to read.</param>
+        /// <returns>The total number of bytes copied to the buffer. May be less than the length specified if the
+        /// length was greater than the amount of available data.</returns>
+        public virtual int ReadOutput(byte[] buffer, int offset, int length)
+        {
+            if (m_blocking)
+                throw new InvalidOperationException("Cannot use ReadOutput() in blocking mode! Use 'Stream() instead.");
+
+            int bytesToRead = System.Math.Min(GetAvailableOutputBytes(), length);
+            m_outputBuffer.Buffer.RemoveData(buffer, offset, bytesToRead, 0);
+            return bytesToRead;
+        }
+
+        protected virtual bool EstablishSession(TlsSession sessionToResume)
+        {
+            this.m_tlsSession = null;
+            this.m_sessionParameters = null;
+            this.m_sessionMasterSecret = null;
+
+            if (null == sessionToResume || !sessionToResume.IsResumable)
+                return false;
+
+            SessionParameters sessionParameters = sessionToResume.ExportSessionParameters();
+            if (null == sessionParameters)
+                return false;
+
+            if (!sessionParameters.IsExtendedMasterSecret)
+            {
+                TlsPeer peer = Peer;
+                if (!peer.AllowLegacyResumption() || peer.RequiresExtendedMasterSecret())
+                    return false;
+
+                /*
+                 * NOTE: For session resumption without extended_master_secret, renegotiation MUST be
+                 * disabled (see RFC 7627 5.4). We currently do not implement renegotiation and it is
+                 * unlikely we ever would since it was removed in TLS 1.3.
+                 */
+            }
+
+            TlsSecret sessionMasterSecret = TlsUtilities.GetSessionMasterSecret(Context.Crypto,
+                sessionParameters.MasterSecret);
+            if (null == sessionMasterSecret)
+                return false;
+
+            this.m_tlsSession = sessionToResume;
+            this.m_sessionParameters = sessionParameters;
+            this.m_sessionMasterSecret = sessionMasterSecret;
+
+            return true;
+        }
+
+        protected virtual void InvalidateSession()
+        {
+            if (m_sessionMasterSecret != null)
+            {
+                m_sessionMasterSecret.Destroy();
+                this.m_sessionMasterSecret = null;
+            }
+
+            if (m_sessionParameters != null)
+            {
+                m_sessionParameters.Clear();
+                this.m_sessionParameters = null;
+            }
+
+            if (m_tlsSession != null)
+            {
+                m_tlsSession.Invalidate();
+                this.m_tlsSession = null;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ProcessFinishedMessage(MemoryStream buf)
+        {
+            TlsContext context = Context;
+            SecurityParameters securityParameters = context.SecurityParameters;
+            bool isServerContext = context.IsServer;
+
+            byte[] verify_data = TlsUtilities.ReadFully(securityParameters.VerifyDataLength, buf);
+
+            AssertEmpty(buf);
+
+            byte[] expected_verify_data = TlsUtilities.CalculateVerifyData(context, m_handshakeHash, !isServerContext);
+
+            /*
+             * Compare both checksums.
+             */
+            if (!Arrays.ConstantTimeAreEqual(expected_verify_data, verify_data))
+            {
+                /*
+                 * Wrong checksum in the finished message.
+                 */
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+            }
+
+            securityParameters.m_peerVerifyData = expected_verify_data;
+
+            if (!m_resumedSession || securityParameters.IsExtendedMasterSecret)
+            {
+                if (null == securityParameters.LocalVerifyData)
+                {
+                    securityParameters.m_tlsUnique = expected_verify_data;
+                }
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Process13FinishedMessage(MemoryStream buf)
+        {
+            TlsContext context = Context;
+            SecurityParameters securityParameters = context.SecurityParameters;
+            bool isServerContext = context.IsServer;
+
+            byte[] verify_data = TlsUtilities.ReadFully(securityParameters.VerifyDataLength, buf);
+
+            AssertEmpty(buf);
+
+            byte[] expected_verify_data = TlsUtilities.CalculateVerifyData(context, m_handshakeHash, !isServerContext);
+
+            /*
+             * Compare both checksums.
+             */
+            if (!Arrays.ConstantTimeAreEqual(expected_verify_data, verify_data))
+            {
+                /*
+                 * Wrong checksum in the finished message.
+                 */
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+            }
+
+            securityParameters.m_peerVerifyData = expected_verify_data;
+            securityParameters.m_tlsUnique = null;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void RaiseAlertFatal(short alertDescription, string message, Exception cause)
+        {
+            Peer.NotifyAlertRaised(AlertLevel.fatal, alertDescription, message, cause);
+
+            byte[] alert = new byte[]{ (byte)AlertLevel.fatal, (byte)alertDescription };
+
+            try
+            {
+                m_recordStream.WriteRecord(ContentType.alert, alert, 0, 2);
+            }
+            catch (Exception)
+            {
+                // We are already processing an exception, so just ignore this
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void RaiseAlertWarning(short alertDescription, string message)
+        {
+            Peer.NotifyAlertRaised(AlertLevel.warning, alertDescription, message, null);
+
+            byte[] alert = new byte[]{ (byte)AlertLevel.warning, (byte)alertDescription };
+
+            SafeWriteRecord(ContentType.alert, alert, 0, 2);
+        }
+
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13KeyUpdate(MemoryStream buf)
+        {
+            // TODO[tls13] This is interesting enough to notify the TlsPeer for possible logging/vetting
+
+            if (!(m_appDataReady && m_keyUpdateEnabled))
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            short requestUpdate = TlsUtilities.ReadUint8(buf);
+
+            AssertEmpty(buf);
+
+            if (!KeyUpdateRequest.IsValid(requestUpdate))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            bool updateRequested = (KeyUpdateRequest.update_requested == requestUpdate);
+
+            TlsUtilities.Update13TrafficSecretPeer(Context);
+            m_recordStream.NotifyKeyUpdateReceived();
+
+            //this.m_keyUpdatePendingReceive &= updateRequested;
+            this.m_keyUpdatePendingSend |= updateRequested;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendCertificateMessage(Certificate certificate, Stream endPointHash)
+        {
+            TlsContext context = Context;
+            SecurityParameters securityParameters = context.SecurityParameters;
+            if (null != securityParameters.LocalCertificate)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (null == certificate)
+            {
+                certificate = Certificate.EmptyChain;
+            }
+
+            if (certificate.IsEmpty && !context.IsServer && securityParameters.NegotiatedVersion.IsSsl)
+            {
+                string message = "SSLv3 client didn't provide credentials";
+                RaiseAlertWarning(AlertDescription.no_certificate, message);
+            }
+            else
+            {
+                HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate);
+                certificate.Encode(context, message, endPointHash);
+                message.Send(this);
+            }
+
+            securityParameters.m_localCertificate = certificate;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13CertificateMessage(Certificate certificate)
+        {
+            if (null == certificate)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            TlsContext context = Context;
+            SecurityParameters securityParameters = context.SecurityParameters;
+            if (null != securityParameters.LocalCertificate)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate);
+            certificate.Encode(context, message, null);
+            message.Send(this);
+
+            securityParameters.m_localCertificate = certificate;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13CertificateVerifyMessage(DigitallySigned certificateVerify)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_verify);
+            certificateVerify.Encode(message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendChangeCipherSpec()
+        {
+            SendChangeCipherSpecMessage();
+            m_recordStream.EnablePendingCipherWrite();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendChangeCipherSpecMessage()
+        {
+            byte[] message = new byte[]{ 1 };
+            SafeWriteRecord(ContentType.change_cipher_spec, message, 0, message.Length);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendFinishedMessage()
+        {
+            TlsContext context = Context;
+            SecurityParameters securityParameters = context.SecurityParameters;
+            bool isServerContext = context.IsServer;
+
+            byte[] verify_data = TlsUtilities.CalculateVerifyData(context, m_handshakeHash, isServerContext);
+
+            securityParameters.m_localVerifyData = verify_data;
+
+            if (!m_resumedSession || securityParameters.IsExtendedMasterSecret)
+            {
+                if (null == securityParameters.PeerVerifyData)
+                {
+                    securityParameters.m_tlsUnique = verify_data;
+                }
+            }
+
+            HandshakeMessageOutput.Send(this, HandshakeType.finished, verify_data);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13FinishedMessage()
+        {
+            TlsContext context = Context;
+            SecurityParameters securityParameters = context.SecurityParameters;
+            bool isServerContext = context.IsServer;
+
+            byte[] verify_data = TlsUtilities.CalculateVerifyData(context, m_handshakeHash, isServerContext);
+
+            securityParameters.m_localVerifyData = verify_data;
+            securityParameters.m_tlsUnique = null;
+
+            HandshakeMessageOutput.Send(this, HandshakeType.finished, verify_data);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13KeyUpdate(bool updateRequested)
+        {
+            // TODO[tls13] This is interesting enough to notify the TlsPeer for possible logging/vetting
+
+            if (!(m_appDataReady && m_keyUpdateEnabled))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            short requestUpdate = updateRequested
+                ? KeyUpdateRequest.update_requested
+                : KeyUpdateRequest.update_not_requested;
+
+            HandshakeMessageOutput.Send(this, HandshakeType.key_update, TlsUtilities.EncodeUint8(requestUpdate));
+
+            TlsUtilities.Update13TrafficSecretLocal(Context);
+            m_recordStream.NotifyKeyUpdateSent();
+
+            //this.m_keyUpdatePendingReceive |= updateRequested;
+            this.m_keyUpdatePendingSend &= updateRequested;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendSupplementalDataMessage(IList supplementalData)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.supplemental_data);
+            WriteSupplementalData(message, supplementalData);
+            message.Send(this);
+        }
+
+        public virtual void Close()
+        {
+            HandleClose(true);
+        }
+
+        public virtual void Flush()
+        {
+        }
+
+        internal bool IsApplicationDataReady
+        {
+            get { return m_appDataReady; }
+        }
+
+        public virtual bool IsClosed
+        {
+            get { return m_closed; }
+        }
+
+        public virtual bool IsHandshaking
+        {
+            get
+            {
+                if (m_closed)
+                    return false;
+
+                AbstractTlsContext context = ContextAdmin;
+
+                return null != context && !context.IsConnected;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual short ProcessMaxFragmentLengthExtension(IDictionary clientExtensions,
+            IDictionary serverExtensions, short alertDescription)
+        {
+            short maxFragmentLength = TlsExtensionsUtilities.GetMaxFragmentLengthExtension(serverExtensions);
+            if (maxFragmentLength >= 0)
+            {
+                if (!MaxFragmentLength.IsValid(maxFragmentLength)
+                    || (!m_resumedSession &&
+                        maxFragmentLength != TlsExtensionsUtilities.GetMaxFragmentLengthExtension(clientExtensions)))
+                {
+                    throw new TlsFatalAlert(alertDescription);
+                }
+            }
+            return maxFragmentLength;
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void RefuseRenegotiation()
+        {
+            /*
+             * RFC 5746 4.5 SSLv3 clients [..] SHOULD use a fatal handshake_failure alert.
+             */
+            if (TlsUtilities.IsSsl(Context))
+                throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+            RaiseAlertWarning(AlertDescription.no_renegotiation, "Renegotiation not supported");
+        }
+
+        /// <summary>Make sure the <see cref="Stream"/> 'buf' is now empty. Fail otherwise.</summary>
+        /// <param name="buf">The <see cref="Stream"/> to check.</param>
+        /// <exception cref="IOException"/>
+        internal static void AssertEmpty(MemoryStream buf)
+        {
+            if (buf.Position < buf.Length)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+        }
+
+        internal static byte[] CreateRandomBlock(bool useGmtUnixTime, TlsContext context)
+        {
+            byte[] result = context.NonceGenerator.GenerateNonce(32);
+
+            if (useGmtUnixTime)
+            {
+                TlsUtilities.WriteGmtUnixTime(result, 0);
+            }
+
+            return result;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection)
+        {
+            return TlsUtilities.EncodeOpaque8(renegotiated_connection);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void EstablishMasterSecret(TlsContext context, TlsKeyExchange keyExchange)
+        {
+            TlsSecret preMasterSecret = keyExchange.GeneratePreMasterSecret();
+            if (preMasterSecret == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            try
+            {
+                context.SecurityParameters.m_masterSecret = TlsUtilities.CalculateMasterSecret(context,
+                    preMasterSecret);
+            }
+            finally
+            {
+                /*
+                 * RFC 2246 8.1. The pre_master_secret should be deleted from memory once the
+                 * master_secret has been computed.
+                 */
+                preMasterSecret.Destroy();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        internal static IDictionary ReadExtensions(MemoryStream input)
+        {
+            if (input.Position >= input.Length)
+                return null;
+
+            byte[] extBytes = TlsUtilities.ReadOpaque16(input);
+
+            AssertEmpty(input);
+
+            return ReadExtensionsData(extBytes);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static IDictionary ReadExtensionsData(byte[] extBytes)
+        {
+            // Int32 -> byte[]
+            IDictionary extensions = Platform.CreateHashtable();
+
+            if (extBytes.Length > 0)
+            {
+                MemoryStream buf = new MemoryStream(extBytes, false);
+
+                do
+                {
+                    int extension_type = TlsUtilities.ReadUint16(buf);
+                    byte[] extension_data = TlsUtilities.ReadOpaque16(buf);
+
+                    /*
+                     * RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
+                     */
+                    Int32 key = extension_type;
+                    if (extensions.Contains(key))
+                        throw new TlsFatalAlert(AlertDescription.illegal_parameter,
+                            "Repeated extension: " + ExtensionType.GetText(extension_type));
+
+                    extensions.Add(key, extension_data);
+                }
+                while (buf.Position < buf.Length);
+            }
+
+            return extensions;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static IDictionary ReadExtensionsData13(int handshakeType, byte[] extBytes)
+        {
+            // Int32 -> byte[]
+            IDictionary extensions = Platform.CreateHashtable();
+
+            if (extBytes.Length > 0)
+            {
+                MemoryStream buf = new MemoryStream(extBytes, false);
+
+                do
+                {
+                    int extension_type = TlsUtilities.ReadUint16(buf);
+
+                    if (!TlsUtilities.IsPermittedExtensionType13(handshakeType, extension_type))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.illegal_parameter,
+                            "Invalid extension: " + ExtensionType.GetText(extension_type));
+                    }
+
+                    byte[] extension_data = TlsUtilities.ReadOpaque16(buf);
+
+                    /*
+                     * RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
+                     */
+                    Int32 key = extension_type;
+                    if (extensions.Contains(key))
+                        throw new TlsFatalAlert(AlertDescription.illegal_parameter,
+                            "Repeated extension: " + ExtensionType.GetText(extension_type));
+
+                    extensions.Add(key, extension_data);
+                }
+                while (buf.Position < buf.Length);
+            }
+
+            return extensions;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static IDictionary ReadExtensionsDataClientHello(byte[] extBytes)
+        {
+            /*
+             * TODO[tls13] We are currently allowing any extensions to appear in ClientHello. It is
+             * somewhat complicated to restrict what can appear based on the specific set of versions
+             * the client is offering, and anyway could be fragile since clients may take a
+             * "kitchen sink" approach to adding extensions independently of the offered versions.
+             */
+
+            // Int32 -> byte[]
+            IDictionary extensions = Platform.CreateHashtable();
+
+            if (extBytes.Length > 0)
+            {
+                MemoryStream buf = new MemoryStream(extBytes, false);
+
+                int extension_type;
+                bool pre_shared_key_found = false;
+
+                do
+                {
+                    extension_type = TlsUtilities.ReadUint16(buf);
+                    byte[] extension_data = TlsUtilities.ReadOpaque16(buf);
+
+                    /*
+                     * RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
+                     */
+                    Int32 key = extension_type;
+                    if (extensions.Contains(key))
+                        throw new TlsFatalAlert(AlertDescription.illegal_parameter,
+                            "Repeated extension: " + ExtensionType.GetText(extension_type));
+
+                    extensions.Add(key, extension_data);
+
+                    pre_shared_key_found |= (ExtensionType.pre_shared_key == extension_type);
+                }
+                while (buf.Position < buf.Length);
+
+                if (pre_shared_key_found && (ExtensionType.pre_shared_key != extension_type))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter,
+                        "'pre_shared_key' MUST be last in ClientHello");
+            }
+
+            return extensions;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static IList ReadSupplementalDataMessage(MemoryStream input)
+        {
+            byte[] supp_data = TlsUtilities.ReadOpaque24(input, 1);
+
+            AssertEmpty(input);
+
+            MemoryStream buf = new MemoryStream(supp_data, false);
+
+            IList supplementalData = Platform.CreateArrayList();
+
+            while (buf.Position < buf.Length)
+            {
+                int supp_data_type = TlsUtilities.ReadUint16(buf);
+                byte[] data = TlsUtilities.ReadOpaque16(buf);
+
+                supplementalData.Add(new SupplementalDataEntry(supp_data_type, data));
+            }
+
+            return supplementalData;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void WriteExtensions(Stream output, IDictionary extensions)
+        {
+            if (null == extensions || extensions.Count < 1)
+                return;
+
+            byte[] extBytes = WriteExtensionsData(extensions);
+
+            TlsUtilities.WriteOpaque16(extBytes, output);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static byte[] WriteExtensionsData(IDictionary extensions)
+        {
+            MemoryStream buf = new MemoryStream();
+            WriteExtensionsData(extensions, buf);
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void WriteExtensionsData(IDictionary extensions, MemoryStream buf)
+        {
+            /*
+             * NOTE: There are reports of servers that don't accept a zero-length extension as the last
+             * one, so we write out any zero-length ones first as a best-effort workaround.
+             */
+            WriteSelectedExtensions(buf, extensions, true);
+            WriteSelectedExtensions(buf, extensions, false);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void WriteSelectedExtensions(Stream output, IDictionary extensions, bool selectEmpty)
+        {
+            foreach (Int32 key in extensions.Keys)
+            {
+                int extension_type = key;
+                byte[] extension_data = (byte[])extensions[key];
+
+                if (selectEmpty == (extension_data.Length == 0))
+                {
+                    TlsUtilities.CheckUint16(extension_type);
+                    TlsUtilities.WriteUint16(extension_type, output);
+                    TlsUtilities.WriteOpaque16(extension_data, output);
+                }
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void WriteSupplementalData(Stream output, IList supplementalData)
+        {
+            MemoryStream buf = new MemoryStream();
+
+            foreach (SupplementalDataEntry entry in supplementalData)
+            {
+                int supp_data_type = entry.DataType;
+                TlsUtilities.CheckUint16(supp_data_type);
+                TlsUtilities.WriteUint16(supp_data_type, buf);
+                TlsUtilities.WriteOpaque16(entry.Data, buf);
+            }
+
+            byte[] supp_data = buf.ToArray();
+
+            TlsUtilities.WriteOpaque24(supp_data, output);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsPskIdentity.cs b/crypto/src/tls/TlsPskIdentity.cs
new file mode 100644
index 000000000..ae78872c3
--- /dev/null
+++ b/crypto/src/tls/TlsPskIdentity.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Processor interface for a PSK identity.</summary>
+    public interface TlsPskIdentity
+    {
+        void SkipIdentityHint();
+
+        void NotifyIdentityHint(byte[] psk_identity_hint);
+
+        byte[] GetPskIdentity();
+
+        byte[] GetPsk();
+    }
+}
diff --git a/crypto/src/tls/TlsPskIdentityManager.cs b/crypto/src/tls/TlsPskIdentityManager.cs
new file mode 100644
index 000000000..5bf10bccd
--- /dev/null
+++ b/crypto/src/tls/TlsPskIdentityManager.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for an object that can process a PSK identity.</summary>
+    public interface TlsPskIdentityManager
+    {
+        byte[] GetHint();
+
+        byte[] GetPsk(byte[] identity);
+    }
+}
diff --git a/crypto/src/tls/TlsPskKeyExchange.cs b/crypto/src/tls/TlsPskKeyExchange.cs
new file mode 100644
index 000000000..1055fdc53
--- /dev/null
+++ b/crypto/src/tls/TlsPskKeyExchange.cs
@@ -0,0 +1,305 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>(D)TLS PSK key exchange (RFC 4279).</summary>
+    public class TlsPskKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.DHE_PSK:
+            case KeyExchangeAlgorithm.ECDHE_PSK:
+            case KeyExchangeAlgorithm.PSK:
+            case KeyExchangeAlgorithm.RSA_PSK:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsPskIdentity m_pskIdentity;
+        protected TlsPskIdentityManager m_pskIdentityManager;
+        protected TlsDHGroupVerifier m_dhGroupVerifier;
+
+        protected byte[] m_psk_identity_hint = null;
+        protected byte[] m_psk = null;
+
+        protected TlsDHConfig m_dhConfig;
+        protected TlsECConfig m_ecConfig;
+        protected TlsAgreement m_agreement;
+
+        protected TlsCredentialedDecryptor m_serverCredentials = null;
+        protected TlsCertificate m_serverCertificate;
+        protected TlsSecret m_preMasterSecret;
+
+        public TlsPskKeyExchange(int keyExchange, TlsPskIdentity pskIdentity, TlsDHGroupVerifier dhGroupVerifier)
+            : this(keyExchange, pskIdentity, null, dhGroupVerifier, null, null)
+        {
+        }
+
+        public TlsPskKeyExchange(int keyExchange, TlsPskIdentityManager pskIdentityManager,
+            TlsDHConfig dhConfig, TlsECConfig ecConfig)
+            : this(keyExchange, null, pskIdentityManager, null, dhConfig, ecConfig)
+        {
+        }
+
+        private TlsPskKeyExchange(int keyExchange, TlsPskIdentity pskIdentity, TlsPskIdentityManager pskIdentityManager,
+            TlsDHGroupVerifier dhGroupVerifier, TlsDHConfig dhConfig, TlsECConfig ecConfig)
+            : base(CheckKeyExchange(keyExchange))
+        {
+            this.m_pskIdentity = pskIdentity;
+            this.m_pskIdentityManager = pskIdentityManager;
+            this.m_dhGroupVerifier = dhGroupVerifier;
+            this.m_dhConfig = dhConfig;
+            this.m_ecConfig = ecConfig;
+        }
+
+        public override void SkipServerCredentials()
+        {
+            if (m_keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            if (m_keyExchange != KeyExchangeAlgorithm.RSA_PSK)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_serverCredentials = TlsUtilities.RequireDecryptorCredentials(serverCredentials);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            if (m_keyExchange != KeyExchangeAlgorithm.RSA_PSK)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            this.m_serverCertificate = serverCertificate.GetCertificateAt(0).CheckUsageInRole(ConnectionEnd.server,
+                TlsCertificateRole.RsaEncryption);
+        }
+
+        public override byte[] GenerateServerKeyExchange()
+        {
+            this.m_psk_identity_hint = m_pskIdentityManager.GetHint();
+
+            if (this.m_psk_identity_hint == null && !RequiresServerKeyExchange)
+                return null;
+
+            MemoryStream buf = new MemoryStream();
+
+            if (this.m_psk_identity_hint == null)
+            {
+                TlsUtilities.WriteOpaque16(TlsUtilities.EmptyBytes, buf);
+            }
+            else
+            {
+                TlsUtilities.WriteOpaque16(this.m_psk_identity_hint, buf);
+            }
+
+            if (this.m_keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+            {
+                if (this.m_dhConfig == null)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                TlsDHUtilities.WriteDHConfig(m_dhConfig, buf);
+
+                this.m_agreement = m_context.Crypto.CreateDHDomain(m_dhConfig).CreateDH();
+
+                GenerateEphemeralDH(buf);
+            }
+            else if (this.m_keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+            {
+                if (this.m_ecConfig == null)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                TlsEccUtilities.WriteECConfig(m_ecConfig, buf);
+
+                this.m_agreement = m_context.Crypto.CreateECDomain(m_ecConfig).CreateECDH();
+
+                GenerateEphemeralECDH(buf);
+            }
+
+            return buf.ToArray();
+        }
+
+        public override bool RequiresServerKeyExchange
+        {
+            get
+            {
+                switch (m_keyExchange)
+                {
+                case KeyExchangeAlgorithm.DHE_PSK:
+                case KeyExchangeAlgorithm.ECDHE_PSK:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+        }
+
+        public override void ProcessServerKeyExchange(Stream input)
+        {
+            this.m_psk_identity_hint = TlsUtilities.ReadOpaque16(input);
+
+            if (this.m_keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+            {
+                this.m_dhConfig = TlsDHUtilities.ReceiveDHConfig(m_context, m_dhGroupVerifier, input);
+
+                byte[] y = TlsUtilities.ReadOpaque16(input, 1);
+
+                this.m_agreement = m_context.Crypto.CreateDHDomain(m_dhConfig).CreateDH();
+
+                ProcessEphemeralDH(y);
+            }
+            else if (this.m_keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+            {
+                this.m_ecConfig = TlsEccUtilities.ReceiveECDHConfig(m_context, input);
+
+                byte[] point = TlsUtilities.ReadOpaque8(input, 1);
+
+                this.m_agreement = m_context.Crypto.CreateECDomain(m_ecConfig).CreateECDH();
+
+                ProcessEphemeralECDH(point);
+            }
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void GenerateClientKeyExchange(Stream output)
+        {
+            if (m_psk_identity_hint == null)
+            {
+                m_pskIdentity.SkipIdentityHint();
+            }
+            else
+            {
+                m_pskIdentity.NotifyIdentityHint(m_psk_identity_hint);
+            }
+
+            byte[] psk_identity = m_pskIdentity.GetPskIdentity();
+            if (psk_identity == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_psk = m_pskIdentity.GetPsk();
+            if (m_psk == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            TlsUtilities.WriteOpaque16(psk_identity, output);
+
+            m_context.SecurityParameters.m_pskIdentity = Arrays.Clone(psk_identity);
+
+            if (this.m_keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+            {
+                GenerateEphemeralDH(output);
+            }
+            else if (this.m_keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+            {
+                GenerateEphemeralECDH(output);
+            }
+            else if (this.m_keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+            {
+                this.m_preMasterSecret = TlsRsaUtilities.GenerateEncryptedPreMasterSecret(m_context,
+                    m_serverCertificate, output);
+            }
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            byte[] psk_identity = TlsUtilities.ReadOpaque16(input);
+
+            this.m_psk = m_pskIdentityManager.GetPsk(psk_identity);
+            if (m_psk == null)
+                throw new TlsFatalAlert(AlertDescription.unknown_psk_identity);
+
+            m_context.SecurityParameters.m_pskIdentity = psk_identity;
+
+            if (this.m_keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+            {
+                byte[] y = TlsUtilities.ReadOpaque16(input, 1);
+
+                ProcessEphemeralDH(y);
+            }
+            else if (this.m_keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+            {
+                byte[] point = TlsUtilities.ReadOpaque8(input, 1);
+
+                ProcessEphemeralECDH(point);
+            }
+            else if (this.m_keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+            {
+                byte[] encryptedPreMasterSecret = TlsUtilities.ReadEncryptedPms(m_context, input);
+
+                this.m_preMasterSecret = m_serverCredentials.Decrypt(new TlsCryptoParameters(m_context),
+                    encryptedPreMasterSecret);
+            }
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            byte[] other_secret = GenerateOtherSecret(m_psk.Length);
+
+            MemoryStream buf = new MemoryStream(4 + other_secret.Length + m_psk.Length);
+            TlsUtilities.WriteOpaque16(other_secret, buf);
+            TlsUtilities.WriteOpaque16(m_psk, buf);
+
+            Array.Clear(m_psk, 0, m_psk.Length);
+            this.m_psk = null;
+
+            return m_context.Crypto.CreateSecret(buf.ToArray());
+        }
+
+        protected virtual void GenerateEphemeralDH(Stream output)
+        {
+            byte[] y = m_agreement.GenerateEphemeral();
+            TlsUtilities.WriteOpaque16(y, output);
+        }
+
+        protected virtual void GenerateEphemeralECDH(Stream output)
+        {
+            byte[] point = m_agreement.GenerateEphemeral();
+            TlsUtilities.WriteOpaque8(point, output);
+        }
+
+        protected virtual byte[] GenerateOtherSecret(int pskLength)
+        {
+            if (this.m_keyExchange == KeyExchangeAlgorithm.PSK)
+                return new byte[pskLength];
+
+            if (this.m_keyExchange == KeyExchangeAlgorithm.DHE_PSK ||
+                this.m_keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+            {
+                if (m_agreement != null)
+                    return m_agreement.CalculateSecret().Extract();
+            }
+
+            if (this.m_keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+            {
+                if (m_preMasterSecret != null)
+                    return this.m_preMasterSecret.Extract();
+            }
+
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        protected virtual void ProcessEphemeralDH(byte[] y)
+        {
+            this.m_agreement.ReceivePeerValue(y);
+        }
+
+        protected virtual void ProcessEphemeralECDH(byte[] point)
+        {
+            TlsEccUtilities.CheckPointEncoding(m_ecConfig.NamedGroup, point);
+
+            this.m_agreement.ReceivePeerValue(point);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsRsaKeyExchange.cs b/crypto/src/tls/TlsRsaKeyExchange.cs
new file mode 100644
index 000000000..5184ca8fa
--- /dev/null
+++ b/crypto/src/tls/TlsRsaKeyExchange.cs
@@ -0,0 +1,80 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>(D)TLS RSA key exchange.</summary>
+    public class TlsRsaKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.RSA:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsCredentialedDecryptor m_serverCredentials = null;
+        protected TlsCertificate m_serverCertificate;
+        protected TlsSecret m_preMasterSecret;
+
+        public TlsRsaKeyExchange(int keyExchange)
+            : base(CheckKeyExchange(keyExchange))
+        {
+        }
+
+        public override void SkipServerCredentials()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            this.m_serverCredentials = TlsUtilities.RequireDecryptorCredentials(serverCredentials);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            this.m_serverCertificate = serverCertificate.GetCertificateAt(0).CheckUsageInRole(ConnectionEnd.server,
+                TlsCertificateRole.RsaEncryption);
+        }
+
+        public override short[] GetClientCertificateTypes()
+        {
+            return new short[]{ ClientCertificateType.rsa_sign, ClientCertificateType.dss_sign,
+                ClientCertificateType.ecdsa_sign };
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            TlsUtilities.RequireSignerCredentials(clientCredentials);
+        }
+
+        public override void GenerateClientKeyExchange(Stream output)
+        {
+            this.m_preMasterSecret = TlsRsaUtilities.GenerateEncryptedPreMasterSecret(m_context, m_serverCertificate,
+                output);
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            byte[] encryptedPreMasterSecret = TlsUtilities.ReadEncryptedPms(m_context, input);
+
+            this.m_preMasterSecret = m_serverCredentials.Decrypt(new TlsCryptoParameters(m_context),
+                encryptedPreMasterSecret);
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            TlsSecret tmp = this.m_preMasterSecret;
+            this.m_preMasterSecret = null;
+            return tmp;
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsRsaUtilities.cs b/crypto/src/tls/TlsRsaUtilities.cs
new file mode 100644
index 000000000..d520d3ea2
--- /dev/null
+++ b/crypto/src/tls/TlsRsaUtilities.cs
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RSA Utility methods.</summary>
+    public abstract class TlsRsaUtilities
+    {
+        /// <summary>Generate a pre_master_secret and send it encrypted to the server.</summary>
+        /// <exception cref="IOException"/>
+        public static TlsSecret GenerateEncryptedPreMasterSecret(TlsContext context, TlsCertificate certificate,
+            Stream output)
+        {
+            TlsSecret preMasterSecret = context.Crypto.GenerateRsaPreMasterSecret(context.RsaPreMasterSecretVersion);
+
+            byte[] encryptedPreMasterSecret = preMasterSecret.Encrypt(certificate);
+            TlsUtilities.WriteEncryptedPms(context, encryptedPreMasterSecret, output);
+
+            return preMasterSecret;
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsServer.cs b/crypto/src/tls/TlsServer.cs
new file mode 100644
index 000000000..783c8c14d
--- /dev/null
+++ b/crypto/src/tls/TlsServer.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Interface describing a TLS server endpoint.</summary>
+    public interface TlsServer
+        : TlsPeer
+    {
+        void Init(TlsServerContext context);
+
+        /// <summary>Return the specified session, if available.</summary>
+        /// <remarks>
+        /// Note that the peer's certificate chain for the session (if any) may need to be periodically revalidated.
+        /// </remarks>
+        /// <param name="sessionID">the ID of the session to resume.</param>
+        /// <returns>A <see cref="TlsSession"/> with the specified session ID, or null.</returns>
+        /// <seealso cref="SessionParameters.PeerCertificate"/>
+        TlsSession GetSessionToResume(byte[] sessionID);
+
+        byte[] GetNewSessionID();
+
+        void NotifySession(TlsSession session);
+
+        /// <exception cref="IOException"/>
+        void NotifyClientVersion(ProtocolVersion clientVersion);
+
+        /// <exception cref="IOException"/>
+        void NotifyFallback(bool isFallback);
+
+        /// <exception cref="IOException"/>
+        void NotifyOfferedCipherSuites(int[] offeredCipherSuites);
+
+        /// <param name="clientExtensions">(Int32 -> byte[])</param>
+        /// <exception cref="IOException"/>
+        void ProcessClientExtensions(IDictionary clientExtensions);
+
+        /// <exception cref="IOException"/>
+        ProtocolVersion GetServerVersion();
+
+        /// <exception cref="IOException"/>
+        int[] GetSupportedGroups();
+
+        /// <exception cref="IOException"/>
+        int GetSelectedCipherSuite();
+
+        /// <returns>(Int32 -> byte[])</returns>
+        /// <exception cref="IOException"/>
+        IDictionary GetServerExtensions();
+
+        /// <param name="serverExtensions">(Int32 -> byte[])</param>
+        /// <exception cref="IOException"/>
+        void GetServerExtensionsForConnection(IDictionary serverExtensions);
+
+        /// <returns>(SupplementalDataEntry)</returns>
+        /// <exception cref="IOException"/>
+        IList GetServerSupplementalData();
+
+        /// <summary>Return server credentials to use.</summary>
+        /// <remarks>
+        /// The returned value may be null, or else it MUST implement <em>exactly one</em> of
+        /// <see cref="TlsCredentialedAgreement"/>, <see cref="TlsCredentialedDecryptor"/>, or
+        /// <see cref = "TlsCredentialedSigner"/>, depending on the key exchange that was negotiated.
+        /// </remarks>
+        /// <returns>a <see cref="TlsCredentials"/> object or null for anonymous key exchanges.</returns>
+        /// <exception cref="IOException"/>
+        TlsCredentials GetCredentials();
+
+        /// <remarks>
+        /// This method will be called (only) if the server included an extension of type "status_request" with empty
+        /// "extension_data" in the extended server hello. See <i>RFC 3546 3.6. Certificate Status Request</i>. If a
+        /// non-null <see cref="CertificateStatus"/> is returned, it is sent to the client as a handshake message of
+        /// type "certificate_status".
+        /// </remarks>
+        /// <returns>A <see cref="CertificateStatus"/> to be sent to the client (or null for none).</returns>
+        /// <exception cref="IOException"/>
+        CertificateStatus GetCertificateStatus();
+
+        /// <exception cref="IOException"/>
+        CertificateRequest GetCertificateRequest();
+
+        /// <exception cref="IOException"/>
+        TlsPskIdentityManager GetPskIdentityManager();
+
+        /// <exception cref="IOException"/>
+        TlsSrpLoginParameters GetSrpLoginParameters();
+
+        /// <exception cref="IOException"/>
+        TlsDHConfig GetDHConfig();
+
+        /// <exception cref="IOException"/>
+        TlsECConfig GetECDHConfig();
+
+        /// <param name="clientSupplementalData">(SupplementalDataEntry)</param>
+        /// <exception cref="IOException"/>
+        void ProcessClientSupplementalData(IList clientSupplementalData);
+
+        /// <summary>Called by the protocol handler to report the client certificate, only if
+        /// <see cref="GetCertificateRequest"/> returned non-null.</summary>
+        /// <remarks>
+        /// Note: this method is responsible for certificate verification and validation.
+        /// </remarks>
+        /// <param name="clientCertificate">the effective client certificate (may be an empty chain).</param>
+        /// <exception cref="IOException"/>
+        void NotifyClientCertificate(Certificate clientCertificate);
+
+        /// <summary>RFC 5077 3.3. NewSessionTicket Handshake Message.</summary>
+        /// <remarks>
+        /// This method will be called (only) if a NewSessionTicket extension was sent by the server. See <i>RFC 5077
+        /// 4. Recommended Ticket Construction</i> for recommended format and protection.
+        /// </remarks>
+        /// <returns>The ticket.</returns>
+        /// <exception cref="IOException"/>
+        NewSessionTicket GetNewSessionTicket();
+    }
+}
diff --git a/crypto/src/tls/TlsServerCertificate.cs b/crypto/src/tls/TlsServerCertificate.cs
new file mode 100644
index 000000000..bea896af7
--- /dev/null
+++ b/crypto/src/tls/TlsServerCertificate.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Server certificate carrier interface.</summary>
+    public interface TlsServerCertificate
+    {
+        Certificate Certificate { get; }
+
+        CertificateStatus CertificateStatus { get; }
+    }
+}
diff --git a/crypto/src/tls/TlsServerCertificateImpl.cs b/crypto/src/tls/TlsServerCertificateImpl.cs
new file mode 100644
index 000000000..db2d0911f
--- /dev/null
+++ b/crypto/src/tls/TlsServerCertificateImpl.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal sealed class TlsServerCertificateImpl
+        : TlsServerCertificate
+    {
+        private readonly Certificate m_certificate;
+        private readonly CertificateStatus m_certificateStatus;
+
+        internal TlsServerCertificateImpl(Certificate certificate, CertificateStatus certificateStatus)
+        {
+            this.m_certificate = certificate;
+            this.m_certificateStatus = certificateStatus;
+        }
+
+        public Certificate Certificate
+        {
+            get { return m_certificate; }
+        }
+
+        public CertificateStatus CertificateStatus
+        {
+            get { return m_certificateStatus; }
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsServerContext.cs b/crypto/src/tls/TlsServerContext.cs
new file mode 100644
index 000000000..85a075154
--- /dev/null
+++ b/crypto/src/tls/TlsServerContext.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Marker interface to distinguish a TLS server context.</summary>
+    public interface TlsServerContext
+        : TlsContext
+    {
+    }
+}
diff --git a/crypto/src/tls/TlsServerContextImpl.cs b/crypto/src/tls/TlsServerContextImpl.cs
new file mode 100644
index 000000000..0c719bce5
--- /dev/null
+++ b/crypto/src/tls/TlsServerContextImpl.cs
@@ -0,0 +1,20 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal class TlsServerContextImpl
+        : AbstractTlsContext, TlsServerContext
+    {
+        internal TlsServerContextImpl(TlsCrypto crypto)
+            : base(crypto, ConnectionEnd.server)
+        {
+        }
+
+        public override bool IsServer
+        {
+            get { return true; }
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsServerProtocol.cs b/crypto/src/tls/TlsServerProtocol.cs
new file mode 100644
index 000000000..a7e0e0120
--- /dev/null
+++ b/crypto/src/tls/TlsServerProtocol.cs
@@ -0,0 +1,1471 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsServerProtocol
+        : TlsProtocol
+    {
+        protected TlsServer m_tlsServer = null;
+        internal TlsServerContextImpl m_tlsServerContext = null;
+
+        protected int[] m_offeredCipherSuites = null;
+        protected TlsKeyExchange m_keyExchange = null;
+        protected CertificateRequest m_certificateRequest = null;
+
+        /// <summary>Constructor for non-blocking mode.</summary>
+        /// <remarks>
+        /// When data is received, use <see cref="TlsProtocol.OfferInput(byte[])"/> to provide the received ciphertext,
+        /// then use <see cref="TlsProtocol.ReadInput(byte[],int,int)"/> to read the corresponding cleartext.<br/><br/>
+        /// Similarly, when data needs to be sent, use <see cref="TlsProtocol.WriteApplicationData(byte[],int,int)"/>
+        /// to provide the cleartext, then use <see cref="TlsProtocol.ReadOutput(byte[],int,int)"/> to get the
+        /// corresponding ciphertext.
+        /// </remarks>
+        public TlsServerProtocol()
+            : base()
+        {
+        }
+
+        /// <summary>Constructor for blocking mode.</summary>
+        /// <param name="stream">The <see cref="Stream"/> of data to/from the server.</param>
+        public TlsServerProtocol(Stream stream)
+            : base(stream)
+        {
+        }
+
+        /// <summary>Constructor for blocking mode.</summary>
+        /// <param name="input">The <see cref="Stream"/> of data from the server.</param>
+        /// <param name="output">The <see cref="Stream"/> of data to the server.</param>
+        public TlsServerProtocol(Stream input, Stream output)
+            : base(input, output)
+        {
+        }
+
+        /// <summary>Receives a TLS handshake in the role of server.</summary>
+        /// <remarks>
+        /// In blocking mode, this will not return until the handshake is complete. In non-blocking mode, use
+        /// <see cref="TlsPeer.NotifyHandshakeComplete"/> to receive a callback when the handshake is complete.
+        /// </remarks>
+        /// <param name="tlsServer">The <see cref="TlsServer"/> to use for the handshake.</param>
+        /// <exception cref="IOException">If in blocking mode and handshake was not successful.</exception>
+        public void Accept(TlsServer tlsServer)
+        {
+            if (tlsServer == null)
+                throw new ArgumentNullException("tlsServer");
+            if (m_tlsServer != null)
+                throw new InvalidOperationException("'Accept' can only be called once");
+
+            this.m_tlsServer = tlsServer;
+            this.m_tlsServerContext = new TlsServerContextImpl(tlsServer.Crypto);
+
+            tlsServer.Init(m_tlsServerContext);
+            tlsServer.NotifyCloseHandle(this);
+
+            BeginHandshake();
+
+            if (m_blocking)
+            {
+                BlockForHandshake();
+            }
+        }
+
+        protected override void CleanupHandshake()
+        {
+            base.CleanupHandshake();
+
+            this.m_offeredCipherSuites = null;
+            this.m_keyExchange = null;
+            this.m_certificateRequest = null;
+        }
+
+        protected virtual bool ExpectCertificateVerifyMessage()
+        {
+            if (null == m_certificateRequest)
+                return false;
+
+            Certificate clientCertificate = m_tlsServerContext.SecurityParameters.PeerCertificate;
+
+            return null != clientCertificate && !clientCertificate.IsEmpty
+                && (null == m_keyExchange || m_keyExchange.RequiresCertificateVerify);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ServerHello Generate13HelloRetryRequest(ClientHello clientHello)
+        {
+            // TODO[tls13] In future there might be other reasons for a HelloRetryRequest.
+            if (m_retryGroup < 0)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+            ProtocolVersion serverVersion = securityParameters.NegotiatedVersion;
+
+            IDictionary serverHelloExtensions = Platform.CreateHashtable();
+            TlsExtensionsUtilities.AddSupportedVersionsExtensionServer(serverHelloExtensions, serverVersion);
+            if (m_retryGroup >= 0)
+            {
+                TlsExtensionsUtilities.AddKeyShareHelloRetryRequest(serverHelloExtensions, m_retryGroup);
+            }
+            if (null != m_retryCookie)
+            {
+                TlsExtensionsUtilities.AddCookieExtension(serverHelloExtensions, m_retryCookie);
+            }
+
+            TlsUtilities.CheckExtensionData13(serverHelloExtensions, HandshakeType.hello_retry_request,
+                AlertDescription.internal_error);
+
+            return new ServerHello(clientHello.SessionID, securityParameters.CipherSuite, serverHelloExtensions);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ServerHello Generate13ServerHello(ClientHello clientHello, bool afterHelloRetryRequest)
+        {
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+
+
+            byte[] legacy_session_id = clientHello.SessionID;
+
+            IDictionary clientHelloExtensions = clientHello.Extensions;
+            if (null == clientHelloExtensions)
+                throw new TlsFatalAlert(AlertDescription.missing_extension);
+
+
+            ProtocolVersion serverVersion = securityParameters.NegotiatedVersion;
+            TlsCrypto crypto = m_tlsServerContext.Crypto;
+
+            IList clientShares = TlsExtensionsUtilities.GetKeyShareClientHello(clientHelloExtensions);
+            KeyShareEntry clientShare = null;
+
+            if (afterHelloRetryRequest)
+            {
+                if (m_retryGroup < 0)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                /*
+                 * TODO[tls13] Confirm fields in the ClientHello haven't changed
+                 * 
+                 * RFC 8446 4.1.2 [..] when the server has responded to its ClientHello with a
+                 * HelloRetryRequest [..] the client MUST send the same ClientHello without
+                 * modification, except as follows: [key_share, early_data, cookie, pre_shared_key,
+                 * padding].
+                 */
+
+                byte[] cookie = TlsExtensionsUtilities.GetCookieExtension(clientHelloExtensions);
+                if (!Arrays.AreEqual(m_retryCookie, cookie))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                this.m_retryCookie = null;
+
+                clientShare = TlsUtilities.SelectKeyShare(clientShares, m_retryGroup);
+                if (null == clientShare)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+            else
+            {
+                this.m_clientExtensions = clientHelloExtensions;
+
+                securityParameters.m_secureRenegotiation = false;
+
+                // NOTE: Validates the padding extension data, if present
+                TlsExtensionsUtilities.GetPaddingExtension(clientHelloExtensions);
+
+                securityParameters.m_clientServerNames = TlsExtensionsUtilities
+                    .GetServerNameExtensionClient(clientHelloExtensions);
+
+                TlsUtilities.EstablishClientSigAlgs(securityParameters, clientHelloExtensions);
+
+                /*
+                 * RFC 8446 4.2.3. If a server is authenticating via a certificate and the client has
+                 * not sent a "signature_algorithms" extension, then the server MUST abort the handshake
+                 * with a "missing_extension" alert.
+                 */
+                // TODO[tls13] Revisit this check if we add support for PSK-only key exchange.
+                if (null == securityParameters.ClientSigAlgs)
+                    throw new TlsFatalAlert(AlertDescription.missing_extension);
+
+                m_tlsServer.ProcessClientExtensions(clientHelloExtensions);
+
+                /*
+                 * NOTE: Currently no server support for session resumption
+                 * 
+                 * If adding support, ensure securityParameters.tlsUnique is set to the localVerifyData, but
+                 * ONLY when extended_master_secret has been negotiated (otherwise NULL).
+                 */
+                {
+                    // TODO[tls13] Resumption/PSK
+
+                    this.m_tlsSession = TlsUtilities.ImportSession(TlsUtilities.EmptyBytes, null);
+                    this.m_sessionParameters = null;
+                    this.m_sessionMasterSecret = null;
+                }
+
+                securityParameters.m_sessionID = m_tlsSession.SessionID;
+
+                m_tlsServer.NotifySession(m_tlsSession);
+
+                TlsUtilities.NegotiatedVersionTlsServer(m_tlsServerContext);
+
+                {
+                    securityParameters.m_serverRandom = CreateRandomBlock(false, m_tlsServerContext);
+
+                    if (!serverVersion.Equals(ProtocolVersion.GetLatestTls(m_tlsServer.GetProtocolVersions())))
+                    {
+                        TlsUtilities.WriteDowngradeMarker(serverVersion, securityParameters.ServerRandom);
+                    }
+                }
+
+                {
+                    int cipherSuite = m_tlsServer.GetSelectedCipherSuite();
+
+                    if (!TlsUtilities.IsValidCipherSuiteSelection(m_offeredCipherSuites, cipherSuite) ||
+                        !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, serverVersion))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+                    }
+
+                    TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+                }
+
+                int[] clientSupportedGroups = securityParameters.ClientSupportedGroups;
+                int[] serverSupportedGroups = securityParameters.ServerSupportedGroups;
+
+                clientShare = TlsUtilities.SelectKeyShare(crypto, serverVersion, clientShares, clientSupportedGroups,
+                    serverSupportedGroups);
+
+                if (null == clientShare)
+                {
+                    this.m_retryGroup = TlsUtilities.SelectKeyShareGroup(crypto, serverVersion, clientSupportedGroups,
+                        serverSupportedGroups);
+                    if (m_retryGroup < 0)
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                    this.m_retryCookie = m_tlsServerContext.NonceGenerator.GenerateNonce(16);
+
+                    return Generate13HelloRetryRequest(clientHello);
+                }
+
+                if (clientShare.NamedGroup != serverSupportedGroups[0])
+                {
+                    /*
+                     * TODO[tls13] RFC 8446 4.2.7. As of TLS 1.3, servers are permitted to send the
+                     * "supported_groups" extension to the client. Clients MUST NOT act upon any
+                     * information found in "supported_groups" prior to successful completion of the
+                     * handshake but MAY use the information learned from a successfully completed
+                     * handshake to change what groups they use in their "key_share" extension in
+                     * subsequent connections. If the server has a group it prefers to the ones in the
+                     * "key_share" extension but is still willing to accept the ClientHello, it SHOULD
+                     * send "supported_groups" to update the client's view of its preferences; this
+                     * extension SHOULD contain all groups the server supports, regardless of whether
+                     * they are currently supported by the client.
+                     */
+                }
+            }
+
+
+            IDictionary serverHelloExtensions = Platform.CreateHashtable();
+            IDictionary serverEncryptedExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                m_tlsServer.GetServerExtensions());
+
+            m_tlsServer.GetServerExtensionsForConnection(serverEncryptedExtensions);
+
+            ProtocolVersion serverLegacyVersion = ProtocolVersion.TLSv12;
+            TlsExtensionsUtilities.AddSupportedVersionsExtensionServer(serverHelloExtensions, serverVersion);
+
+            /*
+             * RFC 8446 Appendix D. Because TLS 1.3 always hashes in the transcript up to the server
+             * Finished, implementations which support both TLS 1.3 and earlier versions SHOULD indicate
+             * the use of the Extended Master Secret extension in their APIs whenever TLS 1.3 is used.
+             */
+            securityParameters.m_extendedMasterSecret = true;
+
+            /*
+             * RFC 7301 3.1. When session resumption or session tickets [...] are used, the previous
+             * contents of this extension are irrelevant, and only the values in the new handshake
+             * messages are considered.
+             */
+            securityParameters.m_applicationProtocol = TlsExtensionsUtilities.GetAlpnExtensionServer(
+                serverEncryptedExtensions);
+            securityParameters.m_applicationProtocolSet = true;
+
+            if (serverEncryptedExtensions.Count > 0)
+            {
+                securityParameters.m_maxFragmentLength = ProcessMaxFragmentLengthExtension(clientHelloExtensions,
+                    serverEncryptedExtensions, AlertDescription.internal_error);
+            }
+
+            securityParameters.m_encryptThenMac = false;
+            securityParameters.m_truncatedHmac = false;
+
+            /*
+             * TODO[tls13] RFC 8446 4.4.2.1. OCSP Status and SCT Extensions.
+             * 
+             * OCSP information is carried in an extension for a CertificateEntry.
+             */
+            securityParameters.m_statusRequestVersion = clientHelloExtensions.Contains(ExtensionType.status_request)
+                ? 1 : 0;
+
+            this.m_expectSessionTicket = false;
+
+            {
+                int namedGroup = clientShare.NamedGroup;
+
+                TlsAgreement agreement;
+                if (NamedGroup.RefersToASpecificCurve(namedGroup))
+                {
+                    agreement = crypto.CreateECDomain(new TlsECConfig(namedGroup)).CreateECDH();
+                }
+                else if (NamedGroup.RefersToASpecificFiniteField(namedGroup))
+                {
+                    agreement = crypto.CreateDHDomain(new TlsDHConfig(namedGroup, true)).CreateDH();
+                }
+                else
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                byte[] key_exchange = agreement.GenerateEphemeral();
+                KeyShareEntry serverShare = new KeyShareEntry(namedGroup, key_exchange);
+                TlsExtensionsUtilities.AddKeyShareServerHello(serverHelloExtensions, serverShare);
+
+                agreement.ReceivePeerValue(clientShare.KeyExchange);
+                securityParameters.m_sharedSecret = agreement.CalculateSecret();
+                TlsUtilities.Establish13PhaseSecrets(m_tlsServerContext);
+            }
+
+            this.m_serverExtensions = serverEncryptedExtensions;
+
+            ApplyMaxFragmentLengthExtension(securityParameters.MaxFragmentLength);
+
+            TlsUtilities.CheckExtensionData13(serverHelloExtensions, HandshakeType.server_hello,
+                AlertDescription.internal_error);
+
+            return new ServerHello(serverLegacyVersion, securityParameters.ServerRandom, legacy_session_id,
+                securityParameters.CipherSuite, serverHelloExtensions);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ServerHello GenerateServerHello(ClientHello clientHello)
+        {
+            ProtocolVersion clientLegacyVersion = clientHello.Version;
+            if (!clientLegacyVersion.IsTls)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            this.m_offeredCipherSuites = clientHello.CipherSuites;
+
+
+ 
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+
+            m_tlsServerContext.SetClientSupportedVersions(
+                TlsExtensionsUtilities.GetSupportedVersionsExtensionClient(clientHello.Extensions));
+
+            ProtocolVersion clientVersion = clientLegacyVersion;
+            if (null == m_tlsServerContext.ClientSupportedVersions)
+            {
+                if (clientVersion.IsLaterVersionOf(ProtocolVersion.TLSv12))
+                {
+                    clientVersion = ProtocolVersion.TLSv12;
+                }
+
+                m_tlsServerContext.SetClientSupportedVersions(clientVersion.DownTo(ProtocolVersion.SSLv3));
+            }
+            else
+            {
+                clientVersion = ProtocolVersion.GetLatestTls(m_tlsServerContext.ClientSupportedVersions);
+            }
+
+            // Set the legacy_record_version to use for early alerts 
+            m_recordStream.SetWriteVersion(clientVersion);
+
+            if (!ProtocolVersion.SERVER_EARLIEST_SUPPORTED_TLS.IsEqualOrEarlierVersionOf(clientVersion))
+                throw new TlsFatalAlert(AlertDescription.protocol_version);
+
+            // NOT renegotiating
+            {
+                m_tlsServerContext.SetClientVersion(clientVersion);
+            }
+
+            m_tlsServer.NotifyClientVersion(m_tlsServerContext.ClientVersion);
+
+            securityParameters.m_clientRandom = clientHello.Random;
+
+            m_tlsServer.NotifyFallback(Arrays.Contains(m_offeredCipherSuites, CipherSuite.TLS_FALLBACK_SCSV));
+
+            m_tlsServer.NotifyOfferedCipherSuites(m_offeredCipherSuites);
+
+            // TODO[tls13] Negotiate cipher suite first?
+
+            ProtocolVersion serverVersion;
+
+            // NOT renegotiating
+            {
+                serverVersion = m_tlsServer.GetServerVersion();
+                if (!ProtocolVersion.Contains(m_tlsServerContext.ClientSupportedVersions, serverVersion))
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                securityParameters.m_negotiatedVersion = serverVersion;
+            }
+
+            securityParameters.m_clientSupportedGroups = TlsExtensionsUtilities.GetSupportedGroupsExtension(
+                clientHello.Extensions);
+            securityParameters.m_serverSupportedGroups = m_tlsServer.GetSupportedGroups();
+
+            if (ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(serverVersion))
+            {
+                // See RFC 8446 D.4.
+                m_recordStream.SetIgnoreChangeCipherSpec(true);
+
+                m_recordStream.SetWriteVersion(ProtocolVersion.TLSv12);
+
+                return Generate13ServerHello(clientHello, false);
+            }
+
+            m_recordStream.SetWriteVersion(serverVersion);
+
+            this.m_clientExtensions = clientHello.Extensions;
+
+            byte[] clientRenegExtData = TlsUtilities.GetExtensionData(m_clientExtensions, ExtensionType.renegotiation_info);
+
+            // NOT renegotiating
+            {
+                /*
+                 * RFC 5746 3.6. Server Behavior: Initial Handshake (both full and session-resumption)
+                 */
+
+                /*
+                 * 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.
+                 */
+
+                /*
+                 * When a ClientHello is received, the server MUST check if it includes the
+                 * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV. If it does, set the secure_renegotiation flag
+                 * to TRUE.
+                 */
+                if (Arrays.Contains(m_offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV))
+                {
+                    securityParameters.m_secureRenegotiation = true;
+                }
+
+                /*
+                 * The server MUST check if the "renegotiation_info" extension is included in the
+                 * ClientHello.
+                 */
+                if (clientRenegExtData != null)
+                {
+                    /*
+                     * If the extension is present, set secure_renegotiation flag to TRUE. The
+                     * server MUST then verify that the length of the "renegotiated_connection"
+                     * field is zero, and if it is not, MUST abort the handshake.
+                     */
+                    securityParameters.m_secureRenegotiation = true;
+
+                    if (!Arrays.ConstantTimeAreEqual(clientRenegExtData,
+                        CreateRenegotiationInfo(TlsUtilities.EmptyBytes)))
+                    {
+                        throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                    }
+                }
+            }
+
+            m_tlsServer.NotifySecureRenegotiation(securityParameters.IsSecureRenegotiation);
+
+            bool offeredExtendedMasterSecret = TlsExtensionsUtilities.HasExtendedMasterSecretExtension(
+                m_clientExtensions);
+
+            if (m_clientExtensions != null)
+            {
+                // NOTE: Validates the padding extension data, if present
+                TlsExtensionsUtilities.GetPaddingExtension(m_clientExtensions);
+
+                securityParameters.m_clientServerNames = TlsExtensionsUtilities.GetServerNameExtensionClient(
+                    m_clientExtensions);
+
+                /*
+                 * RFC 5246 7.4.1.4.1. Note: this extension is not meaningful for TLS versions prior
+                 * to 1.2. Clients MUST NOT offer it if they are offering prior versions.
+                 */
+                if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(clientVersion))
+                {
+                    TlsUtilities.EstablishClientSigAlgs(securityParameters, m_clientExtensions);
+                }
+
+                securityParameters.m_clientSupportedGroups = TlsExtensionsUtilities.GetSupportedGroupsExtension(
+                    m_clientExtensions);
+
+                m_tlsServer.ProcessClientExtensions(m_clientExtensions);
+            }
+
+            this.m_resumedSession = EstablishSession(m_tlsServer.GetSessionToResume(clientHello.SessionID));
+
+            if (!m_resumedSession)
+            {
+                byte[] newSessionID = m_tlsServer.GetNewSessionID();
+                if (null == newSessionID)
+                {
+                    newSessionID = TlsUtilities.EmptyBytes;
+                }
+
+                this.m_tlsSession = TlsUtilities.ImportSession(newSessionID, null);
+                this.m_sessionParameters = null;
+                this.m_sessionMasterSecret = null;
+            }
+
+            securityParameters.m_sessionID = m_tlsSession.SessionID;
+
+            m_tlsServer.NotifySession(m_tlsSession);
+
+            TlsUtilities.NegotiatedVersionTlsServer(m_tlsServerContext);
+
+            {
+                bool useGmtUnixTime = m_tlsServer.ShouldUseGmtUnixTime();
+
+                securityParameters.m_serverRandom = CreateRandomBlock(useGmtUnixTime, m_tlsServerContext);
+
+                if (!serverVersion.Equals(ProtocolVersion.GetLatestTls(m_tlsServer.GetProtocolVersions())))
+                {
+                    TlsUtilities.WriteDowngradeMarker(serverVersion, securityParameters.ServerRandom);
+                }
+            }
+
+            {
+                int cipherSuite = m_resumedSession
+                    ?   m_sessionParameters.CipherSuite
+                    :   m_tlsServer.GetSelectedCipherSuite();
+
+                if (!TlsUtilities.IsValidCipherSuiteSelection(m_offeredCipherSuites, cipherSuite) ||
+                    !TlsUtilities.IsValidVersionForCipherSuite(cipherSuite, serverVersion))
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                TlsUtilities.NegotiatedCipherSuite(securityParameters, cipherSuite);
+            }
+
+            m_tlsServerContext.SetRsaPreMasterSecretVersion(clientLegacyVersion);
+
+            {
+                IDictionary sessionServerExtensions = m_resumedSession
+                    ?   m_sessionParameters.ReadServerExtensions()
+                    :   m_tlsServer.GetServerExtensions();
+
+                this.m_serverExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(sessionServerExtensions);
+            }
+
+            m_tlsServer.GetServerExtensionsForConnection(m_serverExtensions);
+
+            // NOT renegotiating
+            {
+                /*
+                 * RFC 5746 3.6. Server Behavior: Initial Handshake (both full and session-resumption)
+                 */
+                if (securityParameters.IsSecureRenegotiation)
+                {
+                    byte[] serverRenegExtData = TlsUtilities.GetExtensionData(m_serverExtensions,
+                        ExtensionType.renegotiation_info);
+                    bool noRenegExt = (null == serverRenegExtData);
+
+                    if (noRenegExt)
+                    {
+                        /*
+                         * Note that sending a "renegotiation_info" extension in response to a ClientHello
+                         * containing only the SCSV is an explicit exception to the prohibition in RFC 5246,
+                         * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed
+                         * because the client is signaling its willingness to receive the extension via the
+                         * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+                         */
+
+                        /*
+                         * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty
+                         * "renegotiation_info" extension in the ServerHello message.
+                         */
+                        this.m_serverExtensions[ExtensionType.renegotiation_info] = CreateRenegotiationInfo(
+                            TlsUtilities.EmptyBytes);
+                    }
+                }
+            }
+
+            /*
+             * RFC 7627 4. Clients and servers SHOULD NOT accept handshakes that do not use the extended
+             * master secret [..]. (and see 5.2, 5.3)
+             */
+            if (m_resumedSession)
+            {
+                if (!m_sessionParameters.IsExtendedMasterSecret)
+                {
+                    /*
+                     * TODO[resumption] ProvTlsServer currently only resumes EMS sessions. Revisit this
+                     * in relation to 'tlsServer.allowLegacyResumption()'.
+                     */
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+
+                if (!offeredExtendedMasterSecret)
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                securityParameters.m_extendedMasterSecret = true;
+
+                TlsExtensionsUtilities.AddExtendedMasterSecretExtension(m_serverExtensions);
+            }
+            else
+            {
+                securityParameters.m_extendedMasterSecret = offeredExtendedMasterSecret && !serverVersion.IsSsl
+                    && m_tlsServer.ShouldUseExtendedMasterSecret();
+
+                if (securityParameters.IsExtendedMasterSecret)
+                {
+                    TlsExtensionsUtilities.AddExtendedMasterSecretExtension(m_serverExtensions);
+                }
+                else if (m_tlsServer.RequiresExtendedMasterSecret())
+                {
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+                }
+            }
+
+            securityParameters.m_applicationProtocol = TlsExtensionsUtilities.GetAlpnExtensionServer(m_serverExtensions);
+            securityParameters.m_applicationProtocolSet = true;
+
+            if (m_serverExtensions.Count > 0)
+            {
+                securityParameters.m_encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(
+                    m_serverExtensions);
+
+                securityParameters.m_maxFragmentLength = ProcessMaxFragmentLengthExtension(m_clientExtensions,
+                    m_serverExtensions, AlertDescription.internal_error);
+
+                securityParameters.m_truncatedHmac = TlsExtensionsUtilities.HasTruncatedHmacExtension(
+                    m_serverExtensions);
+
+                if (!m_resumedSession)
+                {
+                    if (TlsUtilities.HasExpectedEmptyExtensionData(m_serverExtensions, ExtensionType.status_request_v2,
+                        AlertDescription.internal_error))
+                    {
+                        securityParameters.m_statusRequestVersion = 2;
+                    }
+                    else if (TlsUtilities.HasExpectedEmptyExtensionData(m_serverExtensions, ExtensionType.status_request,
+                        AlertDescription.internal_error))
+                    {
+                        securityParameters.m_statusRequestVersion = 1;
+                    }
+
+                    this.m_expectSessionTicket = TlsUtilities.HasExpectedEmptyExtensionData(m_serverExtensions,
+                        ExtensionType.session_ticket, AlertDescription.internal_error);
+                }
+            }
+
+            ApplyMaxFragmentLengthExtension(securityParameters.MaxFragmentLength);
+
+            return new ServerHello(serverVersion, securityParameters.ServerRandom, m_tlsSession.SessionID,
+                securityParameters.CipherSuite, m_serverExtensions);
+        }
+
+        protected override TlsContext Context
+        {
+            get { return m_tlsServerContext; }
+        }
+
+        internal override AbstractTlsContext ContextAdmin
+        {
+            get { return m_tlsServerContext; }
+        }
+
+        protected override TlsPeer Peer
+        {
+            get { return m_tlsServer; }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Handle13HandshakeMessage(short type, HandshakeMessageInput buf)
+        {
+            if (!IsTlsV13ConnectionState())
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (m_resumedSession)
+            {
+                /*
+                 * TODO[tls13] Abbreviated handshakes (PSK resumption)
+                 * 
+                 * NOTE: No CertificateRequest, Certificate, CertificateVerify messages, but client
+                 * might now send EndOfEarlyData after receiving server Finished message.
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            switch (type)
+            {
+            case HandshakeType.certificate:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_FINISHED:
+                {
+                    Receive13ClientCertificate(buf);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate_verify:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_CERTIFICATE:
+                {
+                    Receive13ClientCertificateVerify(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE_VERIFY;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.client_hello:
+            {
+                switch (m_connectionState)
+                {
+                case CS_START:
+                {
+                    // NOTE: Legacy handler should be dispatching initial ClientHello.
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+                case CS_SERVER_HELLO_RETRY_REQUEST:
+                {
+                    ClientHello clientHelloRetry = ReceiveClientHelloMessage(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_HELLO_RETRY;
+
+                    ServerHello serverHello = Generate13ServerHello(clientHelloRetry, true);
+                    SendServerHelloMessage(serverHello);
+                    this.m_connectionState = CS_SERVER_HELLO;
+
+                    Send13ServerHelloCoda(serverHello, true);
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.finished:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_FINISHED:
+                case CS_CLIENT_CERTIFICATE:
+                case CS_CLIENT_CERTIFICATE_VERIFY:
+                {
+                    if (m_connectionState == CS_SERVER_FINISHED)
+                    {
+                        Skip13ClientCertificate();
+                    }
+                    if (m_connectionState != CS_CLIENT_CERTIFICATE_VERIFY)
+                    {
+                        Skip13ClientCertificateVerify();
+                    }
+
+                    Receive13ClientFinished(buf);
+                    this.m_connectionState = CS_CLIENT_FINISHED;
+
+                    // See RFC 8446 D.4.
+                    m_recordStream.SetIgnoreChangeCipherSpec(false);
+
+                    // NOTE: Completes the switch to application-data phase (server entered after CS_SERVER_FINISHED).
+                    m_recordStream.EnablePendingCipherRead(false);
+
+                    CompleteHandshake();
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.key_update:
+            {
+                Receive13KeyUpdate(buf);
+                break;
+            }
+
+            case HandshakeType.certificate_request:
+            case HandshakeType.certificate_status:
+            case HandshakeType.certificate_url:
+            case HandshakeType.client_key_exchange:
+            case HandshakeType.encrypted_extensions:
+            case HandshakeType.end_of_early_data:
+            case HandshakeType.hello_request:
+            case HandshakeType.hello_verify_request:
+            case HandshakeType.message_hash:
+            case HandshakeType.new_session_ticket:
+            case HandshakeType.server_hello:
+            case HandshakeType.server_hello_done:
+            case HandshakeType.server_key_exchange:
+            case HandshakeType.supplemental_data:
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        protected override void HandleHandshakeMessage(short type, HandshakeMessageInput buf)
+        {
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+
+            if (m_connectionState > CS_CLIENT_HELLO
+                && TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
+            {
+                Handle13HandshakeMessage(type, buf);
+                return;
+            }
+
+            if (!IsLegacyConnectionState())
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (m_resumedSession)
+            {
+                if (type != HandshakeType.finished || m_connectionState != CS_SERVER_FINISHED)
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                ProcessFinishedMessage(buf);
+                this.m_connectionState = CS_CLIENT_FINISHED;
+
+                CompleteHandshake();
+                return;
+            }
+
+            switch (type)
+            {
+            case HandshakeType.client_hello:
+            {
+                if (IsApplicationDataReady)
+                {
+                    RefuseRenegotiation();
+                    break;
+                }
+
+                switch (m_connectionState)
+                {
+                case CS_END:
+                {
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+                }
+                case CS_START:
+                {
+                    ClientHello clientHello = ReceiveClientHelloMessage(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_HELLO;
+
+                    ServerHello serverHello = GenerateServerHello(clientHello);
+                    m_handshakeHash.NotifyPrfDetermined();
+
+                    if (TlsUtilities.IsTlsV13(securityParameters.NegotiatedVersion))
+                    {
+                        if (serverHello.IsHelloRetryRequest())
+                        {
+                            TlsUtilities.AdjustTranscriptForRetry(m_handshakeHash);
+                            SendServerHelloMessage(serverHello);
+                            this.m_connectionState = CS_SERVER_HELLO_RETRY_REQUEST;
+
+                            // See RFC 8446 D.4.
+                            SendChangeCipherSpecMessage();
+                        }
+                        else
+                        {
+                            SendServerHelloMessage(serverHello);
+                            this.m_connectionState = CS_SERVER_HELLO;
+
+                            // See RFC 8446 D.4.
+                            SendChangeCipherSpecMessage();
+
+                            Send13ServerHelloCoda(serverHello, false);
+                        }
+                        break;
+                    }
+
+                    SendServerHelloMessage(serverHello);
+                    this.m_connectionState = CS_SERVER_HELLO;
+
+                    if (m_resumedSession)
+                    {
+                        securityParameters.m_masterSecret = m_sessionMasterSecret;
+                        m_recordStream.SetPendingCipher(TlsUtilities.InitCipher(m_tlsServerContext));
+
+                        SendChangeCipherSpec();
+                        SendFinishedMessage();
+                        this.m_connectionState = CS_SERVER_FINISHED;
+                        break;
+                    }
+
+                    IList serverSupplementalData = m_tlsServer.GetServerSupplementalData();
+                    if (serverSupplementalData != null)
+                    {
+                        SendSupplementalDataMessage(serverSupplementalData);
+                        this.m_connectionState = CS_SERVER_SUPPLEMENTAL_DATA;
+                    }
+
+                    this.m_keyExchange = TlsUtilities.InitKeyExchangeServer(m_tlsServerContext, m_tlsServer);
+
+                    TlsCredentials serverCredentials = TlsUtilities.EstablishServerCredentials(m_tlsServer);
+
+                    // Server certificate
+                    {
+                        Certificate serverCertificate = null;
+
+                        MemoryStream endPointHash = new MemoryStream();
+                        if (null == serverCredentials)
+                        {
+                            m_keyExchange.SkipServerCredentials();
+                        }
+                        else
+                        {
+                            m_keyExchange.ProcessServerCredentials(serverCredentials);
+
+                            serverCertificate = serverCredentials.Certificate;
+                            SendCertificateMessage(serverCertificate, endPointHash);
+                            this.m_connectionState = CS_SERVER_CERTIFICATE;
+                        }
+
+                        securityParameters.m_tlsServerEndPoint = endPointHash.ToArray();
+
+                        // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes
+                        // CertificateStatus
+                        if (null == serverCertificate || serverCertificate.IsEmpty)
+                        {
+                            securityParameters.m_statusRequestVersion = 0;
+                        }
+                    }
+
+                    if (securityParameters.StatusRequestVersion > 0)
+                    {
+                        CertificateStatus certificateStatus = m_tlsServer.GetCertificateStatus();
+                        if (certificateStatus != null)
+                        {
+                            SendCertificateStatusMessage(certificateStatus);
+                            this.m_connectionState = CS_SERVER_CERTIFICATE_STATUS;
+                        }
+                    }
+
+                    byte[] serverKeyExchange = m_keyExchange.GenerateServerKeyExchange();
+                    if (serverKeyExchange != null)
+                    {
+                        SendServerKeyExchangeMessage(serverKeyExchange);
+                        this.m_connectionState = CS_SERVER_KEY_EXCHANGE;
+                    }
+
+                    if (null != serverCredentials)
+                    {
+                        this.m_certificateRequest = m_tlsServer.GetCertificateRequest();
+
+                        if (null == m_certificateRequest)
+                        {
+                            /*
+                             * For static agreement key exchanges, CertificateRequest is required since
+                             * the client Certificate message is mandatory but can only be sent if the
+                             * server requests it.
+                             */
+                            if (!m_keyExchange.RequiresCertificateVerify)
+                                throw new TlsFatalAlert(AlertDescription.internal_error);
+                        }
+                        else
+                        {
+                            if (TlsUtilities.IsTlsV12(m_tlsServerContext)
+                                != (m_certificateRequest.SupportedSignatureAlgorithms != null))
+                            {
+                                throw new TlsFatalAlert(AlertDescription.internal_error);
+                            }
+
+                            this.m_certificateRequest = TlsUtilities.ValidateCertificateRequest(m_certificateRequest,
+                                m_keyExchange);
+
+                            TlsUtilities.EstablishServerSigAlgs(securityParameters, m_certificateRequest);
+
+                            TlsUtilities.TrackHashAlgorithms(m_handshakeHash, securityParameters.ServerSigAlgs);
+
+                            SendCertificateRequestMessage(m_certificateRequest);
+                            this.m_connectionState = CS_SERVER_CERTIFICATE_REQUEST;
+                        }
+                    }
+
+                    SendServerHelloDoneMessage();
+                    this.m_connectionState = CS_SERVER_HELLO_DONE;
+
+                    bool forceBuffering = false;
+                    TlsUtilities.SealHandshakeHash(m_tlsServerContext, m_handshakeHash, forceBuffering);
+
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.supplemental_data:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                {
+                    m_tlsServer.ProcessClientSupplementalData(ReadSupplementalDataMessage(buf));
+                    this.m_connectionState = CS_CLIENT_SUPPLEMENTAL_DATA;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                case CS_CLIENT_SUPPLEMENTAL_DATA:
+                {
+                    if (m_connectionState != CS_CLIENT_SUPPLEMENTAL_DATA)
+                    {
+                        m_tlsServer.ProcessClientSupplementalData(null);
+                    }
+
+                    if (m_certificateRequest == null)
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    ReceiveCertificateMessage(buf);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.client_key_exchange:
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                case CS_CLIENT_SUPPLEMENTAL_DATA:
+                case CS_CLIENT_CERTIFICATE:
+                {
+                    if (m_connectionState == CS_SERVER_HELLO_DONE)
+                    {
+                        m_tlsServer.ProcessClientSupplementalData(null);
+                    }
+                    if (m_connectionState != CS_CLIENT_CERTIFICATE)
+                    {
+                        if (null == m_certificateRequest)
+                        {
+                            m_keyExchange.SkipClientCredentials();
+                        }
+                        else if (TlsUtilities.IsTlsV12(m_tlsServerContext))
+                        {
+                            /*
+                             * RFC 5246 If no suitable certificate is available, the client MUST send a
+                             * certificate message containing no certificates.
+                             * 
+                             * NOTE: In previous RFCs, this was SHOULD instead of MUST.
+                             */
+                            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                        }
+                        else if (TlsUtilities.IsSsl(m_tlsServerContext))
+                        {
+                            /*
+                             * SSL 3.0 If the server has sent a certificate request Message, the client must
+                             * send either the certificate message or a no_certificate alert.
+                             */
+                            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                        }
+                        else
+                        {
+                            NotifyClientCertificate(Certificate.EmptyChain);
+                        }
+                    }
+
+                    ReceiveClientKeyExchangeMessage(buf);
+                    this.m_connectionState = CS_CLIENT_KEY_EXCHANGE;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.certificate_verify:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_KEY_EXCHANGE:
+                {
+                    /*
+                     * RFC 5246 7.4.8 This message is only sent following a client certificate that has
+                     * signing capability (i.e., all certificates except those containing fixed
+                     * Diffie-Hellman parameters).
+                     */
+                    if (!ExpectCertificateVerifyMessage())
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    ReceiveCertificateVerifyMessage(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE_VERIFY;
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+            case HandshakeType.finished:
+            {
+                switch (m_connectionState)
+                {
+                case CS_CLIENT_KEY_EXCHANGE:
+                case CS_CLIENT_CERTIFICATE_VERIFY:
+                {
+                    if (m_connectionState != CS_CLIENT_CERTIFICATE_VERIFY)
+                    {
+                        if (ExpectCertificateVerifyMessage())
+                            throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                    }
+
+                    ProcessFinishedMessage(buf);
+                    buf.UpdateHash(m_handshakeHash);
+                    this.m_connectionState = CS_CLIENT_FINISHED;
+
+                    if (m_expectSessionTicket)
+                    {
+                        SendNewSessionTicketMessage(m_tlsServer.GetNewSessionTicket());
+                        this.m_connectionState = CS_SERVER_SESSION_TICKET;
+                    }
+
+                    SendChangeCipherSpec();
+                    SendFinishedMessage();
+                    this.m_connectionState = CS_SERVER_FINISHED;
+
+                    CompleteHandshake();
+                    break;
+                }
+                default:
+                    throw new TlsFatalAlert(AlertDescription.unexpected_message);
+                }
+                break;
+            }
+
+            case HandshakeType.certificate_request:
+            case HandshakeType.certificate_status:
+            case HandshakeType.certificate_url:
+            case HandshakeType.encrypted_extensions:
+            case HandshakeType.end_of_early_data:
+            case HandshakeType.hello_request:
+            case HandshakeType.hello_verify_request:
+            case HandshakeType.key_update:
+            case HandshakeType.message_hash:
+            case HandshakeType.new_session_ticket:
+            case HandshakeType.server_hello:
+            case HandshakeType.server_hello_done:
+            case HandshakeType.server_key_exchange:
+            default:
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+            }
+        }
+
+        protected override void HandleAlertWarningMessage(short alertDescription)
+        {
+            /*
+             * SSL 3.0 If the server has sent a certificate request Message, the client must send
+             * either the certificate message or a no_certificate alert.
+             */
+            if (AlertDescription.no_certificate == alertDescription && null != m_certificateRequest
+                && TlsUtilities.IsSsl(m_tlsServerContext))
+            {
+                switch (m_connectionState)
+                {
+                case CS_SERVER_HELLO_DONE:
+                case CS_CLIENT_SUPPLEMENTAL_DATA:
+                {
+                    if (m_connectionState != CS_CLIENT_SUPPLEMENTAL_DATA)
+                    {
+                        m_tlsServer.ProcessClientSupplementalData(null);
+                    }
+
+                    NotifyClientCertificate(Certificate.EmptyChain);
+                    this.m_connectionState = CS_CLIENT_CERTIFICATE;
+                    return;
+                }
+                }
+            }
+
+            base.HandleAlertWarningMessage(alertDescription);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            if (null == m_certificateRequest)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            TlsUtilities.ProcessClientCertificate(m_tlsServerContext, clientCertificate, m_keyExchange, m_tlsServer);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13ClientCertificate(MemoryStream buf)
+        {
+            // TODO[tls13] This currently just duplicates 'receiveCertificateMessage'
+
+            Certificate.ParseOptions options = new Certificate.ParseOptions()
+                .SetMaxChainLength(m_tlsServer.GetMaxCertificateChainLength());
+
+            Certificate clientCertificate = Certificate.Parse(options, m_tlsServerContext, buf, null);
+
+            AssertEmpty(buf);
+
+            NotifyClientCertificate(clientCertificate);
+        }
+
+        /// <exception cref="IOException"/>
+        protected void Receive13ClientCertificateVerify(MemoryStream buf)
+        {
+            Certificate clientCertificate = m_tlsServerContext.SecurityParameters.PeerCertificate;
+            if (null == clientCertificate || clientCertificate.IsEmpty)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            // TODO[tls13] Actual structure is 'CertificateVerify' in RFC 8446, consider adding for clarity
+            DigitallySigned certificateVerify = DigitallySigned.Parse(m_tlsServerContext, buf);
+
+            AssertEmpty(buf);
+
+            TlsUtilities.Verify13CertificateVerifyClient(m_tlsServerContext, m_certificateRequest, certificateVerify,
+                m_handshakeHash);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Receive13ClientFinished(MemoryStream buf)
+        {
+            Process13FinishedMessage(buf);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReceiveCertificateMessage(MemoryStream buf)
+        {
+            Certificate.ParseOptions options = new Certificate.ParseOptions()
+                .SetMaxChainLength(m_tlsServer.GetMaxCertificateChainLength());
+
+            Certificate clientCertificate = Certificate.Parse(options, m_tlsServerContext, buf, null);
+
+            AssertEmpty(buf);
+
+            NotifyClientCertificate(clientCertificate);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReceiveCertificateVerifyMessage(MemoryStream buf)
+        {
+            DigitallySigned certificateVerify = DigitallySigned.Parse(m_tlsServerContext, buf);
+
+            AssertEmpty(buf);
+
+            TlsUtilities.VerifyCertificateVerifyClient(m_tlsServerContext, m_certificateRequest, certificateVerify,
+                m_handshakeHash);
+
+            this.m_handshakeHash = m_handshakeHash.StopTracking();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual ClientHello ReceiveClientHelloMessage(MemoryStream buf)
+        {
+            return ClientHello.Parse(buf, null);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ReceiveClientKeyExchangeMessage(MemoryStream buf)
+        {
+            m_keyExchange.ProcessClientKeyExchange(buf);
+
+            AssertEmpty(buf);
+
+            bool isSsl = TlsUtilities.IsSsl(m_tlsServerContext);
+            if (isSsl)
+            {
+                // NOTE: For SSLv3 (only), master_secret needed to calculate session hash
+                EstablishMasterSecret(m_tlsServerContext, m_keyExchange);
+            }
+
+            m_tlsServerContext.SecurityParameters.m_sessionHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+            if (!isSsl)
+            {
+                // NOTE: For (D)TLS, session hash potentially needed for extended_master_secret
+                EstablishMasterSecret(m_tlsServerContext, m_keyExchange);
+            }
+
+            m_recordStream.SetPendingCipher(TlsUtilities.InitCipher(m_tlsServerContext));
+
+            if (!ExpectCertificateVerifyMessage())
+            {
+                this.m_handshakeHash = m_handshakeHash.StopTracking();
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13EncryptedExtensionsMessage(IDictionary serverExtensions)
+        {
+            // TODO[tls13] Avoid extra copy; use placeholder to write opaque-16 data directly to message buffer
+
+            byte[] extBytes = WriteExtensionsData(serverExtensions);
+
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.encrypted_extensions);
+            TlsUtilities.WriteOpaque16(extBytes, message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Send13ServerHelloCoda(ServerHello serverHello, bool afterHelloRetryRequest)
+        {
+            SecurityParameters securityParameters = m_tlsServerContext.SecurityParameters;
+
+            byte[] serverHelloTranscriptHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+            TlsUtilities.Establish13PhaseHandshake(m_tlsServerContext, serverHelloTranscriptHash, m_recordStream);
+
+            m_recordStream.EnablePendingCipherWrite();
+            m_recordStream.EnablePendingCipherRead(true);
+
+            Send13EncryptedExtensionsMessage(m_serverExtensions);
+            this.m_connectionState = CS_SERVER_ENCRYPTED_EXTENSIONS;
+
+            // CertificateRequest
+            {
+                this.m_certificateRequest = m_tlsServer.GetCertificateRequest();
+                if (null != m_certificateRequest)
+                {
+                    if (!m_certificateRequest.HasCertificateRequestContext(TlsUtilities.EmptyBytes))
+                        throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                    TlsUtilities.EstablishServerSigAlgs(securityParameters, m_certificateRequest);
+
+                    SendCertificateRequestMessage(m_certificateRequest);
+                    this.m_connectionState = CS_SERVER_CERTIFICATE_REQUEST;
+                }
+            }
+
+            /*
+             * TODO[tls13] For PSK-only key exchange, there's no Certificate message.
+             */
+
+            TlsCredentialedSigner serverCredentials = TlsUtilities.Establish13ServerCredentials(m_tlsServer);
+            if (null == serverCredentials)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            // Certificate
+            {
+                /*
+                 * TODO[tls13] Note that we are expecting the TlsServer implementation to take care of
+                 * e.g. adding optional "status_request" extension to each CertificateEntry.
+                 */
+                /*
+                 * No CertificateStatus message is sent; TLS 1.3 uses per-CertificateEntry
+                 * "status_request" extension instead.
+                 */
+
+                Certificate serverCertificate = serverCredentials.Certificate;
+                Send13CertificateMessage(serverCertificate);
+                securityParameters.m_tlsServerEndPoint = null;
+                this.m_connectionState = CS_SERVER_CERTIFICATE;
+            }
+
+            // CertificateVerify
+            {
+                DigitallySigned certificateVerify = TlsUtilities.Generate13CertificateVerify(m_tlsServerContext,
+                    serverCredentials, m_handshakeHash);
+                Send13CertificateVerifyMessage(certificateVerify);
+                this.m_connectionState = CS_CLIENT_CERTIFICATE_VERIFY;
+            }
+
+            // Finished
+            {
+                Send13FinishedMessage();
+                this.m_connectionState = CS_SERVER_FINISHED;
+            }
+
+            byte[] serverFinishedTranscriptHash = TlsUtilities.GetCurrentPrfHash(m_handshakeHash);
+
+            TlsUtilities.Establish13PhaseApplication(m_tlsServerContext, serverFinishedTranscriptHash, m_recordStream);
+
+            m_recordStream.EnablePendingCipherWrite();
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendCertificateRequestMessage(CertificateRequest certificateRequest)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_request);
+            certificateRequest.Encode(m_tlsServerContext, message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendCertificateStatusMessage(CertificateStatus certificateStatus)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_status);
+            // TODO[tls13] Ensure this cannot happen for (D)TLS1.3+
+            certificateStatus.Encode(message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendHelloRequestMessage()
+        {
+            HandshakeMessageOutput.Send(this, HandshakeType.hello_request, TlsUtilities.EmptyBytes);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendNewSessionTicketMessage(NewSessionTicket newSessionTicket)
+        {
+            if (newSessionTicket == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.new_session_ticket);
+            newSessionTicket.Encode(message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendServerHelloDoneMessage()
+        {
+            HandshakeMessageOutput.Send(this, HandshakeType.server_hello_done, TlsUtilities.EmptyBytes);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendServerHelloMessage(ServerHello serverHello)
+        {
+            HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.server_hello);
+            serverHello.Encode(m_tlsServerContext, message);
+            message.Send(this);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void SendServerKeyExchangeMessage(byte[] serverKeyExchange)
+        {
+            HandshakeMessageOutput.Send(this, HandshakeType.server_key_exchange, serverKeyExchange);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Skip13ClientCertificate()
+        {
+            if (null != m_certificateRequest)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void Skip13ClientCertificateVerify()
+        {
+            if (ExpectCertificateVerifyMessage())
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsSession.cs b/crypto/src/tls/TlsSession.cs
new file mode 100644
index 000000000..bc68757c2
--- /dev/null
+++ b/crypto/src/tls/TlsSession.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for a carrier object for a TLS session.</summary>
+    public interface TlsSession
+    {
+        SessionParameters ExportSessionParameters();
+
+        byte[] SessionID { get; }
+
+        void Invalidate();
+
+        bool IsResumable { get; }
+    }
+}
diff --git a/crypto/src/tls/TlsSessionImpl.cs b/crypto/src/tls/TlsSessionImpl.cs
new file mode 100644
index 000000000..a4eb6b94a
--- /dev/null
+++ b/crypto/src/tls/TlsSessionImpl.cs
@@ -0,0 +1,52 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal class TlsSessionImpl
+        : TlsSession
+    {
+        private readonly byte[] m_sessionID;
+        private readonly SessionParameters m_sessionParameters;
+        private bool m_resumable;
+
+        internal TlsSessionImpl(byte[] sessionID, SessionParameters sessionParameters)
+        {
+            if (sessionID == null)
+                throw new ArgumentNullException("sessionID");
+            if (sessionID.Length > 32)
+                throw new ArgumentException("cannot be longer than 32 bytes", "sessionID");
+
+            this.m_sessionID = Arrays.Clone(sessionID);
+            this.m_sessionParameters = sessionParameters;
+            this.m_resumable = sessionID.Length > 0 && null != sessionParameters;
+        }
+
+        public SessionParameters ExportSessionParameters()
+        {
+            lock (this)
+            {
+                return m_sessionParameters == null ? null : m_sessionParameters.Copy();
+            }
+        }
+
+        public byte[] SessionID
+        {
+            get { lock (this) return m_sessionID; }
+        }
+
+        public void Invalidate()
+        {
+            lock (this)
+            {
+                this.m_resumable = false;
+            }
+        }
+
+        public bool IsResumable
+        {
+            get { lock (this) return m_resumable; }
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsSrpConfigVerifier.cs b/crypto/src/tls/TlsSrpConfigVerifier.cs
new file mode 100644
index 000000000..e4d83ab24
--- /dev/null
+++ b/crypto/src/tls/TlsSrpConfigVerifier.cs
@@ -0,0 +1,15 @@
+using System;
+
+using Org.BouncyCastle.Tls.Crypto;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Interface for verifying SRP config needs to conform to.</summary>
+    public interface TlsSrpConfigVerifier
+    {
+        /// <summary>Check whether the given SRP configuration is acceptable for use.</summary>
+        /// <param name="srpConfig">the <see cref="TlsSrpConfig"/> to check.</param>
+        /// <returns>true if (and only if) the specified configuration is acceptable.</returns>
+        bool Accept(TlsSrpConfig srpConfig);
+    }
+}
diff --git a/crypto/src/tls/TlsSrpIdentity.cs b/crypto/src/tls/TlsSrpIdentity.cs
new file mode 100644
index 000000000..c0492e4a2
--- /dev/null
+++ b/crypto/src/tls/TlsSrpIdentity.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Processor interface for an SRP identity.</summary>
+    public interface TlsSrpIdentity
+    {
+        byte[] GetSrpIdentity();
+
+        byte[] GetSrpPassword();
+    }
+}
diff --git a/crypto/src/tls/TlsSrpIdentityManager.cs b/crypto/src/tls/TlsSrpIdentityManager.cs
new file mode 100644
index 000000000..1cc2840be
--- /dev/null
+++ b/crypto/src/tls/TlsSrpIdentityManager.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>Base interface for an object that can return login parameters from an SRP identity.</summary>
+    public interface TlsSrpIdentityManager
+    {
+        /// <summary>Lookup the <see cref="TlsSrpLoginParameters"/> corresponding to the specified identity.</summary>
+        /// <remarks>
+        /// NOTE: To avoid "identity probing", unknown identities SHOULD be handled as recommended in RFC 5054 2.5.1.3.
+        /// <see cref="SimulatedTlsSrpIdentityManager"/> is provided for this purpose.
+        /// </remarks>
+        /// <param name="identity">the SRP identity sent by the connecting client.</param>
+        /// <returns>the <see cref="TlsSrpLoginParameters"/> for the specified identity, or else 'simulated' parameters
+        /// if the identity is not recognized. A null value is also allowed, but not recommended.</returns>
+        TlsSrpLoginParameters GetLoginParameters(byte[] identity);
+    }
+}
diff --git a/crypto/src/tls/TlsSrpKeyExchange.cs b/crypto/src/tls/TlsSrpKeyExchange.cs
new file mode 100644
index 000000000..835523e36
--- /dev/null
+++ b/crypto/src/tls/TlsSrpKeyExchange.cs
@@ -0,0 +1,186 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>(D)TLS SRP key exchange (RFC 5054).</summary>
+    public class TlsSrpKeyExchange
+        : AbstractTlsKeyExchange
+    {
+        private static int CheckKeyExchange(int keyExchange)
+        {
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.SRP:
+            case KeyExchangeAlgorithm.SRP_DSS:
+            case KeyExchangeAlgorithm.SRP_RSA:
+                return keyExchange;
+            default:
+                throw new ArgumentException("unsupported key exchange algorithm", "keyExchange");
+            }
+        }
+
+        protected TlsSrpIdentity m_srpIdentity;
+        protected TlsSrpConfigVerifier m_srpConfigVerifier;
+        protected TlsCertificate m_serverCertificate = null;
+        protected byte[] m_srpSalt = null;
+        protected TlsSrp6Client m_srpClient = null;
+
+        protected TlsSrpLoginParameters m_srpLoginParameters;
+        protected TlsCredentialedSigner m_serverCredentials = null;
+        protected TlsSrp6Server m_srpServer = null;
+
+        protected BigInteger m_srpPeerCredentials = null;
+
+        public TlsSrpKeyExchange(int keyExchange, TlsSrpIdentity srpIdentity, TlsSrpConfigVerifier srpConfigVerifier)
+            : base(CheckKeyExchange(keyExchange))
+        {
+            this.m_srpIdentity = srpIdentity;
+            this.m_srpConfigVerifier = srpConfigVerifier;
+        }
+
+        public TlsSrpKeyExchange(int keyExchange, TlsSrpLoginParameters srpLoginParameters)
+            : base(CheckKeyExchange(keyExchange))
+        {
+            this.m_srpLoginParameters = srpLoginParameters;
+        }
+
+        public override void SkipServerCredentials()
+        {
+            if (m_keyExchange != KeyExchangeAlgorithm.SRP)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void ProcessServerCredentials(TlsCredentials serverCredentials)
+        {
+            if (m_keyExchange == KeyExchangeAlgorithm.SRP)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_serverCredentials = TlsUtilities.RequireSignerCredentials(serverCredentials);
+        }
+
+        public override void ProcessServerCertificate(Certificate serverCertificate)
+        {
+            if (m_keyExchange == KeyExchangeAlgorithm.SRP)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_serverCertificate = serverCertificate.GetCertificateAt(0);
+        }
+
+        public override bool RequiresServerKeyExchange
+        {
+            get { return true; }
+        }
+
+        public override byte[] GenerateServerKeyExchange()
+        {
+            TlsSrpConfig config = m_srpLoginParameters.Config;
+
+            this.m_srpServer = m_context.Crypto.CreateSrp6Server(config, m_srpLoginParameters.Verifier);
+
+            BigInteger B = m_srpServer.GenerateServerCredentials();
+
+            BigInteger[] ng = config.GetExplicitNG();
+            ServerSrpParams srpParams = new ServerSrpParams(ng[0], ng[1], m_srpLoginParameters.Salt, B);
+
+            DigestInputBuffer digestBuffer = new DigestInputBuffer();
+
+            srpParams.Encode(digestBuffer);
+
+            if (m_serverCredentials != null)
+            {
+                TlsUtilities.GenerateServerKeyExchangeSignature(m_context, m_serverCredentials, digestBuffer);
+            }
+
+            return digestBuffer.ToArray();
+        }
+
+        public override void ProcessServerKeyExchange(Stream input)
+        {
+            DigestInputBuffer digestBuffer = null;
+            Stream teeIn = input;
+
+            if (m_keyExchange != KeyExchangeAlgorithm.SRP)
+            {
+                digestBuffer = new DigestInputBuffer();
+                teeIn = new TeeInputStream(input, digestBuffer);
+            }
+
+            ServerSrpParams srpParams = ServerSrpParams.Parse(teeIn);
+
+            if (digestBuffer != null)
+            {
+                TlsUtilities.VerifyServerKeyExchangeSignature(m_context, input, m_serverCertificate, digestBuffer);
+            }
+
+            TlsSrpConfig config = new TlsSrpConfig();
+            config.SetExplicitNG(new BigInteger[]{ srpParams.N, srpParams.G });
+
+            if (!m_srpConfigVerifier.Accept(config))
+                throw new TlsFatalAlert(AlertDescription.insufficient_security);
+
+            this.m_srpSalt = srpParams.S;
+
+            /*
+             * RFC 5054 2.5.3: The client MUST abort the handshake with an "illegal_parameter" alert if
+             * B % N = 0.
+             */
+            this.m_srpPeerCredentials = ValidatePublicValue(srpParams.N, srpParams.B);
+            this.m_srpClient = m_context.Crypto.CreateSrp6Client(config);
+        }
+
+        public override void ProcessClientCredentials(TlsCredentials clientCredentials)
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public override void GenerateClientKeyExchange(Stream output)
+        {
+            byte[] identity = m_srpIdentity.GetSrpIdentity();
+            byte[] password = m_srpIdentity.GetSrpPassword();
+
+            BigInteger A = m_srpClient.GenerateClientCredentials(m_srpSalt, identity, password);
+            TlsSrpUtilities.WriteSrpParameter(A, output);
+
+            m_context.SecurityParameters.m_srpIdentity = Arrays.Clone(identity);
+        }
+
+        public override void ProcessClientKeyExchange(Stream input)
+        {
+            /*
+             * RFC 5054 2.5.4: The server MUST abort the handshake with an "illegal_parameter" alert if
+             * A % N = 0.
+             */
+            this.m_srpPeerCredentials = ValidatePublicValue(m_srpLoginParameters.Config.GetExplicitNG()[0],
+                TlsSrpUtilities.ReadSrpParameter(input));
+
+            m_context.SecurityParameters.m_srpIdentity = Arrays.Clone(m_srpLoginParameters.Identity);
+        }
+
+        public override TlsSecret GeneratePreMasterSecret()
+        {
+            BigInteger S = m_srpServer != null
+                ?   m_srpServer.CalculateSecret(m_srpPeerCredentials)
+                :   m_srpClient.CalculateSecret(m_srpPeerCredentials);
+
+            // TODO Check if this needs to be a fixed size
+            return m_context.Crypto.CreateSecret(BigIntegers.AsUnsignedByteArray(S));
+        }
+
+        protected static BigInteger ValidatePublicValue(BigInteger N, BigInteger val)
+        {
+            val = val.Mod(N);
+
+            // Check that val % N != 0
+            if (val.Equals(BigInteger.Zero))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return val;
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsSrpLoginParameters.cs b/crypto/src/tls/TlsSrpLoginParameters.cs
new file mode 100644
index 000000000..2dda942a3
--- /dev/null
+++ b/crypto/src/tls/TlsSrpLoginParameters.cs
@@ -0,0 +1,44 @@
+using System;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsSrpLoginParameters
+    {
+        protected byte[] m_identity;
+        protected TlsSrpConfig m_srpConfig;
+        protected BigInteger m_verifier;
+        protected byte[] m_salt;
+
+        public TlsSrpLoginParameters(byte[] identity, TlsSrpConfig srpConfig, BigInteger verifier, byte[] salt)
+        {
+            this.m_identity = Arrays.Clone(identity);
+            this.m_srpConfig = srpConfig;
+            this.m_verifier = verifier;
+            this.m_salt = Arrays.Clone(salt);
+        }
+
+        public virtual TlsSrpConfig Config
+        {
+            get { return m_srpConfig; }
+        }
+
+        public virtual byte[] Identity
+        {
+            get { return m_identity; }
+        }
+
+        public virtual byte[] Salt
+        {
+            get { return m_salt; }
+        }
+
+        public virtual BigInteger Verifier
+        {
+            get { return m_verifier; }
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsSrpUtilities.cs b/crypto/src/tls/TlsSrpUtilities.cs
new file mode 100644
index 000000000..c36a667ac
--- /dev/null
+++ b/crypto/src/tls/TlsSrpUtilities.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class TlsSrpUtilities
+    {
+        /// <exception cref="IOException"/>
+        public static void AddSrpExtension(IDictionary extensions, byte[] identity)
+        {
+            extensions[ExtensionType.srp] = CreateSrpExtension(identity);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] GetSrpExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.srp);
+            return extensionData == null ? null : ReadSrpExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateSrpExtension(byte[] identity)
+        {
+            if (identity == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return TlsUtilities.EncodeOpaque8(identity);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] ReadSrpExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            return TlsUtilities.DecodeOpaque8(extensionData, 1);
+        }
+
+        /// <exception cref="IOException"/>
+        public static BigInteger ReadSrpParameter(Stream input)
+        {
+            return new BigInteger(1, TlsUtilities.ReadOpaque16(input, 1));
+        }
+
+        /// <exception cref="IOException"/>
+        public static void WriteSrpParameter(BigInteger x, Stream output)
+        {
+            TlsUtilities.WriteOpaque16(BigIntegers.AsUnsignedByteArray(x), output);
+        }
+
+        public static bool IsSrpCipherSuite(int cipherSuite)
+        {
+            switch (TlsUtilities.GetKeyExchangeAlgorithm(cipherSuite))
+            {
+            case KeyExchangeAlgorithm.SRP:
+            case KeyExchangeAlgorithm.SRP_DSS:
+            case KeyExchangeAlgorithm.SRP_RSA:
+                return true;
+
+            default:
+                return false;
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsSrtpUtilities.cs b/crypto/src/tls/TlsSrtpUtilities.cs
new file mode 100644
index 000000000..72a9e774b
--- /dev/null
+++ b/crypto/src/tls/TlsSrtpUtilities.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5764 DTLS Extension to Establish Keys for SRTP.</summary>
+    public abstract class TlsSrtpUtilities
+{
+        /// <exception cref="IOException"/>
+        public static void AddUseSrtpExtension(IDictionary extensions, UseSrtpData useSrtpData)
+        {
+            extensions[ExtensionType.use_srtp] = CreateUseSrtpExtension(useSrtpData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static UseSrtpData GetUseSrtpExtension(IDictionary extensions)
+        {
+            byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.use_srtp);
+            return extensionData == null ? null : ReadUseSrtpExtension(extensionData);
+        }
+
+        /// <exception cref="IOException"/>
+        public static byte[] CreateUseSrtpExtension(UseSrtpData useSrtpData)
+        {
+            if (useSrtpData == null)
+                throw new ArgumentNullException("useSrtpData");
+
+            MemoryStream buf = new MemoryStream();
+
+            // SRTPProtectionProfiles
+            TlsUtilities.WriteUint16ArrayWithUint16Length(useSrtpData.ProtectionProfiles, buf);
+
+            // srtp_mki
+            TlsUtilities.WriteOpaque8(useSrtpData.Mki, buf);
+
+            return buf.ToArray();
+        }
+
+        /// <exception cref="IOException"/>
+        public static UseSrtpData ReadUseSrtpExtension(byte[] extensionData)
+        {
+            if (extensionData == null)
+                throw new ArgumentNullException("extensionData");
+
+            MemoryStream buf = new MemoryStream(extensionData, false);
+
+            // SRTPProtectionProfiles
+            int length = TlsUtilities.ReadUint16(buf);
+            if (length < 2 || (length & 1) != 0)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int[] protectionProfiles = TlsUtilities.ReadUint16Array(length / 2, buf);
+
+            // srtp_mki
+            byte[] mki = TlsUtilities.ReadOpaque8(buf);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            return new UseSrtpData(protectionProfiles, mki);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsStream.cs b/crypto/src/tls/TlsStream.cs
new file mode 100644
index 000000000..9ca88a2a7
--- /dev/null
+++ b/crypto/src/tls/TlsStream.cs
@@ -0,0 +1,96 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    internal class TlsStream
+        : Stream
+    {
+        private readonly TlsProtocol m_handler;
+
+        internal TlsStream(TlsProtocol handler)
+        {
+            this.m_handler = handler;
+        }
+
+        public override bool CanRead
+        {
+            get { return !m_handler.IsClosed; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return false; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return !m_handler.IsClosed; }
+        }
+
+#if PORTABLE
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                m_handler.Close();
+            }
+            base.Dispose(disposing);
+        }
+#else
+        public override void Close()
+        {
+            m_handler.Close();
+            base.Close();
+        }
+#endif
+
+        public override void Flush()
+        {
+            m_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 m_handler.ReadApplicationData(buf, off, len);
+        }
+
+        public override int ReadByte()
+        {
+            byte[] buf = new byte[1];
+            int ret = Read(buf, 0, 1);
+            return ret <= 0 ? -1 : 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)
+        {
+            m_handler.WriteApplicationData(buf, off, len);
+        }
+
+        public override void WriteByte(byte b)
+        {
+            Write(new byte[]{ b }, 0, 1);
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsTimeoutException.cs b/crypto/src/tls/TlsTimeoutException.cs
new file mode 100644
index 000000000..ce2e1ac63
--- /dev/null
+++ b/crypto/src/tls/TlsTimeoutException.cs
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public class TlsTimeoutException
+        : IOException
+    {
+        public TlsTimeoutException()
+            : base()
+        {
+        }
+
+        public TlsTimeoutException(string message)
+            : base(message)
+        {
+        }
+
+        public TlsTimeoutException(string message, Exception cause)
+            : base(message, cause)
+        {
+        }
+    }
+}
diff --git a/crypto/src/tls/TlsUtilities.cs b/crypto/src/tls/TlsUtilities.cs
new file mode 100644
index 000000000..e7555ba9f
--- /dev/null
+++ b/crypto/src/tls/TlsUtilities.cs
@@ -0,0 +1,5305 @@
+using System;
+using System.Collections;
+using System.IO;
+#if !PORTABLE || DOTNET
+using System.Net.Sockets;
+#endif
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Bsi;
+using Org.BouncyCastle.Asn1.Eac;
+using Org.BouncyCastle.Asn1.EdEC;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Oiw;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Rosstandart;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls
+{
+    public abstract class TlsUtilities
+    {
+        private static readonly byte[] DowngradeTlsV11 = Hex.DecodeStrict("444F574E47524400");
+        private static readonly byte[] DowngradeTlsV12 = Hex.DecodeStrict("444F574E47524401");
+
+        private static readonly IDictionary CertSigAlgOids = CreateCertSigAlgOids();
+        private static readonly IList DefaultSupportedSigAlgs = CreateDefaultSupportedSigAlgs();
+
+        private static void AddCertSigAlgOid(IDictionary d, DerObjectIdentifier oid,
+            SignatureAndHashAlgorithm sigAndHash)
+        {
+            d[oid.Id] = sigAndHash;
+        }
+
+        private static void AddCertSigAlgOid(IDictionary d, DerObjectIdentifier oid, short hashAlgorithm,
+            short signatureAlgorithm)
+        {
+            AddCertSigAlgOid(d, oid, SignatureAndHashAlgorithm.GetInstance(hashAlgorithm, signatureAlgorithm));
+        }
+
+        private static IDictionary CreateCertSigAlgOids()
+        {
+            IDictionary d = Platform.CreateHashtable();
+
+            AddCertSigAlgOid(d, NistObjectIdentifiers.DsaWithSha224, HashAlgorithm.sha224, SignatureAlgorithm.dsa);
+            AddCertSigAlgOid(d, NistObjectIdentifiers.DsaWithSha256, HashAlgorithm.sha256, SignatureAlgorithm.dsa);
+            AddCertSigAlgOid(d, NistObjectIdentifiers.DsaWithSha384, HashAlgorithm.sha384, SignatureAlgorithm.dsa);
+            AddCertSigAlgOid(d, NistObjectIdentifiers.DsaWithSha512, HashAlgorithm.sha512, SignatureAlgorithm.dsa);
+
+            AddCertSigAlgOid(d, OiwObjectIdentifiers.DsaWithSha1, HashAlgorithm.sha1, SignatureAlgorithm.dsa);
+            AddCertSigAlgOid(d, OiwObjectIdentifiers.Sha1WithRsa, HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+
+            AddCertSigAlgOid(d, PkcsObjectIdentifiers.Sha1WithRsaEncryption, HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+            AddCertSigAlgOid(d, PkcsObjectIdentifiers.Sha224WithRsaEncryption, HashAlgorithm.sha224, SignatureAlgorithm.rsa);
+            AddCertSigAlgOid(d, PkcsObjectIdentifiers.Sha256WithRsaEncryption, HashAlgorithm.sha256, SignatureAlgorithm.rsa);
+            AddCertSigAlgOid(d, PkcsObjectIdentifiers.Sha384WithRsaEncryption, HashAlgorithm.sha384, SignatureAlgorithm.rsa);
+            AddCertSigAlgOid(d, PkcsObjectIdentifiers.Sha512WithRsaEncryption, HashAlgorithm.sha512, SignatureAlgorithm.rsa);
+
+            AddCertSigAlgOid(d, X9ObjectIdentifiers.ECDsaWithSha1, HashAlgorithm.sha1, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, X9ObjectIdentifiers.ECDsaWithSha224, HashAlgorithm.sha224, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, X9ObjectIdentifiers.ECDsaWithSha256, HashAlgorithm.sha256, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, X9ObjectIdentifiers.ECDsaWithSha384, HashAlgorithm.sha384, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, X9ObjectIdentifiers.ECDsaWithSha512, HashAlgorithm.sha512, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, X9ObjectIdentifiers.IdDsaWithSha1, HashAlgorithm.sha1, SignatureAlgorithm.dsa);
+
+            AddCertSigAlgOid(d, EacObjectIdentifiers.id_TA_ECDSA_SHA_1, HashAlgorithm.sha1, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, EacObjectIdentifiers.id_TA_ECDSA_SHA_224, HashAlgorithm.sha224, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, EacObjectIdentifiers.id_TA_ECDSA_SHA_256, HashAlgorithm.sha256, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, EacObjectIdentifiers.id_TA_ECDSA_SHA_384, HashAlgorithm.sha384, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, EacObjectIdentifiers.id_TA_ECDSA_SHA_512, HashAlgorithm.sha512, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, EacObjectIdentifiers.id_TA_RSA_v1_5_SHA_1, HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+            AddCertSigAlgOid(d, EacObjectIdentifiers.id_TA_RSA_v1_5_SHA_256, HashAlgorithm.sha256, SignatureAlgorithm.rsa);
+
+            AddCertSigAlgOid(d, BsiObjectIdentifiers.ecdsa_plain_SHA1, HashAlgorithm.sha1, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, BsiObjectIdentifiers.ecdsa_plain_SHA224, HashAlgorithm.sha224, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, BsiObjectIdentifiers.ecdsa_plain_SHA256, HashAlgorithm.sha256, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, BsiObjectIdentifiers.ecdsa_plain_SHA384, HashAlgorithm.sha384, SignatureAlgorithm.ecdsa);
+            AddCertSigAlgOid(d, BsiObjectIdentifiers.ecdsa_plain_SHA512, HashAlgorithm.sha512, SignatureAlgorithm.ecdsa);
+
+            AddCertSigAlgOid(d, EdECObjectIdentifiers.id_Ed25519, SignatureAndHashAlgorithm.ed25519);
+            AddCertSigAlgOid(d, EdECObjectIdentifiers.id_Ed448, SignatureAndHashAlgorithm.ed448);
+
+            AddCertSigAlgOid(d, RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_256,
+                SignatureAndHashAlgorithm.gostr34102012_256);
+            AddCertSigAlgOid(d, RosstandartObjectIdentifiers.id_tc26_signwithdigest_gost_3410_12_512,
+                SignatureAndHashAlgorithm.gostr34102012_512);
+
+            // TODO[RFC 8998]
+            //AddCertSigAlgOid(d, GMObjectIdentifiers.sm2sign_with_sm3, HashAlgorithm.sm3, SignatureAlgorithm.sm2);
+
+            return d;
+        }
+
+        private static IList CreateDefaultSupportedSigAlgs()
+        {
+            IList result = Platform.CreateArrayList();
+            result.Add(SignatureAndHashAlgorithm.ed25519);
+            result.Add(SignatureAndHashAlgorithm.ed448);
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha256, SignatureAlgorithm.ecdsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha384, SignatureAlgorithm.ecdsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha512, SignatureAlgorithm.ecdsa));
+            result.Add(SignatureAndHashAlgorithm.rsa_pss_rsae_sha256);
+            result.Add(SignatureAndHashAlgorithm.rsa_pss_rsae_sha384);
+            result.Add(SignatureAndHashAlgorithm.rsa_pss_rsae_sha512);
+            result.Add(SignatureAndHashAlgorithm.rsa_pss_pss_sha256);
+            result.Add(SignatureAndHashAlgorithm.rsa_pss_pss_sha384);
+            result.Add(SignatureAndHashAlgorithm.rsa_pss_pss_sha512);
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha256, SignatureAlgorithm.rsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha384, SignatureAlgorithm.rsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha512, SignatureAlgorithm.rsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha256, SignatureAlgorithm.dsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha384, SignatureAlgorithm.dsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha512, SignatureAlgorithm.dsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha224, SignatureAlgorithm.ecdsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha224, SignatureAlgorithm.rsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha224, SignatureAlgorithm.dsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha1, SignatureAlgorithm.ecdsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha1, SignatureAlgorithm.rsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha1, SignatureAlgorithm.dsa));
+            return result;
+        }
+
+        public static readonly byte[] EmptyBytes = new byte[0];
+        public static readonly short[] EmptyShorts = new short[0];
+        public static readonly int[] EmptyInts = new int[0];
+        public static readonly long[] EmptyLongs = new long[0];
+        public static readonly string[] EmptyStrings = new string[0];
+
+        internal static short MinimumHashStrict = HashAlgorithm.sha1;
+        internal static short MinimumHashPreferred = HashAlgorithm.sha256;
+
+        public static void CheckUint8(short i)
+        {
+            if (!IsValidUint8(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint8(int i)
+        {
+            if (!IsValidUint8(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint8(long i)
+        {
+            if (!IsValidUint8(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint16(int i)
+        {
+            if (!IsValidUint16(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint16(long i)
+        {
+            if (!IsValidUint16(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint24(int i)
+        {
+            if (!IsValidUint24(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint24(long i)
+        {
+            if (!IsValidUint24(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint32(long i)
+        {
+            if (!IsValidUint32(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint48(long i)
+        {
+            if (!IsValidUint48(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static void CheckUint64(long i)
+        {
+            if (!IsValidUint64(i))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public static bool IsValidUint8(short i)
+        {
+            return (i & 0xFF) == i;
+        }
+
+        public static bool IsValidUint8(int i)
+        {
+            return (i & 0xFF) == i;
+        }
+
+        public static bool IsValidUint8(long i)
+        {
+            return (i & 0xFFL) == i;
+        }
+
+        public static bool IsValidUint16(int i)
+        {
+            return (i & 0xFFFF) == i;
+        }
+
+        public static bool IsValidUint16(long i)
+        {
+            return (i & 0xFFFFL) == i;
+        }
+
+        public static bool IsValidUint24(int i)
+        {
+            return (i & 0xFFFFFF) == i;
+        }
+
+        public static bool IsValidUint24(long i)
+        {
+            return (i & 0xFFFFFFL) == i;
+        }
+
+        public static bool IsValidUint32(long i)
+        {
+            return (i & 0xFFFFFFFFL) == i;
+        }
+
+        public static bool IsValidUint48(long i)
+        {
+            return (i & 0xFFFFFFFFFFFFL) == i;
+        }
+
+        public static bool IsValidUint64(long i)
+        {
+            return true;
+        }
+
+        public static bool IsSsl(TlsContext context)
+        {
+            return context.ServerVersion.IsSsl;
+        }
+
+        public static bool IsTlsV10(ProtocolVersion version)
+        {
+            return ProtocolVersion.TLSv10.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static bool IsTlsV10(TlsContext context)
+        {
+            return IsTlsV10(context.ServerVersion);
+        }
+
+        public static bool IsTlsV11(ProtocolVersion version)
+        {
+            return ProtocolVersion.TLSv11.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static bool IsTlsV11(TlsContext context)
+        {
+            return IsTlsV11(context.ServerVersion);
+        }
+
+        public static bool IsTlsV12(ProtocolVersion version)
+        {
+            return ProtocolVersion.TLSv12.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static bool IsTlsV12(TlsContext context)
+        {
+            return IsTlsV12(context.ServerVersion);
+        }
+
+        public static bool IsTlsV13(ProtocolVersion version)
+        {
+            return ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static bool IsTlsV13(TlsContext context)
+        {
+            return IsTlsV13(context.ServerVersion);
+        }
+
+        public static void WriteUint8(short i, Stream output)
+        {
+            output.WriteByte((byte)i);
+        }
+
+        public static void WriteUint8(int i, Stream output)
+        {
+            output.WriteByte((byte)i);
+        }
+
+        public static void WriteUint8(short i, byte[] buf, int offset)
+        {
+            buf[offset] = (byte)i;
+        }
+
+        public static void WriteUint8(int i, byte[] buf, int offset)
+        {
+            buf[offset] = (byte)i;
+        }
+
+        public static void WriteUint16(int i, Stream output)
+        {
+            output.WriteByte((byte)(i >> 8));
+            output.WriteByte((byte)i);
+        }
+
+        public static void WriteUint16(int i, byte[] buf, int offset)
+        {
+            buf[offset    ] = (byte)(i >> 8);
+            buf[offset + 1] = (byte)i;
+        }
+
+        public static void WriteUint24(int i, Stream output)
+        {
+            output.WriteByte((byte)(i >> 16));
+            output.WriteByte((byte)(i >> 8));
+            output.WriteByte((byte)i);
+        }
+
+        public 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;
+        }
+
+        public static void WriteUint32(long i, Stream output)
+        {
+            output.WriteByte((byte)(i >> 24));
+            output.WriteByte((byte)(i >> 16));
+            output.WriteByte((byte)(i >> 8));
+            output.WriteByte((byte)i);
+        }
+
+        public static void WriteUint32(long i, byte[] buf, int offset)
+        {
+            buf[offset    ] = (byte)(i >> 24);
+            buf[offset + 1] = (byte)(i >> 16);
+            buf[offset + 2] = (byte)(i >> 8);
+            buf[offset + 3] = (byte)i;
+        }
+
+        public static void WriteUint48(long i, Stream output)
+        {
+            output.WriteByte((byte)(i >> 40));
+            output.WriteByte((byte)(i >> 32));
+            output.WriteByte((byte)(i >> 24));
+            output.WriteByte((byte)(i >> 16));
+            output.WriteByte((byte)(i >> 8));
+            output.WriteByte((byte)i);
+        }
+
+        public static void WriteUint48(long i, byte[] buf, int offset)
+        {
+            buf[offset    ] = (byte)(i >> 40);
+            buf[offset + 1] = (byte)(i >> 32);
+            buf[offset + 2] = (byte)(i >> 24);
+            buf[offset + 3] = (byte)(i >> 16);
+            buf[offset + 4] = (byte)(i >> 8);
+            buf[offset + 5] = (byte)i;
+        }
+
+        public static void WriteUint64(long i, Stream output)
+        {
+            output.WriteByte((byte)(i >> 56));
+            output.WriteByte((byte)(i >> 48));
+            output.WriteByte((byte)(i >> 40));
+            output.WriteByte((byte)(i >> 32));
+            output.WriteByte((byte)(i >> 24));
+            output.WriteByte((byte)(i >> 16));
+            output.WriteByte((byte)(i >> 8));
+            output.WriteByte((byte) i);
+        }
+
+        public 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;
+        }
+
+        public static void WriteOpaque8(byte[] buf, Stream output)
+        {
+            CheckUint8(buf.Length);
+            WriteUint8(buf.Length, output);
+            output.Write(buf, 0, buf.Length);
+        }
+
+        public static void WriteOpaque8(byte[] data, byte[] buf, int off)
+        {
+            CheckUint8(data.Length);
+            WriteUint8(data.Length, buf, off);
+            Array.Copy(data, 0, buf, off + 1, data.Length);
+        }
+
+        public static void WriteOpaque16(byte[] buf, Stream output)
+        {
+            CheckUint16(buf.Length);
+            WriteUint16(buf.Length, output);
+            output.Write(buf, 0, buf.Length);
+        }
+
+        public static void WriteOpaque16(byte[] data, byte[] buf, int off)
+        {
+            CheckUint16(data.Length);
+            WriteUint16(data.Length, buf, off);
+            Array.Copy(data, 0, buf, off + 2, data.Length);
+        }
+
+        public static void WriteOpaque24(byte[] buf, Stream output)
+        {
+            CheckUint24(buf.Length);
+            WriteUint24(buf.Length, output);
+            output.Write(buf, 0, buf.Length);
+        }
+
+        public static void WriteOpaque24(byte[] data, byte[] buf, int off)
+        {
+            CheckUint24(data.Length);
+            WriteUint24(data.Length, buf, off);
+            Array.Copy(data, 0, buf, off + 3, data.Length);
+        }
+
+        public static void WriteUint8Array(short[] u8s, Stream output)
+        {
+            for (int i = 0; i < u8s.Length; ++i)
+            {
+                WriteUint8(u8s[i], output);
+            }
+        }
+
+        public static void WriteUint8Array(short[] u8s, byte[] buf, int offset)
+        {
+            for (int i = 0; i < u8s.Length; ++i)
+            {
+                WriteUint8(u8s[i], buf, offset);
+                ++offset;
+            }
+        }
+
+        public static void WriteUint8ArrayWithUint8Length(short[] u8s, Stream output)
+        {
+            CheckUint8(u8s.Length);
+            WriteUint8(u8s.Length, output);
+            WriteUint8Array(u8s, output);
+        }
+
+        public static void WriteUint8ArrayWithUint8Length(short[] u8s, byte[] buf, int offset)
+        {
+            CheckUint8(u8s.Length);
+            WriteUint8(u8s.Length, buf, offset);
+            WriteUint8Array(u8s, buf, offset + 1);
+        }
+
+        public static void WriteUint16Array(int[] u16s, Stream output)
+        {
+            for (int i = 0; i < u16s.Length; ++i)
+            {
+                WriteUint16(u16s[i], output);
+            }
+        }
+
+        public static void WriteUint16Array(int[] u16s, byte[] buf, int offset)
+        {
+            for (int i = 0; i < u16s.Length; ++i)
+            {
+                WriteUint16(u16s[i], buf, offset);
+                offset += 2;
+            }
+        }
+
+        public static void WriteUint16ArrayWithUint16Length(int[] u16s, Stream output)
+        {
+            int length = 2 * u16s.Length;
+            CheckUint16(length);
+            WriteUint16(length, output);
+            WriteUint16Array(u16s, output);
+        }
+
+        public static void WriteUint16ArrayWithUint16Length(int[] u16s, byte[] buf, int offset)
+        {
+            int length = 2 * u16s.Length;
+            CheckUint16(length);
+            WriteUint16(length, buf, offset);
+            WriteUint16Array(u16s, buf, offset + 2);
+        }
+
+        public static byte[] DecodeOpaque8(byte[] buf)
+        {
+            return DecodeOpaque8(buf, 0);
+        }
+
+        public static byte[] DecodeOpaque8(byte[] buf, int minLength)
+        {
+            if (buf == null)
+                throw new ArgumentNullException("buf");
+            if (buf.Length < 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            short length = ReadUint8(buf, 0);
+            if (buf.Length != (length + 1) || length < minLength)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return CopyOfRangeExact(buf, 1, buf.Length);
+        }
+
+        public static byte[] DecodeOpaque16(byte[] buf)
+        {
+            return DecodeOpaque16(buf, 0);
+        }
+
+        public static byte[] DecodeOpaque16(byte[] buf, int minLength)
+        {
+            if (buf == null)
+                throw new ArgumentNullException("buf");
+            if (buf.Length < 2)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int length = ReadUint16(buf, 0);
+            if (buf.Length != (length + 2) || length < minLength)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return CopyOfRangeExact(buf, 2, buf.Length);
+        }
+
+        public static short DecodeUint8(byte[] buf)
+        {
+            if (buf == null)
+                throw new ArgumentNullException("buf");
+            if (buf.Length != 1)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return ReadUint8(buf, 0);
+        }
+
+        public static short[] DecodeUint8ArrayWithUint8Length(byte[] buf)
+        {
+            if (buf == null)
+                throw new ArgumentNullException("buf");
+
+            int count = ReadUint8(buf, 0);
+            if (buf.Length != (count + 1))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            short[] uints = new short[count];
+            for (int i = 0; i < count; ++i)
+            {
+                uints[i] = ReadUint8(buf, i + 1);
+            }
+            return uints;
+        }
+
+        public static int DecodeUint16(byte[] buf)
+        {
+            if (buf == null)
+                throw new ArgumentNullException("buf");
+            if (buf.Length != 2)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return ReadUint16(buf, 0);
+        }
+
+        public static long DecodeUint32(byte[] buf)
+        {
+            if (buf == null)
+                throw new ArgumentNullException("buf");
+            if (buf.Length != 4)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return ReadUint32(buf, 0);
+        }
+
+        public static byte[] EncodeOpaque8(byte[] buf)
+        {
+            CheckUint8(buf.Length);
+            return Arrays.Prepend(buf, (byte)buf.Length);
+        }
+
+        public static byte[] EncodeOpaque16(byte[] buf)
+        {
+            return Arrays.Concatenate(EncodeUint16(buf.Length), buf);
+        }
+
+        public static byte[] EncodeUint8(short u8)
+        {
+            CheckUint8(u8);
+
+            byte[] encoding = new byte[1];
+            WriteUint8(u8, encoding, 0);
+            return encoding;
+        }
+
+        public static byte[] EncodeUint8ArrayWithUint8Length(short[] u8s)
+        {
+            byte[] result = new byte[1 + u8s.Length];
+            WriteUint8ArrayWithUint8Length(u8s, result, 0);
+            return result;
+        }
+
+        public static byte[] EncodeUint16(int u16)
+        {
+            CheckUint16(u16);
+
+            byte[] encoding = new byte[2];
+            WriteUint16(u16, encoding, 0);
+            return encoding;
+        }
+
+        public static byte[] EncodeUint16ArrayWithUint16Length(int[] u16s)
+        {
+            int length = 2 * u16s.Length;
+            byte[] result = new byte[2 + length];
+            WriteUint16ArrayWithUint16Length(u16s, result, 0);
+            return result;
+        }
+
+        public static byte[] EncodeUint32(long u32)
+        {
+            CheckUint32(u32);
+
+            byte[] encoding = new byte[4];
+            WriteUint32(u32, encoding, 0);
+            return encoding;
+        }
+
+        public static byte[] EncodeVersion(ProtocolVersion version)
+        {
+            return new byte[]{
+                (byte)version.MajorVersion,
+                (byte)version.MinorVersion
+            };
+        }
+
+        public static int ReadInt32(byte[] buf, int offset)
+        {
+            int n = buf[offset] << 24;
+            n |= (buf[++offset] & 0xff) << 16;
+            n |= (buf[++offset] & 0xff) << 8;
+            n |= (buf[++offset] & 0xff);
+            return n;
+        }
+
+        public static short ReadUint8(Stream input)
+        {
+            int i = input.ReadByte();
+            if (i < 0)
+                throw new EndOfStreamException();
+            return (short)i;
+        }
+
+        public static short ReadUint8(byte[] buf, int offset)
+        {
+            return (short)(buf[offset] & 0xff);
+        }
+
+        public static int ReadUint16(Stream input)
+        {
+            int i1 = input.ReadByte();
+            int i2 = input.ReadByte();
+            if (i2 < 0)
+                throw new EndOfStreamException();
+            return (i1 << 8) | i2;
+        }
+
+        public static int ReadUint16(byte[] buf, int offset)
+        {
+            int n = (buf[offset] & 0xff) << 8;
+            n |= (buf[++offset] & 0xff);
+            return n;
+        }
+
+        public static int ReadUint24(Stream input)
+        {
+            int i1 = input.ReadByte();
+            int i2 = input.ReadByte();
+            int i3 = input.ReadByte();
+            if (i3 < 0)
+                throw new EndOfStreamException();
+
+            return (i1 << 16) | (i2 << 8) | i3;
+        }
+
+        public static int ReadUint24(byte[] buf, int offset)
+        {
+            int n = (buf[offset] & 0xff) << 16;
+            n |= (buf[++offset] & 0xff) << 8;
+            n |= (buf[++offset] & 0xff);
+            return n;
+        }
+
+        public static long ReadUint32(Stream input)
+        {
+            int i1 = input.ReadByte();
+            int i2 = input.ReadByte();
+            int i3 = input.ReadByte();
+            int i4 = input.ReadByte();
+            if (i4 < 0)
+                throw new EndOfStreamException();
+
+            return ((i1 << 24) | (i2 << 16) | (i3 << 8) | i4) & 0xFFFFFFFFL;
+        }
+
+        public static long ReadUint32(byte[] buf, int offset)
+        {
+            int n = (buf[offset] & 0xff) << 24;
+            n |= (buf[++offset] & 0xff) << 16;
+            n |= (buf[++offset] & 0xff) << 8;
+            n |= (buf[++offset] & 0xff);
+            return n & 0xFFFFFFFFL;
+        }
+
+        public static long ReadUint48(Stream input)
+        {
+            int hi = ReadUint24(input);
+            int lo = ReadUint24(input);
+            return ((long)(hi & 0xffffffffL) << 24) | (long)(lo & 0xffffffffL);
+        }
+
+        public static long ReadUint48(byte[] buf, int offset)
+        {
+            int hi = ReadUint24(buf, offset);
+            int lo = ReadUint24(buf, offset + 3);
+            return ((long)(hi & 0xffffffffL) << 24) | (long)(lo & 0xffffffffL);
+        }
+
+        public static byte[] ReadAllOrNothing(int length, Stream input)
+        {
+            if (length < 1)
+                return EmptyBytes;
+            byte[] buf = new byte[length];
+            int read = Streams.ReadFully(input, buf);
+            if (read == 0)
+                return null;
+            if (read != length)
+                throw new EndOfStreamException();
+            return buf;
+        }
+
+        public static byte[] ReadFully(int length, Stream input)
+        {
+            if (length < 1)
+                return EmptyBytes;
+            byte[] buf = new byte[length];
+            if (length != Streams.ReadFully(input, buf))
+                throw new EndOfStreamException();
+            return buf;
+        }
+
+        public static void ReadFully(byte[] buf, Stream input)
+        {
+            int length = buf.Length;
+            if (length > 0 && length != Streams.ReadFully(input, buf))
+                throw new EndOfStreamException();
+        }
+
+        public static byte[] ReadOpaque8(Stream input)
+        {
+            short length = ReadUint8(input);
+            return ReadFully(length, input);
+        }
+
+        public static byte[] ReadOpaque8(Stream input, int minLength)
+        {
+            short length = ReadUint8(input);
+            if (length < minLength)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return ReadFully(length, input);
+        }
+
+        public static byte[] ReadOpaque8(Stream input, int minLength, int maxLength)
+        {
+            short length = ReadUint8(input);
+            if (length < minLength || maxLength < length)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return ReadFully(length, input);
+        }
+
+        public static byte[] ReadOpaque16(Stream input)
+        {
+            int length = ReadUint16(input);
+            return ReadFully(length, input);
+        }
+
+        public static byte[] ReadOpaque16(Stream input, int minLength)
+        {
+            int length = ReadUint16(input);
+            if (length < minLength)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return ReadFully(length, input);
+        }
+
+        public static byte[] ReadOpaque24(Stream input)
+        {
+            int length = ReadUint24(input);
+            return ReadFully(length, input);
+        }
+
+        public static byte[] ReadOpaque24(Stream input, int minLength)
+        {
+            int length = ReadUint24(input);
+            if (length < minLength)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return ReadFully(length, input);
+        }
+
+        public static short[] ReadUint8Array(int count, Stream input)
+        {
+            short[] uints = new short[count];
+            for (int i = 0; i < count; ++i)
+            {
+                uints[i] = ReadUint8(input);
+            }
+            return uints;
+        }
+
+        public static short[] ReadUint8ArrayWithUint8Length(Stream input, int minLength)
+        {
+            int length = ReadUint8(input);
+            if (length < minLength)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return ReadUint8Array(length, input);
+        }
+
+        public static int[] ReadUint16Array(int count, Stream input)
+        {
+            int[] uints = new int[count];
+            for (int i = 0; i < count; ++i)
+            {
+                uints[i] = ReadUint16(input);
+            }
+            return uints;
+        }
+
+        public static ProtocolVersion ReadVersion(byte[] buf, int offset)
+        {
+            return ProtocolVersion.Get(buf[offset], buf[offset + 1]);
+        }
+
+        public static ProtocolVersion ReadVersion(Stream input)
+        {
+            int i1 = input.ReadByte();
+            int i2 = input.ReadByte();
+            if (i2 < 0)
+                throw new EndOfStreamException();
+
+            return ProtocolVersion.Get(i1, i2);
+        }
+
+        public static Asn1Object ReadAsn1Object(byte[] encoding)
+        {
+            Asn1InputStream asn1 = new Asn1InputStream(encoding);
+            Asn1Object result = asn1.ReadObject();
+            if (null == result)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            if (null != asn1.ReadObject())
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return result;
+        }
+
+        public static Asn1Object ReadDerObject(byte[] encoding)
+        {
+            /*
+             * NOTE: The current ASN.1 parsing code can't enforce DER-only parsing, but since DER is
+             * canonical, we can check it by re-encoding the result and comparing to the original.
+             */
+            Asn1Object result = ReadAsn1Object(encoding);
+            byte[] check = result.GetEncoded(Asn1Encodable.Der);
+            if (!Arrays.AreEqual(check, encoding))
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            return result;
+        }
+
+        public 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;
+        }
+
+        public static void WriteVersion(ProtocolVersion version, Stream output)
+        {
+            output.WriteByte((byte)version.MajorVersion);
+            output.WriteByte((byte)version.MinorVersion);
+        }
+
+        public static void WriteVersion(ProtocolVersion version, byte[] buf, int offset)
+        {
+            buf[offset] = (byte)version.MajorVersion;
+            buf[offset + 1] = (byte)version.MinorVersion;
+        }
+
+        public static void AddIfSupported(IList supportedAlgs, TlsCrypto crypto, SignatureAndHashAlgorithm alg)
+        {
+            if (crypto.HasSignatureAndHashAlgorithm(alg))
+            {
+                supportedAlgs.Add(alg);
+            }
+        }
+
+        public static void AddIfSupported(IList supportedGroups, TlsCrypto crypto, int namedGroup)
+        {
+            if (crypto.HasNamedGroup(namedGroup))
+            {
+                supportedGroups.Add(namedGroup);
+            }
+        }
+
+        public static void AddIfSupported(IList supportedGroups, TlsCrypto crypto, int[] namedGroups)
+        {
+            for (int i = 0; i < namedGroups.Length; ++i)
+            {
+                AddIfSupported(supportedGroups, crypto, namedGroups[i]);
+            }
+        }
+
+        public static bool AddToSet(IList s, int i)
+        {
+            bool result = !s.Contains(i);
+            if (result)
+            {
+                s.Add(i);
+            }
+            return result;
+        }
+
+        public static IList GetDefaultDssSignatureAlgorithms()
+        {
+            return GetDefaultSignatureAlgorithms(SignatureAlgorithm.dsa);
+        }
+
+        public static IList GetDefaultECDsaSignatureAlgorithms()
+        {
+            return GetDefaultSignatureAlgorithms(SignatureAlgorithm.ecdsa);
+        }
+
+        public static IList GetDefaultRsaSignatureAlgorithms()
+        {
+            return GetDefaultSignatureAlgorithms(SignatureAlgorithm.rsa);
+        }
+
+        public static SignatureAndHashAlgorithm GetDefaultSignatureAlgorithm(short signatureAlgorithm)
+        {
+            /*
+             * RFC 5246 7.4.1.4.1. If the client does not send the signature_algorithms extension,
+             * the server MUST do the following:
+             * 
+             * - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, DH_RSA, RSA_PSK,
+             * ECDH_RSA, ECDHE_RSA), behave as if client had sent the value {sha1,rsa}.
+             * 
+             * - If the negotiated key exchange algorithm is one of (DHE_DSS, DH_DSS), behave as if
+             * the client had sent the value {sha1,dsa}.
+             * 
+             * - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, ECDHE_ECDSA),
+             * behave as if the client had sent value {sha1,ecdsa}.
+             */
+
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.dsa:
+            case SignatureAlgorithm.ecdsa:
+            case SignatureAlgorithm.rsa:
+                return SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha1, signatureAlgorithm);
+            default:
+                return null;
+            }
+        }
+
+        public static IList GetDefaultSignatureAlgorithms(short signatureAlgorithm)
+        {
+            SignatureAndHashAlgorithm sigAndHashAlg = GetDefaultSignatureAlgorithm(signatureAlgorithm);
+
+            return null == sigAndHashAlg ? Platform.CreateArrayList() : VectorOfOne(sigAndHashAlg);
+        }
+
+        public static IList GetDefaultSupportedSignatureAlgorithms(TlsContext context)
+        {
+            TlsCrypto crypto = context.Crypto;
+
+            IList result = Platform.CreateArrayList(DefaultSupportedSigAlgs.Count);
+            foreach (SignatureAndHashAlgorithm sigAndHashAlg in DefaultSupportedSigAlgs)
+            {
+                AddIfSupported(result, crypto, sigAndHashAlg);
+            }
+            return result;
+        }
+
+        public static SignatureAndHashAlgorithm GetSignatureAndHashAlgorithm(TlsContext context,
+            TlsCredentialedSigner signerCredentials)
+        {
+            return GetSignatureAndHashAlgorithm(context.ServerVersion, signerCredentials);
+        }
+
+        internal static SignatureAndHashAlgorithm GetSignatureAndHashAlgorithm(ProtocolVersion negotiatedVersion,
+            TlsCredentialedSigner signerCredentials)
+        {
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm = null;
+            if (IsTlsV12(negotiatedVersion))
+            {
+                signatureAndHashAlgorithm = signerCredentials.SignatureAndHashAlgorithm;
+                if (signatureAndHashAlgorithm == null)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+            return signatureAndHashAlgorithm;
+        }
+
+        public static byte[] GetExtensionData(IDictionary extensions, int extensionType)
+        {
+            return extensions == null || !extensions.Contains(extensionType)
+                ? null
+                : (byte[])extensions[extensionType];
+        }
+
+        public static bool HasExpectedEmptyExtensionData(IDictionary extensions, int extensionType,
+            short alertDescription)
+        {
+            byte[] extension_data = GetExtensionData(extensions, extensionType);
+            if (extension_data == null)
+                return false;
+
+            if (extension_data.Length != 0)
+                throw new TlsFatalAlert(alertDescription);
+
+            return true;
+        }
+
+        public static TlsSession ImportSession(byte[] sessionID, SessionParameters sessionParameters)
+        {
+            return new TlsSessionImpl(sessionID, sessionParameters);
+        }
+
+        internal static bool IsExtendedMasterSecretOptionalDtls(ProtocolVersion[] activeProtocolVersions)
+        {
+            return ProtocolVersion.Contains(activeProtocolVersions, ProtocolVersion.DTLSv12)
+                || ProtocolVersion.Contains(activeProtocolVersions, ProtocolVersion.DTLSv10);
+        }
+
+        internal static bool IsExtendedMasterSecretOptionalTls(ProtocolVersion[] activeProtocolVersions)
+        {
+            return ProtocolVersion.Contains(activeProtocolVersions, ProtocolVersion.TLSv12)
+                || ProtocolVersion.Contains(activeProtocolVersions, ProtocolVersion.TLSv11)
+                || ProtocolVersion.Contains(activeProtocolVersions, ProtocolVersion.TLSv10);
+        }
+
+        public static bool IsNullOrContainsNull(object[] array)
+        {
+            if (null == array)
+                return true;
+
+            int count = array.Length;
+            for (int i = 0; i < count; ++i)
+            {
+                if (null == array[i])
+                    return true;
+            }
+            return false;
+        }
+
+        public static bool IsNullOrEmpty(byte[] array)
+        {
+            return null == array || array.Length < 1;
+        }
+
+        public static bool IsNullOrEmpty(short[] array)
+        {
+            return null == array || array.Length < 1;
+        }
+
+        public static bool IsNullOrEmpty(int[] array)
+        {
+            return null == array || array.Length < 1;
+        }
+
+        public static bool IsNullOrEmpty(object[] array)
+        {
+            return null == array || array.Length < 1;
+        }
+
+        public static bool IsNullOrEmpty(string s)
+        {
+            return null == s || s.Length < 1;
+        }
+
+        public static bool IsSignatureAlgorithmsExtensionAllowed(ProtocolVersion version)
+        {
+            return null != version
+                && ProtocolVersion.TLSv12.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static short GetLegacyClientCertType(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+                return ClientCertificateType.rsa_sign;
+            case SignatureAlgorithm.dsa:
+                return ClientCertificateType.dss_sign;
+            case SignatureAlgorithm.ecdsa:
+                return ClientCertificateType.ecdsa_sign;
+            default:
+                return -1;
+            }
+        }
+
+        public static short GetLegacySignatureAlgorithmClient(short clientCertificateType)
+        {
+            switch (clientCertificateType)
+            {
+            case ClientCertificateType.dss_sign:
+                return SignatureAlgorithm.dsa;
+            case ClientCertificateType.ecdsa_sign:
+                return SignatureAlgorithm.ecdsa;
+            case ClientCertificateType.rsa_sign:
+                return SignatureAlgorithm.rsa;
+            default:
+                return -1;
+            }
+        }
+
+        public static short GetLegacySignatureAlgorithmClientCert(short clientCertificateType)
+        {
+            switch (clientCertificateType)
+            {
+            case ClientCertificateType.dss_sign:
+            case ClientCertificateType.dss_fixed_dh:
+                return SignatureAlgorithm.dsa;
+
+            case ClientCertificateType.ecdsa_sign:
+            case ClientCertificateType.ecdsa_fixed_ecdh:
+                return SignatureAlgorithm.ecdsa;
+
+            case ClientCertificateType.rsa_sign:
+            case ClientCertificateType.rsa_fixed_dh:
+            case ClientCertificateType.rsa_fixed_ecdh:
+                return SignatureAlgorithm.rsa;
+            default:
+                return -1;
+            }
+        }
+
+        public static short GetLegacySignatureAlgorithmServer(int keyExchangeAlgorithm)
+        {
+            switch (keyExchangeAlgorithm)
+            {
+            case KeyExchangeAlgorithm.DHE_DSS:
+            case KeyExchangeAlgorithm.SRP_DSS:
+                return SignatureAlgorithm.dsa;
+
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+                return SignatureAlgorithm.ecdsa;
+
+            case KeyExchangeAlgorithm.DHE_RSA:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+            case KeyExchangeAlgorithm.SRP_RSA:
+                return SignatureAlgorithm.rsa;
+
+            default:
+                return -1;
+            }
+        }
+
+        public static short GetLegacySignatureAlgorithmServerCert(int keyExchangeAlgorithm)
+        {
+            switch (keyExchangeAlgorithm)
+            {
+            case KeyExchangeAlgorithm.DH_DSS:
+            case KeyExchangeAlgorithm.DHE_DSS:
+            case KeyExchangeAlgorithm.SRP_DSS:
+                return SignatureAlgorithm.dsa;
+
+            case KeyExchangeAlgorithm.ECDH_ECDSA:
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+                return SignatureAlgorithm.ecdsa;
+
+            case KeyExchangeAlgorithm.DH_RSA:
+            case KeyExchangeAlgorithm.DHE_RSA:
+            case KeyExchangeAlgorithm.ECDH_RSA:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+            case KeyExchangeAlgorithm.RSA:
+            case KeyExchangeAlgorithm.RSA_PSK:
+            case KeyExchangeAlgorithm.SRP_RSA:
+                return SignatureAlgorithm.rsa;
+
+            default:
+                return -1;
+            }
+        }
+
+        public static IList GetLegacySupportedSignatureAlgorithms()
+        {
+            IList result = Platform.CreateArrayList(3);
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha1, SignatureAlgorithm.dsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha1, SignatureAlgorithm.ecdsa));
+            result.Add(SignatureAndHashAlgorithm.GetInstance(HashAlgorithm.sha1, SignatureAlgorithm.rsa));
+            return result;
+        }
+
+        /// <exception cref="IOException"/>
+        public static void EncodeSupportedSignatureAlgorithms(IList supportedSignatureAlgorithms, Stream output)
+        {
+            if (supportedSignatureAlgorithms == null || supportedSignatureAlgorithms.Count < 1
+                || supportedSignatureAlgorithms.Count >= (1 << 15))
+            {
+                throw new ArgumentException("must have length from 1 to (2^15 - 1)", "supportedSignatureAlgorithms");
+            }
+
+            // supported_signature_algorithms
+            int length = 2 * supportedSignatureAlgorithms.Count;
+            CheckUint16(length);
+            WriteUint16(length, output);
+            foreach (SignatureAndHashAlgorithm entry in supportedSignatureAlgorithms)
+            {
+                if (entry.Signature == SignatureAlgorithm.anonymous)
+                {
+                    /*
+                     * RFC 5246 7.4.1.4.1 The "anonymous" value is meaningless in this context but used
+                     * in Section 7.4.3. It MUST NOT appear in this extension.
+                     */
+                    throw new ArgumentException(
+                        "SignatureAlgorithm.anonymous MUST NOT appear in the signature_algorithms extension");
+                }
+                entry.Encode(output);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public static IList ParseSupportedSignatureAlgorithms(Stream input)
+        {
+            // supported_signature_algorithms
+            int length = ReadUint16(input);
+            if (length < 2 || (length & 1) != 0)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int count = length / 2;
+            IList supportedSignatureAlgorithms = Platform.CreateArrayList(count);
+            for (int i = 0; i < count; ++i)
+            {
+                SignatureAndHashAlgorithm sigAndHashAlg = SignatureAndHashAlgorithm.Parse(input);
+
+                if (SignatureAlgorithm.anonymous != sigAndHashAlg.Signature)
+                {
+                    supportedSignatureAlgorithms.Add(sigAndHashAlg);
+                }
+            }
+            return supportedSignatureAlgorithms;
+        }
+
+        /// <exception cref="IOException"/>
+        public static void VerifySupportedSignatureAlgorithm(IList supportedSignatureAlgorithms,
+            SignatureAndHashAlgorithm signatureAlgorithm)
+        {
+            if (supportedSignatureAlgorithms == null || supportedSignatureAlgorithms.Count < 1
+                || supportedSignatureAlgorithms.Count >= (1 << 15))
+            {
+                throw new ArgumentException("must have length from 1 to (2^15 - 1)", "supportedSignatureAlgorithms");
+            }
+            if (signatureAlgorithm == null)
+                throw new ArgumentNullException("signatureAlgorithm");
+
+            if (signatureAlgorithm.Signature == SignatureAlgorithm.anonymous
+                || !ContainsSignatureAlgorithm(supportedSignatureAlgorithms, signatureAlgorithm))
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool ContainsSignatureAlgorithm(IList supportedSignatureAlgorithms,
+            SignatureAndHashAlgorithm signatureAlgorithm)
+        {
+            foreach (SignatureAndHashAlgorithm entry in supportedSignatureAlgorithms)
+            {
+                if (entry.Equals(signatureAlgorithm))
+                    return true;
+            }
+
+            return false;
+        }
+
+        public static bool ContainsAnySignatureAlgorithm(IList supportedSignatureAlgorithms, short signatureAlgorithm)
+        {
+            foreach (SignatureAndHashAlgorithm entry in supportedSignatureAlgorithms)
+            {
+                if (entry.Signature == signatureAlgorithm)
+                    return true;
+            }
+
+            return false;
+        }
+
+        public static TlsSecret Prf(SecurityParameters securityParameters, TlsSecret secret, string asciiLabel,
+            byte[] seed, int length)
+        {
+            return secret.DeriveUsingPrf(securityParameters.PrfAlgorithm, asciiLabel, seed, length);
+        }
+
+        public static byte[] Clone(byte[] data)
+        {
+            return null == data ? null : data.Length == 0 ? EmptyBytes : (byte[])data.Clone();
+        }
+
+        public static string[] Clone(string[] s)
+        {
+            return null == s ? null : s.Length < 1 ? EmptyStrings : (string[])s.Clone();
+        }
+
+        public static bool ConstantTimeAreEqual(int len, byte[] a, int aOff, byte[] b, int bOff)
+        {
+            int d = 0;
+            for (int i = 0; i < len; ++i)
+            {
+                d |= a[aOff + i] ^ b[bOff + i];
+            }
+            return 0 == d;
+        }
+
+        public static byte[] CopyOfRangeExact(byte[] original, int from, int to)
+        {
+            int newLength = to - from;
+            byte[] copy = new byte[newLength];
+            Array.Copy(original, from, copy, 0, newLength);
+            return copy;
+        }
+
+        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;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static byte[] CalculateEndPointHash(TlsContext context, TlsCertificate certificate, byte[] enc)
+        {
+            return CalculateEndPointHash(context, certificate, enc, 0, enc.Length);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static byte[] CalculateEndPointHash(TlsContext context, TlsCertificate certificate, byte[] enc,
+            int encOff, int encLen)
+        {
+            short hashAlgorithm = HashAlgorithm.none;
+
+            string sigAlgOid = certificate.SigAlgOid;
+            if (sigAlgOid != null)
+            {
+                if (PkcsObjectIdentifiers.IdRsassaPss.Id.Equals(sigAlgOid))
+                {
+                    RsassaPssParameters pssParams = RsassaPssParameters.GetInstance(certificate.GetSigAlgParams());
+                    if (null != pssParams)
+                    {
+                        DerObjectIdentifier hashOid = pssParams.HashAlgorithm.Algorithm;
+                        if (NistObjectIdentifiers.IdSha256.Equals(hashOid))
+                        {
+                            hashAlgorithm = HashAlgorithm.sha256;
+                        }
+                        else if (NistObjectIdentifiers.IdSha384.Equals(hashOid))
+                        {
+                            hashAlgorithm = HashAlgorithm.sha384;
+                        }
+                        else if (NistObjectIdentifiers.IdSha512.Equals(hashOid))
+                        {
+                            hashAlgorithm = HashAlgorithm.sha512;
+                        }
+                    }
+                }
+                else
+                {
+                    if (CertSigAlgOids.Contains(sigAlgOid))
+                    {
+                        hashAlgorithm = ((SignatureAndHashAlgorithm)CertSigAlgOids[sigAlgOid]).Hash;
+                    }
+                }
+            }
+
+            switch (hashAlgorithm)
+            {
+            case HashAlgorithm.Intrinsic:
+                hashAlgorithm = HashAlgorithm.none;
+                break;
+            case HashAlgorithm.md5:
+            case HashAlgorithm.sha1:
+                hashAlgorithm = HashAlgorithm.sha256;
+                break;
+            }
+
+            if (HashAlgorithm.none != hashAlgorithm)
+            {
+                TlsHash hash = CreateHash(context.Crypto, hashAlgorithm);
+                if (hash != null)
+                {                
+                    hash.Update(enc, encOff, encLen);
+                    return hash.CalculateHash();
+                }
+            }
+
+            return EmptyBytes;
+        }
+
+        public static byte[] CalculateExporterSeed(SecurityParameters securityParameters, byte[] context)
+        {
+            byte[] cr = securityParameters.ClientRandom, sr = securityParameters.ServerRandom;
+            if (null == context)
+                return Arrays.Concatenate(cr, sr);
+
+            if (!IsValidUint16(context.Length))
+                throw new ArgumentException("must have length less than 2^16 (or be null)", "context");
+
+            byte[] contextLength = new byte[2];
+            WriteUint16(context.Length, contextLength, 0);
+
+            return Arrays.ConcatenateAll(cr, sr, contextLength, context);
+        }
+
+        internal static TlsSecret CalculateMasterSecret(TlsContext context, TlsSecret preMasterSecret)
+        {
+            SecurityParameters sp = context.SecurityParameters;
+
+            string asciiLabel;
+            byte[] seed;
+            if (sp.IsExtendedMasterSecret)
+            {
+                asciiLabel = ExporterLabel.extended_master_secret;
+                seed = sp.SessionHash;
+            }
+            else
+            {
+                asciiLabel = ExporterLabel.master_secret;
+                seed = Concat(sp.ClientRandom, sp.ServerRandom);
+            }
+
+            return Prf(sp, preMasterSecret, asciiLabel, seed, 48);
+        }
+
+        internal static byte[] CalculateVerifyData(TlsContext context, TlsHandshakeHash handshakeHash, bool isServer)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (IsTlsV13(negotiatedVersion))
+            {
+                TlsSecret baseKey = isServer
+                    ?   securityParameters.BaseKeyServer
+                    :   securityParameters.BaseKeyClient;
+
+                TlsSecret finishedKey = DeriveSecret(securityParameters, baseKey, "finished", EmptyBytes);
+                byte[] transcriptHash = GetCurrentPrfHash(handshakeHash);
+
+                TlsCrypto crypto = context.Crypto;
+                byte[] hmacKey = crypto.AdoptSecret(finishedKey).Extract();
+                TlsHmac hmac = crypto.CreateHmacForHash(TlsCryptoUtilities.GetHash(securityParameters.PrfHashAlgorithm));
+                hmac.SetKey(hmacKey, 0, hmacKey.Length);
+                hmac.Update(transcriptHash, 0, transcriptHash.Length);
+                return hmac.CalculateMac();
+            }
+
+            if (negotiatedVersion.IsSsl)
+            {
+                return Ssl3Utilities.CalculateVerifyData(handshakeHash, isServer);
+            }
+
+            string asciiLabel = isServer ? ExporterLabel.server_finished : ExporterLabel.client_finished;
+            byte[] prfHash = GetCurrentPrfHash(handshakeHash);
+
+            TlsSecret master_secret = securityParameters.MasterSecret;
+            int verify_data_length = securityParameters.VerifyDataLength;
+
+            return Prf(securityParameters, master_secret, asciiLabel, prfHash, verify_data_length).Extract();
+        }
+
+        internal static void Establish13PhaseSecrets(TlsContext context)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            int cryptoHashAlgorithm = TlsCryptoUtilities.GetHash(securityParameters.PrfHashAlgorithm);
+            int hashLen = securityParameters.PrfHashLength;
+            byte[] zeroes = new byte[hashLen];
+
+            byte[] psk = securityParameters.Psk;
+            if (null == psk)
+            {
+                psk = zeroes;
+            }
+            else
+            {
+                securityParameters.m_psk = null;
+            }
+
+            byte[] ecdhe = zeroes;
+            TlsSecret sharedSecret = securityParameters.SharedSecret;
+            if (null != sharedSecret)
+            {
+                securityParameters.m_sharedSecret = null;
+                ecdhe = sharedSecret.Extract();
+            }
+
+            TlsCrypto crypto = context.Crypto;
+
+            byte[] emptyTranscriptHash = crypto.CreateHash(cryptoHashAlgorithm).CalculateHash();
+
+            TlsSecret earlySecret = crypto.HkdfInit(cryptoHashAlgorithm)
+                .HkdfExtract(cryptoHashAlgorithm, psk);
+            TlsSecret handshakeSecret = DeriveSecret(securityParameters, earlySecret, "derived", emptyTranscriptHash)
+                .HkdfExtract(cryptoHashAlgorithm, ecdhe);
+            TlsSecret masterSecret = DeriveSecret(securityParameters, handshakeSecret, "derived", emptyTranscriptHash)
+                .HkdfExtract(cryptoHashAlgorithm, zeroes);
+
+            securityParameters.m_earlySecret = earlySecret;
+            securityParameters.m_handshakeSecret = handshakeSecret;
+            securityParameters.m_masterSecret = masterSecret;
+        }
+
+        private static void Establish13TrafficSecrets(TlsContext context, byte[] transcriptHash, TlsSecret phaseSecret,
+            string clientLabel, string serverLabel, RecordStream recordStream)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            securityParameters.m_trafficSecretClient = DeriveSecret(securityParameters, phaseSecret, clientLabel,
+                transcriptHash);
+
+            if (null != serverLabel)
+            {
+                securityParameters.m_trafficSecretServer = DeriveSecret(securityParameters, phaseSecret, serverLabel,
+                    transcriptHash);
+            }
+
+            // TODO[tls13] Early data (client->server only)
+
+            recordStream.SetPendingCipher(InitCipher(context));
+        }
+
+        internal static void Establish13PhaseApplication(TlsContext context, byte[] serverFinishedTranscriptHash,
+            RecordStream recordStream)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            TlsSecret phaseSecret = securityParameters.MasterSecret;
+
+            Establish13TrafficSecrets(context, serverFinishedTranscriptHash, phaseSecret, "c ap traffic",
+                "s ap traffic", recordStream);
+
+            securityParameters.m_exporterMasterSecret = DeriveSecret(securityParameters, phaseSecret, "exp master",
+                serverFinishedTranscriptHash);
+        }
+
+        internal static void Establish13PhaseEarly(TlsContext context, byte[] clientHelloTranscriptHash,
+            RecordStream recordStream)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            TlsSecret phaseSecret = securityParameters.EarlySecret;
+
+            // TODO[tls13] binder_key
+
+            // TODO[tls13] Early data (client->server only)
+            if (null != recordStream)
+            {
+                Establish13TrafficSecrets(context, clientHelloTranscriptHash, phaseSecret, "c e traffic", null,
+                    recordStream);
+            }
+
+            securityParameters.m_earlyExporterMasterSecret = DeriveSecret(securityParameters, phaseSecret,
+                "e exp master", clientHelloTranscriptHash);
+        }
+
+        internal static void Establish13PhaseHandshake(TlsContext context, byte[] serverHelloTranscriptHash,
+            RecordStream recordStream)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            TlsSecret phaseSecret = securityParameters.HandshakeSecret;
+
+            Establish13TrafficSecrets(context, serverHelloTranscriptHash, phaseSecret, "c hs traffic", "s hs traffic",
+                recordStream);
+
+            securityParameters.m_baseKeyClient = securityParameters.TrafficSecretClient;
+            securityParameters.m_baseKeyServer = securityParameters.TrafficSecretServer;
+        }
+
+        internal static void Update13TrafficSecretLocal(TlsContext context)
+        {
+            Update13TrafficSecret(context, context.IsServer);
+        }
+
+        internal static void Update13TrafficSecretPeer(TlsContext context)
+        {
+            Update13TrafficSecret(context, !context.IsServer);
+        }
+
+        private static void Update13TrafficSecret(TlsContext context, bool forServer)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+
+            TlsSecret current;
+            if (forServer)
+            {
+                current = securityParameters.TrafficSecretServer;
+                securityParameters.m_trafficSecretServer = Update13TrafficSecret(securityParameters, current);
+            }
+            else
+            {
+                current = securityParameters.TrafficSecretClient;
+                securityParameters.m_trafficSecretClient = Update13TrafficSecret(securityParameters, current);
+            }
+
+            if (null != current)
+            {
+                current.Destroy();
+            }
+        }
+
+        private static TlsSecret Update13TrafficSecret(SecurityParameters securityParameters, TlsSecret secret)
+        {
+            return TlsCryptoUtilities.HkdfExpandLabel(secret, securityParameters.PrfHashAlgorithm, "traffic upd",
+                EmptyBytes, securityParameters.PrfHashLength);
+        }
+
+        public static short GetHashAlgorithmForPrfAlgorithm(int prfAlgorithm)
+        {
+            switch (prfAlgorithm)
+            {
+            case PrfAlgorithm.ssl_prf_legacy:
+            case PrfAlgorithm.tls_prf_legacy:
+                throw new ArgumentException("legacy PRF not a valid algorithm");
+            case PrfAlgorithm.tls_prf_sha256:
+            case PrfAlgorithm.tls13_hkdf_sha256:
+                return HashAlgorithm.sha256;
+            case PrfAlgorithm.tls_prf_sha384:
+            case PrfAlgorithm.tls13_hkdf_sha384:
+                return HashAlgorithm.sha384;
+            // TODO[RFC 8998]
+            //case PrfAlgorithm.tls13_hkdf_sm3:
+            //    return HashAlgorithm.sm3;
+            default:
+                throw new ArgumentException("unknown PrfAlgorithm: " + PrfAlgorithm.GetText(prfAlgorithm));
+            }
+        }
+
+        public static DerObjectIdentifier GetOidForHashAlgorithm(short hashAlgorithm)
+        {
+            switch (hashAlgorithm)
+            {
+            case HashAlgorithm.md5:
+                return PkcsObjectIdentifiers.MD5;
+            case HashAlgorithm.sha1:
+                return X509ObjectIdentifiers.IdSha1;
+            case HashAlgorithm.sha224:
+                return NistObjectIdentifiers.IdSha224;
+            case HashAlgorithm.sha256:
+                return NistObjectIdentifiers.IdSha256;
+            case HashAlgorithm.sha384:
+                return NistObjectIdentifiers.IdSha384;
+            case HashAlgorithm.sha512:
+                return NistObjectIdentifiers.IdSha512;
+                // TODO[RFC 8998]
+            //case HashAlgorithm.sm3:
+            //    return GMObjectIdentifiers.sm3;
+            default:
+                throw new ArgumentException("invalid HashAlgorithm: " + HashAlgorithm.GetText(hashAlgorithm));
+            }
+        }
+
+        internal static int GetPrfAlgorithm(SecurityParameters securityParameters, int cipherSuite)
+        {
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            bool isTlsV13 = IsTlsV13(negotiatedVersion);
+            bool isTlsV12Exactly = !isTlsV13 && IsTlsV12(negotiatedVersion);
+            bool isSsl = negotiatedVersion.IsSsl;
+
+            switch (cipherSuite)
+            {
+            case CipherSuite.TLS_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_CHACHA20_POLY1305_SHA256:
+            {
+                if (isTlsV13)
+                    return PrfAlgorithm.tls13_hkdf_sha256;
+
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            case CipherSuite.TLS_AES_256_GCM_SHA384:
+            {
+                if (isTlsV13)
+                    return PrfAlgorithm.tls13_hkdf_sha384;
+
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            case CipherSuite.TLS_SM4_CCM_SM3:
+            case CipherSuite.TLS_SM4_GCM_SM3:
+            {
+                if (isTlsV13)
+                    return PrfAlgorithm.tls13_hkdf_sm3;
+
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+            {
+                if (isTlsV12Exactly)
+                    return PrfAlgorithm.tls_prf_sha256;
+
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            {
+                if (isTlsV12Exactly)
+                    return PrfAlgorithm.tls_prf_sha384;
+
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+            {
+                if (isTlsV13)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                if (isTlsV12Exactly)
+                    return PrfAlgorithm.tls_prf_sha384;
+
+                if (isSsl)
+                    return PrfAlgorithm.ssl_prf_legacy;
+
+                return PrfAlgorithm.tls_prf_legacy;
+            }
+
+            default:
+            {
+                if (isTlsV13)
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                if (isTlsV12Exactly)
+                    return PrfAlgorithm.tls_prf_sha256;
+
+                if (isSsl)
+                    return PrfAlgorithm.ssl_prf_legacy;
+
+                return PrfAlgorithm.tls_prf_legacy;
+            }
+            }
+        }
+
+        internal static byte[] CalculateSignatureHash(TlsContext context, SignatureAndHashAlgorithm algorithm,
+            DigestInputBuffer buf)
+        {
+            TlsCrypto crypto = context.Crypto;
+
+            TlsHash h = algorithm == null
+                ? new CombinedHash(crypto)
+                : CreateHash(crypto, algorithm.Hash);
+
+            SecurityParameters sp = context.SecurityParameters;
+            byte[] cr = sp.ClientRandom, sr = sp.ServerRandom;
+            h.Update(cr, 0, cr.Length);
+            h.Update(sr, 0, sr.Length);
+            buf.UpdateDigest(h);
+
+            return h.CalculateHash();
+        }
+
+        internal static void SendSignatureInput(TlsContext context, DigestInputBuffer buf, Stream output)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            // NOTE: The implicit copy here is intended (and important)
+            byte[] randoms = Arrays.Concatenate(securityParameters.ClientRandom, securityParameters.ServerRandom);
+            output.Write(randoms, 0, randoms.Length);
+            buf.CopyTo(output);
+            Platform.Dispose(output);
+        }
+
+        internal static DigitallySigned GenerateCertificateVerifyClient(TlsClientContext clientContext,
+            TlsCredentialedSigner credentialedSigner, TlsStreamSigner streamSigner, TlsHandshakeHash handshakeHash)
+        {
+            SecurityParameters securityParameters = clientContext.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (IsTlsV13(negotiatedVersion))
+            {
+                //  Should be using GenerateCertificateVerify13 instead
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            /*
+             * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
+             */
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm = GetSignatureAndHashAlgorithm(negotiatedVersion,
+                credentialedSigner);
+
+            byte[] signature;
+            if (streamSigner != null)
+            {
+                handshakeHash.CopyBufferTo(streamSigner.GetOutputStream());
+                signature = streamSigner.GetSignature();
+            }
+            else
+            {
+                byte[] hash;
+                if (signatureAndHashAlgorithm == null)
+                {
+                    hash = securityParameters.SessionHash;
+                }
+                else
+                {
+                    int signatureScheme = SignatureScheme.From(signatureAndHashAlgorithm);
+                    int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+                    hash = handshakeHash.GetFinalHash(cryptoHashAlgorithm);
+                }
+
+                signature = credentialedSigner.GenerateRawSignature(hash);
+            }
+
+            return new DigitallySigned(signatureAndHashAlgorithm, signature);
+        }
+
+        internal static DigitallySigned Generate13CertificateVerify(TlsContext context,
+            TlsCredentialedSigner credentialedSigner, TlsHandshakeHash handshakeHash)
+        {
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm = credentialedSigner.SignatureAndHashAlgorithm;
+            if (null == signatureAndHashAlgorithm)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            string contextString = context.IsServer
+                ? "TLS 1.3, server CertificateVerify"
+                : "TLS 1.3, client CertificateVerify";
+
+            byte[] signature = Generate13CertificateVerify(context.Crypto, credentialedSigner, contextString,
+                handshakeHash, signatureAndHashAlgorithm);
+
+            return new DigitallySigned(signatureAndHashAlgorithm, signature);
+        }
+
+        private static byte[] Generate13CertificateVerify(TlsCrypto crypto, TlsCredentialedSigner credentialedSigner,
+            string contextString, TlsHandshakeHash handshakeHash, SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            TlsStreamSigner streamSigner = credentialedSigner.GetStreamSigner();
+
+            byte[] header = GetCertificateVerifyHeader(contextString);
+            byte[] prfHash = GetCurrentPrfHash(handshakeHash);
+
+            if (null != streamSigner)
+            {
+                Stream output = streamSigner.GetOutputStream();
+                output.Write(header, 0, header.Length);
+                output.Write(prfHash, 0, prfHash.Length);
+                return streamSigner.GetSignature();
+            }
+
+            int signatureScheme = SignatureScheme.From(signatureAndHashAlgorithm);
+            int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+            TlsHash tlsHash = crypto.CreateHash(cryptoHashAlgorithm);
+            tlsHash.Update(header, 0, header.Length);
+            tlsHash.Update(prfHash, 0, prfHash.Length);
+            byte[] hash = tlsHash.CalculateHash();
+            return credentialedSigner.GenerateRawSignature(hash);
+        }
+
+        internal static void VerifyCertificateVerifyClient(TlsServerContext serverContext,
+            CertificateRequest certificateRequest, DigitallySigned certificateVerify, TlsHandshakeHash handshakeHash)
+        {
+            SecurityParameters securityParameters = serverContext.SecurityParameters;
+            Certificate clientCertificate = securityParameters.PeerCertificate;
+            TlsCertificate verifyingCert = clientCertificate.GetCertificateAt(0);
+            SignatureAndHashAlgorithm sigAndHashAlg = certificateVerify.Algorithm;
+            short signatureAlgorithm;
+
+            if (null == sigAndHashAlg)
+            {
+                signatureAlgorithm = verifyingCert.GetLegacySignatureAlgorithm();
+
+                short clientCertType = GetLegacyClientCertType(signatureAlgorithm);
+                if (clientCertType < 0 || !Arrays.Contains(certificateRequest.CertificateTypes, clientCertType))
+                    throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+            }
+            else
+            {
+                signatureAlgorithm = sigAndHashAlg.Signature;
+
+                // TODO Is it possible (maybe only pre-1.2 to check this immediately when the Certificate arrives?
+                if (!IsValidSignatureAlgorithmForCertificateVerify(signatureAlgorithm,
+                    certificateRequest.CertificateTypes))
+                {
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+                }
+
+                VerifySupportedSignatureAlgorithm(securityParameters.ServerSigAlgs, sigAndHashAlg);
+            }
+
+            // Verify the CertificateVerify message contains a correct signature.
+            bool verified;
+            try
+            {
+                TlsVerifier verifier = verifyingCert.CreateVerifier(signatureAlgorithm);
+                TlsStreamVerifier streamVerifier = verifier.GetStreamVerifier(certificateVerify);
+
+                if (streamVerifier != null)
+                {
+                    handshakeHash.CopyBufferTo(streamVerifier.GetOutputStream());
+                    verified = streamVerifier.IsVerified();
+                }
+                else
+                {
+                    byte[] hash;
+                    if (IsTlsV12(serverContext))
+                    {
+                        int signatureScheme = SignatureScheme.From(sigAndHashAlg);
+                        int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+                        hash = handshakeHash.GetFinalHash(cryptoHashAlgorithm);
+                    }
+                    else
+                    {
+                        hash = securityParameters.SessionHash;
+                    }
+
+                    verified = verifier.VerifyRawSignature(certificateVerify, hash);
+                }
+            }
+            catch (TlsFatalAlert e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error, e);
+            }
+
+            if (!verified)
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+            }
+        }
+
+        internal static void Verify13CertificateVerifyClient(TlsServerContext serverContext,
+            CertificateRequest certificateRequest, DigitallySigned certificateVerify, TlsHandshakeHash handshakeHash)
+        {
+            SecurityParameters securityParameters = serverContext.SecurityParameters;
+            Certificate clientCertificate = securityParameters.PeerCertificate;
+            TlsCertificate verifyingCert = clientCertificate.GetCertificateAt(0);
+
+            SignatureAndHashAlgorithm sigAndHashAlg = certificateVerify.Algorithm;
+            VerifySupportedSignatureAlgorithm(securityParameters.ServerSigAlgs, sigAndHashAlg);
+
+            int signatureScheme = SignatureScheme.From(sigAndHashAlg);
+
+            // Verify the CertificateVerify message contains a correct signature.
+            bool verified;
+            try
+            {
+                TlsVerifier verifier = verifyingCert.CreateVerifier(signatureScheme);
+
+                verified = Verify13CertificateVerify(serverContext.Crypto, certificateVerify, verifier,
+                    "TLS 1.3, client CertificateVerify", handshakeHash);
+            }
+            catch (TlsFatalAlert e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error, e);
+            }
+
+            if (!verified)
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+            }
+        }
+
+        internal static void Verify13CertificateVerifyServer(TlsClientContext clientContext,
+            DigitallySigned certificateVerify, TlsHandshakeHash handshakeHash)
+        {
+            SecurityParameters securityParameters = clientContext.SecurityParameters;
+            Certificate serverCertificate = securityParameters.PeerCertificate;
+            TlsCertificate verifyingCert = serverCertificate.GetCertificateAt(0);
+
+            SignatureAndHashAlgorithm sigAndHashAlg = certificateVerify.Algorithm;
+            VerifySupportedSignatureAlgorithm(securityParameters.ClientSigAlgs, sigAndHashAlg);
+
+            int signatureScheme = SignatureScheme.From(sigAndHashAlg);
+
+            // Verify the CertificateVerify message contains a correct signature.
+            bool verified;
+            try
+            {
+                TlsVerifier verifier = verifyingCert.CreateVerifier(signatureScheme);
+
+                verified = Verify13CertificateVerify(clientContext.Crypto, certificateVerify, verifier,
+                    "TLS 1.3, server CertificateVerify", handshakeHash);
+            }
+            catch (TlsFatalAlert e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error, e);
+            }
+
+            if (!verified)
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+            }
+        }
+
+        private static bool Verify13CertificateVerify(TlsCrypto crypto, DigitallySigned certificateVerify,
+            TlsVerifier verifier, string contextString, TlsHandshakeHash handshakeHash)
+        {
+            TlsStreamVerifier streamVerifier = verifier.GetStreamVerifier(certificateVerify);
+
+            byte[] header = GetCertificateVerifyHeader(contextString);
+            byte[] prfHash = GetCurrentPrfHash(handshakeHash);
+
+            if (null != streamVerifier)
+            {
+                Stream output = streamVerifier.GetOutputStream();
+                output.Write(header, 0, header.Length);
+                output.Write(prfHash, 0, prfHash.Length);
+                return streamVerifier.IsVerified();
+            }
+
+            int signatureScheme = SignatureScheme.From(certificateVerify.Algorithm);
+            int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+            TlsHash tlsHash = crypto.CreateHash(cryptoHashAlgorithm);
+            tlsHash.Update(header, 0, header.Length);
+            tlsHash.Update(prfHash, 0, prfHash.Length);
+            byte[] hash = tlsHash.CalculateHash();
+            return verifier.VerifyRawSignature(certificateVerify, hash);
+        }
+
+        private static byte[] GetCertificateVerifyHeader(string contextString)
+        {
+            int count = contextString.Length;
+            byte[] header = new byte[64 + count + 1];
+            for (int i = 0; i < 64; ++i)
+            {
+                header[i] = 0x20;
+            }
+            for (int i = 0; i < count; ++i)
+            {
+                char c = contextString[i];
+                header[64 + i] = (byte)c;
+            }
+            header[64 + count] = 0x00;
+            return header;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void GenerateServerKeyExchangeSignature(TlsContext context, TlsCredentialedSigner credentials,
+            DigestInputBuffer digestBuffer)
+        {
+            /*
+             * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
+             */
+            SignatureAndHashAlgorithm algorithm = GetSignatureAndHashAlgorithm(context, credentials);
+            TlsStreamSigner streamSigner = credentials.GetStreamSigner();
+
+            byte[] signature;
+            if (streamSigner != null)
+            {
+                SendSignatureInput(context, digestBuffer, streamSigner.GetOutputStream());
+                signature = streamSigner.GetSignature();
+            }
+            else
+            {
+                byte[] hash = CalculateSignatureHash(context, algorithm, digestBuffer);
+                signature = credentials.GenerateRawSignature(hash);
+            }
+
+            DigitallySigned digitallySigned = new DigitallySigned(algorithm, signature);
+
+            digitallySigned.Encode(digestBuffer);
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void VerifyServerKeyExchangeSignature(TlsContext context, Stream signatureInput,
+            TlsCertificate serverCertificate, DigestInputBuffer digestBuffer)
+        {
+            DigitallySigned digitallySigned = DigitallySigned.Parse(context, signatureInput);
+
+            SecurityParameters securityParameters = context.SecurityParameters;
+            int keyExchangeAlgorithm = securityParameters.KeyExchangeAlgorithm;
+
+            SignatureAndHashAlgorithm sigAndHashAlg = digitallySigned.Algorithm;
+            short signatureAlgorithm;
+
+            if (sigAndHashAlg == null)
+            {
+                signatureAlgorithm = GetLegacySignatureAlgorithmServer(keyExchangeAlgorithm);
+            }
+            else
+            {
+                signatureAlgorithm = sigAndHashAlg.Signature;
+
+                if (!IsValidSignatureAlgorithmForServerKeyExchange(signatureAlgorithm, keyExchangeAlgorithm))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                VerifySupportedSignatureAlgorithm(securityParameters.ClientSigAlgs, sigAndHashAlg);
+            }
+
+            TlsVerifier verifier = serverCertificate.CreateVerifier(signatureAlgorithm);
+            TlsStreamVerifier streamVerifier = verifier.GetStreamVerifier(digitallySigned);
+
+            bool verified;
+            if (streamVerifier != null)
+            {
+                SendSignatureInput(context, digestBuffer, streamVerifier.GetOutputStream());
+                verified = streamVerifier.IsVerified();
+            }
+            else
+            {
+                byte[] hash = CalculateSignatureHash(context, sigAndHashAlg, digestBuffer);
+                verified = verifier.VerifyRawSignature(digitallySigned, hash);
+            }
+
+            if (!verified)
+            {
+                throw new TlsFatalAlert(AlertDescription.decrypt_error);
+            }
+        }
+
+        internal static void TrackHashAlgorithms(TlsHandshakeHash handshakeHash, IList supportedSignatureAlgorithms)
+        {
+            if (supportedSignatureAlgorithms != null)
+            {
+                foreach (SignatureAndHashAlgorithm signatureAndHashAlgorithm in supportedSignatureAlgorithms)
+                {
+                    /*
+                     * TODO We could validate the signature algorithm part. Currently the impact is
+                     * that we might be tracking extra hashes pointlessly (but there are only a
+                     * limited number of recognized hash algorithms).
+                     */
+                    int signatureScheme = SignatureScheme.From(signatureAndHashAlgorithm);
+                    int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+                    if (cryptoHashAlgorithm >= 0)
+                    {
+                        handshakeHash.TrackHashAlgorithm(cryptoHashAlgorithm);
+                    }
+                    else if (HashAlgorithm.Intrinsic == signatureAndHashAlgorithm.Hash)
+                    {
+                        handshakeHash.ForceBuffering();
+                    }
+                }
+            }
+        }
+
+        public static bool HasSigningCapability(short clientCertificateType)
+        {
+            switch (clientCertificateType)
+            {
+            case ClientCertificateType.dss_sign:
+            case ClientCertificateType.ecdsa_sign:
+            case ClientCertificateType.rsa_sign:
+                return true;
+            default:
+                return false;
+            }
+        }
+
+        public static IList VectorOfOne(object obj)
+        {
+            IList v = Platform.CreateArrayList(1);
+            v.Add(obj);
+            return v;
+        }
+
+        public static int GetCipherType(int cipherSuite)
+        {
+            int encryptionAlgorithm = GetEncryptionAlgorithm(cipherSuite);
+
+            return GetEncryptionAlgorithmType(encryptionAlgorithm);
+        }
+
+        public static int GetEncryptionAlgorithm(int cipherSuite)
+        {
+            switch (cipherSuite)
+            {
+            case CipherSuite.TLS_DH_anon_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_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA:
+                return EncryptionAlgorithm.cls_3DES_EDE_CBC;
+
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA:
+                return EncryptionAlgorithm.AES_128_CBC;
+
+            case CipherSuite.TLS_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+                return EncryptionAlgorithm.AES_128_CCM;
+
+            case CipherSuite.TLS_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+                return EncryptionAlgorithm.AES_128_CCM_8;
+
+            case CipherSuite.TLS_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+                return EncryptionAlgorithm.AES_128_GCM;
+
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA:
+                return EncryptionAlgorithm.AES_256_CBC;
+
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+                return EncryptionAlgorithm.AES_256_CCM;
+
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+                return EncryptionAlgorithm.AES_256_CCM_8;
+
+            case CipherSuite.TLS_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+                return EncryptionAlgorithm.AES_256_GCM;
+
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256:
+                return EncryptionAlgorithm.ARIA_128_CBC;
+
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256:
+                return EncryptionAlgorithm.ARIA_128_GCM;
+
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384:
+                return EncryptionAlgorithm.ARIA_256_CBC;
+
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384:
+                return EncryptionAlgorithm.ARIA_256_GCM;
+
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+                return EncryptionAlgorithm.CAMELLIA_128_CBC;
+
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+                return EncryptionAlgorithm.CAMELLIA_128_GCM;
+
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+                return EncryptionAlgorithm.CAMELLIA_256_CBC;
+
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+                return EncryptionAlgorithm.CAMELLIA_256_GCM;
+
+            case CipherSuite.TLS_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+                return EncryptionAlgorithm.CHACHA20_POLY1305;
+
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+                return EncryptionAlgorithm.NULL;
+
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+                return EncryptionAlgorithm.NULL;
+
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+                return EncryptionAlgorithm.NULL;
+
+            case CipherSuite.TLS_DH_anon_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+                return EncryptionAlgorithm.SEED_CBC;
+
+            case CipherSuite.TLS_SM4_CCM_SM3:
+                return EncryptionAlgorithm.SM4_CCM;
+
+            case CipherSuite.TLS_SM4_GCM_SM3:
+                return EncryptionAlgorithm.SM4_GCM;
+
+            default:
+                return -1;
+            }
+        }
+
+        public static int GetEncryptionAlgorithmType(int encryptionAlgorithm)
+        {
+            switch (encryptionAlgorithm)
+            {
+            case EncryptionAlgorithm.AES_128_CCM:
+            case EncryptionAlgorithm.AES_128_CCM_8:
+            case EncryptionAlgorithm.AES_128_GCM:
+            case EncryptionAlgorithm.AES_256_CCM:
+            case EncryptionAlgorithm.AES_256_CCM_8:
+            case EncryptionAlgorithm.AES_256_GCM:
+            case EncryptionAlgorithm.ARIA_128_GCM:
+            case EncryptionAlgorithm.ARIA_256_GCM:
+            case EncryptionAlgorithm.CAMELLIA_128_GCM:
+            case EncryptionAlgorithm.CAMELLIA_256_GCM:
+            case EncryptionAlgorithm.CHACHA20_POLY1305:
+            case EncryptionAlgorithm.SM4_CCM:
+            case EncryptionAlgorithm.SM4_GCM:
+                return CipherType.aead;
+
+            case EncryptionAlgorithm.RC2_CBC_40:
+            case EncryptionAlgorithm.IDEA_CBC:
+            case EncryptionAlgorithm.DES40_CBC:
+            case EncryptionAlgorithm.DES_CBC:
+            case EncryptionAlgorithm.cls_3DES_EDE_CBC:
+            case EncryptionAlgorithm.AES_128_CBC:
+            case EncryptionAlgorithm.AES_256_CBC:
+            case EncryptionAlgorithm.ARIA_128_CBC:
+            case EncryptionAlgorithm.ARIA_256_CBC:
+            case EncryptionAlgorithm.CAMELLIA_128_CBC:
+            case EncryptionAlgorithm.CAMELLIA_256_CBC:
+            case EncryptionAlgorithm.SEED_CBC:
+            case EncryptionAlgorithm.SM4_CBC:
+                return CipherType.block;
+
+            case EncryptionAlgorithm.NULL:
+            case EncryptionAlgorithm.RC4_40:
+            case EncryptionAlgorithm.RC4_128:
+                return CipherType.stream;
+
+            default:
+                return -1;
+            }
+        }
+
+        public static int GetKeyExchangeAlgorithm(int cipherSuite)
+        {
+            switch (cipherSuite)
+            {
+            case CipherSuite.TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_SEED_CBC_SHA:
+                return KeyExchangeAlgorithm.DH_anon;
+
+            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_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+                return 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_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+                return 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_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+                return KeyExchangeAlgorithm.DHE_DSS;
+
+            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_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+                return KeyExchangeAlgorithm.DHE_PSK;
+
+            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_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+                return KeyExchangeAlgorithm.DHE_RSA;
+
+            case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA:
+                return KeyExchangeAlgorithm.ECDH_anon;
+
+            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_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+                return KeyExchangeAlgorithm.ECDH_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_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+                return KeyExchangeAlgorithm.ECDH_RSA;
+
+            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_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+                return KeyExchangeAlgorithm.ECDHE_ECDSA;
+
+            case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+                return KeyExchangeAlgorithm.ECDHE_PSK;
+
+            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_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+                return KeyExchangeAlgorithm.ECDHE_RSA;
+
+            case CipherSuite.TLS_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_SM4_CCM_SM3:
+            case CipherSuite.TLS_SM4_GCM_SM3:
+                return KeyExchangeAlgorithm.NULL;
+
+            case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+                return KeyExchangeAlgorithm.PSK;
+
+            case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+                return KeyExchangeAlgorithm.RSA;
+
+            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_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+                return KeyExchangeAlgorithm.RSA_PSK;
+
+            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 KeyExchangeAlgorithm.SRP;
+
+            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 KeyExchangeAlgorithm.SRP_DSS;
+
+            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 KeyExchangeAlgorithm.SRP_RSA;
+
+            default:
+                return -1;
+            }
+        }
+
+        public static IList GetKeyExchangeAlgorithms(int[] cipherSuites)
+        {
+            IList result = Platform.CreateArrayList();
+            if (null != cipherSuites)
+            {
+                for (int i = 0; i < cipherSuites.Length; ++i)
+                {
+                    AddToSet(result, GetKeyExchangeAlgorithm(cipherSuites[i]));
+                }
+                result.Remove(-1);
+            }
+            return result;
+        }
+
+        public static int GetMacAlgorithm(int cipherSuite)
+        {
+            switch (cipherSuite)
+            {
+            case CipherSuite.TLS_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_SM4_CCM_SM3:
+            case CipherSuite.TLS_SM4_GCM_SM3:
+                return MacAlgorithm.cls_null;
+
+            case CipherSuite.TLS_DH_anon_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_anon_WITH_SEED_CBC_SHA:
+            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:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_DSS_WITH_SEED_CBC_SHA:
+            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:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DH_RSA_WITH_SEED_CBC_SHA:
+            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:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_DSS_WITH_SEED_CBC_SHA:
+            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:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+            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:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDH_anon_WITH_NULL_SHA:
+            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:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+            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:
+            case CipherSuite.TLS_ECDH_RSA_WITH_NULL_SHA:
+            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:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+            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:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+            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:
+            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:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+            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:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+            case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+            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:
+            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:
+            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 MacAlgorithm.hmac_sha1;
+
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+                return MacAlgorithm.hmac_sha256;
+
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_CBC_SHA384:
+                return MacAlgorithm.hmac_sha384;
+
+            default:
+                return -1;
+            }
+        }
+
+        public static ProtocolVersion GetMinimumVersion(int cipherSuite)
+        {
+            switch (cipherSuite)
+            {
+            case CipherSuite.TLS_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_SM4_CCM_SM3:
+            case CipherSuite.TLS_SM4_GCM_SM3:
+                return ProtocolVersion.TLSv13;
+
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+            case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+            case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+            case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_CBC_SHA384:
+            case CipherSuite.TLS_RSA_WITH_ARIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+            case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+            case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+                return ProtocolVersion.TLSv12;
+
+            default:
+                return ProtocolVersion.SSLv3;
+            }
+        }
+
+        public static IList GetNamedGroupRoles(int[] cipherSuites)
+        {
+            return GetNamedGroupRoles(GetKeyExchangeAlgorithms(cipherSuites));
+        }
+
+        public static IList GetNamedGroupRoles(IList keyExchangeAlgorithms)
+        {
+            IList result = Platform.CreateArrayList();
+            foreach (int keyExchangeAlgorithm in keyExchangeAlgorithms)
+            {
+                switch (keyExchangeAlgorithm)
+                {
+                case KeyExchangeAlgorithm.DH_anon:
+                case KeyExchangeAlgorithm.DH_DSS:
+                case KeyExchangeAlgorithm.DH_RSA:
+                case KeyExchangeAlgorithm.DHE_DSS:
+                case KeyExchangeAlgorithm.DHE_PSK:
+                case KeyExchangeAlgorithm.DHE_RSA:
+                {
+                    AddToSet(result, NamedGroupRole.dh);
+                    break;
+                }
+
+                case KeyExchangeAlgorithm.ECDH_anon:
+                case KeyExchangeAlgorithm.ECDH_RSA:
+                case KeyExchangeAlgorithm.ECDHE_PSK:
+                case KeyExchangeAlgorithm.ECDHE_RSA:
+                {
+                    AddToSet(result, NamedGroupRole.ecdh);
+                    break;
+                }
+
+                case KeyExchangeAlgorithm.ECDH_ECDSA:
+                case KeyExchangeAlgorithm.ECDHE_ECDSA:
+                {
+                    AddToSet(result, NamedGroupRole.ecdh);
+                    AddToSet(result, NamedGroupRole.ecdsa);
+                    break;
+                }
+
+                case KeyExchangeAlgorithm.NULL:
+                {
+                    // TODO[tls13] We're conservatively adding both here, though maybe only one is needed
+                    AddToSet(result, NamedGroupRole.dh);
+                    AddToSet(result, NamedGroupRole.ecdh);
+                    break;
+                }
+                }
+            }
+            return result;
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool IsAeadCipherSuite(int cipherSuite)
+        {
+            return CipherType.aead == GetCipherType(cipherSuite);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool IsBlockCipherSuite(int cipherSuite)
+        {
+            return CipherType.block == GetCipherType(cipherSuite);
+        }
+
+        /// <exception cref="IOException"/>
+        public static bool IsStreamCipherSuite(int cipherSuite)
+        {
+            return CipherType.stream == GetCipherType(cipherSuite);
+        }
+
+        /// <returns>Whether a server can select the specified cipher suite given the available signature algorithms
+        /// for ServerKeyExchange.</returns>
+        public static bool IsValidCipherSuiteForSignatureAlgorithms(int cipherSuite, IList sigAlgs)
+        {
+            int keyExchangeAlgorithm = GetKeyExchangeAlgorithm(cipherSuite);
+
+            switch (keyExchangeAlgorithm)
+            {
+            case KeyExchangeAlgorithm.DHE_DSS:
+            case KeyExchangeAlgorithm.DHE_RSA:
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+            case KeyExchangeAlgorithm.NULL:
+            case KeyExchangeAlgorithm.SRP_RSA:
+            case KeyExchangeAlgorithm.SRP_DSS:
+                break;
+
+            default:
+                return true;
+            }
+
+            foreach (short signatureAlgorithm in sigAlgs)
+            {
+                if (IsValidSignatureAlgorithmForServerKeyExchange(signatureAlgorithm, keyExchangeAlgorithm))
+                    return true;
+            }
+
+            return false;
+        }
+
+        internal static bool IsValidCipherSuiteSelection(int[] offeredCipherSuites, int cipherSuite)
+        {
+            return null != offeredCipherSuites
+                && Arrays.Contains(offeredCipherSuites, cipherSuite)
+                && CipherSuite.TLS_NULL_WITH_NULL_NULL != cipherSuite
+                && !CipherSuite.IsScsv(cipherSuite);
+        }
+
+        internal static bool IsValidKeyShareSelection(ProtocolVersion negotiatedVersion, int[] clientSupportedGroups,
+            IDictionary clientAgreements, int keyShareGroup)
+        {
+            return null != clientSupportedGroups
+                && Arrays.Contains(clientSupportedGroups, keyShareGroup)
+                && !clientAgreements.Contains(keyShareGroup)
+                && NamedGroup.CanBeNegotiated(keyShareGroup, negotiatedVersion);
+        }
+
+        internal static bool IsValidSignatureAlgorithmForCertificateVerify(short signatureAlgorithm,
+            short[] clientCertificateTypes)
+        {
+            short clientCertificateType = SignatureAlgorithm.GetClientCertificateType(signatureAlgorithm);
+
+            return clientCertificateType >= 0 &&  Arrays.Contains(clientCertificateTypes, clientCertificateType);
+        }
+
+        internal static bool IsValidSignatureAlgorithmForServerKeyExchange(short signatureAlgorithm,
+            int keyExchangeAlgorithm)
+        {
+            // TODO[tls13]
+
+            switch (keyExchangeAlgorithm)
+            {
+            case KeyExchangeAlgorithm.DHE_RSA:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+            case KeyExchangeAlgorithm.SRP_RSA:
+                switch (signatureAlgorithm)
+                {
+                case SignatureAlgorithm.rsa:
+                case SignatureAlgorithm.rsa_pss_rsae_sha256:
+                case SignatureAlgorithm.rsa_pss_rsae_sha384:
+                case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                case SignatureAlgorithm.rsa_pss_pss_sha256:
+                case SignatureAlgorithm.rsa_pss_pss_sha384:
+                case SignatureAlgorithm.rsa_pss_pss_sha512:
+                    return true;
+                default:
+                    return false;
+                }
+
+            case KeyExchangeAlgorithm.DHE_DSS:
+            case KeyExchangeAlgorithm.SRP_DSS:
+                return SignatureAlgorithm.dsa == signatureAlgorithm;
+
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+                switch (signatureAlgorithm)
+                {
+                case SignatureAlgorithm.ecdsa:
+                case SignatureAlgorithm.ed25519:
+                case SignatureAlgorithm.ed448:
+                    return true;
+                default:
+                    return false;
+                }
+
+            case KeyExchangeAlgorithm.NULL:
+                return SignatureAlgorithm.anonymous != signatureAlgorithm;
+
+            default:
+                return false;
+            }
+        }
+
+        public static bool IsValidSignatureSchemeForServerKeyExchange(int signatureScheme, int keyExchangeAlgorithm)
+        {
+            short signatureAlgorithm = SignatureScheme.GetSignatureAlgorithm(signatureScheme);
+
+            return IsValidSignatureAlgorithmForServerKeyExchange(signatureAlgorithm, keyExchangeAlgorithm);
+        }
+
+        public static bool IsValidVersionForCipherSuite(int cipherSuite, ProtocolVersion version)
+        {
+            version = version.GetEquivalentTlsVersion();
+
+            ProtocolVersion minimumVersion = GetMinimumVersion(cipherSuite);
+            if (minimumVersion == version)
+                return true;
+
+            if (!minimumVersion.IsEarlierVersionOf(version))
+                return false;
+
+            return ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(minimumVersion)
+                || ProtocolVersion.TLSv13.IsLaterVersionOf(version);
+        }
+
+        /// <exception cref="IOException"/>
+        public static SignatureAndHashAlgorithm ChooseSignatureAndHashAlgorithm(TlsContext context, IList sigHashAlgs,
+            short signatureAlgorithm)
+        {
+            return ChooseSignatureAndHashAlgorithm(context.ServerVersion, sigHashAlgs, signatureAlgorithm);
+        }
+
+        /// <exception cref="IOException"/>
+        public static SignatureAndHashAlgorithm ChooseSignatureAndHashAlgorithm(ProtocolVersion negotiatedVersion,
+            IList sigHashAlgs, short signatureAlgorithm)
+        {
+            if (!IsTlsV12(negotiatedVersion))
+                return null;
+
+
+            if (sigHashAlgs == null)
+            {
+                /*
+                 * TODO[tls13] RFC 8446 4.2.3 Clients which desire the server to authenticate itself via
+                 * a certificate MUST send the "signature_algorithms" extension.
+                 */
+
+                sigHashAlgs = GetDefaultSignatureAlgorithms(signatureAlgorithm);
+            }
+
+            SignatureAndHashAlgorithm result = null;
+            foreach (SignatureAndHashAlgorithm sigHashAlg in sigHashAlgs)
+            {
+                if (sigHashAlg.Signature != signatureAlgorithm)
+                    continue;
+
+                short hash = sigHashAlg.Hash;
+                if (hash < MinimumHashStrict)
+                    continue;
+
+                if (result == null)
+                {
+                    result = sigHashAlg;
+                    continue;
+                }
+
+                short current = result.Hash;
+                if (current < MinimumHashPreferred)
+                {
+                    if (hash > current)
+                    {
+                        result = sigHashAlg;
+                    }
+                }
+                else if (hash >= MinimumHashPreferred)
+                {
+                    if (hash < current)
+                    {
+                        result = sigHashAlg;
+                    }
+                }
+            }
+            if (result == null)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return result;
+        }
+
+        public static IList GetUsableSignatureAlgorithms(IList sigHashAlgs)
+        {
+            if (sigHashAlgs == null)
+            {
+                IList v = Platform.CreateArrayList(3);
+                v.Add(SignatureAlgorithm.rsa);
+                v.Add(SignatureAlgorithm.dsa);
+                v.Add(SignatureAlgorithm.ecdsa);
+                return v;
+            }
+            else
+            {
+                IList v = Platform.CreateArrayList();
+                foreach (SignatureAndHashAlgorithm sigHashAlg in sigHashAlgs)
+                {
+                    if (sigHashAlg.Hash >= MinimumHashStrict)
+                    {
+                        short sigAlg = sigHashAlg.Signature;
+                        if (!v.Contains(sigAlg))
+                        {
+                            // TODO Check for crypto support before choosing (or pass in cached list?)
+                            v.Add(sigAlg);
+                        }
+                    }
+                }
+                return v;
+            }
+        }
+
+        public static int GetCommonCipherSuite13(ProtocolVersion negotiatedVersion, int[] peerCipherSuites,
+            int[] localCipherSuites, bool useLocalOrder)
+        {
+            int[] ordered = peerCipherSuites, unordered = localCipherSuites;
+            if (useLocalOrder)
+            {
+                ordered = localCipherSuites;
+                unordered = peerCipherSuites;
+            }
+
+            for (int i = 0; i < ordered.Length; ++i)
+            {
+                int candidate = ordered[i];
+                if (Arrays.Contains(unordered, candidate) &&
+                    IsValidVersionForCipherSuite(candidate, negotiatedVersion))
+                {
+                    return candidate;
+                }
+            }
+
+            return -1;
+        }
+
+        public static int[] GetCommonCipherSuites(int[] peerCipherSuites, int[] localCipherSuites, bool useLocalOrder)
+        {
+            int[] ordered = peerCipherSuites, unordered = localCipherSuites;
+            if (useLocalOrder)
+            {
+                ordered = localCipherSuites;
+                unordered = peerCipherSuites;
+            }
+
+            int count = 0, limit = System.Math.Min(ordered.Length, unordered.Length);
+            int[] candidates = new int[limit];
+            for (int i = 0; i < ordered.Length; ++i)
+            {
+                int candidate = ordered[i];
+                if (!Contains(candidates, 0, count, candidate)
+                    && Arrays.Contains(unordered, candidate))
+                {
+                    candidates[count++] = candidate;
+                }
+            }
+
+            if (count < limit)
+            {
+                candidates = Arrays.CopyOf(candidates, count);
+            }
+
+            return candidates;
+        }
+
+        public static int[] GetSupportedCipherSuites(TlsCrypto crypto, int[] suites)
+        {
+            return GetSupportedCipherSuites(crypto, suites, 0, suites.Length);
+        }
+
+        public static int[] GetSupportedCipherSuites(TlsCrypto crypto, int[] suites, int suitesOff, int suitesCount)
+        {
+            int[] supported = new int[suitesCount];
+            int count = 0;
+
+            for (int i = 0; i < suitesCount; ++i)
+            {
+                int suite = suites[suitesOff + i];
+                if (IsSupportedCipherSuite(crypto, suite))
+                {
+                    supported[count++] = suite;
+                }
+            }
+
+            if (count < suitesCount)
+            {
+                supported = Arrays.CopyOf(supported, count);
+            }
+
+            return supported;
+        }
+
+        public static bool IsSupportedCipherSuite(TlsCrypto crypto, int cipherSuite)
+        {
+            return IsSupportedKeyExchange(crypto, GetKeyExchangeAlgorithm(cipherSuite))
+                && crypto.HasEncryptionAlgorithm(GetEncryptionAlgorithm(cipherSuite))
+                && crypto.HasMacAlgorithm(GetMacAlgorithm(cipherSuite));
+        }
+
+        public static bool IsSupportedKeyExchange(TlsCrypto crypto, int keyExchangeAlgorithm)
+        {
+            switch (keyExchangeAlgorithm)
+            {
+            case KeyExchangeAlgorithm.DH_anon:
+            case KeyExchangeAlgorithm.DH_DSS:
+            case KeyExchangeAlgorithm.DH_RSA:
+            case KeyExchangeAlgorithm.DHE_PSK:
+                return crypto.HasDHAgreement();
+
+            case KeyExchangeAlgorithm.DHE_DSS:
+                return crypto.HasDHAgreement()
+                    && crypto.HasSignatureAlgorithm(SignatureAlgorithm.dsa);
+
+            case KeyExchangeAlgorithm.DHE_RSA:
+                return crypto.HasDHAgreement()
+                    && HasAnyRsaSigAlgs(crypto);
+
+            case KeyExchangeAlgorithm.ECDH_anon:
+            case KeyExchangeAlgorithm.ECDH_ECDSA:
+            case KeyExchangeAlgorithm.ECDH_RSA:
+            case KeyExchangeAlgorithm.ECDHE_PSK:
+                return crypto.HasECDHAgreement();
+
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+                return crypto.HasECDHAgreement()
+                    && (crypto.HasSignatureAlgorithm(SignatureAlgorithm.ecdsa)
+                        || crypto.HasSignatureAlgorithm(SignatureAlgorithm.ed25519)
+                        || crypto.HasSignatureAlgorithm(SignatureAlgorithm.ed448));
+
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+                return crypto.HasECDHAgreement()
+                    && HasAnyRsaSigAlgs(crypto);
+
+            case KeyExchangeAlgorithm.NULL:
+            case KeyExchangeAlgorithm.PSK:
+                return true;
+
+            case KeyExchangeAlgorithm.RSA:
+            case KeyExchangeAlgorithm.RSA_PSK:
+                return crypto.HasRsaEncryption();
+
+            case KeyExchangeAlgorithm.SRP:
+                return crypto.HasSrpAuthentication();
+
+            case KeyExchangeAlgorithm.SRP_DSS:
+                return crypto.HasSrpAuthentication()
+                    && crypto.HasSignatureAlgorithm(SignatureAlgorithm.dsa);
+
+            case KeyExchangeAlgorithm.SRP_RSA:
+                return crypto.HasSrpAuthentication()
+                    && HasAnyRsaSigAlgs(crypto);
+
+            default:
+                return false;
+            }
+        }
+
+        internal static bool HasAnyRsaSigAlgs(TlsCrypto crypto)
+        {
+            return crypto.HasSignatureAlgorithm(SignatureAlgorithm.rsa)
+                || crypto.HasSignatureAlgorithm(SignatureAlgorithm.rsa_pss_rsae_sha256)
+                || crypto.HasSignatureAlgorithm(SignatureAlgorithm.rsa_pss_rsae_sha384)
+                || crypto.HasSignatureAlgorithm(SignatureAlgorithm.rsa_pss_rsae_sha512)
+                || crypto.HasSignatureAlgorithm(SignatureAlgorithm.rsa_pss_pss_sha256)
+                || crypto.HasSignatureAlgorithm(SignatureAlgorithm.rsa_pss_pss_sha384)
+                || crypto.HasSignatureAlgorithm(SignatureAlgorithm.rsa_pss_pss_sha512);
+        }
+
+        internal static byte[] GetCurrentPrfHash(TlsHandshakeHash handshakeHash)
+        {
+            return handshakeHash.ForkPrfHash().CalculateHash();
+        }
+
+        internal static void SealHandshakeHash(TlsContext context, TlsHandshakeHash handshakeHash, bool forceBuffering)
+        {
+            if (forceBuffering || !context.Crypto.HasAllRawSignatureAlgorithms())
+            {
+                handshakeHash.ForceBuffering();
+            }
+
+            handshakeHash.SealHashAlgorithms();
+        }
+
+        private static TlsHash CreateHash(TlsCrypto crypto, short hashAlgorithm)
+        {
+            int cryptoHashAlgorithm = TlsCryptoUtilities.GetHash(hashAlgorithm);
+
+            return crypto.CreateHash(cryptoHashAlgorithm);
+        }
+
+        /// <exception cref="IOException"/>
+        private static TlsKeyExchange CreateKeyExchangeClient(TlsClient client, int keyExchange)
+        {
+            TlsKeyExchangeFactory factory = client.GetKeyExchangeFactory();
+
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.DH_anon:
+                return factory.CreateDHanonKeyExchangeClient(keyExchange, client.GetDHGroupVerifier());
+
+            case KeyExchangeAlgorithm.DH_DSS:
+            case KeyExchangeAlgorithm.DH_RSA:
+                return factory.CreateDHKeyExchange(keyExchange);
+
+            case KeyExchangeAlgorithm.DHE_DSS:
+            case KeyExchangeAlgorithm.DHE_RSA:
+                return factory.CreateDheKeyExchangeClient(keyExchange, client.GetDHGroupVerifier());
+
+            case KeyExchangeAlgorithm.ECDH_anon:
+                return factory.CreateECDHanonKeyExchangeClient(keyExchange);
+
+            case KeyExchangeAlgorithm.ECDH_ECDSA:
+            case KeyExchangeAlgorithm.ECDH_RSA:
+                return factory.CreateECDHKeyExchange(keyExchange);
+
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+                return factory.CreateECDheKeyExchangeClient(keyExchange);
+
+            case KeyExchangeAlgorithm.RSA:
+                return factory.CreateRsaKeyExchange(keyExchange);
+
+            case KeyExchangeAlgorithm.DHE_PSK:
+                return factory.CreatePskKeyExchangeClient(keyExchange, client.GetPskIdentity(),
+                    client.GetDHGroupVerifier());
+
+            case KeyExchangeAlgorithm.ECDHE_PSK:
+            case KeyExchangeAlgorithm.PSK:
+            case KeyExchangeAlgorithm.RSA_PSK:
+                return factory.CreatePskKeyExchangeClient(keyExchange, client.GetPskIdentity(), null);
+
+            case KeyExchangeAlgorithm.SRP:
+            case KeyExchangeAlgorithm.SRP_DSS:
+            case KeyExchangeAlgorithm.SRP_RSA:
+                return factory.CreateSrpKeyExchangeClient(keyExchange, client.GetSrpIdentity(),
+                    client.GetSrpConfigVerifier());
+
+            default:
+                /*
+                 * Note: internal error here; the TlsProtocol implementation 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);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        private static TlsKeyExchange CreateKeyExchangeServer(TlsServer server, int keyExchange)
+        {
+            TlsKeyExchangeFactory factory = server.GetKeyExchangeFactory();
+
+            switch (keyExchange)
+            {
+            case KeyExchangeAlgorithm.DH_anon:
+                return factory.CreateDHanonKeyExchangeServer(keyExchange, server.GetDHConfig());
+
+            case KeyExchangeAlgorithm.DH_DSS:
+            case KeyExchangeAlgorithm.DH_RSA:
+                return factory.CreateDHKeyExchange(keyExchange);
+
+            case KeyExchangeAlgorithm.DHE_DSS:
+            case KeyExchangeAlgorithm.DHE_RSA:
+                return factory.CreateDheKeyExchangeServer(keyExchange, server.GetDHConfig());
+
+            case KeyExchangeAlgorithm.ECDH_anon:
+                return factory.CreateECDHanonKeyExchangeServer(keyExchange, server.GetECDHConfig());
+
+            case KeyExchangeAlgorithm.ECDH_ECDSA:
+            case KeyExchangeAlgorithm.ECDH_RSA:
+                return factory.CreateECDHKeyExchange(keyExchange);
+
+            case KeyExchangeAlgorithm.ECDHE_ECDSA:
+            case KeyExchangeAlgorithm.ECDHE_RSA:
+                return factory.CreateECDheKeyExchangeServer(keyExchange, server.GetECDHConfig());
+
+            case KeyExchangeAlgorithm.RSA:
+                return factory.CreateRsaKeyExchange(keyExchange);
+
+            case KeyExchangeAlgorithm.DHE_PSK:
+                return factory.CreatePskKeyExchangeServer(keyExchange, server.GetPskIdentityManager(),
+                    server.GetDHConfig(), null);
+
+            case KeyExchangeAlgorithm.ECDHE_PSK:
+                return factory.CreatePskKeyExchangeServer(keyExchange, server.GetPskIdentityManager(), null,
+                    server.GetECDHConfig());
+
+            case KeyExchangeAlgorithm.PSK:
+            case KeyExchangeAlgorithm.RSA_PSK:
+                return factory.CreatePskKeyExchangeServer(keyExchange, server.GetPskIdentityManager(), null, null);
+
+            case KeyExchangeAlgorithm.SRP:
+            case KeyExchangeAlgorithm.SRP_DSS:
+            case KeyExchangeAlgorithm.SRP_RSA:
+                return factory.CreateSrpKeyExchangeServer(keyExchange, server.GetSrpLoginParameters());
+
+            default:
+                /*
+                 * Note: internal error here; the TlsProtocol implementation 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);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        internal static TlsKeyExchange InitKeyExchangeClient(TlsClientContext clientContext, TlsClient client)
+        {
+            SecurityParameters securityParameters = clientContext.SecurityParameters;
+            TlsKeyExchange keyExchange = CreateKeyExchangeClient(client, securityParameters.KeyExchangeAlgorithm);
+            keyExchange.Init(clientContext);
+            return keyExchange;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static TlsKeyExchange InitKeyExchangeServer(TlsServerContext serverContext, TlsServer server)
+        {
+            SecurityParameters securityParameters = serverContext.SecurityParameters;
+            TlsKeyExchange keyExchange = CreateKeyExchangeServer(server, securityParameters.KeyExchangeAlgorithm);
+            keyExchange.Init(serverContext);
+            return keyExchange;
+        }
+
+        internal static TlsCipher InitCipher(TlsContext context)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            int cipherSuite = securityParameters.CipherSuite;
+            int encryptionAlgorithm = GetEncryptionAlgorithm(cipherSuite);
+            int macAlgorithm = GetMacAlgorithm(cipherSuite);
+
+            if (encryptionAlgorithm < 0 || macAlgorithm < 0)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return context.Crypto.CreateCipher(new TlsCryptoParameters(context), encryptionAlgorithm, macAlgorithm);
+        }
+
+        /// <summary>Check the signature algorithm for certificates in the peer's CertPath as specified in RFC 5246
+        /// 7.4.2, 7.4.4, 7.4.6 and similar rules for earlier TLS versions.</summary>
+        /// <remarks>
+        /// The supplied CertPath should include the trust anchor (its signature algorithm isn't checked, but in the
+        /// general case checking a certificate requires the issuer certificate).
+        /// </remarks>
+        /// <exception cref="IOException">if any certificate in the CertPath (excepting the trust anchor) has a
+        /// signature algorithm that is not one of the locally supported signature algorithms.</exception>
+        public static void CheckPeerSigAlgs(TlsContext context, TlsCertificate[] peerCertPath)
+        {
+            if (context.IsServer)
+            {
+                CheckSigAlgOfClientCerts(context, peerCertPath);
+            }
+            else
+            {
+                CheckSigAlgOfServerCerts(context, peerCertPath);
+            }
+        }
+
+        private static void CheckSigAlgOfClientCerts(TlsContext context, TlsCertificate[] clientCertPath)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            short[] clientCertTypes = securityParameters.ClientCertTypes;
+            IList serverSigAlgsCert = securityParameters.ServerSigAlgsCert;
+
+            int trustAnchorPos = clientCertPath.Length - 1;
+            for (int i = 0; i < trustAnchorPos; ++i)
+            {
+                TlsCertificate subjectCert = clientCertPath[i];
+                TlsCertificate issuerCert = clientCertPath[i + 1];
+
+                SignatureAndHashAlgorithm sigAndHashAlg = GetCertSigAndHashAlg(subjectCert, issuerCert);
+
+                bool valid = false;
+                if (null == sigAndHashAlg)
+                {
+                    // We don't recognize the 'signatureAlgorithm' of the certificate
+                }
+                else if (null == serverSigAlgsCert)
+                {
+                    // TODO Review this (legacy) logic with RFC 4346 (7.4?.2?)
+                    if (null != clientCertTypes)
+                    {
+                        for (int j = 0; j < clientCertTypes.Length; ++j)
+                        {
+                            short signatureAlgorithm = GetLegacySignatureAlgorithmClientCert(clientCertTypes[j]);
+                            if (sigAndHashAlg.Signature == signatureAlgorithm)
+                            {
+                                valid = true;
+                                break;
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    /*
+                     * RFC 5246 7.4.4 Any certificates provided by the client MUST be signed using a
+                     * hash/signature algorithm pair found in supported_signature_algorithms.
+                     */
+                    valid = ContainsSignatureAlgorithm(serverSigAlgsCert, sigAndHashAlg);
+                }
+
+                if (!valid)
+                {
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+                }
+            }
+        }
+
+        private static void CheckSigAlgOfServerCerts(TlsContext context, TlsCertificate[] serverCertPath)
+        {
+            SecurityParameters securityParameters = context.SecurityParameters;
+            IList clientSigAlgsCert = securityParameters.ClientSigAlgsCert;
+            IList clientSigAlgs = securityParameters.ClientSigAlgs;
+
+            /*
+             * NOTE: For TLS 1.2, we'll check 'signature_algorithms' too (if it's distinct), since
+             * there's no way of knowing whether the server understood 'signature_algorithms_cert'.
+             */
+            if (clientSigAlgs == clientSigAlgsCert || IsTlsV13(securityParameters.NegotiatedVersion))
+            {
+                clientSigAlgs = null;
+            }
+
+            int trustAnchorPos = serverCertPath.Length - 1;
+            for (int i = 0; i < trustAnchorPos; ++i)
+            {
+                TlsCertificate subjectCert = serverCertPath[i];
+                TlsCertificate issuerCert = serverCertPath[i + 1];
+
+                SignatureAndHashAlgorithm sigAndHashAlg = GetCertSigAndHashAlg(subjectCert, issuerCert);
+
+                bool valid = false;
+                if (null == sigAndHashAlg)
+                {
+                    // We don't recognize the 'signatureAlgorithm' of the certificate
+                }
+                else if (null == clientSigAlgsCert)
+                {
+                    /*
+                     * RFC 4346 7.4.2. Unless otherwise specified, the signing algorithm for the
+                     * certificate MUST be the same as the algorithm for the certificate key.
+                     */
+                    short signatureAlgorithm = GetLegacySignatureAlgorithmServerCert(
+                        securityParameters.KeyExchangeAlgorithm);
+
+                    valid = (signatureAlgorithm == sigAndHashAlg.Signature); 
+                }
+                else
+                {
+                    /*
+                     * RFC 5246 7.4.2. If the client provided a "signature_algorithms" extension, then
+                     * all certificates provided by the server MUST be signed by a hash/signature algorithm
+                     * pair that appears in that extension.
+                     */
+                    valid = ContainsSignatureAlgorithm(clientSigAlgsCert, sigAndHashAlg)
+                        || (null != clientSigAlgs && ContainsSignatureAlgorithm(clientSigAlgs, sigAndHashAlg));
+                }
+
+                if (!valid)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+            }
+        }
+
+        internal static void CheckTlsFeatures(Certificate serverCertificate, IDictionary clientExtensions,
+            IDictionary serverExtensions)
+        {
+            /*
+             * RFC 7633 4.3.3. A client MUST treat a certificate with a TLS feature extension as an
+             * invalid certificate if the features offered by the server do not contain all features
+             * present in both the client's ClientHello message and the TLS feature extension.
+             */
+            byte[] tlsFeatures = serverCertificate.GetCertificateAt(0).GetExtension(TlsObjectIdentifiers.id_pe_tlsfeature);
+            if (tlsFeatures != null)
+            {
+                foreach (DerInteger tlsExtension in Asn1Sequence.GetInstance(ReadDerObject(tlsFeatures)))
+                {
+                    int extensionType = tlsExtension.IntValueExact;
+                    CheckUint16(extensionType);
+
+                    if (clientExtensions.Contains(extensionType) && !serverExtensions.Contains(extensionType))
+                        throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+                }
+            }
+        }
+
+        internal static void ProcessClientCertificate(TlsServerContext serverContext, Certificate clientCertificate,
+            TlsKeyExchange keyExchange, TlsServer server)
+        {
+            SecurityParameters securityParameters = serverContext.SecurityParameters;
+            if (null != securityParameters.PeerCertificate)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            bool isTlsV13 = IsTlsV13(securityParameters.NegotiatedVersion);
+            if (isTlsV13)
+            {
+                // 'keyExchange' not used
+            }
+            else if (clientCertificate.IsEmpty)
+            {
+                /*
+                 * NOTE: We tolerate SSLv3 clients sending an empty chain, although "If no suitable
+                 * certificate is available, the client should send a no_certificate alert instead".
+                 */
+
+                keyExchange.SkipClientCredentials();
+            }
+            else
+            {
+                keyExchange.ProcessClientCertificate(clientCertificate);
+            }
+
+            securityParameters.m_peerCertificate = clientCertificate;
+
+            /*
+             * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its
+             * discretion either continue the handshake without client authentication, or respond with a
+             * fatal handshake_failure alert. Also, if some aspect of the certificate chain was
+             * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its
+             * discretion either continue the handshake (considering the client unauthenticated) or send
+             * a fatal alert.
+             */
+            server.NotifyClientCertificate(clientCertificate);
+        }
+
+        internal static void ProcessServerCertificate(TlsClientContext clientContext,
+            CertificateStatus serverCertificateStatus, TlsKeyExchange keyExchange,
+            TlsAuthentication clientAuthentication, IDictionary clientExtensions, IDictionary serverExtensions)
+        {
+            SecurityParameters securityParameters = clientContext.SecurityParameters;
+            bool isTlsV13 = IsTlsV13(securityParameters.NegotiatedVersion);
+
+            if (null == clientAuthentication)
+            {
+                if (isTlsV13)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+
+                // There was no server certificate message; check it's OK
+                keyExchange.SkipServerCredentials();
+                securityParameters.m_tlsServerEndPoint = EmptyBytes;
+                return;
+            }
+
+            Certificate serverCertificate = securityParameters.PeerCertificate;
+
+            CheckTlsFeatures(serverCertificate, clientExtensions, serverExtensions);
+
+            if (!isTlsV13)
+            {
+                keyExchange.ProcessServerCertificate(serverCertificate);
+            }
+
+            clientAuthentication.NotifyServerCertificate(
+                new TlsServerCertificateImpl(serverCertificate, serverCertificateStatus));
+        }
+
+        internal static SignatureAndHashAlgorithm GetCertSigAndHashAlg(TlsCertificate subjectCert, TlsCertificate issuerCert)
+        {
+            string sigAlgOid = subjectCert.SigAlgOid;
+
+            if (null != sigAlgOid)
+            {
+                if (!PkcsObjectIdentifiers.IdRsassaPss.Id.Equals(sigAlgOid))
+                {
+                    if (!CertSigAlgOids.Contains(sigAlgOid))
+                        return null;
+
+                    return (SignatureAndHashAlgorithm)CertSigAlgOids[sigAlgOid];
+                }
+
+                RsassaPssParameters pssParams = RsassaPssParameters.GetInstance(subjectCert.GetSigAlgParams());
+                if (null != pssParams)
+                {
+                    DerObjectIdentifier hashOid = pssParams.HashAlgorithm.Algorithm;
+                    if (NistObjectIdentifiers.IdSha256.Equals(hashOid))
+                    {
+                        if (issuerCert.SupportsSignatureAlgorithmCA(SignatureAlgorithm.rsa_pss_pss_sha256))
+                            return SignatureAndHashAlgorithm.rsa_pss_pss_sha256;
+
+                        if (issuerCert.SupportsSignatureAlgorithmCA(SignatureAlgorithm.rsa_pss_rsae_sha256))
+                            return SignatureAndHashAlgorithm.rsa_pss_rsae_sha256;
+                    }
+                    else if (NistObjectIdentifiers.IdSha384.Equals(hashOid))
+                    {
+                        if (issuerCert.SupportsSignatureAlgorithmCA(SignatureAlgorithm.rsa_pss_pss_sha384))
+                            return SignatureAndHashAlgorithm.rsa_pss_pss_sha384;
+
+                        if (issuerCert.SupportsSignatureAlgorithmCA(SignatureAlgorithm.rsa_pss_rsae_sha384))
+                            return SignatureAndHashAlgorithm.rsa_pss_rsae_sha384;
+                    }
+                    else if (NistObjectIdentifiers.IdSha512.Equals(hashOid))
+                    {
+                        if (issuerCert.SupportsSignatureAlgorithmCA(SignatureAlgorithm.rsa_pss_pss_sha512))
+                            return SignatureAndHashAlgorithm.rsa_pss_pss_sha512;
+
+                        if (issuerCert.SupportsSignatureAlgorithmCA(SignatureAlgorithm.rsa_pss_rsae_sha512))
+                            return SignatureAndHashAlgorithm.rsa_pss_rsae_sha512;
+                    }
+                }
+            }
+
+            return null;
+        }
+
+        internal static CertificateRequest ValidateCertificateRequest(CertificateRequest certificateRequest,
+            TlsKeyExchange keyExchange)
+        {
+            short[] validClientCertificateTypes = keyExchange.GetClientCertificateTypes();
+            if (IsNullOrEmpty(validClientCertificateTypes))
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            certificateRequest = NormalizeCertificateRequest(certificateRequest, validClientCertificateTypes);
+            if (certificateRequest == null)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return certificateRequest;
+        }
+
+        internal static CertificateRequest NormalizeCertificateRequest(CertificateRequest certificateRequest,
+            short[] validClientCertificateTypes)
+        {
+            if (ContainsAll(validClientCertificateTypes, certificateRequest.CertificateTypes))
+                return certificateRequest;
+
+            short[] retained = RetainAll(certificateRequest.CertificateTypes, validClientCertificateTypes);
+            if (retained.Length < 1)
+                return null;
+
+            // TODO Filter for unique sigAlgs/CAs only
+            return new CertificateRequest(retained, certificateRequest.SupportedSignatureAlgorithms,
+                certificateRequest.CertificateAuthorities);
+        }
+
+        internal static bool Contains(int[] buf, int off, int len, int value)
+        {
+            for (int i = 0; i < len; ++i)
+            {
+                if (value == buf[off + i])
+                    return true;
+            }
+            return false;
+        }
+
+        internal static bool ContainsAll(short[] container, short[] elements)
+        {
+            for (int i = 0; i < elements.Length; ++i)
+            {
+                if (!Arrays.Contains(container, elements[i]))
+                    return false;
+            }
+            return true;
+        }
+
+        internal static short[] RetainAll(short[] retainer, short[] elements)
+        {
+            short[] retained = new short[System.Math.Min(retainer.Length, elements.Length)];
+
+            int count = 0;
+            for (int i = 0; i < elements.Length; ++i)
+            {
+                if (Arrays.Contains(retainer, elements[i]))
+                {
+                    retained[count++] = elements[i];
+                }
+            }
+
+            return Truncate(retained, count);
+        }
+
+        internal static short[] Truncate(short[] a, int n)
+        {
+            if (n < a.Length)
+                return a;
+
+            short[] t = new short[n];
+            Array.Copy(a, 0, t, 0, n);
+            return t;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static TlsCredentialedAgreement RequireAgreementCredentials(TlsCredentials credentials)
+        {
+            if (!(credentials is TlsCredentialedAgreement))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return (TlsCredentialedAgreement)credentials;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static TlsCredentialedDecryptor RequireDecryptorCredentials(TlsCredentials credentials)
+        {
+            if (!(credentials is TlsCredentialedDecryptor))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return (TlsCredentialedDecryptor)credentials;
+        }
+
+        /// <exception cref="IOException"/>
+        internal static TlsCredentialedSigner RequireSignerCredentials(TlsCredentials credentials)
+        {
+            if (!(credentials is TlsCredentialedSigner))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return (TlsCredentialedSigner)credentials;
+        }
+
+        private static void CheckDowngradeMarker(byte[] randomBlock, byte[] downgradeMarker)
+        {
+            int len = downgradeMarker.Length;
+            if (ConstantTimeAreEqual(len, downgradeMarker, 0, randomBlock, randomBlock.Length - len))
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+        }
+
+        internal static void CheckDowngradeMarker(ProtocolVersion version, byte[] randomBlock)
+        {
+            version = version.GetEquivalentTlsVersion();
+
+            if (version.IsEqualOrEarlierVersionOf(ProtocolVersion.TLSv11))
+            {
+                CheckDowngradeMarker(randomBlock, DowngradeTlsV11);
+            }
+            if (version.IsEqualOrEarlierVersionOf(ProtocolVersion.TLSv12))
+            {
+                CheckDowngradeMarker(randomBlock, DowngradeTlsV12);
+            }
+        }
+
+        internal static void WriteDowngradeMarker(ProtocolVersion version, byte[] randomBlock)
+        {
+            version = version.GetEquivalentTlsVersion();
+
+            byte[] marker;
+            if (ProtocolVersion.TLSv12 == version)
+            {
+                marker = DowngradeTlsV12;
+            }
+            else if (version.IsEqualOrEarlierVersionOf(ProtocolVersion.TLSv11))
+            {
+                marker = DowngradeTlsV11;
+            }
+            else
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            Array.Copy(marker, 0, randomBlock, randomBlock.Length - marker.Length, marker.Length);
+        }
+
+        internal static TlsAuthentication ReceiveServerCertificate(TlsClientContext clientContext, TlsClient client,
+            MemoryStream buf)
+        {
+            SecurityParameters securityParameters = clientContext.SecurityParameters;
+            if (null != securityParameters.PeerCertificate)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            MemoryStream endPointHash = new MemoryStream();
+
+            Certificate.ParseOptions options = new Certificate.ParseOptions()
+                .SetMaxChainLength(client.GetMaxCertificateChainLength());
+
+            Certificate serverCertificate = Certificate.Parse(options, clientContext, buf, endPointHash);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            if (serverCertificate.IsEmpty)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            securityParameters.m_peerCertificate = serverCertificate;
+            securityParameters.m_tlsServerEndPoint = endPointHash.ToArray();
+
+            TlsAuthentication authentication = client.GetAuthentication();
+            if (null == authentication)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return authentication;
+        }
+
+        internal static TlsAuthentication Receive13ServerCertificate(TlsClientContext clientContext, TlsClient client,
+            MemoryStream buf)
+        {
+            SecurityParameters securityParameters = clientContext.SecurityParameters;
+            if (null != securityParameters.PeerCertificate)
+                throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+            Certificate.ParseOptions options = new Certificate.ParseOptions()
+                .SetMaxChainLength(client.GetMaxCertificateChainLength());
+
+            Certificate serverCertificate = Certificate.Parse(options, clientContext, buf, null);
+
+            TlsProtocol.AssertEmpty(buf);
+
+            if (serverCertificate.GetCertificateRequestContext().Length > 0)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            if (serverCertificate.IsEmpty)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            securityParameters.m_peerCertificate = serverCertificate;
+            securityParameters.m_tlsServerEndPoint = null;
+
+            TlsAuthentication authentication = client.GetAuthentication();
+            if (null == authentication)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return authentication;
+        }
+
+        public static bool ContainsNonAscii(byte[] bs)
+        {
+            for (int i = 0; i < bs.Length; ++i)
+            {
+                int c = bs[i];
+                if (c >= 0x80)
+                    return true;
+            }
+            return false;
+        }
+
+        public static bool ContainsNonAscii(string s)
+        {
+            for (int i = 0; i < s.Length; ++i)
+            {
+                int c = s[i];
+                if (c >= 0x80)
+                    return true;
+            }
+            return false;
+        }
+
+        internal static IDictionary AddEarlyKeySharesToClientHello(TlsClientContext clientContext, TlsClient client,
+            IDictionary clientExtensions)
+        {
+            /*
+             * RFC 8446 9.2. If containing a "supported_groups" extension, it MUST also contain a
+             * "key_share" extension, and vice versa. An empty KeyShare.client_shares vector is
+             * permitted.
+             */
+            if (!IsTlsV13(clientContext.ClientVersion)
+                || !clientExtensions.Contains(ExtensionType.supported_groups))
+            {
+                return null;
+            }
+
+            int[] supportedGroups = TlsExtensionsUtilities.GetSupportedGroupsExtension(clientExtensions);
+            IList keyShareGroups = client.GetEarlyKeyShareGroups();
+            IDictionary clientAgreements = Platform.CreateHashtable(3);
+            IList clientShares = Platform.CreateArrayList(2);
+
+            CollectKeyShares(clientContext.Crypto, supportedGroups, keyShareGroups, clientAgreements, clientShares);
+
+            TlsExtensionsUtilities.AddKeyShareClientHello(clientExtensions, clientShares);
+
+            return clientAgreements;
+        }
+
+        internal static IDictionary AddKeyShareToClientHelloRetry(TlsClientContext clientContext,
+            IDictionary clientExtensions, int keyShareGroup)
+        {
+            int[] supportedGroups = new int[]{ keyShareGroup };
+            IList keyShareGroups = VectorOfOne(keyShareGroup);
+            IDictionary clientAgreements = Platform.CreateHashtable(1);
+            IList clientShares = Platform.CreateArrayList(1);
+
+            CollectKeyShares(clientContext.Crypto, supportedGroups, keyShareGroups, clientAgreements, clientShares);
+
+            TlsExtensionsUtilities.AddKeyShareClientHello(clientExtensions, clientShares);
+
+            if (clientAgreements.Count < 1 || clientShares.Count < 1)
+            {
+                // NOTE: Probable cause is declaring an unsupported NamedGroup in supported_groups extension 
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            return clientAgreements;
+        }
+
+        private static void CollectKeyShares(TlsCrypto crypto, int[] supportedGroups, IList keyShareGroups,
+            IDictionary clientAgreements, IList clientShares)
+        {
+            if (IsNullOrEmpty(supportedGroups))
+                return;
+
+            if (null == keyShareGroups || keyShareGroups.Count < 1)
+                return;
+
+            for (int i = 0; i < supportedGroups.Length; ++i)
+            {
+                int supportedGroup = supportedGroups[i];
+
+                if (!keyShareGroups.Contains(supportedGroup)
+                    || clientAgreements.Contains(supportedGroup)
+                    || !crypto.HasNamedGroup(supportedGroup))
+                {
+                    continue;
+                }
+
+                TlsAgreement agreement = null;
+                if (NamedGroup.RefersToASpecificCurve(supportedGroup))
+                {
+                    if (crypto.HasECDHAgreement())
+                    {
+                        agreement = crypto.CreateECDomain(new TlsECConfig(supportedGroup)).CreateECDH();
+                    }
+                }
+                else if (NamedGroup.RefersToASpecificFiniteField(supportedGroup))
+                {
+                    if (crypto.HasDHAgreement())
+                    {
+                        agreement = crypto.CreateDHDomain(new TlsDHConfig(supportedGroup, true)).CreateDH();
+                    }
+                }
+
+                if (null != agreement)
+                {
+                    byte[] key_exchange = agreement.GenerateEphemeral();
+                    KeyShareEntry clientShare = new KeyShareEntry(supportedGroup, key_exchange);
+
+                    clientShares.Add(clientShare);
+                    clientAgreements[supportedGroup] = agreement;
+                }
+            }
+        }
+
+        internal static KeyShareEntry SelectKeyShare(IList clientShares, int keyShareGroup)
+        {
+            if (null != clientShares && 1 == clientShares.Count)
+            {
+                KeyShareEntry clientShare = (KeyShareEntry)clientShares[0];
+                if (null != clientShare && clientShare.NamedGroup == keyShareGroup)
+                {
+                    return clientShare;
+                }
+            }
+            return null;
+        }
+
+        internal static KeyShareEntry SelectKeyShare(TlsCrypto crypto, ProtocolVersion negotiatedVersion,
+            IList clientShares, int[] clientSupportedGroups, int[] serverSupportedGroups)
+        {
+            if (null != clientShares && !IsNullOrEmpty(clientSupportedGroups) && !IsNullOrEmpty(serverSupportedGroups))
+            {
+                foreach (KeyShareEntry clientShare in clientShares)
+                {
+                    int group = clientShare.NamedGroup;
+
+                    if (!NamedGroup.CanBeNegotiated(group, negotiatedVersion))
+                        continue;
+
+                    if (!Arrays.Contains(serverSupportedGroups, group) ||
+                        !Arrays.Contains(clientSupportedGroups, group))
+                    {
+                        continue;
+                    }
+
+                    if (!crypto.HasNamedGroup(group))
+                        continue;
+
+                    if ((NamedGroup.RefersToASpecificCurve(group) && !crypto.HasECDHAgreement()) ||
+                        (NamedGroup.RefersToASpecificFiniteField(group) && !crypto.HasDHAgreement())) 
+                    {
+                        continue;
+                    }
+
+                    return clientShare;
+                }
+            }
+            return null;
+        }
+
+        internal static int SelectKeyShareGroup(TlsCrypto crypto, ProtocolVersion negotiatedVersion,
+            int[] clientSupportedGroups, int[] serverSupportedGroups)
+        {
+            if (!IsNullOrEmpty(clientSupportedGroups) && !IsNullOrEmpty(serverSupportedGroups))
+            {
+                foreach (int group in clientSupportedGroups)
+                {
+                    if (!NamedGroup.CanBeNegotiated(group, negotiatedVersion))
+                        continue;
+
+                    if (!Arrays.Contains(serverSupportedGroups, group))
+                        continue;
+
+                    if (!crypto.HasNamedGroup(group))
+                        continue;
+
+                    if ((NamedGroup.RefersToASpecificCurve(group) && !crypto.HasECDHAgreement()) ||
+                        (NamedGroup.RefersToASpecificFiniteField(group) && !crypto.HasDHAgreement())) 
+                    {
+                        continue;
+                    }
+
+                    return group;
+                }
+            }
+            return -1;
+        }
+
+        internal static byte[] ReadEncryptedPms(TlsContext context, Stream input)
+        {
+            if (IsSsl(context))
+                return Ssl3Utilities.ReadEncryptedPms(input);
+
+            return ReadOpaque16(input);
+        }
+
+        internal static void WriteEncryptedPms(TlsContext context, byte[] encryptedPms, Stream output)
+        {
+            if (IsSsl(context))
+            {
+                Ssl3Utilities.WriteEncryptedPms(encryptedPms, output);
+            }
+            else
+            {
+                WriteOpaque16(encryptedPms, output);
+            }
+        }
+
+        internal static byte[] GetSessionID(TlsSession tlsSession)
+        {
+            if (null != tlsSession)
+            {
+                byte[] sessionID = tlsSession.SessionID;
+                if (null != sessionID
+                    && sessionID.Length > 0
+                    && sessionID.Length <= 32)
+                {
+                    return sessionID;
+                }
+            }
+            return EmptyBytes;
+        }
+
+        internal static void AdjustTranscriptForRetry(TlsHandshakeHash handshakeHash)
+        {
+            byte[] clientHelloHash = GetCurrentPrfHash(handshakeHash);
+            handshakeHash.Reset();
+
+            int length = clientHelloHash.Length;
+            CheckUint8(length);
+
+            byte[] synthetic = new byte[4 + length];
+            WriteUint8(HandshakeType.message_hash, synthetic, 0);
+            WriteUint24(length, synthetic, 1);
+            Array.Copy(clientHelloHash, 0, synthetic, 4, length);
+
+            handshakeHash.Update(synthetic, 0, synthetic.Length);
+        }
+
+        internal static TlsCredentials EstablishClientCredentials(TlsAuthentication clientAuthentication,
+            CertificateRequest certificateRequest)
+        {
+            return ValidateCredentials(clientAuthentication.GetClientCredentials(certificateRequest));
+        }
+
+        internal static TlsCredentialedSigner Establish13ClientCredentials(TlsAuthentication clientAuthentication,
+            CertificateRequest certificateRequest)
+        {
+            return Validate13Credentials(clientAuthentication.GetClientCredentials(certificateRequest));
+        }
+
+        internal static void EstablishClientSigAlgs(SecurityParameters securityParameters,
+            IDictionary clientExtensions)
+        {
+            securityParameters.m_clientSigAlgs = TlsExtensionsUtilities.GetSignatureAlgorithmsExtension(
+                clientExtensions);
+            securityParameters.m_clientSigAlgsCert = TlsExtensionsUtilities.GetSignatureAlgorithmsCertExtension(
+                clientExtensions);
+        }
+
+        internal static TlsCredentials EstablishServerCredentials(TlsServer server)
+        {
+            return ValidateCredentials(server.GetCredentials());
+        }
+
+        internal static TlsCredentialedSigner Establish13ServerCredentials(TlsServer server)
+        {
+            return Validate13Credentials(server.GetCredentials());
+        }
+
+        internal static void EstablishServerSigAlgs(SecurityParameters securityParameters,
+            CertificateRequest certificateRequest)
+        {
+            securityParameters.m_clientCertTypes = certificateRequest.CertificateTypes;
+            securityParameters.m_serverSigAlgs = certificateRequest.SupportedSignatureAlgorithms;
+            securityParameters.m_serverSigAlgsCert = certificateRequest.SupportedSignatureAlgorithmsCert;
+
+            if (null == securityParameters.ServerSigAlgsCert)
+            {
+                securityParameters.m_serverSigAlgsCert = securityParameters.ServerSigAlgs;
+            }
+        }
+
+        internal static TlsCredentials ValidateCredentials(TlsCredentials credentials)
+        {
+            if (null != credentials)
+            {
+                int count = 0;
+                count += (credentials is TlsCredentialedAgreement) ? 1 : 0;
+                count += (credentials is TlsCredentialedDecryptor) ? 1 : 0;
+                count += (credentials is TlsCredentialedSigner) ? 1 : 0;
+                if (count != 1)
+                    throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+            return credentials;
+        }
+
+        internal static TlsCredentialedSigner Validate13Credentials(TlsCredentials credentials)
+        {
+            if (null == credentials)
+                return null;
+
+            if (!(credentials is TlsCredentialedSigner))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return (TlsCredentialedSigner)credentials;
+        }
+
+        internal static void NegotiatedCipherSuite(SecurityParameters securityParameters, int cipherSuite)
+        {
+            securityParameters.m_cipherSuite = cipherSuite;
+            securityParameters.m_keyExchangeAlgorithm = GetKeyExchangeAlgorithm(cipherSuite);
+
+            int prfAlgorithm = GetPrfAlgorithm(securityParameters, cipherSuite);
+            securityParameters.m_prfAlgorithm = prfAlgorithm;
+
+            switch (prfAlgorithm)
+            {
+            case PrfAlgorithm.ssl_prf_legacy:
+            case PrfAlgorithm.tls_prf_legacy:
+            {
+                securityParameters.m_prfHashAlgorithm = -1;
+                securityParameters.m_prfHashLength = -1;
+                break;
+            }
+            default:
+            {
+                short prfHashAlgorithm = GetHashAlgorithmForPrfAlgorithm(prfAlgorithm);
+
+                securityParameters.m_prfHashAlgorithm = prfHashAlgorithm;
+                securityParameters.m_prfHashLength = HashAlgorithm.GetOutputSize(prfHashAlgorithm);
+                break;
+            }
+            }
+
+            /*
+             * TODO[tls13] We're slowly moving towards negotiating cipherSuite THEN version. We could
+             * move this to "after parameter negotiation" i.e. after ServerHello/EncryptedExtensions.
+             */
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+            if (IsTlsV13(negotiatedVersion))
+            {
+                securityParameters.m_verifyDataLength = securityParameters.PrfHashLength;
+            }
+            else
+            {
+                securityParameters.m_verifyDataLength = negotiatedVersion.IsSsl ? 36 : 12;
+            }
+        }
+
+        internal static void NegotiatedVersion(SecurityParameters securityParameters)
+        {
+            if (!IsSignatureAlgorithmsExtensionAllowed(securityParameters.NegotiatedVersion))
+            {
+                securityParameters.m_clientSigAlgs = null;
+                securityParameters.m_clientSigAlgsCert = null;
+                return;
+            }
+
+            if (null == securityParameters.ClientSigAlgs)
+            {
+                securityParameters.m_clientSigAlgs = GetLegacySupportedSignatureAlgorithms();
+            }
+
+            if (null == securityParameters.ClientSigAlgsCert)
+            {
+                securityParameters.m_clientSigAlgsCert = securityParameters.ClientSigAlgs;
+            }
+        }
+
+        internal static void NegotiatedVersionDtlsClient(TlsClientContext clientContext, TlsClient client)
+        {
+            SecurityParameters securityParameters = clientContext.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (!ProtocolVersion.IsSupportedDtlsVersionClient(negotiatedVersion))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            NegotiatedVersion(securityParameters);
+
+            client.NotifyServerVersion(negotiatedVersion);
+        }
+
+        internal static void NegotiatedVersionDtlsServer(TlsServerContext serverContext)
+        {
+            SecurityParameters securityParameters = serverContext.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (!ProtocolVersion.IsSupportedDtlsVersionServer(negotiatedVersion))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            NegotiatedVersion(securityParameters);
+        }
+
+        internal static void NegotiatedVersionTlsClient(TlsClientContext clientContext, TlsClient client)
+        {
+            SecurityParameters securityParameters = clientContext.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (!ProtocolVersion.IsSupportedTlsVersionClient(negotiatedVersion))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            NegotiatedVersion(securityParameters);
+
+            client.NotifyServerVersion(negotiatedVersion);
+        }
+
+        internal static void NegotiatedVersionTlsServer(TlsServerContext serverContext)
+        {
+            SecurityParameters securityParameters = serverContext.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (!ProtocolVersion.IsSupportedTlsVersionServer(negotiatedVersion))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            NegotiatedVersion(securityParameters);
+        }
+
+        internal static TlsSecret DeriveSecret(SecurityParameters securityParameters, TlsSecret secret, string label,
+            byte[] transcriptHash)
+        {
+            return TlsCryptoUtilities.HkdfExpandLabel(secret, securityParameters.PrfHashAlgorithm, label,
+                transcriptHash, securityParameters.PrfHashLength);
+        }
+
+        internal static TlsSecret GetSessionMasterSecret(TlsCrypto crypto, TlsSecret masterSecret)
+        {
+            if (null != masterSecret)
+            {
+                lock (masterSecret)
+                {
+                    if (masterSecret.IsAlive())
+                        return crypto.AdoptSecret(masterSecret);
+                }
+            }
+
+            return null;
+        }
+
+        internal static bool IsPermittedExtensionType13(int handshakeType, int extensionType)
+        {
+            switch (extensionType)
+            {
+            case ExtensionType.server_name:
+            case ExtensionType.max_fragment_length:
+            case ExtensionType.supported_groups:
+            case ExtensionType.use_srtp:
+            case ExtensionType.heartbeat:
+            case ExtensionType.application_layer_protocol_negotiation:
+            case ExtensionType.client_certificate_type:
+            case ExtensionType.server_certificate_type:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.client_hello:
+                case HandshakeType.encrypted_extensions:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            case ExtensionType.status_request:
+            case ExtensionType.signed_certificate_timestamp:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.client_hello:
+                case HandshakeType.certificate_request:
+                case HandshakeType.certificate:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            case ExtensionType.signature_algorithms:
+            case ExtensionType.certificate_authorities:
+            case ExtensionType.signature_algorithms_cert:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.client_hello:
+                case HandshakeType.certificate_request:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            case ExtensionType.padding:
+            case ExtensionType.psk_key_exchange_modes:
+            case ExtensionType.post_handshake_auth:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.client_hello:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            case ExtensionType.key_share:
+            case ExtensionType.supported_versions:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.client_hello:
+                case HandshakeType.server_hello:
+                case HandshakeType.hello_retry_request:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            case ExtensionType.pre_shared_key:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.client_hello:
+                case HandshakeType.server_hello:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            case ExtensionType.early_data:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.client_hello:
+                case HandshakeType.encrypted_extensions:
+                case HandshakeType.new_session_ticket:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            case ExtensionType.cookie:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.client_hello:
+                case HandshakeType.hello_retry_request:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            case ExtensionType.oid_filters:
+            {
+                switch (handshakeType)
+                {
+                case HandshakeType.certificate_request:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+            default:
+            {
+                return !ExtensionType.IsRecognized(extensionType);
+            }
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        internal static void CheckExtensionData13(IDictionary extensions, int handshakeType, short alertDescription)
+        {
+            foreach (int extensionType in extensions.Keys)
+            {
+                if (!IsPermittedExtensionType13(handshakeType, extensionType))
+                    throw new TlsFatalAlert(alertDescription, "Invalid extension: "
+                        + ExtensionType.GetText(extensionType));
+            }
+        }
+
+#if !PORTABLE || DOTNET
+        public static bool IsTimeout(SocketException e)
+        {
+#if NET_1_1
+            return 10060 == e.ErrorCode;
+#else
+            return SocketError.TimedOut == e.SocketErrorCode;
+#endif
+        }
+#endif
+    }
+}
diff --git a/crypto/src/tls/TrustedAuthority.cs b/crypto/src/tls/TrustedAuthority.cs
new file mode 100644
index 000000000..cd564ebfa
--- /dev/null
+++ b/crypto/src/tls/TrustedAuthority.cs
@@ -0,0 +1,149 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    public sealed class TrustedAuthority
+    {
+        private readonly short m_identifierType;
+        private readonly object m_identifier;
+
+        public TrustedAuthority(short identifierType, object identifier)
+        {
+            if (!IsCorrectType(identifierType, identifier))
+                throw new ArgumentException("not an instance of the correct type", "identifier");
+
+            this.m_identifierType = identifierType;
+            this.m_identifier = identifier;
+        }
+
+        public short IdentifierType
+        {
+            get { return m_identifierType; }
+        }
+
+        public object Identifier
+        {
+            get { return m_identifier; }
+        }
+
+        public byte[] GetCertSha1Hash()
+        {
+            return Arrays.Clone((byte[])m_identifier);
+        }
+
+        public byte[] GetKeySha1Hash()
+        {
+            return Arrays.Clone((byte[])m_identifier);
+        }
+
+        public X509Name X509Name
+        {
+            get
+            {
+                CheckCorrectType(Tls.IdentifierType.x509_name);
+                return (X509Name)m_identifier;
+            }
+        }
+
+        /// <summary>Encode this <see cref="TrustedAuthority"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            TlsUtilities.WriteUint8(m_identifierType, output);
+
+            switch (m_identifierType)
+            {
+            case Tls.IdentifierType.cert_sha1_hash:
+            case Tls.IdentifierType.key_sha1_hash:
+            {
+                byte[] sha1Hash = (byte[])m_identifier;
+                output.Write(sha1Hash, 0, sha1Hash.Length);
+                break;
+            }
+            case Tls.IdentifierType.pre_agreed:
+            {
+                break;
+            }
+            case Tls.IdentifierType.x509_name:
+            {
+                X509Name dn = (X509Name)m_identifier;
+                byte[] derEncoding = dn.GetEncoded(Asn1Encodable.Der);
+                TlsUtilities.WriteOpaque16(derEncoding, output);
+                break;
+            }
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        /// <summary>Parse a <see cref="TrustedAuthority"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="TrustedAuthority"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static TrustedAuthority Parse(Stream input)
+        {
+            short identifier_type = TlsUtilities.ReadUint8(input);
+            object identifier;
+
+            switch (identifier_type)
+            {
+            case Tls.IdentifierType.cert_sha1_hash:
+            case Tls.IdentifierType.key_sha1_hash:
+            {
+                identifier = TlsUtilities.ReadFully(20, input);
+                break;
+            }
+            case Tls.IdentifierType.pre_agreed:
+            {
+                identifier = null;
+                break;
+            }
+            case Tls.IdentifierType.x509_name:
+            {
+                byte[] derEncoding = TlsUtilities.ReadOpaque16(input, 1);
+                Asn1Object asn1 = TlsUtilities.ReadDerObject(derEncoding);
+                identifier = X509Name.GetInstance(asn1);
+                break;
+            }
+            default:
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+            }
+
+            return new TrustedAuthority(identifier_type, identifier);
+        }
+
+        private void CheckCorrectType(short expectedIdentifierType)
+        {
+            if (m_identifierType != expectedIdentifierType || !IsCorrectType(expectedIdentifierType, m_identifier))
+                throw new InvalidOperationException("TrustedAuthority is not of type "
+                    + Tls.IdentifierType.GetName(expectedIdentifierType));
+        }
+
+        private static bool IsCorrectType(short identifierType, object identifier)
+        {
+            switch (identifierType)
+            {
+            case Tls.IdentifierType.cert_sha1_hash:
+            case Tls.IdentifierType.key_sha1_hash:
+                return IsSha1Hash(identifier);
+            case Tls.IdentifierType.pre_agreed:
+                return identifier == null;
+            case Tls.IdentifierType.x509_name:
+                return identifier is X509Name;
+            default:
+                throw new ArgumentException("unsupported IdentifierType", "identifierType");
+            }
+        }
+
+        private static bool IsSha1Hash(object identifier)
+        {
+            return identifier is byte[] && ((byte[])identifier).Length == 20;
+        }
+    }
+}
diff --git a/crypto/src/tls/UrlAndHash.cs b/crypto/src/tls/UrlAndHash.cs
new file mode 100644
index 000000000..47347c185
--- /dev/null
+++ b/crypto/src/tls/UrlAndHash.cs
@@ -0,0 +1,83 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 6066 5.</summary>
+    public sealed class UrlAndHash
+    {
+        private readonly string m_url;
+        private readonly byte[] m_sha1Hash;
+
+        public UrlAndHash(string url, byte[] sha1Hash)
+        {
+            if (TlsUtilities.IsNullOrEmpty(url) || url.Length >= (1 << 16))
+                throw new ArgumentException("must have length from 1 to (2^16 - 1)", "url");
+            if (sha1Hash != null && sha1Hash.Length != 20)
+                throw new ArgumentException("must have length == 20, if present", "sha1Hash");
+
+            this.m_url = url;
+            this.m_sha1Hash = sha1Hash;
+        }
+
+        public string Url
+        {
+            get { return m_url; }
+        }
+
+        public byte[] Sha1Hash
+        {
+            get { return m_sha1Hash; }
+        }
+
+        /// <summary>Encode this <see cref="UrlAndHash"/> to a <see cref="Stream"/>.</summary>
+        /// <param name="output">the <see cref="Stream"/> to encode to.</param>
+        /// <exception cref="IOException"/>
+        public void Encode(Stream output)
+        {
+            byte[] urlEncoding = Strings.ToByteArray(m_url);
+            TlsUtilities.WriteOpaque16(urlEncoding, output);
+
+            if (m_sha1Hash == null)
+            {
+                TlsUtilities.WriteUint8(0, output);
+            }
+            else
+            {
+                TlsUtilities.WriteUint8(1, output);
+                output.Write(m_sha1Hash, 0, m_sha1Hash.Length);
+            }
+        }
+
+        /// <summary>Parse a <see cref="UrlAndHash"/> from a <see cref="Stream"/>.</summary>
+        /// <param name="context">the <see cref="TlsContext"/> of the current connection.</param>
+        /// <param name="input">the <see cref="Stream"/> to parse from.</param>
+        /// <returns>a <see cref="UrlAndHash"/> object.</returns>
+        /// <exception cref="IOException"/>
+        public static UrlAndHash Parse(TlsContext context, Stream input)
+        {
+            byte[] urlEncoding = TlsUtilities.ReadOpaque16(input, 1);
+            string url = Strings.FromByteArray(urlEncoding);
+
+            byte[] sha1Hash = null;
+            short padding = TlsUtilities.ReadUint8(input);
+            switch (padding)
+            {
+            case 0:
+                if (TlsUtilities.IsTlsV12(context))
+                    throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+                break;
+            case 1:
+                sha1Hash = TlsUtilities.ReadFully(20, input);
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+            }
+
+            return new UrlAndHash(url, sha1Hash);
+        }
+    }
+}
diff --git a/crypto/src/tls/UseSrtpData.cs b/crypto/src/tls/UseSrtpData.cs
new file mode 100644
index 000000000..6c7e7da23
--- /dev/null
+++ b/crypto/src/tls/UseSrtpData.cs
@@ -0,0 +1,43 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 5764 4.1.1</summary>
+    public sealed class UseSrtpData
+    {
+        private readonly int[] m_protectionProfiles;
+        private readonly byte[] m_mki;
+
+        /// <param name="protectionProfiles">see <see cref="SrtpProtectionProfile"/> for valid constants.</param>
+        /// <param name="mki">valid lengths from 0 to 255.</param>
+        public UseSrtpData(int[] protectionProfiles, byte[] mki)
+        {
+            if (TlsUtilities.IsNullOrEmpty(protectionProfiles) || protectionProfiles.Length >= (1 << 15))
+                throw new ArgumentException("must have length from 1 to (2^15 - 1)", "protectionProfiles");
+
+            if (mki == null)
+            {
+                mki = TlsUtilities.EmptyBytes;
+            }
+            else if (mki.Length > 255)
+            {
+                throw new ArgumentException("cannot be longer than 255 bytes", "mki");
+            }
+
+            this.m_protectionProfiles = protectionProfiles;
+            this.m_mki = mki;
+        }
+
+        /// <returns>see <see cref="SrtpProtectionProfile"/> for valid constants.</returns>
+        public int[] ProtectionProfiles
+        {
+            get { return m_protectionProfiles; }
+        }
+
+        /// <returns>valid lengths from 0 to 255.</returns>
+        public byte[] Mki
+        {
+            get { return m_mki; }
+        }
+    }
+}
diff --git a/crypto/src/tls/UserMappingType.cs b/crypto/src/tls/UserMappingType.cs
new file mode 100644
index 000000000..ba02cbc5b
--- /dev/null
+++ b/crypto/src/tls/UserMappingType.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Org.BouncyCastle.Tls
+{
+    /// <summary>RFC 4681</summary>
+    public abstract class UserMappingType
+    {
+        /*
+         * RFC 4681
+         */
+        public const short upn_domain_hint = 64;
+    }
+}
diff --git a/crypto/src/tls/crypto/CryptoHashAlgorithm.cs b/crypto/src/tls/crypto/CryptoHashAlgorithm.cs
new file mode 100644
index 000000000..dc3c86956
--- /dev/null
+++ b/crypto/src/tls/crypto/CryptoHashAlgorithm.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public abstract class CryptoHashAlgorithm
+    {
+        public const int md5 = 1;
+        public const int sha1 = 2;
+        public const int sha224 = 3;
+        public const int sha256 = 4;
+        public const int sha384 = 5;
+        public const int sha512 = 6;
+        public const int sm3 = 7;
+    }
+}
diff --git a/crypto/src/tls/crypto/CryptoSignatureAlgorithm.cs b/crypto/src/tls/crypto/CryptoSignatureAlgorithm.cs
new file mode 100644
index 000000000..ed58820b8
--- /dev/null
+++ b/crypto/src/tls/crypto/CryptoSignatureAlgorithm.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public abstract class CryptoSignatureAlgorithm
+    {
+        public const int rsa = 1;
+        public const int dsa = 2;
+        public const int ecdsa = 3;
+        public const int rsa_pss_rsae_sha256 = 4;
+        public const int rsa_pss_rsae_sha384 = 5;
+        public const int rsa_pss_rsae_sha512 = 6;
+        public const int ed25519 = 7;
+        public const int ed448 = 8;
+        public const int rsa_pss_pss_sha256 = 9;
+        public const int rsa_pss_pss_sha384 = 10;
+        public const int rsa_pss_pss_sha512 = 11;
+        public const int gostr34102012_256 = 64;
+        public const int gostr34102012_512 = 65;
+        public const int sm2 = 200;
+    }
+}
diff --git a/crypto/src/tls/crypto/DHGroup.cs b/crypto/src/tls/crypto/DHGroup.cs
new file mode 100644
index 000000000..364a60429
--- /dev/null
+++ b/crypto/src/tls/crypto/DHGroup.cs
@@ -0,0 +1,46 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Carrier class for Diffie-Hellman group parameters.</summary>
+    public class DHGroup
+    {
+        private readonly BigInteger g, p, q;
+        private readonly int l;
+
+        /// <summary>Base constructor with the prime factor of (p - 1).</summary>
+        /// <param name="p">the prime modulus.</param>
+        /// <param name="q">specifies the prime factor of (p - 1).</param>
+        /// <param name="g">the base generator.</param>
+        /// <param name="l"></param>
+        public DHGroup(BigInteger p, BigInteger q, BigInteger g, int l)
+        {
+            this.p = p;
+            this.g = g;
+            this.q = q;
+            this.l = l;
+        }
+
+        public virtual BigInteger G
+        {
+            get { return g; }
+        }
+
+        public virtual int L
+        {
+            get { return l; }
+        }
+
+        public virtual BigInteger P
+        {
+            get { return p; }
+        }
+
+        public virtual BigInteger Q
+        {
+            get { return q; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/DHStandardGroups.cs b/crypto/src/tls/crypto/DHStandardGroups.cs
new file mode 100644
index 000000000..40ddb5725
--- /dev/null
+++ b/crypto/src/tls/crypto/DHStandardGroups.cs
@@ -0,0 +1,248 @@
+using System;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Standard Diffie-Hellman groups from various IETF specifications.</summary>
+    public class DHStandardGroups
+    {
+        private static readonly BigInteger Two = BigInteger.Two;
+
+        private static BigInteger FromHex(string hex)
+        {
+            return new BigInteger(1, Hex.DecodeStrict(hex));
+        }
+
+        //private static DHGroup FromPG(string hexP, string hexG)
+        //{
+        //    return new DHGroup(FromHex(hexP), null, FromHex(hexG), 0);
+        //}
+
+        private static DHGroup SafePrimeGen2(string hexP)
+        {
+            return SafePrimeGen2(hexP, 0);
+        }
+
+        private static DHGroup SafePrimeGen2(string hexP, int l)
+        {
+            // NOTE: A group using a safe prime (i.e. q = (p-1)/2), and generator g = 2
+            BigInteger p = FromHex(hexP);
+            return new DHGroup(p, p.ShiftRight(1), Two, l);
+        }
+
+        /*
+         * RFC 2409
+         */
+        private static readonly string rfc2409_768_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A63A3620FFFFFFFFFFFFFFFF";
+        public static readonly DHGroup rfc2409_768 = SafePrimeGen2(rfc2409_768_p);
+
+        private static readonly string rfc2409_1024_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381"
+            + "FFFFFFFFFFFFFFFF";
+        public static readonly DHGroup rfc2409_1024 = SafePrimeGen2(rfc2409_1024_p);
+
+        /*
+         * RFC 3526
+         */
+        private static readonly string rfc3526_1536_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF";
+        private static readonly int rfc3526_1536_l = 200; // RFC3526/RFC7919
+        public static readonly DHGroup rfc3526_1536 = SafePrimeGen2(rfc3526_1536_p, rfc3526_1536_l);
+
+        private static readonly string rfc3526_2048_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+            + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AACAA68FFFFFFFFFFFFFFFF";
+        private static readonly int rfc3526_2048_l = System.Math.Max(225, 112 * 2); // MAX(RFC3526/RFC7919,FIPS)
+        public static readonly DHGroup rfc3526_2048 = SafePrimeGen2(rfc3526_2048_p, rfc3526_2048_l);
+
+        private static readonly string rfc3526_3072_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+            + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+            + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+            + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+            + "43DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
+        private static readonly int rfc3526_3072_l = System.Math.Max(275, 128 * 2); // MAX(RFC3526/RFC7919,FIPS)
+        public static readonly DHGroup rfc3526_3072 = SafePrimeGen2(rfc3526_3072_p, rfc3526_3072_l);
+
+        private static readonly string rfc3526_4096_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+            + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+            + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+            + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+            + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+            + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+            + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199"
+            + "FFFFFFFFFFFFFFFF";
+        private static readonly int rfc3526_4096_l = System.Math.Max(325, 152 * 2); // MAX(RFC3526/RFC7919,FIPS)
+        public static readonly DHGroup rfc3526_4096 = SafePrimeGen2(rfc3526_4096_p, rfc3526_4096_l);
+
+        private static readonly string rfc3526_6144_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+            + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+            + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+            + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+            + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+            + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+            + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+            + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+            + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+            + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+            + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+            + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+            + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+            + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+            + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+            + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+            + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+            + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+            + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+            + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+            + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+            + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+            + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+            + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+            + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+            + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E"
+            + "6DCC4024FFFFFFFFFFFFFFFF";
+        private static readonly int rfc3526_6144_l = System.Math.Max(375, 176 * 2); // MAX(RFC3526/RFC7919,FIPS)
+        public static readonly DHGroup rfc3526_6144 = SafePrimeGen2(rfc3526_6144_p, rfc3526_6144_l);
+
+        private static readonly string rfc3526_8192_p = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
+            + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
+            + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
+            + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + "83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
+            + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + "15728E5A8AAAC42DAD33170D04507A33A85521ABDF1CBA64"
+            + "ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7" + "ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6B"
+            + "F12FFA06D98A0864D87602733EC86A64521F2B18177B200C" + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB31"
+            + "43DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7" + "88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA"
+            + "2583E9CA2AD44CE8DBBBC2DB04DE8EF92E8EFC141FBECAA6" + "287C59474E6BC05D99B2964FA090C3A2233BA186515BE7ED"
+            + "1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9" + "93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+            + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BD" + "F8FF9406AD9E530EE5DB382F413001AEB06A53ED9027D831"
+            + "179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B" + "DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF"
+            + "5983CA01C64B92ECF032EA15D1721D03F482D7CE6E74FEF6" + "D55E702F46980C82B5A84031900B1C9E59E7C97FBEC7E8F3"
+            + "23A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA" + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE328"
+            + "06A1D58BB7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55C" + "DA56C9EC2EF29632387FE8D76E3C0468043E8F663F4860EE"
+            + "12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4" + "38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300"
+            + "741FA7BF8AFC47ED2576F6936BA424663AAB639C5AE4F568" + "3423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
+            + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B" + "4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A"
+            + "062B3CF5B3A278A66D2A13F83F44F82DDF310EE074AB6A36" + "4597E899A0255DC164F31CC50846851DF9AB48195DED7EA1"
+            + "B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92" + "4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E47"
+            + "9558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF";
+        private static readonly int rfc3526_8192_l = System.Math.Max(400, 200 * 2); // MAX(RFC3526/RFC7919,FIPS)
+        public static readonly DHGroup rfc3526_8192 = SafePrimeGen2(rfc3526_8192_p, rfc3526_8192_l);
+
+        /*
+         * RFC 4306
+         */
+        public static readonly DHGroup rfc4306_768 = rfc2409_768;
+        public static readonly DHGroup rfc4306_1024 = rfc2409_1024;
+
+        /*
+         * RFC 5996
+         */
+        public static readonly DHGroup rfc5996_768 = rfc4306_768;
+        public static readonly DHGroup rfc5996_1024 = rfc4306_1024;
+
+        /*
+         * RFC 7919
+         */
+        private static readonly string rfc7919_ffdhe2048_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B423861285C97FFFFFFFFFFFFFFFF";
+        private static readonly int rfc7919_ffdhe2048_l = System.Math.Max(225, 112 * 2); // MAX(RFC7919,FIPS)
+        public static readonly DHGroup rfc7919_ffdhe2048 = SafePrimeGen2(rfc7919_ffdhe2048_p, rfc7919_ffdhe2048_l);
+
+        private static readonly string rfc7919_ffdhe3072_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
+            + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
+            + "3C1B20EE3FD59D7C25E41D2B66C62E37FFFFFFFFFFFFFFFF";
+        private static readonly int rfc7919_ffdhe3072_l = System.Math.Max(275, 128 * 2); // MAX(RFC7919,FIPS)
+        public static readonly DHGroup rfc7919_ffdhe3072 = SafePrimeGen2(rfc7919_ffdhe3072_p, rfc7919_ffdhe3072_l);
+
+        private static readonly string rfc7919_ffdhe4096_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
+            + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
+            + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004"
+            + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A"
+            + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E655F6A"
+            + "FFFFFFFFFFFFFFFF";
+        private static readonly int rfc7919_ffdhe4096_l = System.Math.Max(325, 152 * 2); // MAX(RFC7919,FIPS)
+        public static readonly DHGroup rfc7919_ffdhe4096 = SafePrimeGen2(rfc7919_ffdhe4096_p, rfc7919_ffdhe4096_l);
+
+        private static readonly string rfc7919_ffdhe6144_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
+            + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
+            + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004"
+            + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A"
+            + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902"
+            + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A"
+            + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3"
+            + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6"
+            + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + "D72B03746AE77F5E62292C311562A846505DC82DB854338A"
+            + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1"
+            + "A41D570D7938DAD4A40E329CD0E40E65FFFFFFFFFFFFFFFF";
+        private static readonly int rfc7919_ffdhe6144_l = System.Math.Max(375, 176 * 2); // MAX(RFC7919,FIPS)
+        public static readonly DHGroup rfc7919_ffdhe6144 = SafePrimeGen2(rfc7919_ffdhe6144_p, rfc7919_ffdhe6144_l);
+
+        private static readonly string rfc7919_ffdhe8192_p = "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1"
+            + "D8B9C583CE2D3695A9E13641146433FBCC939DCE249B3EF9" + "7D2FE363630C75D8F681B202AEC4617AD3DF1ED5D5FD6561"
+            + "2433F51F5F066ED0856365553DED1AF3B557135E7F57C935" + "984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE735"
+            + "30ACCA4F483A797ABC0AB182B324FB61D108A94BB2C8E3FB" + "B96ADAB760D7F4681D4F42A3DE394DF4AE56EDE76372BB19"
+            + "0B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61" + "9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD73"
+            + "3BB5FCBC2EC22005C58EF1837D1683B2C6F34A26C1B2EFFA" + "886B4238611FCFDCDE355B3B6519035BBC34F4DEF99C0238"
+            + "61B46FC9D6E6C9077AD91D2691F7F7EE598CB0FAC186D91C" + "AEFE130985139270B4130C93BC437944F4FD4452E2D74DD3"
+            + "64F2E21E71F54BFF5CAE82AB9C9DF69EE86D2BC522363A0D" + "ABC521979B0DEADA1DBF9A42D5C4484E0ABCD06BFA53DDEF"
+            + "3C1B20EE3FD59D7C25E41D2B669E1EF16E6F52C3164DF4FB" + "7930E9E4E58857B6AC7D5F42D69F6D187763CF1D55034004"
+            + "87F55BA57E31CC7A7135C886EFB4318AED6A1E012D9E6832" + "A907600A918130C46DC778F971AD0038092999A333CB8B7A"
+            + "1A1DB93D7140003C2A4ECEA9F98D0ACC0A8291CDCEC97DCF" + "8EC9B55A7F88A46B4DB5A851F44182E1C68A007E5E0DD902"
+            + "0BFD64B645036C7A4E677D2C38532A3A23BA4442CAF53EA6" + "3BB454329B7624C8917BDD64B1C0FD4CB38E8C334C701C3A"
+            + "CDAD0657FCCFEC719B1F5C3E4E46041F388147FB4CFDB477" + "A52471F7A9A96910B855322EDB6340D8A00EF092350511E3"
+            + "0ABEC1FFF9E3A26E7FB29F8C183023C3587E38DA0077D9B4" + "763E4E4B94B2BBC194C6651E77CAF992EEAAC0232A281BF6"
+            + "B3A739C1226116820AE8DB5847A67CBEF9C9091B462D538C" + "D72B03746AE77F5E62292C311562A846505DC82DB854338A"
+            + "E49F5235C95B91178CCF2DD5CACEF403EC9D1810C6272B04" + "5B3B71F9DC6B80D63FDD4A8E9ADB1E6962A69526D43161C1"
+            + "A41D570D7938DAD4A40E329CCFF46AAA36AD004CF600C838" + "1E425A31D951AE64FDB23FCEC9509D43687FEB69EDD1CC5E"
+            + "0B8CC3BDF64B10EF86B63142A3AB8829555B2F747C932665" + "CB2C0F1CC01BD70229388839D2AF05E454504AC78B758282"
+            + "2846C0BA35C35F5C59160CC046FD8251541FC68C9C86B022" + "BB7099876A460E7451A8A93109703FEE1C217E6C3826E52C"
+            + "51AA691E0E423CFC99E9E31650C1217B624816CDAD9A95F9" + "D5B8019488D9C0A0A1FE3075A577E23183F81D4A3F2FA457"
+            + "1EFC8CE0BA8A4FE8B6855DFE72B0A66EDED2FBABFBE58A30" + "FAFABE1C5D71A87E2F741EF8C1FE86FEA6BBFDE530677F0D"
+            + "97D11D49F7A8443D0822E506A9F4614E011E2A94838FF88C" + "D68C8BB7C5C6424CFFFFFFFFFFFFFFFF";
+        private static readonly int rfc7919_ffdhe8192_l = System.Math.Max(400, 200 * 2); // MAX(RFC7919,FIPS)
+        public static readonly DHGroup rfc7919_ffdhe8192 = SafePrimeGen2(rfc7919_ffdhe8192_p, rfc7919_ffdhe8192_l);
+    }
+}
diff --git a/crypto/src/tls/crypto/Srp6Group.cs b/crypto/src/tls/crypto/Srp6Group.cs
new file mode 100644
index 000000000..dae4ca1d7
--- /dev/null
+++ b/crypto/src/tls/crypto/Srp6Group.cs
@@ -0,0 +1,31 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Carrier class for SRP-6 group parameters.</summary>
+    public class Srp6Group
+    {
+        private readonly BigInteger n, g;
+
+        /// <summary>Base constructor.</summary>
+        /// <param name="n">the n value.</param>
+        /// <param name="g">the g value.</param>
+        public Srp6Group(BigInteger n, BigInteger g)
+        {
+            this.n = n;
+            this.g = g;
+        }
+
+        public virtual BigInteger G
+        {
+            get { return g; }
+        }
+
+        public virtual BigInteger N
+        {
+            get { return n; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/Srp6StandardGroups.cs b/crypto/src/tls/crypto/Srp6StandardGroups.cs
new file mode 100644
index 000000000..371079cc3
--- /dev/null
+++ b/crypto/src/tls/crypto/Srp6StandardGroups.cs
@@ -0,0 +1,159 @@
+using System;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>A selection of standard groups for SRP-6.</summary>
+    public class Srp6StandardGroups
+    {
+        private static BigInteger FromHex(string hex)
+        {
+            return new BigInteger(1, Hex.DecodeStrict(hex));
+        }
+
+        private static Srp6Group FromNG(string hexN, string hexG)
+        {
+            return new Srp6Group(FromHex(hexN), FromHex(hexG));
+        }
+
+        /*
+         * RFC 5054
+         */
+        private static readonly string rfc5054_1024_N = "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C"
+            + "9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4"
+            + "8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29"
+            + "7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A" + "FD5138FE8376435B9FC61D2FC0EB06E3";
+        private static readonly string rfc5054_1024_g = "02";
+        public static readonly Srp6Group rfc5054_1024 = FromNG(rfc5054_1024_N, rfc5054_1024_g);
+
+        private static readonly string rfc5054_1536_N = "9DEF3CAFB939277AB1F12A8617A47BBBDBA51DF499AC4C80BEEEA961"
+            + "4B19CC4D5F4F5F556E27CBDE51C6A94BE4607A291558903BA0D0F843"
+            + "80B655BB9A22E8DCDF028A7CEC67F0D08134B1C8B97989149B609E0B"
+            + "E3BAB63D47548381DBC5B1FC764E3F4B53DD9DA1158BFD3E2B9C8CF5"
+            + "6EDF019539349627DB2FD53D24B7C48665772E437D6C7F8CE442734A"
+            + "F7CCB7AE837C264AE3A9BEB87F8A2FE9B8B5292E5A021FFF5E91479E"
+            + "8CE7A28C2442C6F315180F93499A234DCF76E3FED135F9BB";
+        private static readonly string rfc5054_1536_g = "02";
+        public static readonly Srp6Group rfc5054_1536 = FromNG(rfc5054_1536_N, rfc5054_1536_g);
+
+        private static readonly string rfc5054_2048_N = "AC6BDB41324A9A9BF166DE5E1389582FAF72B6651987EE07FC319294"
+            + "3DB56050A37329CBB4A099ED8193E0757767A13DD52312AB4B03310D"
+            + "CD7F48A9DA04FD50E8083969EDB767B0CF6095179A163AB3661A05FB"
+            + "D5FAAAE82918A9962F0B93B855F97993EC975EEAA80D740ADBF4FF74"
+            + "7359D041D5C33EA71D281E446B14773BCA97B43A23FB801676BD207A"
+            + "436C6481F1D2B9078717461A5B9D32E688F87748544523B524B0D57D"
+            + "5EA77A2775D2ECFA032CFBDBF52FB3786160279004E57AE6AF874E73"
+            + "03CE53299CCC041C7BC308D82A5698F3A8D0C38271AE35F8E9DBFBB6"
+            + "94B5C803D89F7AE435DE236D525F54759B65E372FCD68EF20FA7111F" + "9E4AFF73";
+        private static readonly string rfc5054_2048_g = "02";
+        public static readonly Srp6Group rfc5054_2048 = FromNG(rfc5054_2048_N, rfc5054_2048_g);
+
+        private static readonly string rfc5054_3072_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+            + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+            + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+            + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+            + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+            + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+            + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+            + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+            + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+            + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+            + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+            + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC" + "E0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
+        private static readonly string rfc5054_3072_g = "05";
+        public static readonly Srp6Group rfc5054_3072 = FromNG(rfc5054_3072_N, rfc5054_3072_g);
+
+        private static readonly string rfc5054_4096_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+            + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+            + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+            + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+            + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+            + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+            + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+            + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+            + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+            + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+            + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+            + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+            + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+            + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+            + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+            + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+            + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199" + "FFFFFFFFFFFFFFFF";
+        private static readonly string rfc5054_4096_g = "05";
+        public static readonly Srp6Group rfc5054_4096 = FromNG(rfc5054_4096_N, rfc5054_4096_g);
+
+        private static readonly string rfc5054_6144_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+            + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+            + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+            + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+            + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+            + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+            + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+            + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+            + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+            + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+            + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+            + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+            + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+            + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+            + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+            + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+            + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+            + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+            + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+            + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+            + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+            + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+            + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+            + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+            + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+            + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E" + "6DCC4024FFFFFFFFFFFFFFFF";
+        private static readonly string rfc5054_6144_g = "05";
+        public static readonly Srp6Group rfc5054_6144 = FromNG(rfc5054_6144_N, rfc5054_6144_g);
+
+        private static readonly string rfc5054_8192_N = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E08"
+            + "8A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B"
+            + "302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9"
+            + "A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE6"
+            + "49286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8"
+            + "FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D"
+            + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C"
+            + "180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
+            + "3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D"
+            + "04507A33A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7D"
+            + "B3970F85A6E1E4C7ABF5AE8CDB0933D71E8C94E04A25619DCEE3D226"
+            + "1AD2EE6BF12FFA06D98A0864D87602733EC86A64521F2B18177B200C"
+            + "BBE117577A615D6C770988C0BAD946E208E24FA074E5AB3143DB5BFC"
+            + "E0FD108E4B82D120A92108011A723C12A787E6D788719A10BDBA5B26"
+            + "99C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8DBBBC2DB"
+            + "04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2"
+            + "233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127"
+            + "D5B05AA993B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934028492"
+            + "36C3FAB4D27C7026C1D4DCB2602646DEC9751E763DBA37BDF8FF9406"
+            + "AD9E530EE5DB382F413001AEB06A53ED9027D831179727B0865A8918"
+            + "DA3EDBEBCF9B14ED44CE6CBACED4BB1BDB7F1447E6CC254B33205151"
+            + "2BD7AF426FB8F401378CD2BF5983CA01C64B92ECF032EA15D1721D03"
+            + "F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E59E7C97F"
+            + "BEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA"
+            + "CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58B"
+            + "B7C5DA76F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632"
+            + "387FE8D76E3C0468043E8F663F4860EE12BF2D5B0B7474D6E694F91E"
+            + "6DBE115974A3926F12FEE5E438777CB6A932DF8CD8BEC4D073B931BA"
+            + "3BC832B68D9DD300741FA7BF8AFC47ED2576F6936BA424663AAB639C"
+            + "5AE4F5683423B4742BF1C978238F16CBE39D652DE3FDB8BEFC848AD9"
+            + "22222E04A4037C0713EB57A81A23F0C73473FC646CEA306B4BCBC886"
+            + "2F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6"
+            + "6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC5"
+            + "0846851DF9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268"
+            + "359046F4EB879F924009438B481C6CD7889A002ED5EE382BC9190DA6"
+            + "FC026E479558E4475677E9AA9E3050E2765694DFC81F56E880B96E71" + "60C980DD98EDD3DFFFFFFFFFFFFFFFFF";
+        private static readonly string rfc5054_8192_g = "13";
+        public static readonly Srp6Group rfc5054_8192 = FromNG(rfc5054_8192_N, rfc5054_8192_g);
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsAgreement.cs b/crypto/src/tls/crypto/TlsAgreement.cs
new file mode 100644
index 000000000..0635b4e9e
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsAgreement.cs
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Base interface for ephemeral key agreement calculator.</summary>
+    public interface TlsAgreement
+    {
+        /// <summary>Generate an ephemeral key pair, returning the encoding of the public key.</summary>
+        /// <returns>a byte encoding of the public key.</returns>
+        /// <exception cref="IOException"/>
+        byte[] GenerateEphemeral();
+
+        /// <summary>Pass in the public key for the peer to the agreement calculator.</summary>
+        /// <param name="peerValue">a byte encoding of the peer public key.</param>
+        /// <exception cref="IOException"/>
+        void ReceivePeerValue(byte[] peerValue);
+
+        /// <summary>Calculate the agreed secret based on the calculator's current state.</summary>
+        /// <returns>the calculated secret.</returns>
+        /// <exception cref="IOException"/>
+        TlsSecret CalculateSecret();
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsCertificate.cs b/crypto/src/tls/crypto/TlsCertificate.cs
new file mode 100644
index 000000000..7bd8e0359
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsCertificate.cs
@@ -0,0 +1,52 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Interface providing the functional representation of a single X.509 certificate.</summary>
+    public interface TlsCertificate
+    {
+        /// <param name="signatureAlgorithm"><see cref="SignatureAlgorithm"/></param>
+        /// <exception cref="IOException"/>
+        TlsVerifier CreateVerifier(short signatureAlgorithm);
+
+        /// <param name="signatureScheme"><see cref="SignatureScheme"/></param>
+        /// <exception cref="IOException"/>
+        TlsVerifier CreateVerifier(int signatureScheme);
+
+        /// <exception cref="IOException"/>
+        byte[] GetEncoded();
+
+        /// <exception cref="IOException"/>
+        byte[] GetExtension(DerObjectIdentifier extensionOid);
+
+        BigInteger SerialNumber { get; }
+
+        /// <returns>the OID of this certificate's 'signatureAlgorithm', as a string.</returns>
+        string SigAlgOid { get; }
+
+        /// <exception cref="IOException"/>
+        Asn1Encodable GetSigAlgParams();
+
+        /// <returns><see cref="SignatureAlgorithm"/></returns>
+        /// <exception cref="IOException"/>
+        short GetLegacySignatureAlgorithm();
+
+        /// <param name="signatureAlgorithm"><see cref="SignatureAlgorithm"/></param>
+        /// <returns>true if (and only if) this certificate can be used to verify the given signature algorithm.
+        /// </returns>
+        /// <exception cref="IOException"/>
+        bool SupportsSignatureAlgorithm(short signatureAlgorithm);
+
+        /// <exception cref="IOException"/>
+        bool SupportsSignatureAlgorithmCA(short signatureAlgorithm);
+
+        /// <param name="connectionEnd"><see cref="ConnectionEnd"/></param>
+        /// <param name="tlsCertificateRole"><see cref="TlsCertificateRole"/></param>
+        /// <exception cref="IOException"/>
+        TlsCertificate CheckUsageInRole(int connectionEnd, int tlsCertificateRole);
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsCertificateRole.cs b/crypto/src/tls/crypto/TlsCertificateRole.cs
new file mode 100644
index 000000000..f7d81b19e
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsCertificateRole.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public abstract class TlsCertificateRole
+    {
+        public const int DH = 1;
+        public const int ECDH = 2;
+        public const int RsaEncryption = 3;
+        public const int Sm2Encryption = 4;
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsCipher.cs b/crypto/src/tls/crypto/TlsCipher.cs
new file mode 100644
index 000000000..4c2147bf7
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsCipher.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Base interface for a TLS bulk cipher.</summary>
+    public interface TlsCipher
+    {
+        /// <summary>Return the maximum input size for a ciphertext given a maximum output size for the plaintext of
+        /// plaintextLimit bytes.</summary>
+        /// <param name="plaintextLimit">the maximum output size for the plaintext.</param>
+        /// <returns>the maximum input size of the ciphertext for plaintextlimit bytes of output.</returns>
+        int GetCiphertextDecodeLimit(int plaintextLimit);
+
+        /// <summary>Return the maximum output size for a ciphertext given an actual input plaintext size of
+        /// plaintextLength bytes and a maximum input plaintext size of plaintextLimit bytes.</summary>
+        /// <param name="plaintextLength">the actual input size for the plaintext.</param>
+        /// <param name="plaintextLimit">the maximum input size for the plaintext.</param>
+        /// <returns>the maximum output size of the ciphertext for plaintextlimit bytes of input.</returns>
+        int GetCiphertextEncodeLimit(int plaintextLength, int plaintextLimit);
+
+        /// <summary>Return the maximum size for the plaintext given ciphertextlimit bytes of ciphertext.</summary>
+        /// <param name="ciphertextLimit">the maximum number of bytes of ciphertext.</param>
+        /// <returns>the maximum size of the plaintext for ciphertextlimit bytes of input.</returns>
+        int GetPlaintextLimit(int ciphertextLimit);
+
+        /// <summary>Encode the passed in plaintext using the current bulk cipher.</summary>
+        /// <param name="seqNo">sequence number of the message represented by plaintext.</param>
+        /// <param name="contentType">content type of the message represented by plaintext.</param>
+        /// <param name="recordVersion"><see cref="ProtocolVersion"/> used for the record.</param>
+        /// <param name="headerAllocation">extra bytes to allocate at start of returned byte array.</param>
+        /// <param name="plaintext">array holding input plaintext to the cipher.</param>
+        /// <param name="offset">offset into input array the plaintext starts at.</param>
+        /// <param name="len">length of the plaintext in the array.</param>
+        /// <returns>A <see cref="TlsEncodeResult"/> containing the result of encoding (after 'headerAllocation' unused
+        /// bytes).</returns>
+        /// <exception cref="IOException"/>
+        TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, byte[] plaintext, int offset, int len);
+
+        /// <summary>Decode the passed in ciphertext using the current bulk cipher.</summary>
+        /// <param name="seqNo">sequence number of the message represented by ciphertext.</param>
+        /// <param name="recordType">content type used in the record for this message.</param>
+        /// <param name="recordVersion"><see cref="ProtocolVersion"/> used for the record.</param>
+        /// <param name="ciphertext">array holding input ciphertext to the cipher.</param>
+        /// <param name="offset">offset into input array the ciphertext starts at.</param>
+        /// <param name="len">length of the ciphertext in the array.</param>
+        /// <returns>A <see cref="TlsDecodeResult"/> containing the result of decoding.</returns>
+        /// <exception cref="IOException"/>
+        TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
+            byte[] ciphertext, int offset, int len);
+
+        /// <exception cref="IOException"/>
+        void RekeyDecoder();
+
+        /// <exception cref="IOException"/>
+        void RekeyEncoder();
+
+        bool UsesOpaqueRecordType { get; }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsCrypto.cs b/crypto/src/tls/crypto/TlsCrypto.cs
new file mode 100644
index 000000000..bd003aefa
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsCrypto.cs
@@ -0,0 +1,181 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Service and object creation interface for the primitive types and services that are associated with
+    /// cryptography in the API.</summary>
+    public interface TlsCrypto
+    {
+        /// <summary>Return true if this TlsCrypto can perform raw signatures and verifications for all supported
+        /// algorithms.</summary>
+        /// <returns>true if this instance can perform raw signatures and verifications for all supported algorithms,
+        /// false otherwise.</returns>
+        bool HasAllRawSignatureAlgorithms();
+
+        /// <summary>Return true if this TlsCrypto can support DH key agreement.</summary>
+        /// <returns>true if this instance can support DH key agreement, false otherwise.</returns>
+        bool HasDHAgreement();
+
+        /// <summary>Return true if this TlsCrypto can support ECDH key agreement.</summary>
+        /// <returns>true if this instance can support ECDH key agreement, false otherwise.</returns>
+        bool HasECDHAgreement();
+
+        /// <summary>Return true if this TlsCrypto can support the passed in block/stream encryption algorithm.
+        /// </summary>
+        /// <param name="encryptionAlgorithm">the algorithm of interest.</param>
+        /// <returns>true if encryptionAlgorithm is supported, false otherwise.</returns>
+        bool HasEncryptionAlgorithm(int encryptionAlgorithm);
+
+        /// <summary>Return true if this TlsCrypto can support the passed in hash algorithm.</summary>
+        /// <param name="cryptoHashAlgorithm">the algorithm of interest.</param>
+        /// <returns>true if cryptoHashAlgorithm is supported, false otherwise.</returns>
+        bool HasCryptoHashAlgorithm(int cryptoHashAlgorithm);
+
+        /// <summary>Return true if this TlsCrypto can support the passed in signature algorithm (not necessarily in
+        /// combination with EVERY hash algorithm).</summary>
+        /// <param name="cryptoSignatureAlgorithm">the algorithm of interest.</param>
+        /// <returns>true if cryptoSignatureAlgorithm is supported, false otherwise.</returns>
+        bool HasCryptoSignatureAlgorithm(int cryptoSignatureAlgorithm);
+
+        /// <summary>Return true if this TlsCrypto can support the passed in MAC algorithm.</summary>
+        /// <param name="macAlgorithm">the algorithm of interest.</param>
+        /// <returns>true if macAlgorithm is supported, false otherwise.</returns>
+        bool HasMacAlgorithm(int macAlgorithm);
+
+        /// <summary>Return true if this TlsCrypto supports the passed in <see cref="NamedGroup">named group</see>
+        /// value.</summary>
+        /// <returns>true if this instance supports the passed in <see cref="NamedGroup">named group</see> value.
+        /// </returns>
+        bool HasNamedGroup(int namedGroup);
+
+        /// <summary>Return true if this TlsCrypto can support RSA encryption/decryption.</summary>
+        /// <returns>true if this instance can support RSA encryption/decryption, false otherwise.</returns>
+        bool HasRsaEncryption();
+
+        /// <summary>Return true if this TlsCrypto can support the passed in signature algorithm (not necessarily in
+        /// combination with EVERY hash algorithm).</summary>
+        /// <returns>true if signatureAlgorithm is supported, false otherwise.</returns>
+        bool HasSignatureAlgorithm(short signatureAlgorithm);
+
+        /// <summary>Return true if this TlsCrypto can support the passed in signature algorithm.</summary>
+        /// <param name="sigAndHashAlgorithm">the algorithm of interest.</param>
+        /// <returns>true if sigAndHashAlgorithm is supported, false otherwise.</returns>
+        bool HasSignatureAndHashAlgorithm(SignatureAndHashAlgorithm sigAndHashAlgorithm);
+
+        /// <summary>Return true if this TlsCrypto can support the passed in signature scheme.</summary>
+        /// <param name="signatureScheme">the scheme of interest.</param>
+        /// <returns>true if signatureScheme is supported, false otherwise.</returns>
+        bool HasSignatureScheme(int signatureScheme);
+
+        /// <summary>Return true if this TlsCrypto can support SRP authentication.</summary>
+        /// <returns>true if this instance can support SRP authentication, false otherwise.</returns>
+        bool HasSrpAuthentication();
+
+        /// <summary>Create a TlsSecret object based on provided data.</summary>
+        /// <param name="data">the data to base the TlsSecret on.</param>
+        /// <returns>a TlsSecret based on the provided data.</returns>
+        TlsSecret CreateSecret(byte[] data);
+
+        /// <summary>Create a TlsSecret object containing a randomly-generated RSA PreMasterSecret</summary>
+        /// <param name="clientVersion">the client version to place in the first 2 bytes</param>
+        /// <returns>a TlsSecret containing the PreMasterSecret.</returns>
+        TlsSecret GenerateRsaPreMasterSecret(ProtocolVersion clientVersion);
+
+        /// <summary>Return the primary (safest) SecureRandom for this crypto.</summary>
+        /// <returns>a SecureRandom suitable for key generation.</returns>
+        SecureRandom SecureRandom { get; }
+
+        /// <summary>Create a TlsCertificate from an ASN.1 binary encoding of an X.509 certificate.</summary>
+        /// <param name="encoding">DER/BER encoding of the certificate of interest.</param>
+        /// <returns>a TlsCertificate.</returns>
+        /// <exception cref="IOException">if there is an issue on decoding or constructing the certificate.</exception>
+        TlsCertificate CreateCertificate(byte[] encoding);
+
+        /// <summary>Create a cipher for the specified encryption and MAC algorithms.</summary>
+        /// <remarks>
+        /// See enumeration classes <see cref="EncryptionAlgorithm"/>, <see cref="MacAlgorithm"/> for appropriate
+        /// argument values.
+        /// </remarks>
+        /// <param name="cryptoParams">context specific parameters.</param>
+        /// <param name="encryptionAlgorithm">the encryption algorithm to be employed by the cipher.</param>
+        /// <param name="macAlgorithm">the MAC algorithm to be employed by the cipher.</param>
+        /// <returns>a <see cref="TlsCipher"/> implementing the encryption and MAC algorithms.</returns>
+        /// <exception cref="IOException"/>
+        TlsCipher CreateCipher(TlsCryptoParameters cryptoParams, int encryptionAlgorithm, int macAlgorithm);
+
+        /// <summary>Create a domain object supporting the domain parameters described in dhConfig.</summary>
+        /// <param name="dhConfig">the config describing the DH parameters to use.</param>
+        /// <returns>a TlsDHDomain supporting the parameters in dhConfig.</returns>
+        TlsDHDomain CreateDHDomain(TlsDHConfig dhConfig);
+
+        /// <summary>Create a domain object supporting the domain parameters described in ecConfig.</summary>
+        /// <param name="ecConfig">the config describing the EC parameters to use.</param>
+        /// <returns>a TlsECDomain supporting the parameters in ecConfig.</returns>
+        TlsECDomain CreateECDomain(TlsECConfig ecConfig);
+
+        /// <summary>Adopt the passed in secret, creating a new copy of it.</summary>
+        /// <param name="secret">the secret to make a copy of.</param>
+        /// <returns>a TlsSecret based on the original secret.</returns>
+        TlsSecret AdoptSecret(TlsSecret secret);
+
+        /// <summary>Create a suitable hash for the hash algorithm identifier passed in.</summary>
+        /// <remarks>
+        /// See enumeration class <see cref="CryptoHashAlgorithm"/> for appropriate argument values.
+        /// </remarks>
+        /// <param name="cryptoHashAlgorithm">the hash algorithm the hash needs to implement.</param>
+        /// <returns>a <see cref="TlsHash"/>.</returns>
+        TlsHash CreateHash(int cryptoHashAlgorithm);
+
+        /// <summary>Create a suitable HMAC for the MAC algorithm identifier passed in.</summary>
+        /// <remarks>
+        /// See enumeration class <see cref="MacAlgorithm"/> for appropriate argument values.
+        /// </remarks>
+        /// <param name="macAlgorithm">the MAC algorithm the HMAC needs to match.</param>
+        /// <returns>a <see cref="TlsHmac"/>.</returns>
+        TlsHmac CreateHmac(int macAlgorithm);
+
+        /// <summary>Create a suitable HMAC using the hash algorithm identifier passed in.</summary>
+        /// <remarks>
+        /// See enumeration class <see cref="CryptoHashAlgorithm"/> for appropriate argument values.
+        /// </remarks>
+        /// <param name="cryptoHashAlgorithm">the hash algorithm the HMAC should use.</param>
+        /// <returns>a <see cref="TlsHmac"/>.</returns>
+        TlsHmac CreateHmacForHash(int cryptoHashAlgorithm);
+
+        /// <summary>Create a nonce generator.</summary>
+        /// <remarks>
+        /// Each call should construct a new generator, and the generator should be returned from this call only after
+        /// automatically seeding from this <see cref="TlsCrypto"/>'s entropy source, and from the provided additional
+        /// seed material. The output of each returned generator must be completely independent of the others.
+        /// </remarks>
+        /// <param name="additionalSeedMaterial">context-specific seed material</param>
+        /// <returns>a <see cref="TlsNonceGenerator"/>.</returns>
+        TlsNonceGenerator CreateNonceGenerator(byte[] additionalSeedMaterial);
+
+        /// <summary>Create an SRP-6 client.</summary>
+        /// <param name="srpConfig">client config.</param>
+        /// <returns>an initialised SRP6 client object.</returns>
+        TlsSrp6Client CreateSrp6Client(TlsSrpConfig srpConfig);
+
+        /// <summary>Create an SRP-6 server.</summary>
+        /// <param name="srpConfig">server config.</param>
+        /// <param name="srpVerifier">the SRP6 verifier value.</param>
+        /// <returns>an initialised SRP6 server object.</returns>
+        TlsSrp6Server CreateSrp6Server(TlsSrpConfig srpConfig, BigInteger srpVerifier);
+
+        /// <summary>Create an SRP-6 verifier generator.</summary>
+        /// <param name="srpConfig">generator config.</param>
+        /// <returns>an initialized SRP6 verifier generator.</returns>
+        TlsSrp6VerifierGenerator CreateSrp6VerifierGenerator(TlsSrpConfig srpConfig);
+
+        /// <summary>Setup an initial "secret" for a chain of HKDF calls (RFC 5869), containing a string of HashLen
+        /// zeroes.</summary>
+        /// <param name="cryptoHashAlgorithm">the hash algorithm to instantiate HMAC with. See
+        /// <see cref="CryptoHashAlgorithm"/> for values.</param>
+        TlsSecret HkdfInit(int cryptoHashAlgorithm);
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsCryptoException.cs b/crypto/src/tls/crypto/TlsCryptoException.cs
new file mode 100644
index 000000000..83c3ef791
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsCryptoException.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Basic exception class for crypto services to pass back a cause.</summary>
+    public class TlsCryptoException
+        : TlsException
+    {
+        public TlsCryptoException(string msg)
+            : base(msg)
+        {
+        }
+
+        public TlsCryptoException(string msg, Exception cause)
+            : base(msg, cause)
+        {
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsCryptoParameters.cs b/crypto/src/tls/crypto/TlsCryptoParameters.cs
new file mode 100644
index 000000000..008a2dd28
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsCryptoParameters.cs
@@ -0,0 +1,49 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    // TODO[tls-port] Would rather this be sealed
+    /// <summary>Carrier class for context-related parameters needed for creating secrets and ciphers.</summary>
+    public class TlsCryptoParameters
+    {
+        private readonly TlsContext m_context;
+
+        /// <summary>Base constructor.</summary>
+        /// <param name="context">the context for this parameters object.</param>
+        public TlsCryptoParameters(TlsContext context)
+        {
+            this.m_context = context;
+        }
+
+        public SecurityParameters SecurityParameters
+        {
+            get { return m_context.SecurityParameters; }
+        }
+
+        public ProtocolVersion ClientVersion
+        {
+            get { return m_context.ClientVersion; }
+        }
+
+        public ProtocolVersion RsaPreMasterSecretVersion
+        {
+            get { return m_context.RsaPreMasterSecretVersion; }
+        }
+
+        // TODO[tls-port] Would rather this be non-virtual
+        public virtual ProtocolVersion ServerVersion
+        {
+            get { return m_context.ServerVersion; }
+        }
+
+        public bool IsServer
+        {
+            get { return m_context.IsServer; }
+        }
+
+        public TlsNonceGenerator NonceGenerator
+        {
+            get { return m_context.NonceGenerator; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsCryptoUtilities.cs b/crypto/src/tls/crypto/TlsCryptoUtilities.cs
new file mode 100644
index 000000000..adea49017
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsCryptoUtilities.cs
@@ -0,0 +1,180 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public abstract class TlsCryptoUtilities
+    {
+        // "tls13 "
+        private static readonly byte[] Tls13Prefix = new byte[] { 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20 };
+
+        public static int GetHash(short hashAlgorithm)
+        {
+            switch (hashAlgorithm)
+            {
+            case HashAlgorithm.md5:
+                return CryptoHashAlgorithm.md5;
+            case HashAlgorithm.sha1:
+                return CryptoHashAlgorithm.sha1;
+            case HashAlgorithm.sha224:
+                return CryptoHashAlgorithm.sha224;
+            case HashAlgorithm.sha256:
+                return CryptoHashAlgorithm.sha256;
+            case HashAlgorithm.sha384:
+                return CryptoHashAlgorithm.sha384;
+            case HashAlgorithm.sha512:
+                return CryptoHashAlgorithm.sha512;
+            default:
+                throw new ArgumentException("specified HashAlgorithm invalid: " + HashAlgorithm.GetText(hashAlgorithm));
+            }
+        }
+
+        public static int GetHashForHmac(int macAlgorithm)
+        {
+            switch (macAlgorithm)
+            {
+            case MacAlgorithm.hmac_md5:
+                return CryptoHashAlgorithm.md5;
+            case MacAlgorithm.hmac_sha1:
+                return CryptoHashAlgorithm.sha1;
+            case MacAlgorithm.hmac_sha256:
+                return CryptoHashAlgorithm.sha256;
+            case MacAlgorithm.hmac_sha384:
+                return CryptoHashAlgorithm.sha384;
+            case MacAlgorithm.hmac_sha512:
+                return CryptoHashAlgorithm.sha512;
+            default:
+                throw new ArgumentException("specified MacAlgorithm not an HMAC: " + MacAlgorithm.GetText(macAlgorithm));
+            }
+        }
+
+        public static int GetHashForPrf(int prfAlgorithm)
+        {
+            switch (prfAlgorithm)
+            {
+            case PrfAlgorithm.ssl_prf_legacy:
+            case PrfAlgorithm.tls_prf_legacy:
+                throw new ArgumentException("legacy PRF not a valid algorithm");
+            case PrfAlgorithm.tls_prf_sha256:
+            case PrfAlgorithm.tls13_hkdf_sha256:
+                return CryptoHashAlgorithm.sha256;
+            case PrfAlgorithm.tls_prf_sha384:
+            case PrfAlgorithm.tls13_hkdf_sha384:
+                return CryptoHashAlgorithm.sha384;
+            case PrfAlgorithm.tls13_hkdf_sm3:
+                return CryptoHashAlgorithm.sm3;
+            default:
+                throw new ArgumentException("unknown PrfAlgorithm: " + PrfAlgorithm.GetText(prfAlgorithm));
+            }
+        }
+
+        public static int GetHashOutputSize(int cryptoHashAlgorithm)
+        {
+            switch (cryptoHashAlgorithm)
+            {
+            case CryptoHashAlgorithm.md5:
+                return 16;
+            case CryptoHashAlgorithm.sha1:
+                return 20;
+            case CryptoHashAlgorithm.sha224:
+                return 28;
+            case CryptoHashAlgorithm.sha256:
+            case CryptoHashAlgorithm.sm3:
+                return 32;
+            case CryptoHashAlgorithm.sha384:
+                return 48;
+            case CryptoHashAlgorithm.sha512:
+                return 64;
+            default:
+                throw new ArgumentException();
+            }
+        }
+
+        public static int GetSignature(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+                return CryptoSignatureAlgorithm.rsa;
+            case SignatureAlgorithm.dsa:
+                return CryptoSignatureAlgorithm.dsa;
+            case SignatureAlgorithm.ecdsa:
+                return CryptoSignatureAlgorithm.ecdsa;
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+                return CryptoSignatureAlgorithm.rsa_pss_rsae_sha256;
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+                return CryptoSignatureAlgorithm.rsa_pss_rsae_sha384;
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                return CryptoSignatureAlgorithm.rsa_pss_rsae_sha512;
+            case SignatureAlgorithm.ed25519:
+                return CryptoSignatureAlgorithm.ed25519;
+            case SignatureAlgorithm.ed448:
+                return CryptoSignatureAlgorithm.ed448;
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+                return CryptoSignatureAlgorithm.rsa_pss_pss_sha256;
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+                return CryptoSignatureAlgorithm.rsa_pss_pss_sha384;
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return CryptoSignatureAlgorithm.rsa_pss_pss_sha512;
+            case SignatureAlgorithm.gostr34102012_256:
+                return CryptoSignatureAlgorithm.gostr34102012_256;
+            case SignatureAlgorithm.gostr34102012_512:
+                return CryptoSignatureAlgorithm.gostr34102012_512;
+            default:
+                throw new ArgumentException("specified SignatureAlgorithm invalid: "
+                    + SignatureAlgorithm.GetText(signatureAlgorithm));
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public static TlsSecret HkdfExpandLabel(TlsSecret secret, short hashAlgorithm, string label, byte[] context,
+            int length)
+        {
+            int cryptoHashAlgorithm = GetHash(hashAlgorithm);
+
+            return HkdfExpandLabel(secret, cryptoHashAlgorithm, label, context, length);
+        }
+
+        /// <exception cref="IOException"/>
+        public static TlsSecret HkdfExpandLabel(TlsSecret secret, int cryptoHashAlgorithm, string label,
+            byte[] context, int length)
+        {
+            int labelLength = label.Length;
+            if (labelLength < 1)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            int contextLength = context.Length;
+            int expandedLabelLength = Tls13Prefix.Length + labelLength;
+
+            byte[] hkdfLabel = new byte[2 + (1 + expandedLabelLength) + (1 + contextLength)];
+
+            // uint16 length
+            {
+                TlsUtilities.CheckUint16(length);
+                TlsUtilities.WriteUint16(length, hkdfLabel, 0);
+            }
+
+            // opaque label<7..255>
+            {
+                TlsUtilities.CheckUint8(expandedLabelLength);
+                TlsUtilities.WriteUint8(expandedLabelLength, hkdfLabel, 2);
+
+                Array.Copy(Tls13Prefix, 0, hkdfLabel, 2 + 1, Tls13Prefix.Length);
+
+                int labelPos = 2 + (1 + Tls13Prefix.Length);
+                for (int i = 0; i < labelLength; ++i)
+                {
+                    char c = label[i];
+                    hkdfLabel[labelPos + i] = (byte)c;
+                }
+            }
+
+            // context
+            {
+                TlsUtilities.WriteOpaque8(context, hkdfLabel, 2 + (1 + expandedLabelLength));
+            }
+
+            return secret.HkdfExpand(cryptoHashAlgorithm, hkdfLabel, length);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsDHConfig.cs b/crypto/src/tls/crypto/TlsDHConfig.cs
new file mode 100644
index 000000000..8ec703080
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsDHConfig.cs
@@ -0,0 +1,41 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Basic config for Diffie-Hellman.</summary>
+    public class TlsDHConfig
+    {
+        protected readonly DHGroup m_explicitGroup;
+        protected readonly int m_namedGroup;
+        protected readonly bool m_padded;
+
+        public TlsDHConfig(DHGroup explicitGroup)
+        {
+            this.m_explicitGroup = explicitGroup;
+            this.m_namedGroup = -1;
+            this.m_padded = false;
+        }
+
+        public TlsDHConfig(int namedGroup, bool padded)
+        {
+            this.m_explicitGroup = null;
+            this.m_namedGroup = namedGroup;
+            this.m_padded = padded;
+        }
+
+        public virtual DHGroup ExplicitGroup
+        {
+            get { return m_explicitGroup; }
+        }
+
+        public virtual int NamedGroup
+        {
+            get { return m_namedGroup; }
+        }
+
+        public virtual bool IsPadded
+        {
+            get { return m_padded; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsDHDomain.cs b/crypto/src/tls/crypto/TlsDHDomain.cs
new file mode 100644
index 000000000..118441267
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsDHDomain.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Domain interface to service factory for creating Diffie-Hellman operators.</summary>
+    public interface TlsDHDomain
+    {
+        /// <summary>Return an agreement operator suitable for ephemeral Diffie-Hellman.</summary>
+        /// <returns>a key agreement operator.</returns>
+        TlsAgreement CreateDH();
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsDecodeResult.cs b/crypto/src/tls/crypto/TlsDecodeResult.cs
new file mode 100644
index 000000000..84b911bcd
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsDecodeResult.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public sealed class TlsDecodeResult
+    {
+        public readonly byte[] buf;
+        public readonly int off, len;
+        public readonly short contentType;
+
+        public TlsDecodeResult(byte[] buf, int off, int len, short contentType)
+        {
+            this.buf = buf;
+            this.off = off;
+            this.len = len;
+            this.contentType = contentType;
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsECConfig.cs b/crypto/src/tls/crypto/TlsECConfig.cs
new file mode 100644
index 000000000..156e59df5
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsECConfig.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Carrier class for Elliptic Curve parameter configuration.</summary>
+    public class TlsECConfig
+    {
+        protected readonly int m_namedGroup;
+
+        public TlsECConfig(int namedGroup)
+        {
+            this.m_namedGroup = namedGroup;
+        }
+
+        /// <summary>Return the group used.</summary>
+        /// <returns>the <see cref="NamedGroup">named group</see> used.</returns>
+        public virtual int NamedGroup
+        {
+            get { return m_namedGroup; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsECDomain.cs b/crypto/src/tls/crypto/TlsECDomain.cs
new file mode 100644
index 000000000..74567b38b
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsECDomain.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Domain interface to service factory for creating Elliptic-Curve (EC) based operators.</summary>
+    public interface TlsECDomain
+    {
+        /// <summary>Return an agreement operator suitable for ephemeral EC Diffie-Hellman.</summary>
+        /// <returns>a key agreement operator.</returns>
+        TlsAgreement CreateECDH();
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsEncodeResult.cs b/crypto/src/tls/crypto/TlsEncodeResult.cs
new file mode 100644
index 000000000..963e4563a
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsEncodeResult.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public sealed class TlsEncodeResult
+    {
+        public readonly byte[] buf;
+        public readonly int off, len;
+        public readonly short recordType;
+
+        public TlsEncodeResult(byte[] buf, int off, int len, short recordType)
+        {
+            this.buf = buf;
+            this.off = off;
+            this.len = len;
+            this.recordType = recordType;
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsHash.cs b/crypto/src/tls/crypto/TlsHash.cs
new file mode 100644
index 000000000..4732fc280
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsHash.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Interface for message digest, or hash, services.</summary>
+    public interface TlsHash
+    {
+        /// <summary>Update the hash with the passed in input.</summary>
+        /// <param name="input">input array containing the data.</param>
+        /// <param name="inOff">offset into the input array the input starts at.</param>
+        /// <param name="length">the length of the input data.</param>
+        void Update(byte[] input, int inOff, int length);
+
+        /// <summary>Return calculated hash for any input passed in.</summary>
+        /// <returns>the hash value.</returns>
+        byte[] CalculateHash();
+
+        /// <summary>Return a clone of this hash object representing its current state.</summary>
+        /// <returns>a clone of the current hash.</returns>
+        TlsHash CloneHash();
+
+        /// <summary>Reset the hash underlying this service.</summary>
+        void Reset();
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsHashSink.cs b/crypto/src/tls/crypto/TlsHashSink.cs
new file mode 100644
index 000000000..64496744d
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsHashSink.cs
@@ -0,0 +1,35 @@
+using System;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public class TlsHashSink
+        : BaseOutputStream
+    {
+        private readonly TlsHash m_hash;
+
+        public TlsHashSink(TlsHash hash)
+        {
+            this.m_hash = hash;
+        }
+
+        public virtual TlsHash Hash
+        {
+            get { return m_hash; }
+        }
+
+        public override void WriteByte(byte b)
+        {
+            m_hash.Update(new byte[] { b }, 0, 1);
+        }
+
+        public override void Write(byte[] buf, int off, int len)
+        {
+            if (len > 0)
+            {
+                m_hash.Update(buf, off, len);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsHmac.cs b/crypto/src/tls/crypto/TlsHmac.cs
new file mode 100644
index 000000000..762954899
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsHmac.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Interface for MAC services based on HMAC.</summary>
+    public interface TlsHmac
+        : TlsMac
+    {
+        /// <summary>Return the internal block size for the message digest underlying this HMAC service.</summary>
+        /// <returns>the internal block size for the digest (in bytes).</returns>
+        int InternalBlockSize { get; }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsMac.cs b/crypto/src/tls/crypto/TlsMac.cs
new file mode 100644
index 000000000..f92d946f1
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsMac.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Interface for MAC services.</summary>
+    public interface TlsMac
+    {
+        /// <summary>Set the key to be used by the MAC implementation supporting this service.</summary>
+        /// <param name="key">array holding the MAC key.</param>
+        /// <param name="keyOff">offset into the array the key starts at.</param>
+        /// <param name="keyLen">length of the key in the array.</param>
+        void SetKey(byte[] key, int keyOff, int keyLen);
+
+        /// <summary>Update the MAC with the passed in input.</summary>
+        /// <param name="input">input array containing the data.</param>
+        /// <param name="inOff">offset into the input array the input starts at.</param>
+        /// <param name="length">the length of the input data.</param>
+        void Update(byte[] input, int inOff, int length);
+
+        /// <summary>Return calculated MAC for any input passed in.</summary>
+        /// <returns>the MAC value.</returns>
+        byte[] CalculateMac();
+
+        /// <summary>Write the calculated MAC to an output buffer.</summary>
+        /// <param name="output">output array to write the MAC to.</param>
+        /// <param name="outOff">offset into the output array to write the MAC to.</param>
+        void CalculateMac(byte[] output, int outOff);
+
+        /// <summary>Return the length of the MAC generated by this service.</summary>
+        /// <returns>the MAC length.</returns>
+        int MacLength { get; }
+
+        /// <summary>Reset the MAC underlying this service.</summary>
+        void Reset();
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsMacSink.cs b/crypto/src/tls/crypto/TlsMacSink.cs
new file mode 100644
index 000000000..58e65c731
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsMacSink.cs
@@ -0,0 +1,35 @@
+using System;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public class TlsMacSink
+        : BaseOutputStream
+    {
+        private readonly TlsMac m_mac;
+
+        public TlsMacSink(TlsMac mac)
+        {
+            this.m_mac = mac;
+        }
+
+        public virtual TlsMac Mac
+        {
+            get { return m_mac; }
+        }
+
+        public override void WriteByte(byte b)
+        {
+            m_mac.Update(new byte[]{ b }, 0, 1);
+        }
+
+        public override void Write(byte[] buf, int off, int len)
+        {
+            if (len > 0)
+            {
+                m_mac.Update(buf, off, len);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsNonceGenerator.cs b/crypto/src/tls/crypto/TlsNonceGenerator.cs
new file mode 100644
index 000000000..00cadc75d
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsNonceGenerator.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public interface TlsNonceGenerator
+    {
+        /// <summary>Generate a nonce byte[] string.</summary>
+        /// <param name="size">the length, in bytes, of the nonce to generate.</param>
+        /// <returns>the nonce value.</returns>
+        byte[] GenerateNonce(int size);
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsNullNullCipher.cs b/crypto/src/tls/crypto/TlsNullNullCipher.cs
new file mode 100644
index 000000000..082dff358
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsNullNullCipher.cs
@@ -0,0 +1,55 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>The cipher for TLS_NULL_WITH_NULL_NULL.</summary>
+    public sealed class TlsNullNullCipher
+        : TlsCipher
+    {
+        public static readonly TlsNullNullCipher Instance = new TlsNullNullCipher();
+
+        public int GetCiphertextDecodeLimit(int plaintextLimit)
+        {
+            return plaintextLimit;
+        }
+
+        public int GetCiphertextEncodeLimit(int plaintextLength, int plaintextLimit)
+        {
+            return plaintextLength;
+        }
+
+        public int GetPlaintextLimit(int ciphertextLimit)
+        {
+            return ciphertextLimit;
+        }
+
+        public TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, byte[] plaintext, int offset, int len)
+        {
+            byte[] result = new byte[headerAllocation + len];
+            Array.Copy(plaintext, offset, result, headerAllocation, len);
+            return new TlsEncodeResult(result, 0, result.Length, contentType);
+        }
+
+        public TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
+            byte[] ciphertext, int offset, int len)
+        {
+            return new TlsDecodeResult(ciphertext, offset, len, recordType);
+        }
+
+        public void RekeyDecoder()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public void RekeyEncoder()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public bool UsesOpaqueRecordType
+        {
+            get { return false; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsSecret.cs b/crypto/src/tls/crypto/TlsSecret.cs
new file mode 100644
index 000000000..9b092fc40
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsSecret.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Interface supporting the generation of key material and other SSL/TLS secret values from PRFs.
+    /// </summary>
+    public interface TlsSecret
+    {
+        /// <summary>Return a new secret based on applying a PRF to this one.</summary>
+        /// <param name="prfAlgorithm">PRF algorithm to use.</param>
+        /// <param name="label">the label details.</param>
+        /// <param name="seed">the seed details.</param>
+        /// <param name="length">the size (in bytes) of the secret to generate.</param>
+        /// <returns>the new secret.</returns>
+        TlsSecret DeriveUsingPrf(int prfAlgorithm, string label, byte[] seed, int length);
+
+        /// <summary>Destroy the internal state of the secret.</summary>
+        /// <remarks>
+        /// After this call, any attempt to use the <see cref="TlsSecret"/> will result in an
+        /// <see cref="InvalidOperationException"/> being thrown.
+        /// </remarks>
+        void Destroy();
+
+        /// <summary>Return an encrypted copy of the data this secret is based on.</summary>
+        /// <param name="certificate">the certificate containing the public key to use for protecting the internal
+        /// data.</param>
+        /// <returns>an encrypted copy of this secret's internal data.</returns>
+        /// <exception cref="IOException"/>
+        byte[] Encrypt(TlsCertificate certificate);
+
+        /// <summary>Return the internal data from this secret.</summary>
+        /// <remarks>
+        /// The <see cref="TlsSecret"/> does not keep a copy of the data. After this call, any attempt to use the
+        /// <see cref="TlsSecret"/> will result in an <see cref="InvalidOperationException"/> being thrown.
+        /// </remarks>
+        /// <returns>the secret's internal data.</returns>
+        byte[] Extract();
+
+        /// <summary>RFC 5869 HKDF-Expand function, with this secret's data as the pseudo-random key ('prk').</summary>
+        /// <param name="cryptoHashAlgorithm">the hash algorithm to instantiate HMAC with. See
+        /// <see cref="CryptoHashAlgorithm"/> for values.</param>
+        /// <param name="info">optional context and application specific information (can be zero-length).</param>
+        /// <param name="length">length of output keying material in octets.</param>
+        /// <returns> output keying material (of 'length' octets).</returns>
+        TlsSecret HkdfExpand(int cryptoHashAlgorithm, byte[] info, int length);
+
+        /// <summary>RFC 5869 HKDF-Extract function, with this secret's data as the 'salt'.</summary>
+        /// <remarks>
+        /// The <see cref="TlsSecret"/> does not keep a copy of the data. After this call, any attempt to use
+        /// the <see cref="TlsSecret"/> will result in an <see cref="InvalidOperationException"/> being thrown.
+        /// </remarks>
+        /// <param name="cryptoHashAlgorithm">the hash algorithm to instantiate HMAC with. See
+        /// <see cref="CryptoHashAlgorithm"/> for values.</param>
+        /// <param name="ikm">input keying material.</param>
+        /// <returns>a pseudo-random key (of HashLen octets).</returns>
+        TlsSecret HkdfExtract(int cryptoHashAlgorithm, byte[] ikm);
+
+        bool IsAlive();
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsSigner.cs b/crypto/src/tls/crypto/TlsSigner.cs
new file mode 100644
index 000000000..ffd05d935
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsSigner.cs
@@ -0,0 +1,19 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Base interface for a TLS signer that works on raw message digests.</summary>
+    public interface TlsSigner
+    {
+        /// <summary>Generate an encoded signature based on the passed in hash.</summary>
+        /// <param name="algorithm">the signature algorithm to use.</param>
+        /// <param name="hash">the hash calculated for the signature.</param>
+        /// <returns>an encoded signature.</returns>
+        /// <exception cref="IOException">in case of an exception processing the hash.</exception>
+        byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash);
+
+        /// <exception cref="IOException"/>
+        TlsStreamSigner GetStreamSigner(SignatureAndHashAlgorithm algorithm);
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsSrp6Client.cs b/crypto/src/tls/crypto/TlsSrp6Client.cs
new file mode 100644
index 000000000..e2b545ada
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsSrp6Client.cs
@@ -0,0 +1,24 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Basic interface for an SRP-6 client implementation.</summary>
+    public interface TlsSrp6Client
+    {
+        /// <summary>Generates the secret S given the server's credentials</summary>
+        /// <param name="serverB">The server's credentials</param>
+        /// <returns>Client's verification message for the server</returns>
+        /// <exception cref="IOException">If server's credentials are invalid</exception>
+        BigInteger CalculateSecret(BigInteger serverB);
+
+        /// <summary>Generates client's credentials given the client's salt, identity and password</summary>
+        /// <param name="salt">The salt used in the client's verifier.</param>
+        /// <param name="identity">The user's identity (eg. username)</param>
+        /// <param name="password">The user's password</param>
+        /// <returns>Client's public value to send to server</returns>
+        BigInteger GenerateClientCredentials(byte[] salt, byte[] identity, byte[] password);
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsSrp6Server.cs b/crypto/src/tls/crypto/TlsSrp6Server.cs
new file mode 100644
index 000000000..75dc6fcb7
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsSrp6Server.cs
@@ -0,0 +1,22 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Basic interface for an SRP-6 server implementation.</summary>
+    public interface TlsSrp6Server
+    {
+        /// <summary>Generates the server's credentials that are to be sent to the client.</summary>
+        /// <returns>The server's public value to the client</returns>
+        BigInteger GenerateServerCredentials();
+
+        /// <summary>Processes the client's credentials. If valid the shared secret is generated and returned.
+        /// </summary>
+        /// <param name="clientA">The client's credentials.</param>
+        /// <returns>A shared secret <see cref="BigInteger"/>.</returns>
+        /// <exception cref="IOException">If client's credentials are invalid.</exception>
+        BigInteger CalculateSecret(BigInteger clientA);
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsSrp6VerifierGenerator.cs b/crypto/src/tls/crypto/TlsSrp6VerifierGenerator.cs
new file mode 100644
index 000000000..238de98e9
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsSrp6VerifierGenerator.cs
@@ -0,0 +1,17 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Base interface for a generator for SRP-6 verifiers.</summary>
+    public interface TlsSrp6VerifierGenerator
+    {
+        /// <summary>Creates a new SRP-6 verifier value.</summary>
+        /// <param name="salt">The salt to use, generally should be large and random</param>
+        /// <param name="identity">The user's identifying information (eg. username)</param>
+        /// <param name="password">The user's password</param>
+        /// <returns>A new verifier for use in future SRP authentication</returns>
+        BigInteger GenerateVerifier(byte[] salt, byte[] identity, byte[] password);
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsSrpConfig.cs b/crypto/src/tls/crypto/TlsSrpConfig.cs
new file mode 100644
index 000000000..e31e4a5f2
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsSrpConfig.cs
@@ -0,0 +1,26 @@
+using System;
+
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Basic config for SRP.</summary>
+    public class TlsSrpConfig
+    {
+        protected BigInteger[] m_explicitNG;
+
+        /// <summary>Return the (N, g) values used in SRP-6.</summary>
+        /// <returns>(N, g) as a BigInteger array (N=[0], g=[1]).</returns>
+        public BigInteger[] GetExplicitNG()
+        {
+            return (BigInteger[])m_explicitNG.Clone();
+        }
+
+        /// <summary>Set the (N, g) values used for SRP-6.</summary>
+        /// <param name="explicitNG">(N, g) as a BigInteger array (N=[0], g=[1]).</param>
+        public void SetExplicitNG(BigInteger[] explicitNG)
+        {
+            this.m_explicitNG = (BigInteger[])explicitNG.Clone();
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsStreamSigner.cs b/crypto/src/tls/crypto/TlsStreamSigner.cs
new file mode 100644
index 000000000..8f79346bf
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsStreamSigner.cs
@@ -0,0 +1,14 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public interface TlsStreamSigner
+    {
+        /// <exception cref="IOException"/>
+        Stream GetOutputStream();
+
+        /// <exception cref="IOException"/>
+        byte[] GetSignature();
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsStreamVerifier.cs b/crypto/src/tls/crypto/TlsStreamVerifier.cs
new file mode 100644
index 000000000..9d18a9c9c
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsStreamVerifier.cs
@@ -0,0 +1,14 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    public interface TlsStreamVerifier
+    {
+        /// <exception cref="IOException"/>
+        Stream GetOutputStream();
+
+        /// <exception cref="IOException"/>
+        bool IsVerified();
+    }
+}
diff --git a/crypto/src/tls/crypto/TlsVerifier.cs b/crypto/src/tls/crypto/TlsVerifier.cs
new file mode 100644
index 000000000..ad2ddbc84
--- /dev/null
+++ b/crypto/src/tls/crypto/TlsVerifier.cs
@@ -0,0 +1,20 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto
+{
+    /// <summary>Base interface for a TLS verifier that works with signatures and either raw message digests, or entire
+    /// messages.</summary>
+    public interface TlsVerifier
+    {
+        /// <exception cref="IOException"/>
+        TlsStreamVerifier GetStreamVerifier(DigitallySigned signature);
+
+        /// <summary>Return true if the passed in signature and hash represent a real signature.</summary>
+        /// <param name="signature">the signature object containing the signature to be verified.</param>
+        /// <param name="hash">the hash calculated for the signature.</param>
+        /// <returns>true if signature verifies, false otherwise.</returns>
+        /// <exception cref="IOException">in case of an exception verifying signature.</exception>
+        bool VerifyRawSignature(DigitallySigned signature, byte[] hash);
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs b/crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs
new file mode 100644
index 000000000..f0b2b03f6
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs
@@ -0,0 +1,90 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>Base class for a TlsCrypto implementation that provides some needed methods from elsewhere in the impl
+    /// package.</summary>
+    public abstract class AbstractTlsCrypto
+        : TlsCrypto
+    {
+        public abstract bool HasAllRawSignatureAlgorithms();
+
+        public abstract bool HasDHAgreement();
+
+        public abstract bool HasECDHAgreement();
+
+        public abstract bool HasEncryptionAlgorithm(int encryptionAlgorithm);
+
+        public abstract bool HasCryptoHashAlgorithm(int cryptoHashAlgorithm);
+
+        public abstract bool HasCryptoSignatureAlgorithm(int cryptoSignatureAlgorithm);
+
+        public abstract bool HasMacAlgorithm(int macAlgorithm);
+
+        public abstract bool HasNamedGroup(int namedGroup);
+
+        public abstract bool HasRsaEncryption();
+
+        public abstract bool HasSignatureAlgorithm(short signatureAlgorithm);
+
+        public abstract bool HasSignatureAndHashAlgorithm(SignatureAndHashAlgorithm sigAndHashAlgorithm);
+
+        public abstract bool HasSignatureScheme(int signatureScheme);
+
+        public abstract bool HasSrpAuthentication();
+
+        public abstract TlsSecret CreateSecret(byte[] data);
+
+        public abstract TlsSecret GenerateRsaPreMasterSecret(ProtocolVersion clientVersion);
+
+        public abstract SecureRandom SecureRandom { get; }
+
+        public abstract TlsCertificate CreateCertificate(byte[] encoding);
+
+        public abstract TlsCipher CreateCipher(TlsCryptoParameters cryptoParams, int encryptionAlgorithm, int macAlgorithm);
+
+        public abstract TlsDHDomain CreateDHDomain(TlsDHConfig dhConfig);
+
+        public abstract TlsECDomain CreateECDomain(TlsECConfig ecConfig);
+
+        public virtual TlsSecret AdoptSecret(TlsSecret secret)
+        {
+            // TODO[tls] Need an alternative that doesn't require AbstractTlsSecret (which holds literal data)
+            if (secret is AbstractTlsSecret)
+            {
+                AbstractTlsSecret sec = (AbstractTlsSecret)secret;
+
+                return CreateSecret(sec.CopyData());
+            }
+
+            throw new ArgumentException("unrecognized TlsSecret - cannot copy data: " + Platform.GetTypeName(secret));
+        }
+
+        public abstract TlsHash CreateHash(int cryptoHashAlgorithm);
+
+        public abstract TlsHmac CreateHmac(int macAlgorithm);
+
+        public abstract TlsHmac CreateHmacForHash(int cryptoHashAlgorithm);
+
+        public abstract TlsNonceGenerator CreateNonceGenerator(byte[] additionalSeedMaterial);
+
+        public abstract TlsSrp6Client CreateSrp6Client(TlsSrpConfig srpConfig);
+
+        public abstract TlsSrp6Server CreateSrp6Server(TlsSrpConfig srpConfig, BigInteger srpVerifier);
+
+        public abstract TlsSrp6VerifierGenerator CreateSrp6VerifierGenerator(TlsSrpConfig srpConfig);
+
+        public abstract TlsSecret HkdfInit(int cryptoHashAlgorithm);
+
+        /// <summary>Return an encryptor based on the public key in certificate.</summary>
+        /// <param name="certificate">the certificate carrying the public key.</param>
+        /// <returns>a <see cref="TlsEncryptor"/> based on the certificate's public key.</returns>
+        /// <exception cref="IOException"/>
+        public abstract TlsEncryptor CreateEncryptor(TlsCertificate certificate);
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/AbstractTlsSecret.cs b/crypto/src/tls/crypto/impl/AbstractTlsSecret.cs
new file mode 100644
index 000000000..634b86732
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/AbstractTlsSecret.cs
@@ -0,0 +1,87 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>Base class for a TlsSecret implementation which captures common code and fields.</summary>
+    public abstract class AbstractTlsSecret
+        : TlsSecret
+    {
+        protected byte[] m_data;
+
+        /// <summary>Base constructor.</summary>
+        /// <param name="data">the byte[] making up the secret value.</param>
+        protected AbstractTlsSecret(byte[] data)
+        {
+            this.m_data = data;
+        }
+
+        protected virtual void CheckAlive()
+        {
+            if (m_data == null)
+                throw new InvalidOperationException("Secret has already been extracted or destroyed");
+        }
+
+        protected abstract AbstractTlsCrypto Crypto { get; }
+
+        public abstract TlsSecret DeriveUsingPrf(int prfAlgorithm, string label, byte[] seed, int length);
+
+        public virtual void Destroy()
+        {
+            lock (this)
+            {
+                if (m_data != null)
+                {
+                    // TODO Is there a way to ensure the data is really overwritten?
+                    Array.Clear(m_data, 0, m_data.Length);
+                    this.m_data = null;
+                }
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual byte[] Encrypt(TlsCertificate certificate)
+        {
+            lock (this)
+            {
+                CheckAlive();
+
+                return Crypto.CreateEncryptor(certificate).Encrypt(m_data, 0, m_data.Length);
+            }
+        }
+
+        public virtual byte[] Extract()
+        {
+            lock (this)
+            {
+                CheckAlive();
+
+                byte[] result = m_data;
+                this.m_data = null;
+                return result;
+            }
+        }
+
+        public abstract TlsSecret HkdfExpand(int cryptoHashAlgorithm, byte[] info, int length);
+
+        public abstract TlsSecret HkdfExtract(int cryptoHashAlgorithm, byte[] ikm);
+
+        public virtual bool IsAlive()
+        {
+            lock (this)
+            {
+                return null != m_data;
+            }
+        }
+
+        internal virtual byte[] CopyData()
+        {
+            lock (this)
+            {
+                return Arrays.Clone(m_data);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/RsaUtilities.cs b/crypto/src/tls/crypto/impl/RsaUtilities.cs
new file mode 100644
index 000000000..83782a2ac
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/RsaUtilities.cs
@@ -0,0 +1,136 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.Nist;
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    public abstract class RsaUtilities
+    {
+        private static readonly byte[] RSAPSSParams_256_A, RSAPSSParams_384_A, RSAPSSParams_512_A;
+        private static readonly byte[] RSAPSSParams_256_B, RSAPSSParams_384_B, RSAPSSParams_512_B;
+
+        static RsaUtilities()
+        {
+            /*
+             * RFC 4055
+             */
+
+            AlgorithmIdentifier sha256Identifier_A = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256);
+            AlgorithmIdentifier sha384Identifier_A = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha384);
+            AlgorithmIdentifier sha512Identifier_A = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha512);
+            AlgorithmIdentifier sha256Identifier_B = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha256, DerNull.Instance);
+            AlgorithmIdentifier sha384Identifier_B = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha384, DerNull.Instance);
+            AlgorithmIdentifier sha512Identifier_B = new AlgorithmIdentifier(NistObjectIdentifiers.IdSha512, DerNull.Instance);
+
+            AlgorithmIdentifier mgf1SHA256Identifier_A = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, sha256Identifier_A);
+            AlgorithmIdentifier mgf1SHA384Identifier_A = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, sha384Identifier_A);
+            AlgorithmIdentifier mgf1SHA512Identifier_A = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, sha512Identifier_A);
+            AlgorithmIdentifier mgf1SHA256Identifier_B = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, sha256Identifier_B);
+            AlgorithmIdentifier mgf1SHA384Identifier_B = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, sha384Identifier_B);
+            AlgorithmIdentifier mgf1SHA512Identifier_B = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, sha512Identifier_B);
+
+            DerInteger sha256Size = new DerInteger(TlsCryptoUtilities.GetHashOutputSize(CryptoHashAlgorithm.sha256));
+            DerInteger sha384Size = new DerInteger(TlsCryptoUtilities.GetHashOutputSize(CryptoHashAlgorithm.sha384));
+            DerInteger sha512Size = new DerInteger(TlsCryptoUtilities.GetHashOutputSize(CryptoHashAlgorithm.sha512));
+
+            DerInteger trailerField = new DerInteger(1);
+
+            try
+            {
+                RSAPSSParams_256_A = new RsassaPssParameters(sha256Identifier_A, mgf1SHA256Identifier_A, sha256Size, trailerField)
+                    .GetEncoded(Asn1Encodable.Der);
+                RSAPSSParams_384_A = new RsassaPssParameters(sha384Identifier_A, mgf1SHA384Identifier_A, sha384Size, trailerField)
+                    .GetEncoded(Asn1Encodable.Der);
+                RSAPSSParams_512_A = new RsassaPssParameters(sha512Identifier_A, mgf1SHA512Identifier_A, sha512Size, trailerField)
+                    .GetEncoded(Asn1Encodable.Der);
+                RSAPSSParams_256_B = new RsassaPssParameters(sha256Identifier_B, mgf1SHA256Identifier_B, sha256Size, trailerField)
+                    .GetEncoded(Asn1Encodable.Der);
+                RSAPSSParams_384_B = new RsassaPssParameters(sha384Identifier_B, mgf1SHA384Identifier_B, sha384Size, trailerField)
+                    .GetEncoded(Asn1Encodable.Der);
+                RSAPSSParams_512_B = new RsassaPssParameters(sha512Identifier_B, mgf1SHA512Identifier_B, sha512Size, trailerField)
+                    .GetEncoded(Asn1Encodable.Der);
+            }
+            catch (IOException e)
+            {
+                throw new InvalidOperationException(e.Message);
+            }
+        }
+
+        public static bool SupportsPkcs1(AlgorithmIdentifier pubKeyAlgID)
+        {
+            DerObjectIdentifier oid = pubKeyAlgID.Algorithm;
+            return PkcsObjectIdentifiers.RsaEncryption.Equals(oid)
+                || X509ObjectIdentifiers.IdEARsa.Equals(oid);
+        }
+
+        public static bool SupportsPss_Pss(short signatureAlgorithm, AlgorithmIdentifier pubKeyAlgID)
+        {
+            DerObjectIdentifier oid = pubKeyAlgID.Algorithm;
+            if (!PkcsObjectIdentifiers.IdRsassaPss.Equals(oid))
+                return false;
+
+            /*
+             * TODO ASN.1 NULL shouldn't really be allowed here; it's a workaround for e.g. Oracle JDK
+             * 1.8.0_241, where the X.509 certificate implementation adds the NULL when re-encoding the
+             * original parameters. It appears it was fixed at some later date (OpenJDK 12.0.2 does not
+             * have the issue), but not sure exactly when.
+             */
+            Asn1Encodable pssParams = pubKeyAlgID.Parameters;
+            if (null == pssParams || pssParams is DerNull)
+            {
+                switch (signatureAlgorithm)
+                {
+                case SignatureAlgorithm.rsa_pss_pss_sha256:
+                case SignatureAlgorithm.rsa_pss_pss_sha384:
+                case SignatureAlgorithm.rsa_pss_pss_sha512:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+
+            byte[] encoded;
+            try
+            {
+                encoded = pssParams.ToAsn1Object().GetEncoded(Asn1Encodable.Der);
+            }
+            catch (Exception)
+            {
+                return false;
+            }
+
+            byte[] expected_A, expected_B;
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+                expected_A = RSAPSSParams_256_A;
+                expected_B = RSAPSSParams_256_B;
+                break;
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+                expected_A = RSAPSSParams_384_A;
+                expected_B = RSAPSSParams_384_B;
+                break;
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                expected_A = RSAPSSParams_512_A;
+                expected_B = RSAPSSParams_512_B;
+                break;
+            default:
+                return false;
+            }
+
+            return Arrays.AreEqual(expected_A, encoded)
+                || Arrays.AreEqual(expected_B, encoded);
+        }
+
+        public static bool SupportsPss_Rsae(AlgorithmIdentifier pubKeyAlgID)
+        {
+            DerObjectIdentifier oid = pubKeyAlgID.Algorithm;
+            return PkcsObjectIdentifiers.RsaEncryption.Equals(oid);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsAeadCipher.cs b/crypto/src/tls/crypto/impl/TlsAeadCipher.cs
new file mode 100644
index 000000000..80851e440
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsAeadCipher.cs
@@ -0,0 +1,377 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>A generic TLS 1.2 AEAD cipher.</summary>
+    public class TlsAeadCipher
+        : TlsCipher
+    {
+        public const int AEAD_CCM = 1;
+        public const int AEAD_CHACHA20_POLY1305 = 2;
+        public const int AEAD_GCM = 3;
+
+        private const int NONCE_RFC5288 = 1;
+        private const int NONCE_RFC7905 = 2;
+
+        protected readonly TlsCryptoParameters m_cryptoParams;
+        protected readonly int m_keySize;
+        protected readonly int m_macSize;
+        protected readonly int m_fixed_iv_length;
+        protected readonly int m_record_iv_length;
+
+        protected readonly TlsAeadCipherImpl m_decryptCipher, m_encryptCipher;
+        protected readonly byte[] m_decryptNonce, m_encryptNonce;
+
+        protected readonly bool m_isTlsV13;
+        protected readonly int m_nonceMode;
+
+        /// <exception cref="IOException"/>
+        public TlsAeadCipher(TlsCryptoParameters cryptoParams, TlsAeadCipherImpl encryptCipher,
+            TlsAeadCipherImpl decryptCipher, int keySize, int macSize, int aeadType)
+        {
+            SecurityParameters securityParameters = cryptoParams.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (!TlsImplUtilities.IsTlsV12(negotiatedVersion))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_isTlsV13 = TlsImplUtilities.IsTlsV13(negotiatedVersion);
+            this.m_nonceMode = GetNonceMode(m_isTlsV13, aeadType);
+
+            switch (m_nonceMode)
+            {
+            case NONCE_RFC5288:
+                this.m_fixed_iv_length = 4;
+                this.m_record_iv_length = 8;
+                break;
+            case NONCE_RFC7905:
+                this.m_fixed_iv_length = 12;
+                this.m_record_iv_length = 0;
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            this.m_cryptoParams = cryptoParams;
+            this.m_keySize = keySize;
+            this.m_macSize = macSize;
+
+            this.m_decryptCipher = decryptCipher;
+            this.m_encryptCipher = encryptCipher;
+
+            this.m_decryptNonce = new byte[m_fixed_iv_length];
+            this.m_encryptNonce = new byte[m_fixed_iv_length];
+
+            bool isServer = cryptoParams.IsServer;
+            if (m_isTlsV13)
+            {
+                RekeyCipher(securityParameters, decryptCipher, m_decryptNonce, !isServer);
+                RekeyCipher(securityParameters, encryptCipher, m_encryptNonce, isServer);
+                return;
+            }
+
+            int keyBlockSize = (2 * keySize) + (2 * m_fixed_iv_length);
+            byte[] keyBlock = TlsImplUtilities.CalculateKeyBlock(cryptoParams, keyBlockSize);
+            int pos = 0;
+
+            if (isServer)
+            {
+                decryptCipher.SetKey(keyBlock, pos, keySize); pos += keySize;
+                encryptCipher.SetKey(keyBlock, pos, keySize); pos += keySize;
+
+                Array.Copy(keyBlock, pos, m_decryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
+                Array.Copy(keyBlock, pos, m_encryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
+            }
+            else
+            {
+                encryptCipher.SetKey(keyBlock, pos, keySize); pos += keySize;
+                decryptCipher.SetKey(keyBlock, pos, keySize); pos += keySize;
+
+                Array.Copy(keyBlock, pos, m_encryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
+                Array.Copy(keyBlock, pos, m_decryptNonce, 0, m_fixed_iv_length); pos += m_fixed_iv_length;
+            }
+
+            if (keyBlockSize != pos)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            int nonceLength = m_fixed_iv_length + m_record_iv_length;
+
+            // NOTE: Ensure dummy nonce is not part of the generated sequence(s)
+            byte[] dummyNonce = new byte[nonceLength];
+            dummyNonce[0] = (byte)~m_encryptNonce[0];
+            dummyNonce[1] = (byte)~m_decryptNonce[1];
+
+            encryptCipher.Init(dummyNonce, macSize, null);
+            decryptCipher.Init(dummyNonce, macSize, null);
+        }
+
+        public virtual int GetCiphertextDecodeLimit(int plaintextLimit)
+        {
+            return plaintextLimit + m_macSize + m_record_iv_length + (m_isTlsV13 ? 1 : 0);
+        }
+
+        public virtual int GetCiphertextEncodeLimit(int plaintextLength, int plaintextLimit)
+        {
+            int innerPlaintextLimit = plaintextLength;
+            if (m_isTlsV13)
+            {
+                // TODO[tls13] Add support for padding
+                int maxPadding = 0;
+
+                innerPlaintextLimit = 1 + System.Math.Min(plaintextLimit, plaintextLength + maxPadding);
+            }
+
+            return innerPlaintextLimit + m_macSize + m_record_iv_length;
+        }
+
+        public virtual int GetPlaintextLimit(int ciphertextLimit)
+        {
+            return ciphertextLimit - m_macSize - m_record_iv_length - (m_isTlsV13 ? 1 : 0);
+        }
+
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, byte[] plaintext, int plaintextOffset, int plaintextLength)
+        {
+            byte[] nonce = new byte[m_encryptNonce.Length + m_record_iv_length];
+
+            switch (m_nonceMode)
+            {
+            case NONCE_RFC5288:
+                Array.Copy(m_encryptNonce, 0, nonce, 0, m_encryptNonce.Length);
+                // RFC 5288/6655: The nonce_explicit MAY be the 64-bit sequence number.
+                TlsUtilities.WriteUint64(seqNo, nonce, m_encryptNonce.Length);
+                break;
+            case NONCE_RFC7905:
+                TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8);
+                for (int i = 0; i < m_encryptNonce.Length; ++i)
+                {
+                    nonce[i] ^= m_encryptNonce[i];
+                }
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            int extraLength = m_isTlsV13 ? 1 : 0;
+
+            // TODO[tls13] If we support adding padding to TLSInnerPlaintext, this will need review
+            int encryptionLength = m_encryptCipher.GetOutputSize(plaintextLength + extraLength);
+            int ciphertextLength = m_record_iv_length + encryptionLength;
+
+            byte[] output = new byte[headerAllocation + ciphertextLength];
+            int outputPos = headerAllocation;
+
+            if (m_record_iv_length != 0)
+            {
+                Array.Copy(nonce, nonce.Length - m_record_iv_length, output, outputPos, m_record_iv_length);
+                outputPos += m_record_iv_length;
+            }
+
+            short recordType = m_isTlsV13 ? ContentType.application_data : contentType;
+
+            byte[] additionalData = GetAdditionalData(seqNo, recordType, recordVersion, ciphertextLength,
+                plaintextLength);
+
+            try
+            {
+                m_encryptCipher.Init(nonce, m_macSize, additionalData);
+
+                Array.Copy(plaintext, plaintextOffset, output, outputPos, plaintextLength);
+                if (m_isTlsV13)
+                {
+                    output[outputPos + plaintextLength] = (byte)contentType;
+                }
+
+                outputPos += m_encryptCipher.DoFinal(output, outputPos, plaintextLength + extraLength, output,
+                    outputPos);
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+
+            if (outputPos != output.Length)
+            {
+                // NOTE: The additional data mechanism for AEAD ciphers requires exact output size prediction.
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            return new TlsEncodeResult(output, 0, output.Length, recordType);
+        }
+
+        public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
+            byte[] ciphertext, int ciphertextOffset, int ciphertextLength)
+        {
+            if (GetPlaintextLimit(ciphertextLength) < 0)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            byte[] nonce = new byte[m_decryptNonce.Length + m_record_iv_length];
+
+            switch (m_nonceMode)
+            {
+            case NONCE_RFC5288:
+                Array.Copy(m_decryptNonce, 0, nonce, 0, m_decryptNonce.Length);
+                Array.Copy(ciphertext, ciphertextOffset, nonce, nonce.Length - m_record_iv_length,
+                    m_record_iv_length);
+                break;
+            case NONCE_RFC7905:
+                TlsUtilities.WriteUint64(seqNo, nonce, nonce.Length - 8);
+                for (int i = 0; i < m_decryptNonce.Length; ++i)
+                {
+                    nonce[i] ^= m_decryptNonce[i];
+                }
+                break;
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            int encryptionOffset = ciphertextOffset + m_record_iv_length;
+            int encryptionLength = ciphertextLength - m_record_iv_length;
+            int plaintextLength = m_decryptCipher.GetOutputSize(encryptionLength);
+
+            byte[] additionalData = GetAdditionalData(seqNo, recordType, recordVersion, ciphertextLength,
+                plaintextLength);
+
+            int outputPos;
+            try
+            {
+                m_decryptCipher.Init(nonce, m_macSize, additionalData);
+                outputPos = m_decryptCipher.DoFinal(ciphertext, encryptionOffset, encryptionLength, ciphertext,
+                    encryptionOffset);
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac, e);
+            }
+
+            if (outputPos != plaintextLength)
+            {
+                // NOTE: The additional data mechanism for AEAD ciphers requires exact output size prediction.
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+
+            short contentType = recordType;
+            if (m_isTlsV13)
+            {
+                // Strip padding and read true content type from TLSInnerPlaintext
+                int pos = plaintextLength;
+                for (;;)
+                {
+                    if (--pos < 0)
+                        throw new TlsFatalAlert(AlertDescription.unexpected_message);
+
+                    byte octet = ciphertext[encryptionOffset + pos];
+                    if (0 != octet)
+                    {
+                        contentType = (short)(octet & 0xFF);
+                        plaintextLength = pos;
+                        break;
+                    }
+                }
+            }
+
+            return new TlsDecodeResult(ciphertext, encryptionOffset, plaintextLength, contentType);
+        }
+
+        public virtual void RekeyDecoder()
+        {
+            RekeyCipher(m_cryptoParams.SecurityParameters, m_decryptCipher, m_decryptNonce, !m_cryptoParams.IsServer);
+        }
+
+        public virtual void RekeyEncoder()
+        {
+            RekeyCipher(m_cryptoParams.SecurityParameters, m_encryptCipher, m_encryptNonce, m_cryptoParams.IsServer);
+        }
+
+        public virtual bool UsesOpaqueRecordType
+        {
+            get { return m_isTlsV13; }
+        }
+
+        protected virtual byte[] GetAdditionalData(long seqNo, short recordType, ProtocolVersion recordVersion,
+            int ciphertextLength, int plaintextLength)
+        {
+            if (m_isTlsV13)
+            {
+                /*
+                 * TLSCiphertext.opaque_type || TLSCiphertext.legacy_record_version || TLSCiphertext.length
+                 */
+                byte[] additional_data = new byte[5];
+                TlsUtilities.WriteUint8(recordType, additional_data, 0);
+                TlsUtilities.WriteVersion(recordVersion, additional_data, 1);
+                TlsUtilities.WriteUint16(ciphertextLength, additional_data, 3);
+                return additional_data;
+            }
+            else
+            {
+                /*
+                 * seq_num + TLSCompressed.type + TLSCompressed.version + TLSCompressed.length
+                 */
+                byte[] additional_data = new byte[13];
+                TlsUtilities.WriteUint64(seqNo, additional_data, 0);
+                TlsUtilities.WriteUint8(recordType, additional_data, 8);
+                TlsUtilities.WriteVersion(recordVersion, additional_data, 9);
+                TlsUtilities.WriteUint16(plaintextLength, additional_data, 11);
+                return additional_data;
+            }
+        }
+
+        protected virtual void RekeyCipher(SecurityParameters securityParameters, TlsAeadCipherImpl cipher,
+            byte[] nonce, bool serverSecret)
+        {
+            if (!m_isTlsV13)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            TlsSecret secret = serverSecret
+                ?   securityParameters.TrafficSecretServer
+                :   securityParameters.TrafficSecretClient;
+
+            // TODO[tls13] For early data, have to disable server->client
+            if (null == secret)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            Setup13Cipher(cipher, nonce, secret, TlsCryptoUtilities.GetHash(securityParameters.PrfHashAlgorithm));
+        }
+
+        protected virtual void Setup13Cipher(TlsAeadCipherImpl cipher, byte[] nonce, TlsSecret secret,
+            int cryptoHashAlgorithm)
+        {
+            byte[] key = TlsCryptoUtilities.HkdfExpandLabel(secret, cryptoHashAlgorithm, "key",
+                TlsUtilities.EmptyBytes, m_keySize).Extract();
+            byte[] iv = TlsCryptoUtilities.HkdfExpandLabel(secret, cryptoHashAlgorithm, "iv", TlsUtilities.EmptyBytes,
+                m_fixed_iv_length).Extract();
+
+            cipher.SetKey(key, 0, m_keySize);
+            Array.Copy(iv, 0, nonce, 0, m_fixed_iv_length);
+
+            // NOTE: Ensure dummy nonce is not part of the generated sequence(s)
+            iv [0] ^= 0x80;
+            cipher.Init(iv, m_macSize, null);
+        }
+
+        private static int GetNonceMode(bool isTLSv13, int aeadType)
+        {
+            switch (aeadType)
+            {
+            case AEAD_CCM:
+            case AEAD_GCM:
+                return isTLSv13 ? NONCE_RFC7905 : NONCE_RFC5288;
+
+            case AEAD_CHACHA20_POLY1305:
+                return NONCE_RFC7905;
+
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs b/crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs
new file mode 100644
index 000000000..44e6fda84
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs
@@ -0,0 +1,41 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>Base interface for services supporting AEAD encryption/decryption.</summary>
+    public interface TlsAeadCipherImpl
+    {
+        /// <summary>Set the key to be used by the AEAD cipher implementation supporting this service.</summary>
+        /// <param name="key">array holding the AEAD cipher key.</param>
+        /// <param name="keyOff">offset into the array the key starts at.</param>
+        /// <param name="keyLen">length of the key in the array.</param>
+        /// <exception cref="IOException"/>
+        void SetKey(byte[] key, int keyOff, int keyLen);
+
+        /// <summary>Initialise the parameters for the AEAD operator.</summary>
+        /// <param name="nonce">the nonce.</param>
+        /// <param name="macSize">MAC size in bytes.</param>
+        /// <param name="additionalData">any additional data to be included in the MAC calculation.</param>
+        /// <exception cref="IOException">if the parameters are inappropriate.</exception>
+        void Init(byte[] nonce, int macSize, byte[] additionalData);
+
+        /// <summary>Return the maximum size of the output for input of inputLength bytes.</summary>
+        /// <param name="inputLength">the length (in bytes) of the proposed input.</param>
+        /// <returns>the maximum size of the output.</returns>
+        int GetOutputSize(int inputLength);
+
+        /// <summary>Perform the cipher encryption/decryption returning the output in output.</summary>
+        /// <remarks>
+        /// Note: we have to use DoFinal() here as it is the only way to guarantee output from the underlying cipher.
+        /// </remarks>
+        /// <param name="input">array holding input data to the cipher.</param>
+        /// <param name="inputOffset">offset into input array data starts at.</param>
+        /// <param name="inputLength">length of the input data in the array.</param>
+        /// <param name="output">array to hold the cipher output.</param>
+        /// <param name="outputOffset">offset into output array to start saving output.</param>
+        /// <returns>the amount of data written to output.</returns>
+        /// <exception cref="IOException">in case of failure.</exception>
+        int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset);
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsBlockCipher.cs b/crypto/src/tls/crypto/impl/TlsBlockCipher.cs
new file mode 100644
index 000000000..64cfc752a
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsBlockCipher.cs
@@ -0,0 +1,423 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>A generic TLS 1.0-1.2 block cipher. This can be used for AES or 3DES for example.</summary>
+    public class TlsBlockCipher
+        : TlsCipher
+    {
+        protected readonly TlsCryptoParameters m_cryptoParams;
+        protected readonly byte[] m_randomData;
+        protected readonly bool m_encryptThenMac;
+        protected readonly bool m_useExplicitIV;
+        protected readonly bool m_acceptExtraPadding;
+        protected readonly bool m_useExtraPadding;
+
+        protected readonly TlsBlockCipherImpl m_decryptCipher, m_encryptCipher;
+        protected readonly TlsSuiteMac m_readMac, m_writeMac;
+
+        /// <exception cref="IOException"/>
+        public TlsBlockCipher(TlsCryptoParameters cryptoParams, TlsBlockCipherImpl encryptCipher,
+            TlsBlockCipherImpl decryptCipher, TlsHmac clientMac, TlsHmac serverMac, int cipherKeySize)
+        {
+            SecurityParameters securityParameters = cryptoParams.SecurityParameters;
+            ProtocolVersion negotiatedVersion = securityParameters.NegotiatedVersion;
+
+            if (TlsImplUtilities.IsTlsV13(negotiatedVersion))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_cryptoParams = cryptoParams;
+            this.m_randomData = cryptoParams.NonceGenerator.GenerateNonce(256);
+
+            this.m_encryptThenMac = securityParameters.IsEncryptThenMac;
+            this.m_useExplicitIV = TlsImplUtilities.IsTlsV11(negotiatedVersion);
+
+            this.m_acceptExtraPadding = !negotiatedVersion.IsSsl;
+
+            /*
+             * Don't use variable-length padding with truncated MACs.
+             * 
+             * See "Tag Size Does Matter: Attacks and Proofs for the TLS Record Protocol", Paterson,
+             * Ristenpart, Shrimpton.
+             *
+             * TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though)
+             */
+            this.m_useExtraPadding = securityParameters.IsExtendedPadding
+                && ProtocolVersion.TLSv10.IsEqualOrEarlierVersionOf(negotiatedVersion)
+                && (m_encryptThenMac || !securityParameters.IsTruncatedHmac);
+
+            this.m_encryptCipher = encryptCipher;
+            this.m_decryptCipher = decryptCipher;
+
+            TlsBlockCipherImpl clientCipher, serverCipher;
+            if (cryptoParams.IsServer)
+            {
+                clientCipher = decryptCipher;
+                serverCipher = encryptCipher;
+            }
+            else
+            {
+                clientCipher = encryptCipher;
+                serverCipher = decryptCipher;
+            }
+
+            int key_block_size = (2 * cipherKeySize) + clientMac.MacLength + serverMac.MacLength;
+
+            // From TLS 1.1 onwards, block ciphers don't need IVs from the key_block
+            if (!m_useExplicitIV)
+            {
+                key_block_size += clientCipher.GetBlockSize() + serverCipher.GetBlockSize();
+            }
+
+            byte[] key_block = TlsImplUtilities.CalculateKeyBlock(cryptoParams, key_block_size);
+
+            int offset = 0;
+
+            clientMac.SetKey(key_block, offset, clientMac.MacLength);
+            offset += clientMac.MacLength;
+            serverMac.SetKey(key_block, offset, serverMac.MacLength);
+            offset += serverMac.MacLength;
+
+            clientCipher.SetKey(key_block, offset, cipherKeySize);
+            offset += cipherKeySize;
+            serverCipher.SetKey(key_block, offset, cipherKeySize);
+            offset += cipherKeySize;
+
+            int clientIVLength = clientCipher.GetBlockSize();
+            int serverIVLength = serverCipher.GetBlockSize();
+
+            if (m_useExplicitIV)
+            {
+                clientCipher.Init(new byte[clientIVLength], 0, clientIVLength);
+                serverCipher.Init(new byte[serverIVLength], 0, serverIVLength);
+            }
+            else
+            {
+                clientCipher.Init(key_block, offset, clientIVLength);
+                offset += clientIVLength;
+                serverCipher.Init(key_block, offset, serverIVLength);
+                offset += serverIVLength;
+            }
+
+            if (offset != key_block_size)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (cryptoParams.IsServer)
+            {
+                this.m_writeMac = new TlsSuiteHmac(cryptoParams, serverMac);
+                this.m_readMac = new TlsSuiteHmac(cryptoParams, clientMac);
+            }
+            else
+            {
+                this.m_writeMac = new TlsSuiteHmac(cryptoParams, clientMac);
+                this.m_readMac = new TlsSuiteHmac(cryptoParams, serverMac);
+            }
+        }
+
+        public virtual int GetCiphertextDecodeLimit(int plaintextLimit)
+        {
+            int blockSize = m_decryptCipher.GetBlockSize();
+            int macSize = m_readMac.Size;
+            int maxPadding = 256;
+
+            return GetCiphertextLength(blockSize, macSize, maxPadding, plaintextLimit);
+        }
+
+        public virtual int GetCiphertextEncodeLimit(int plaintextLength, int plaintextLimit)
+        {
+            int blockSize = m_encryptCipher.GetBlockSize();
+            int macSize = m_writeMac.Size;
+            int maxPadding = m_useExtraPadding ? 256 : blockSize;
+
+            return GetCiphertextLength(blockSize, macSize, maxPadding, plaintextLength);
+        }
+
+        public virtual int GetPlaintextLimit(int ciphertextLimit)
+        {
+            int blockSize = m_encryptCipher.GetBlockSize();
+            int macSize = m_writeMac.Size;
+
+            int plaintextLimit = ciphertextLimit;
+
+            // Leave room for the MAC, and require block-alignment
+            if (m_encryptThenMac)
+            {
+                plaintextLimit -= macSize;
+                plaintextLimit -= plaintextLimit % blockSize;
+            }
+            else
+            {
+                plaintextLimit -= plaintextLimit % blockSize;
+                plaintextLimit -= macSize;
+            }
+
+            // Minimum 1 byte of padding
+            --plaintextLimit;
+
+            // An explicit IV consumes 1 block
+            if (m_useExplicitIV)
+            {
+                plaintextLimit -= blockSize;
+            }
+
+            return plaintextLimit;
+        }
+
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, byte[] plaintext, int offset, int len)
+        {
+            int blockSize = m_encryptCipher.GetBlockSize();
+            int macSize = m_writeMac.Size;
+
+            int enc_input_length = len;
+            if (!m_encryptThenMac)
+            {
+                enc_input_length += macSize;
+            }
+
+            int padding_length = blockSize - (enc_input_length % blockSize);
+            if (m_useExtraPadding)
+            {
+                // Add a random number of extra blocks worth of padding
+                int maxExtraPadBlocks = (256 - padding_length) / blockSize;
+                int actualExtraPadBlocks = ChooseExtraPadBlocks(maxExtraPadBlocks);
+                padding_length += actualExtraPadBlocks * blockSize;
+            }
+
+            int totalSize = len + macSize + padding_length;
+            if (m_useExplicitIV)
+            {
+                totalSize += blockSize;
+            }
+
+            byte[] outBuf = new byte[headerAllocation + totalSize];
+            int outOff = headerAllocation;
+
+            if (m_useExplicitIV)
+            {
+                // Technically the explicit IV will be the encryption of this nonce
+                byte[] explicitIV = m_cryptoParams.NonceGenerator.GenerateNonce(blockSize);
+                Array.Copy(explicitIV, 0, outBuf, outOff, blockSize);
+                outOff += blockSize;
+            }
+
+            Array.Copy(plaintext, offset, outBuf, outOff, len);
+            outOff += len;
+
+            if (!m_encryptThenMac)
+            {
+                byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, plaintext, offset, len);
+                Array.Copy(mac, 0, outBuf, outOff, mac.Length);
+                outOff += mac.Length;
+            }
+
+            byte padByte = (byte)(padding_length - 1);
+            for (int i = 0; i < padding_length; ++i)
+            {
+                outBuf[outOff++] = padByte;
+            }
+
+            m_encryptCipher.DoFinal(outBuf, headerAllocation, outOff - headerAllocation, outBuf, headerAllocation);
+
+            if (m_encryptThenMac)
+            {
+                byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, outBuf, headerAllocation,
+                    outOff - headerAllocation);
+                Array.Copy(mac, 0, outBuf, outOff, mac.Length);
+                outOff += mac.Length;
+            }
+
+            if (outOff != outBuf.Length)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            return new TlsEncodeResult(outBuf, 0, outBuf.Length, contentType);
+        }
+
+        public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
+            byte[] ciphertext, int offset, int len)
+        {
+            int blockSize = m_decryptCipher.GetBlockSize();
+            int macSize = m_readMac.Size;
+
+            int minLen = blockSize;
+            if (m_encryptThenMac)
+            {
+                minLen += macSize;
+            }
+            else
+            {
+                minLen = System.Math.Max(minLen, macSize + 1);
+            }
+
+            if (m_useExplicitIV)
+            {
+                minLen += blockSize;
+            }
+
+            if (len < minLen)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int blocks_length = len;
+            if (m_encryptThenMac)
+            {
+                blocks_length -= macSize;
+            }
+
+            if (blocks_length % blockSize != 0)
+                throw new TlsFatalAlert(AlertDescription.decryption_failed);
+
+            if (m_encryptThenMac)
+            {
+                byte[] expectedMac = m_readMac.CalculateMac(seqNo, recordType, ciphertext, offset, len - macSize);
+
+                bool checkMac = TlsUtilities.ConstantTimeAreEqual(macSize, expectedMac, 0, ciphertext,
+                    offset + len - macSize);
+                if (!checkMac)
+                {
+                    /*
+                     * RFC 7366 3. The MAC SHALL be evaluated before any further processing such as
+                     * decryption is performed, and if the MAC verification fails, then processing SHALL
+                     * terminate immediately. For TLS, a fatal bad_record_mac MUST be generated [2]. For
+                     * DTLS, the record MUST be discarded, and a fatal bad_record_mac MAY be generated
+                     * [4]. This immediate response to a bad MAC eliminates any timing channels that may
+                     * be available through the use of manipulated packet data.
+                     */
+                    throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+                }
+            }
+
+            m_decryptCipher.DoFinal(ciphertext, offset, blocks_length, ciphertext, offset);
+
+            if (m_useExplicitIV)
+            {
+                offset += blockSize;
+                blocks_length -= blockSize;
+            }
+
+            // If there's anything wrong with the padding, this will return zero
+            int totalPad = CheckPaddingConstantTime(ciphertext, offset, blocks_length, blockSize,
+                m_encryptThenMac ? 0 : macSize);
+            bool badMac = (totalPad == 0);
+
+            int dec_output_length = blocks_length - totalPad;
+
+            if (!m_encryptThenMac)
+            {
+                dec_output_length -= macSize;
+
+                byte[] expectedMac = m_readMac.CalculateMacConstantTime(seqNo, recordType, ciphertext, offset,
+                    dec_output_length, blocks_length - macSize, m_randomData);
+
+                badMac |= !TlsUtilities.ConstantTimeAreEqual(macSize, expectedMac, 0, ciphertext,
+                    offset + dec_output_length);
+            }
+
+            if (badMac)
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+
+            return new TlsDecodeResult(ciphertext, offset, dec_output_length, recordType);
+        }
+
+        public virtual void RekeyDecoder()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual void RekeyEncoder()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual bool UsesOpaqueRecordType
+        {
+            get { return false; }
+        }
+
+        protected virtual int CheckPaddingConstantTime(byte[] buf, int off, int len, int blockSize, int macSize)
+        {
+            int end = off + len;
+            byte lastByte = buf[end - 1];
+            int padlen = lastByte & 0xff;
+            int totalPad = padlen + 1;
+
+            int dummyIndex = 0;
+            byte padDiff = 0;
+
+            int totalPadLimit = System.Math.Min(m_acceptExtraPadding ? 256 : blockSize, len - macSize);
+
+            if (totalPad > totalPadLimit)
+            {
+                totalPad = 0;
+            }
+            else
+            {
+                int padPos = end - totalPad;
+                do
+                {
+                    padDiff |= (byte)(buf[padPos++] ^ lastByte);
+                }
+                while (padPos < end);
+
+                dummyIndex = totalPad;
+
+                if (padDiff != 0)
+                {
+                    totalPad = 0;
+                }
+            }
+
+            // Run some extra dummy checks so the number of checks is always constant
+            {
+                byte[] dummyPad = m_randomData;
+                while (dummyIndex < 256)
+                {
+                    padDiff |= (byte)(dummyPad[dummyIndex++] ^ lastByte);
+                }
+                // Ensure the above loop is not eliminated
+                dummyPad[0] ^= padDiff;
+            }
+
+            return totalPad;
+        }
+
+        protected virtual int ChooseExtraPadBlocks(int max)
+        {
+            byte[] random = m_cryptoParams.NonceGenerator.GenerateNonce(4);
+            int x = (int)Pack.LE_To_UInt32(random, 0);
+            int n = Integers.NumberOfTrailingZeros(x);
+            return System.Math.Min(n, max);
+        }
+
+        protected virtual int GetCiphertextLength(int blockSize, int macSize, int maxPadding, int plaintextLength)
+        {
+            int ciphertextLength = plaintextLength;
+
+            // An explicit IV consumes 1 block
+            if (m_useExplicitIV)
+            {
+                ciphertextLength += blockSize;
+            }
+
+            // Leave room for the MAC and (block-aligning) padding
+
+            ciphertextLength += maxPadding;
+
+            if (m_encryptThenMac)
+            {
+                ciphertextLength -= (ciphertextLength % blockSize);
+                ciphertextLength += macSize;
+            }
+            else
+            {
+                ciphertextLength += macSize;
+                ciphertextLength -= (ciphertextLength % blockSize);
+            }
+
+            return ciphertextLength;
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs b/crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs
new file mode 100644
index 000000000..7df2ed70f
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs
@@ -0,0 +1,40 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>Interface for block cipher services.</summary>
+    public interface TlsBlockCipherImpl
+    {
+        /// <summary>Set the key to be used by the block cipher implementation supporting this service.</summary>
+        /// <param name="key">array holding the block cipher key.</param>
+        /// <param name="keyOff">offset into the array the key starts at.</param>
+        /// <param name="keyLen">length of the key in the array.</param>
+        /// <exception cref="IOException"/>
+        void SetKey(byte[] key, int keyOff, int keyLen);
+
+        /// <summary>Initialise the parameters for operator.</summary>
+        /// <param name="iv">array holding the initialization vector (IV).</param>
+        /// <param name="ivOff">offset into the array the IV starts at.</param>
+        /// <param name="ivLen">length of the IV in the array.</param>
+        /// <exception cref="IOException">if the parameters are inappropriate.</exception>
+        void Init(byte[] iv, int ivOff, int ivLen);
+
+        /// <summary>Perform the cipher encryption/decryption returning the output in output.</summary>
+        /// <remarks>
+        /// Note: we have to use DoFinal() here as it is the only way to guarantee output from the underlying cipher.
+        /// </remarks>
+        /// <param name="input">array holding input data to the cipher.</param>
+        /// <param name="inputOffset">offset into input array data starts at.</param>
+        /// <param name="inputLength">length of the input data in the array.</param>
+        /// <param name="output">array to hold the cipher output.</param>
+        /// <param name="outputOffset">offset into output array to start saving output.</param>
+        /// <returns>the amount of data written to output.</returns>
+        /// <exception cref="IOException">in case of failure.</exception>
+        int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset);
+
+        /// <summary>Return the blocksize (in bytes) of the underlying block cipher.</summary>
+        /// <returns>the cipher's blocksize.</returns>
+        int GetBlockSize();
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsEncryptor.cs b/crypto/src/tls/crypto/impl/TlsEncryptor.cs
new file mode 100644
index 000000000..6e4ef0c44
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsEncryptor.cs
@@ -0,0 +1,17 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>Base interface for an encryptor based on a public key.</summary>
+    public interface TlsEncryptor
+    {
+        /// <summary>Encrypt data from the passed in input array.</summary>
+        /// <param name="input">byte array containing the input data.</param>
+        /// <param name="inOff">offset into input where the data starts.</param>
+        /// <param name="length">the length of the data to encrypt.</param>
+        /// <returns>the encrypted data.</returns>
+        /// <exception cref="IOException"/>
+        byte[] Encrypt(byte[] input, int inOff, int length);
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsImplUtilities.cs b/crypto/src/tls/crypto/impl/TlsImplUtilities.cs
new file mode 100644
index 000000000..db936e6b7
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsImplUtilities.cs
@@ -0,0 +1,75 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>Useful utility methods.</summary>
+    public abstract class TlsImplUtilities
+    {
+        public static bool IsSsl(TlsCryptoParameters cryptoParams)
+        {
+            return cryptoParams.ServerVersion.IsSsl;
+        }
+
+        public static bool IsTlsV10(ProtocolVersion version)
+        {
+            return ProtocolVersion.TLSv10.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static bool IsTlsV10(TlsCryptoParameters cryptoParams)
+        {
+            return IsTlsV10(cryptoParams.ServerVersion);
+        }
+
+        public static bool IsTlsV11(ProtocolVersion version)
+        {
+            return ProtocolVersion.TLSv11.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static bool IsTlsV11(TlsCryptoParameters cryptoParams)
+        {
+            return IsTlsV11(cryptoParams.ServerVersion);
+        }
+
+        public static bool IsTlsV12(ProtocolVersion version)
+        {
+            return ProtocolVersion.TLSv12.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static bool IsTlsV12(TlsCryptoParameters cryptoParams)
+        {
+            return IsTlsV12(cryptoParams.ServerVersion);
+        }
+
+        public static bool IsTlsV13(ProtocolVersion version)
+        {
+            return ProtocolVersion.TLSv13.IsEqualOrEarlierVersionOf(version.GetEquivalentTlsVersion());
+        }
+
+        public static bool IsTlsV13(TlsCryptoParameters cryptoParams)
+        {
+            return IsTlsV13(cryptoParams.ServerVersion);
+        }
+
+        public static byte[] CalculateKeyBlock(TlsCryptoParameters cryptoParams, int length)
+        {
+            SecurityParameters securityParameters = cryptoParams.SecurityParameters;
+            TlsSecret master_secret = securityParameters.MasterSecret;
+            byte[] seed = Arrays.Concatenate(securityParameters.ServerRandom, securityParameters.ClientRandom);
+            return Prf(securityParameters, master_secret, ExporterLabel.key_expansion, seed, length).Extract();
+        }
+
+        public static TlsSecret Prf(SecurityParameters securityParameters, TlsSecret secret, string asciiLabel,
+            byte[] seed, int length)
+        {
+            return secret.DeriveUsingPrf(securityParameters.PrfAlgorithm, asciiLabel, seed, length);
+        }
+
+        public static TlsSecret Prf(TlsCryptoParameters cryptoParams, TlsSecret secret, string asciiLabel, byte[] seed,
+            int length)
+        {
+            return Prf(cryptoParams.SecurityParameters, secret, asciiLabel, seed, length);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsNullCipher.cs b/crypto/src/tls/crypto/impl/TlsNullCipher.cs
new file mode 100644
index 000000000..3ca4951a6
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsNullCipher.cs
@@ -0,0 +1,104 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>The NULL cipher.</summary>
+    public class TlsNullCipher
+        : TlsCipher
+    {
+        protected readonly TlsCryptoParameters m_cryptoParams;
+        protected readonly TlsSuiteHmac m_readMac, m_writeMac;
+
+        /// <exception cref="IOException"/>
+        public TlsNullCipher(TlsCryptoParameters cryptoParams, TlsHmac clientMac, TlsHmac serverMac)
+        {
+            if (TlsImplUtilities.IsTlsV13(cryptoParams))
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            this.m_cryptoParams = cryptoParams;
+
+            int key_block_size = clientMac.MacLength + serverMac.MacLength;
+            byte[] key_block = TlsImplUtilities.CalculateKeyBlock(cryptoParams, key_block_size);
+
+            int offset = 0;
+
+            clientMac.SetKey(key_block, offset, clientMac.MacLength);
+            offset += clientMac.MacLength;
+            serverMac.SetKey(key_block, offset, serverMac.MacLength);
+            offset += serverMac.MacLength;
+
+            if (offset != key_block_size)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (cryptoParams.IsServer)
+            {
+                this.m_writeMac = new TlsSuiteHmac(cryptoParams, serverMac);
+                this.m_readMac = new TlsSuiteHmac(cryptoParams, clientMac);
+            }
+            else
+            {
+                this.m_writeMac = new TlsSuiteHmac(cryptoParams, clientMac);
+                this.m_readMac = new TlsSuiteHmac(cryptoParams, serverMac);
+            }
+        }
+
+        public virtual int GetCiphertextDecodeLimit(int plaintextLimit)
+        {
+            return plaintextLimit + m_writeMac.Size;
+        }
+
+        public virtual int GetCiphertextEncodeLimit(int plaintextLength, int plaintextLimit)
+        {
+            return plaintextLength + m_writeMac.Size;
+        }
+
+        public virtual int GetPlaintextLimit(int ciphertextLimit)
+        {
+            return ciphertextLimit - m_writeMac.Size;
+        }
+
+        public virtual TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion,
+            int headerAllocation, byte[] plaintext, int offset, int len)
+        {
+            byte[] mac = m_writeMac.CalculateMac(seqNo, contentType, plaintext, offset, len);
+            byte[] ciphertext = new byte[headerAllocation + len + mac.Length];
+            Array.Copy(plaintext, offset, ciphertext, headerAllocation, len);
+            Array.Copy(mac, 0, ciphertext, headerAllocation + len, mac.Length);
+            return new TlsEncodeResult(ciphertext, 0, ciphertext.Length, contentType);
+        }
+
+        public virtual TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion,
+            byte[] ciphertext, int offset, int len)
+        {
+            int macSize = m_readMac.Size;
+            if (len < macSize)
+                throw new TlsFatalAlert(AlertDescription.decode_error);
+
+            int macInputLen = len - macSize;
+
+            byte[] expectedMac = m_readMac.CalculateMac(seqNo, recordType, ciphertext, offset, macInputLen);
+
+            bool badMac = !TlsUtilities.ConstantTimeAreEqual(macSize, expectedMac, 0, ciphertext, offset + macInputLen);
+            if (badMac)
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+
+            return new TlsDecodeResult(ciphertext, offset, macInputLen, recordType);
+        }
+
+        public virtual void RekeyDecoder()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual void RekeyEncoder()
+        {
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        public virtual bool UsesOpaqueRecordType
+        {
+            get { return false; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs b/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs
new file mode 100644
index 000000000..9f43f4382
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsSuiteHmac.cs
@@ -0,0 +1,121 @@
+using System;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest.</summary>
+    public class TlsSuiteHmac
+        : TlsSuiteMac
+    {
+        protected static int GetMacSize(TlsCryptoParameters cryptoParams, TlsMac mac)
+        {
+            int macSize = mac.MacLength;
+            if (cryptoParams.SecurityParameters.IsTruncatedHmac)
+            {
+                macSize = System.Math.Min(macSize, 10);
+            }
+            return macSize;
+        }
+
+        protected readonly TlsCryptoParameters m_cryptoParams;
+        protected readonly TlsHmac m_mac;
+        protected readonly int m_digestBlockSize;
+        protected readonly int m_digestOverhead;
+        protected readonly int m_macSize;
+
+        /// <summary>Generate a new instance of a TlsMac.</summary>
+        /// <param name="cryptoParams">the TLS client context specific crypto parameters.</param>
+        /// <param name="mac">The MAC to use.</param>
+        public TlsSuiteHmac(TlsCryptoParameters cryptoParams, TlsHmac mac)
+        {
+            this.m_cryptoParams = cryptoParams;
+            this.m_mac = mac;
+            this.m_macSize = GetMacSize(cryptoParams, mac);
+            this.m_digestBlockSize = mac.InternalBlockSize;
+
+            // TODO This should check the actual algorithm, not assume based on the digest size
+            if (TlsImplUtilities.IsSsl(cryptoParams) && mac.MacLength == 20)
+            {
+                /*
+                 * NOTE: For the SSL 3.0 MAC with SHA-1, the secret + input pad is not block-aligned.
+                 */
+                this.m_digestOverhead = 4;
+            }
+            else
+            {
+                this.m_digestOverhead = m_digestBlockSize / 8;
+            }
+        }
+
+        public virtual int Size
+        {
+            get { return m_macSize; }
+        }
+
+        public virtual byte[] CalculateMac(long seqNo, short type, byte[] msg, int msgOff, int msgLen)
+        {
+            ProtocolVersion serverVersion = m_cryptoParams.ServerVersion;
+            bool isSsl = serverVersion.IsSsl;
+
+            byte[] macHeader = new byte[isSsl ? 11 : 13];
+            TlsUtilities.WriteUint64(seqNo, macHeader, 0);
+            TlsUtilities.WriteUint8(type, macHeader, 8);
+            if (!isSsl)
+            {
+                TlsUtilities.WriteVersion(serverVersion, macHeader, 9);
+            }
+            TlsUtilities.WriteUint16(msgLen, macHeader, macHeader.Length - 2);
+
+            m_mac.Update(macHeader, 0, macHeader.Length);
+            m_mac.Update(msg, msgOff, msgLen);
+
+            return Truncate(m_mac.CalculateMac());
+        }
+
+        public virtual byte[] CalculateMacConstantTime(long seqNo, short type, byte[] msg, int msgOff, int msgLen,
+            int fullLength, byte[] dummyData)
+        {
+            /*
+             * Actual MAC only calculated on 'length' bytes...
+             */
+            byte[] result = CalculateMac(seqNo, type, msg, msgOff, msgLen);
+
+            /*
+             * ...but ensure a constant number of complete digest blocks are processed (as many as would
+             * be needed for 'fullLength' bytes of input).
+             */
+            int headerLength = TlsImplUtilities.IsSsl(m_cryptoParams) ? 11 : 13;
+
+            // How many extra full blocks do we need to calculate?
+            int extra = GetDigestBlockCount(headerLength + fullLength) - GetDigestBlockCount(headerLength + msgLen);
+
+            while (--extra >= 0)
+            {
+                m_mac.Update(dummyData, 0, m_digestBlockSize);
+            }
+
+            // One more byte in case the implementation is "lazy" about processing blocks
+            m_mac.Update(dummyData, 0, 1);
+            m_mac.Reset();
+
+            return result;
+        }
+
+        protected virtual int GetDigestBlockCount(int inputLength)
+        {
+            // NOTE: The input pad for HMAC is always a full digest block
+
+            // NOTE: This calculation assumes a minimum of 1 pad byte
+            return (inputLength + m_digestOverhead) / m_digestBlockSize;
+        }
+
+        protected virtual byte[] Truncate(byte[] bs)
+        {
+            if (bs.Length <= m_macSize)
+                return bs;
+
+            return Arrays.CopyOf(bs, m_macSize);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/TlsSuiteMac.cs b/crypto/src/tls/crypto/impl/TlsSuiteMac.cs
new file mode 100644
index 000000000..6e4942928
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/TlsSuiteMac.cs
@@ -0,0 +1,33 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl
+{
+    /// <summary>Base interface for a generic TLS MAC implementation for use with a bulk cipher.</summary>
+    public interface TlsSuiteMac
+    {
+        /// <summary>Return the output length (in bytes) of this MAC.</summary>
+        /// <returns>The output length of this MAC.</returns>
+        int Size { get; }
+
+        /// <summary>Calculate the MAC for some given data.</summary>
+        /// <param name="seqNo">The sequence number of the record.</param>
+        /// <param name="type">The content type of the message.</param>
+        /// <param name="message">A byte array containing the message.</param>
+        /// <param name="offset">The number of bytes to skip, before the message starts.</param>
+        /// <param name="length">The length of the message.</param>
+        /// <returns>A new byte array containing the MAC value.</returns>
+        byte[] CalculateMac(long seqNo, short type, byte[] message, int offset, int length);
+
+        /// <summary>Constant time calculation of the MAC for some given data with a given expected length.</summary>
+        /// <param name="seqNo">The sequence number of the record.</param>
+        /// <param name="type">The content type of the message.</param>
+        /// <param name="message">A byte array containing the message.</param>
+        /// <param name="offset">The number of bytes to skip, before the message starts.</param>
+        /// <param name="length">The length of the message.</param>
+        /// <param name="expectedLength">The expected length of the full message.</param>
+        /// <param name="randomData">Random data for padding out the MAC calculation if required.</param>
+        /// <returns>A new byte array containing the MAC value.</returns>
+        byte[] CalculateMacConstantTime(long seqNo, short type, byte[] message, int offset, int length,
+            int expectedLength, byte[] randomData);
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs b/crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs
new file mode 100644
index 000000000..8d801ed7a
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs
@@ -0,0 +1,124 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public sealed class BcChaCha20Poly1305
+        : TlsAeadCipherImpl
+    {
+        private static readonly byte[] Zeroes = new byte[15];
+
+        private readonly ChaCha7539Engine m_cipher = new ChaCha7539Engine();
+        private readonly Poly1305 m_mac = new Poly1305();
+
+        private readonly bool m_isEncrypting;
+
+        private int m_additionalDataLength;
+
+        public BcChaCha20Poly1305(bool isEncrypting)
+        {
+            this.m_isEncrypting = isEncrypting;
+        }
+
+        public int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset)
+        {
+            if (m_isEncrypting)
+            {
+                int ciphertextLength = inputLength;
+
+                m_cipher.ProcessBytes(input, inputOffset, inputLength, output, outputOffset);
+                int outputLength = inputLength;
+
+                if (ciphertextLength != outputLength)
+                    throw new InvalidOperationException();
+
+                UpdateMac(output, outputOffset, ciphertextLength);
+
+                byte[] lengths = new byte[16];
+                Pack.UInt64_To_LE((ulong)m_additionalDataLength, lengths, 0);
+                Pack.UInt64_To_LE((ulong)ciphertextLength, lengths, 8);
+                m_mac.BlockUpdate(lengths, 0, 16);
+
+                m_mac.DoFinal(output, outputOffset + ciphertextLength);
+
+                return ciphertextLength + 16;
+            }
+            else
+            {
+                int ciphertextLength = inputLength - 16;
+
+                UpdateMac(input, inputOffset, ciphertextLength);
+
+                byte[] expectedMac = new byte[16];
+                Pack.UInt64_To_LE((ulong)m_additionalDataLength, expectedMac, 0);
+                Pack.UInt64_To_LE((ulong)ciphertextLength, expectedMac, 8);
+                m_mac.BlockUpdate(expectedMac, 0, 16);
+                m_mac.DoFinal(expectedMac, 0);
+
+                bool badMac = !TlsUtilities.ConstantTimeAreEqual(16, expectedMac, 0, input, inputOffset + ciphertextLength);
+                if (badMac)
+                    throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+
+                m_cipher.ProcessBytes(input, inputOffset, ciphertextLength, output, outputOffset);
+                int outputLength = ciphertextLength;
+
+                if (ciphertextLength != outputLength)
+                    throw new InvalidOperationException();
+
+                return ciphertextLength;
+            }
+        }
+
+        public int GetOutputSize(int inputLength)
+        {
+            return m_isEncrypting ? inputLength + 16 : inputLength - 16;
+        }
+
+        public void Init(byte[] nonce, int macSize, byte[] additionalData)
+        {
+            if (nonce == null || nonce.Length != 12 || macSize != 16)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            m_cipher.Init(m_isEncrypting, new ParametersWithIV(null, nonce));
+            InitMac();
+            if (additionalData == null)
+            {
+                this.m_additionalDataLength = 0;
+            }
+            else
+            {
+                this.m_additionalDataLength = additionalData.Length;
+                UpdateMac(additionalData, 0, additionalData.Length);
+            }
+        }
+
+        public void SetKey(byte[] key, int keyOff, int keyLen)
+        {
+            KeyParameter cipherKey = new KeyParameter(key, keyOff, keyLen);
+            m_cipher.Init(m_isEncrypting, new ParametersWithIV(cipherKey, Zeroes, 0, 12));
+        }
+
+        private void InitMac()
+        {
+            byte[] firstBlock = new byte[64];
+            m_cipher.ProcessBytes(firstBlock, 0, 64, firstBlock, 0);
+            m_mac.Init(new KeyParameter(firstBlock, 0, 32));
+            Array.Clear(firstBlock, 0, firstBlock.Length);
+        }
+
+        private void UpdateMac(byte[] buf, int off, int len)
+        {
+            m_mac.BlockUpdate(buf, off, len);
+
+            int partial = len % 16;
+            if (partial != 0)
+            {
+                m_mac.BlockUpdate(Zeroes, 0, 16 - partial);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedAgreement.cs b/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedAgreement.cs
new file mode 100644
index 000000000..15944cd89
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedAgreement.cs
@@ -0,0 +1,112 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summay>Credentialed class generating agreed secrets from a peer's public key for our end of the TLS connection
+    /// using the BC light-weight API.</summay>
+    public class BcDefaultTlsCredentialedAgreement
+        : TlsCredentialedAgreement
+    {
+        protected readonly TlsCredentialedAgreement m_agreementCredentials;
+
+        public BcDefaultTlsCredentialedAgreement(BcTlsCrypto crypto, Certificate certificate,
+            AsymmetricKeyParameter privateKey)
+        {
+            if (crypto == null)
+                throw new ArgumentNullException("crypto");
+            if (certificate == null)
+                throw new ArgumentNullException("certificate");
+            if (certificate.IsEmpty)
+                throw new ArgumentException("cannot be empty", "certificate");
+            if (privateKey == null)
+                throw new ArgumentNullException("privateKey");
+            if (!privateKey.IsPrivate)
+                throw new ArgumentException("must be private", "privateKey");
+
+            if (privateKey is DHPrivateKeyParameters)
+            {
+                this.m_agreementCredentials = new DHCredentialedAgreement(crypto, certificate,
+                    (DHPrivateKeyParameters)privateKey);
+            }
+            else if (privateKey is ECPrivateKeyParameters)
+            {
+                this.m_agreementCredentials = new ECCredentialedAgreement(crypto, certificate,
+                    (ECPrivateKeyParameters)privateKey);
+            }
+            else
+            {
+                throw new ArgumentException("'privateKey' type not supported: " + Platform.GetTypeName(privateKey));
+            }
+        }
+
+        public virtual Certificate Certificate
+        {
+            get { return m_agreementCredentials.Certificate; }
+        }
+
+        public virtual TlsSecret GenerateAgreement(TlsCertificate peerCertificate)
+        {
+            return m_agreementCredentials.GenerateAgreement(peerCertificate);
+        }
+
+        private sealed class DHCredentialedAgreement
+            : TlsCredentialedAgreement
+        {
+            private readonly BcTlsCrypto m_crypto;
+            private readonly Certificate m_certificate;
+            private readonly DHPrivateKeyParameters m_privateKey;
+
+            internal DHCredentialedAgreement(BcTlsCrypto crypto, Certificate certificate,
+                DHPrivateKeyParameters privateKey)
+            {
+                this.m_crypto = crypto;
+                this.m_certificate = certificate;
+                this.m_privateKey = privateKey;
+            }
+
+            public TlsSecret GenerateAgreement(TlsCertificate peerCertificate)
+            {
+                BcTlsCertificate bcCert = BcTlsCertificate.Convert(m_crypto, peerCertificate);
+                DHPublicKeyParameters peerPublicKey = bcCert.GetPubKeyDH();
+                return BcTlsDHDomain.CalculateDHAgreement(m_crypto, m_privateKey, peerPublicKey, false);
+            }
+
+            public Certificate Certificate
+            {
+                get { return m_certificate; }
+            }
+        }
+
+        private sealed class ECCredentialedAgreement
+            : TlsCredentialedAgreement
+        {
+            private readonly BcTlsCrypto m_crypto;
+            private readonly Certificate m_certificate;
+            private readonly ECPrivateKeyParameters m_privateKey;
+
+            internal ECCredentialedAgreement(BcTlsCrypto crypto, Certificate certificate,
+                ECPrivateKeyParameters privateKey)
+            {
+                this.m_crypto = crypto;
+                this.m_certificate = certificate;
+                this.m_privateKey = privateKey;
+            }
+
+            public TlsSecret GenerateAgreement(TlsCertificate peerCertificate)
+            {
+                BcTlsCertificate bcCert = BcTlsCertificate.Convert(m_crypto, peerCertificate);
+                ECPublicKeyParameters peerPublicKey = bcCert.GetPubKeyEC();
+                return BcTlsECDomain.CalculateBasicAgreement(m_crypto, m_privateKey, peerPublicKey);
+            }
+
+            public Certificate Certificate
+            {
+                get { return m_certificate; }
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedDecryptor.cs b/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedDecryptor.cs
new file mode 100644
index 000000000..b0e9f125e
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedDecryptor.cs
@@ -0,0 +1,139 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Encodings;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Credentialed class decrypting RSA encrypted secrets sent from a peer for our end of the TLS connection
+    /// using the BC light-weight API.</summary>
+    public class BcDefaultTlsCredentialedDecryptor
+        : TlsCredentialedDecryptor
+    {
+        protected readonly BcTlsCrypto m_crypto;
+        protected readonly Certificate m_certificate;
+        protected readonly AsymmetricKeyParameter m_privateKey;
+
+        public BcDefaultTlsCredentialedDecryptor(BcTlsCrypto crypto, Certificate certificate,
+            AsymmetricKeyParameter privateKey)
+        {
+            if (crypto == null)
+                throw new ArgumentNullException("crypto");
+            if (certificate == null)
+                throw new ArgumentNullException("certificate");
+            if (certificate.IsEmpty)
+                throw new ArgumentException("cannot be empty", "certificate");
+            if (privateKey == null)
+                throw new ArgumentNullException("privateKey");
+            if (!privateKey.IsPrivate)
+                throw new ArgumentException("must be private", "privateKey");
+
+            if (privateKey is RsaKeyParameters)
+            {
+            }
+            else
+            {
+                throw new ArgumentException("'privateKey' type not supported: " + Platform.GetTypeName(privateKey));
+            }
+
+            this.m_crypto = crypto;
+            this.m_certificate = certificate;
+            this.m_privateKey = privateKey;
+        }
+
+        public virtual Certificate Certificate
+        {
+            get { return m_certificate; }
+        }
+
+        public virtual TlsSecret Decrypt(TlsCryptoParameters cryptoParams, byte[] ciphertext)
+        {
+            // TODO Keep only the decryption itself here - move error handling outside 
+            return SafeDecryptPreMasterSecret(cryptoParams, (RsaKeyParameters)m_privateKey, ciphertext);
+        }
+
+        /*
+         * TODO[tls-ops] Probably need to make RSA encryption/decryption into TlsCrypto functions so
+         * that users can implement "generic" encryption credentials externally
+         */
+        protected virtual TlsSecret SafeDecryptPreMasterSecret(TlsCryptoParameters cryptoParams,
+            RsaKeyParameters rsaServerPrivateKey, byte[] encryptedPreMasterSecret)
+        {
+            SecureRandom secureRandom = m_crypto.SecureRandom;
+
+            /*
+             * RFC 5246 7.4.7.1.
+             */
+            ProtocolVersion expectedVersion = cryptoParams.RsaPreMasterSecretVersion;
+
+            // TODO Provide as configuration option?
+            bool versionNumberCheckDisabled = false;
+
+            /*
+             * Generate 48 random bytes we can use as a Pre-Master-Secret, if the
+             * PKCS1 padding check should fail.
+             */
+            byte[] fallback = new byte[48];
+            secureRandom.NextBytes(fallback);
+
+            byte[] M = Arrays.Clone(fallback);
+            try
+            {
+                Pkcs1Encoding encoding = new Pkcs1Encoding(new RsaBlindedEngine(), fallback);
+                encoding.Init(false, new ParametersWithRandom(rsaServerPrivateKey, secureRandom));
+
+                M = encoding.ProcessBlock(encryptedPreMasterSecret, 0, encryptedPreMasterSecret.Length);
+            }
+            catch (Exception)
+            {
+                /*
+                 * This should never happen since the decryption should never throw an exception
+                 * and return a random value instead.
+                 *
+                 * In any case, a TLS server MUST NOT generate an alert if processing an
+                 * RSA-encrypted premaster secret message fails, or the version number is not as
+                 * expected. Instead, it MUST continue the handshake with a randomly generated
+                 * premaster secret.
+                 */
+            }
+
+            /*
+             * If ClientHello.legacy_version is TLS 1.1 or higher, server implementations MUST check the
+             * version number [..].
+             */
+            if (versionNumberCheckDisabled && !TlsImplUtilities.IsTlsV11(expectedVersion))
+            {
+                /*
+                 * If the version number is TLS 1.0 or earlier, server implementations SHOULD check the
+                 * version number, but MAY have a configuration option to disable the check.
+                 */
+            }
+            else
+            {
+                /*
+                 * Compare the version number in the decrypted Pre-Master-Secret with the legacy_version
+                 * field from the ClientHello. If they don't match, continue the handshake with the
+                 * randomly generated 'fallback' value.
+                 *
+                 * NOTE: The comparison and replacement must be constant-time.
+                 */
+                int mask = (expectedVersion.MajorVersion ^ (M[0] & 0xFF))
+                         | (expectedVersion.MinorVersion ^ (M[1] & 0xFF));
+
+                // 'mask' will be all 1s if the versions matched, or else all 0s.
+                mask = (mask - 1) >> 31;
+
+                for (int i = 0; i < 48; i++)
+                {
+                    M[i] = (byte)((M[i] & mask) | (fallback[i] & ~mask));
+                }
+            }
+
+            return m_crypto.CreateSecret(M);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedSigner.cs b/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedSigner.cs
new file mode 100644
index 000000000..6db84cdd8
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedSigner.cs
@@ -0,0 +1,85 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Credentialed class for generating signatures based on the use of primitives from the BC light-weight API.</summary>
+    public class BcDefaultTlsCredentialedSigner
+        : DefaultTlsCredentialedSigner
+    {
+        private static BcTlsCertificate GetEndEntity(BcTlsCrypto crypto, Certificate certificate)
+        {
+            if (certificate == null || certificate.IsEmpty)
+                throw new ArgumentException("No certificate");
+
+            return BcTlsCertificate.Convert(crypto, certificate.GetCertificateAt(0));
+        }
+
+        private static TlsSigner MakeSigner(BcTlsCrypto crypto, AsymmetricKeyParameter privateKey,
+            Certificate certificate, SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            TlsSigner signer;
+            if (privateKey is RsaKeyParameters)
+            {
+                RsaKeyParameters privKeyRsa = (RsaKeyParameters)privateKey;
+
+                if (signatureAndHashAlgorithm != null)
+                {
+                    int signatureScheme = SignatureScheme.From(signatureAndHashAlgorithm);
+                    if (SignatureScheme.IsRsaPss(signatureScheme))
+                    {
+                        return new BcTlsRsaPssSigner(crypto, privKeyRsa, signatureScheme);
+                    }
+                }
+
+                RsaKeyParameters pubKeyRsa = GetEndEntity(crypto, certificate).GetPubKeyRsa();
+
+                signer = new BcTlsRsaSigner(crypto, privKeyRsa, pubKeyRsa);
+            }
+            else if (privateKey is DsaPrivateKeyParameters)
+            {
+                signer = new BcTlsDsaSigner(crypto, (DsaPrivateKeyParameters)privateKey);
+            }
+            else if (privateKey is ECPrivateKeyParameters)
+            {
+                ECPrivateKeyParameters privKeyEC = (ECPrivateKeyParameters)privateKey;
+
+                if (signatureAndHashAlgorithm != null)
+                {
+                    int signatureScheme = SignatureScheme.From(signatureAndHashAlgorithm);
+                    if (SignatureScheme.IsECDsa(signatureScheme))
+                    {
+                        return new BcTlsECDsa13Signer(crypto, privKeyEC, signatureScheme);
+                    }
+                }
+
+                signer = new BcTlsECDsaSigner(crypto, privKeyEC);
+            }
+            else if (privateKey is Ed25519PrivateKeyParameters)
+            {
+                signer = new BcTlsEd25519Signer(crypto, (Ed25519PrivateKeyParameters)privateKey);
+            }
+            else if (privateKey is Ed448PrivateKeyParameters)
+            {
+                signer = new BcTlsEd448Signer(crypto, (Ed448PrivateKeyParameters)privateKey);
+            }
+            else
+            {
+                throw new ArgumentException("'privateKey' type not supported: " + Platform.GetTypeName(privateKey));
+            }
+
+            return signer;
+        }
+
+        public BcDefaultTlsCredentialedSigner(TlsCryptoParameters cryptoParams, BcTlsCrypto crypto,
+            AsymmetricKeyParameter privateKey, Certificate certificate,
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+            : base(cryptoParams, MakeSigner(crypto, privateKey, certificate, signatureAndHashAlgorithm), certificate,
+                signatureAndHashAlgorithm)
+        {
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs b/crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs
new file mode 100644
index 000000000..df2ccd2c1
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs
@@ -0,0 +1,112 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>HMAC implementation based on original internet draft for HMAC (RFC 2104).</summary>
+    /// <remarks>
+    /// The difference is that padding is concatenated versus XORed with the key, e.g:
+    /// <code>H(K + opad, H(K + ipad, text))</code>
+    /// </remarks>
+    internal class BcSsl3Hmac
+        : TlsHmac
+    {
+        private const byte IPAD_BYTE = (byte)0x36;
+        private const byte OPAD_BYTE = (byte)0x5C;
+
+        private static readonly byte[] IPAD = GenPad(IPAD_BYTE, 48);
+        private static readonly byte[] OPAD = GenPad(OPAD_BYTE, 48);
+
+        private readonly IDigest m_digest;
+        private readonly int m_padLength;
+
+        private byte[] m_secret;
+
+        /// <summary>Base constructor for one of the standard digest algorithms for which the byteLength is known.
+        /// </summary>
+        /// <remarks>
+        /// Behaviour is undefined for digests other than MD5 or SHA1.
+        /// </remarks>
+        /// <param name="digest">the digest.</param>
+        internal BcSsl3Hmac(IDigest digest)
+        {
+            this.m_digest = digest;
+
+            if (digest.GetDigestSize() == 20)
+            {
+                this.m_padLength = 40;
+            }
+            else
+            {
+                this.m_padLength = 48;
+            }
+        }
+
+        public virtual void SetKey(byte[] key, int keyOff, int keyLen)
+        {
+            this.m_secret = TlsUtilities.CopyOfRangeExact(key, keyOff, keyOff + keyLen);
+
+            Reset();
+        }
+
+        public virtual void Update(byte[] input, int inOff, int len)
+        {
+            m_digest.BlockUpdate(input, inOff, len);
+        }
+
+        public virtual byte[] CalculateMac()
+        {
+            byte[] result = new byte[m_digest.GetDigestSize()];
+            DoFinal(result, 0);
+            return result;
+        }
+
+        public virtual void CalculateMac(byte[] output, int outOff)
+        {
+            DoFinal(output, outOff);
+        }
+
+        public virtual int InternalBlockSize
+        {
+            get { return m_digest.GetByteLength(); }
+        }
+
+        public virtual int MacLength
+        {
+            get { return m_digest.GetDigestSize(); }
+        }
+
+        /**
+         * Reset the mac generator.
+         */
+        public virtual void Reset()
+        {
+            m_digest.Reset();
+            m_digest.BlockUpdate(m_secret, 0, m_secret.Length);
+            m_digest.BlockUpdate(IPAD, 0, m_padLength);
+        }
+
+        private void DoFinal(byte[] output, int outOff)
+        {
+            byte[] tmp = new byte[m_digest.GetDigestSize()];
+            m_digest.DoFinal(tmp, 0);
+
+            m_digest.BlockUpdate(m_secret, 0, m_secret.Length);
+            m_digest.BlockUpdate(OPAD, 0, m_padLength);
+            m_digest.BlockUpdate(tmp, 0, tmp.Length);
+
+            m_digest.DoFinal(output, outOff);
+
+            Reset();
+        }
+
+        private static byte[] GenPad(byte b, int count)
+        {
+            byte[] padding = new byte[count];
+            Arrays.Fill(padding, b);
+            return padding;
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs b/crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs
new file mode 100644
index 000000000..ae05a1664
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs
@@ -0,0 +1,54 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsAeadCipherImpl
+        : TlsAeadCipherImpl
+    {
+        private readonly bool m_isEncrypting;
+        private readonly IAeadBlockCipher m_cipher;
+
+        private KeyParameter key;
+
+        internal BcTlsAeadCipherImpl(IAeadBlockCipher cipher, bool isEncrypting)
+        {
+            this.m_cipher = cipher;
+            this.m_isEncrypting = isEncrypting;
+        }
+
+        public void SetKey(byte[] key, int keyOff, int keyLen)
+        {
+            this.key = new KeyParameter(key, keyOff, keyLen);
+        }
+
+        public void Init(byte[] nonce, int macSize, byte[] additionalData)
+        {
+            m_cipher.Init(m_isEncrypting, new AeadParameters(key, macSize * 8, nonce, additionalData));
+        }
+
+        public int GetOutputSize(int inputLength)
+        {
+            return m_cipher.GetOutputSize(inputLength);
+        }
+
+        public int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset)
+        {
+            int len = m_cipher.ProcessBytes(input, inputOffset, inputLength, output, outputOffset);
+
+            try
+            {
+                len += m_cipher.DoFinal(output, outputOffset + len);
+            }
+            catch (InvalidCipherTextException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.bad_record_mac, e);
+            }
+
+            return len;
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs b/crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs
new file mode 100644
index 000000000..b51d38c0d
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs
@@ -0,0 +1,49 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsBlockCipherImpl
+        : TlsBlockCipherImpl
+    {
+        private readonly bool m_isEncrypting;
+        private readonly IBlockCipher m_cipher;
+
+        private KeyParameter key;
+
+        internal BcTlsBlockCipherImpl(IBlockCipher cipher, bool isEncrypting)
+        {
+            this.m_cipher = cipher;
+            this.m_isEncrypting = isEncrypting;
+        }
+
+        public void SetKey(byte[] key, int keyOff, int keyLen)
+        {
+            this.key = new KeyParameter(key, keyOff, keyLen);
+        }
+
+        public void Init(byte[] iv, int ivOff, int ivLen)
+        {
+            m_cipher.Init(m_isEncrypting, new ParametersWithIV(key, iv, ivOff, ivLen));
+        }
+
+        public int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset)
+        {
+            int blockSize = m_cipher.GetBlockSize();
+
+            for (int i = 0; i < inputLength; i += blockSize)
+            {
+                m_cipher.ProcessBlock(input, inputOffset + i, output, outputOffset + i);
+            }
+
+            return inputLength;
+        }
+
+        public int GetBlockSize()
+        {
+            return m_cipher.GetBlockSize();
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs b/crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs
new file mode 100644
index 000000000..e1243087d
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs
@@ -0,0 +1,484 @@
+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.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Implementation class for a single X.509 certificate based on the BC light-weight API.</summary>
+    public class BcTlsCertificate
+        : TlsCertificate
+    {
+        /// <exception cref="IOException"/>
+        public static BcTlsCertificate Convert(BcTlsCrypto crypto, TlsCertificate certificate)
+        {
+            if (certificate is BcTlsCertificate)
+                return (BcTlsCertificate)certificate;
+
+            return new BcTlsCertificate(crypto, certificate.GetEncoded());
+        }
+
+        /// <exception cref="IOException"/>
+        public static X509CertificateStructure ParseCertificate(byte[] encoding)
+        {
+            try
+            {
+                return X509CertificateStructure.GetInstance(encoding);
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.bad_certificate, e);
+            }
+        }
+
+        protected readonly BcTlsCrypto m_crypto;
+        protected readonly X509CertificateStructure m_certificate;
+
+        protected DHPublicKeyParameters m_pubKeyDH = null;
+        protected ECPublicKeyParameters m_pubKeyEC = null;
+        protected Ed25519PublicKeyParameters m_pubKeyEd25519 = null;
+        protected Ed448PublicKeyParameters m_pubKeyEd448 = null;
+        protected RsaKeyParameters m_pubKeyRsa = null;
+
+        /// <exception cref="IOException"/>
+        public BcTlsCertificate(BcTlsCrypto crypto, byte[] encoding)
+            : this(crypto, ParseCertificate(encoding))
+        {
+        }
+
+        public BcTlsCertificate(BcTlsCrypto crypto, X509CertificateStructure certificate)
+        {
+            this.m_crypto = crypto;
+            this.m_certificate = certificate;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsVerifier CreateVerifier(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+            case SignatureAlgorithm.ed25519:
+            case SignatureAlgorithm.ed448:
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return CreateVerifier(SignatureScheme.From(HashAlgorithm.Intrinsic, signatureAlgorithm));
+            }
+
+            ValidateKeyUsage(KeyUsage.DigitalSignature);
+
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+                ValidateRsa_Pkcs1();
+                return new BcTlsRsaVerifier(m_crypto, GetPubKeyRsa());
+
+            case SignatureAlgorithm.dsa:
+                return new BcTlsDsaVerifier(m_crypto, GetPubKeyDss());
+
+            case SignatureAlgorithm.ecdsa:
+                return new BcTlsECDsaVerifier(m_crypto, GetPubKeyEC());
+
+            default:
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsVerifier CreateVerifier(int signatureScheme)
+        {
+            ValidateKeyUsage(KeyUsage.DigitalSignature);
+
+            switch (signatureScheme)
+            {
+            case SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256:
+            case SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384:
+            case SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512:
+            case SignatureScheme.ecdsa_secp256r1_sha256:
+            case SignatureScheme.ecdsa_secp384r1_sha384:
+            case SignatureScheme.ecdsa_secp521r1_sha512:
+            case SignatureScheme.ecdsa_sha1:
+                return new BcTlsECDsa13Verifier(m_crypto, GetPubKeyEC(), signatureScheme);
+
+            case SignatureScheme.ed25519:
+                return new BcTlsEd25519Verifier(m_crypto, GetPubKeyEd25519());
+
+            case SignatureScheme.ed448:
+                return new BcTlsEd448Verifier(m_crypto, GetPubKeyEd448());
+
+            case SignatureScheme.rsa_pkcs1_sha1:
+            case SignatureScheme.rsa_pkcs1_sha256:
+            case SignatureScheme.rsa_pkcs1_sha384:
+            case SignatureScheme.rsa_pkcs1_sha512:
+            {
+                ValidateRsa_Pkcs1();
+                return new BcTlsRsaVerifier(m_crypto, GetPubKeyRsa());
+            }
+
+            case SignatureScheme.rsa_pss_pss_sha256:
+            case SignatureScheme.rsa_pss_pss_sha384:
+            case SignatureScheme.rsa_pss_pss_sha512:
+            {
+                ValidateRsa_Pss_Pss(SignatureScheme.GetSignatureAlgorithm(signatureScheme));
+                return new BcTlsRsaPssVerifier(m_crypto, GetPubKeyRsa(), signatureScheme);
+            }
+
+            case SignatureScheme.rsa_pss_rsae_sha256:
+            case SignatureScheme.rsa_pss_rsae_sha384:
+            case SignatureScheme.rsa_pss_rsae_sha512:
+            {
+                ValidateRsa_Pss_Rsae();
+                return new BcTlsRsaPssVerifier(m_crypto, GetPubKeyRsa(), signatureScheme);
+            }
+
+            // TODO[RFC 8998]
+            //case SignatureScheme.sm2sig_sm3:
+            //    return new BcTlsSM2Verifier(m_crypto, GetPubKeyEC(), Strings.ToByteArray("TLSv1.3+GM+Cipher+Suite"));
+
+            default:
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual byte[] GetEncoded()
+        {
+            return m_certificate.GetEncoded(Asn1Encodable.Der);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual byte[] GetExtension(DerObjectIdentifier extensionOid)
+        {
+            X509Extensions extensions = m_certificate.TbsCertificate.Extensions;
+            if (extensions != null)
+            {
+                X509Extension extension = extensions.GetExtension(extensionOid);
+                if (extension != null)
+                {
+                    return Arrays.Clone(extension.Value.GetOctets());
+                }
+            }
+            return null;
+        }
+
+        public virtual BigInteger SerialNumber
+        {
+            get { return m_certificate.SerialNumber.Value; }
+        }
+
+        public virtual string SigAlgOid
+        {
+            get { return m_certificate.SignatureAlgorithm.Algorithm.Id; }
+        }
+
+        public virtual Asn1Encodable GetSigAlgParams()
+        {
+            return m_certificate.SignatureAlgorithm.Parameters;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual short GetLegacySignatureAlgorithm()
+        {
+            AsymmetricKeyParameter publicKey = GetPublicKey();
+            if (publicKey.IsPrivate)
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+
+            if (!SupportsKeyUsage(KeyUsage.DigitalSignature))
+                return -1;
+
+            /*
+             * RFC 5246 7.4.6. Client Certificate
+             */
+
+            /*
+             * RSA public key; the certificate MUST allow the key to be used for signing with the
+             * signature scheme and hash algorithm that will be employed in the certificate verify
+             * message.
+             */
+            if (publicKey is RsaKeyParameters)
+                return SignatureAlgorithm.rsa;
+
+            /*
+                * DSA public key; the certificate MUST allow the key to be used for signing with the
+                * hash algorithm that will be employed in the certificate verify message.
+                */
+            if (publicKey is DsaPublicKeyParameters)
+                return SignatureAlgorithm.dsa;
+
+            /*
+             * ECDSA-capable public key; the certificate MUST allow the key to be used for signing
+             * with the hash algorithm that will be employed in the certificate verify message; the
+             * public key MUST use a curve and point format supported by the server.
+             */
+            if (publicKey is ECPublicKeyParameters)
+            {
+                // TODO Check the curve and point format
+                return SignatureAlgorithm.ecdsa;
+            }
+
+            return -1;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual DHPublicKeyParameters GetPubKeyDH()
+        {
+            try
+            {
+                return (DHPublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual DsaPublicKeyParameters GetPubKeyDss()
+        {
+            try
+            {
+                return (DsaPublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual ECPublicKeyParameters GetPubKeyEC()
+        {
+            try
+            {
+                return (ECPublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual Ed25519PublicKeyParameters GetPubKeyEd25519()
+        {
+            try
+            {
+                return (Ed25519PublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual Ed448PublicKeyParameters GetPubKeyEd448()
+        {
+            try
+            {
+                return (Ed448PublicKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual RsaKeyParameters GetPubKeyRsa()
+        {
+            try
+            {
+                return (RsaKeyParameters)GetPublicKey();
+            }
+            catch (InvalidCastException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual bool SupportsSignatureAlgorithm(short signatureAlgorithm)
+        {
+            return SupportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.DigitalSignature);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual bool SupportsSignatureAlgorithmCA(short signatureAlgorithm)
+        {
+            return SupportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.KeyCertSign);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsCertificate CheckUsageInRole(int connectionEnd, int tlsCertificateRole)
+        {
+            switch (tlsCertificateRole)
+            {
+            case TlsCertificateRole.DH:
+            {
+                ValidateKeyUsage(KeyUsage.KeyAgreement);
+                this.m_pubKeyDH = GetPubKeyDH();
+                return this;
+            }
+            case TlsCertificateRole.ECDH:
+            {
+                ValidateKeyUsage(KeyUsage.KeyAgreement);
+                this.m_pubKeyEC = GetPubKeyEC();
+                return this;
+            }
+            }
+
+            if (connectionEnd == ConnectionEnd.server)
+            {
+                switch (tlsCertificateRole)
+                {
+                case TlsCertificateRole.RsaEncryption:
+                {
+                    ValidateKeyUsage(KeyUsage.KeyEncipherment);
+                    this.m_pubKeyRsa = GetPubKeyRsa();
+                    return this;
+                }
+                case TlsCertificateRole.Sm2Encryption:
+                {
+                    ValidateKeyUsage(KeyUsage.KeyEncipherment);
+                    this.m_pubKeyEC = GetPubKeyEC();
+                    return this;
+                }
+                }
+            }
+
+            throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual AsymmetricKeyParameter GetPublicKey()
+        {
+            SubjectPublicKeyInfo keyInfo = m_certificate.SubjectPublicKeyInfo;
+            try
+            {
+                return PublicKeyFactory.CreateKey(keyInfo);
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.unsupported_certificate, e);
+            }
+        }
+
+        protected virtual bool SupportsKeyUsage(int keyUsageBits)
+        {
+            X509Extensions exts = m_certificate.TbsCertificate.Extensions;
+            if (exts != null)
+            {
+                KeyUsage ku = KeyUsage.FromExtensions(exts);
+                if (ku != null)
+                {
+                    int bits = ku.GetBytes()[0] & 0xff;
+                    if ((bits & keyUsageBits) != keyUsageBits)
+                        return false;
+                }
+            }
+            return true;
+        }
+
+        protected virtual bool SupportsRsa_Pkcs1()
+        {
+            AlgorithmIdentifier pubKeyAlgID = m_certificate.SubjectPublicKeyInfo.AlgorithmID;
+            return RsaUtilities.SupportsPkcs1(pubKeyAlgID);
+        }
+
+        protected virtual bool SupportsRsa_Pss_Pss(short signatureAlgorithm)
+        {
+            AlgorithmIdentifier pubKeyAlgID = m_certificate.SubjectPublicKeyInfo.AlgorithmID;
+            return RsaUtilities.SupportsPss_Pss(signatureAlgorithm, pubKeyAlgID);
+        }
+
+        protected virtual bool SupportsRsa_Pss_Rsae()
+        {
+            AlgorithmIdentifier pubKeyAlgID = m_certificate.SubjectPublicKeyInfo.AlgorithmID;
+            return RsaUtilities.SupportsPss_Rsae(pubKeyAlgID);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual bool SupportsSignatureAlgorithm(short signatureAlgorithm, int keyUsage)
+        {
+            if (!SupportsKeyUsage(keyUsage))
+                return false;
+
+            AsymmetricKeyParameter publicKey = GetPublicKey();
+
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+                return SupportsRsa_Pkcs1()
+                    && publicKey is RsaKeyParameters;
+
+            case SignatureAlgorithm.dsa:
+                return publicKey is DsaPublicKeyParameters;
+
+            case SignatureAlgorithm.ecdsa:
+            case SignatureAlgorithm.ecdsa_brainpoolP256r1tls13_sha256:
+            case SignatureAlgorithm.ecdsa_brainpoolP384r1tls13_sha384:
+            case SignatureAlgorithm.ecdsa_brainpoolP512r1tls13_sha512:
+                return publicKey is ECPublicKeyParameters;
+
+            case SignatureAlgorithm.ed25519:
+                return publicKey is Ed25519PublicKeyParameters;
+
+            case SignatureAlgorithm.ed448:
+                return publicKey is Ed448PublicKeyParameters;
+
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                return SupportsRsa_Pss_Rsae()
+                    && publicKey is RsaKeyParameters;
+
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return SupportsRsa_Pss_Pss(signatureAlgorithm)
+                    && publicKey is RsaKeyParameters;
+
+            default:
+                return false;
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void ValidateKeyUsage(int keyUsageBits)
+        {
+            if (!SupportsKeyUsage(keyUsageBits))
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ValidateRsa_Pkcs1()
+        {
+            if (!SupportsRsa_Pkcs1())
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ValidateRsa_Pss_Pss(short signatureAlgorithm)
+        {
+            if (!SupportsRsa_Pss_Pss(signatureAlgorithm))
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+
+        /// <exception cref="IOException"/>
+        protected virtual void ValidateRsa_Pss_Rsae()
+        {
+            if (!SupportsRsa_Pss_Rsae())
+                throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs b/crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs
new file mode 100644
index 000000000..aa9985ed9
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs
@@ -0,0 +1,655 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+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.Macs;
+using Org.BouncyCastle.Crypto.Modes;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Prng;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /**
+     * Class for providing cryptographic services for TLS based on implementations in the BC light-weight API.
+     * <p>
+     *     This class provides default implementations for everything. If you need to customise it, extend the class
+     *     and override the appropriate methods.
+     * </p>
+     */
+    public class BcTlsCrypto
+        : AbstractTlsCrypto
+    {
+        private readonly SecureRandom m_entropySource;
+
+        public BcTlsCrypto(SecureRandom entropySource)
+        {
+            this.m_entropySource = entropySource;
+        }
+
+        internal virtual BcTlsSecret AdoptLocalSecret(byte[] data)
+        {
+            return new BcTlsSecret(this, data);
+        }
+
+        public override SecureRandom SecureRandom
+        {
+            get { return m_entropySource; }
+        }
+
+        public override TlsCertificate CreateCertificate(byte[] encoding)
+        {
+            return new BcTlsCertificate(this, encoding);
+        }
+
+        public override TlsCipher CreateCipher(TlsCryptoParameters cryptoParams, int encryptionAlgorithm,
+            int macAlgorithm)
+        {
+            switch (encryptionAlgorithm)
+            {
+            case EncryptionAlgorithm.AES_128_CBC:
+            case EncryptionAlgorithm.ARIA_128_CBC:
+            case EncryptionAlgorithm.CAMELLIA_128_CBC:
+            case EncryptionAlgorithm.SEED_CBC:
+            case EncryptionAlgorithm.SM4_CBC:
+                return CreateCipher_Cbc(cryptoParams, encryptionAlgorithm, 16, macAlgorithm);
+
+            case EncryptionAlgorithm.cls_3DES_EDE_CBC:
+                return CreateCipher_Cbc(cryptoParams, encryptionAlgorithm, 24, macAlgorithm);
+
+            case EncryptionAlgorithm.AES_256_CBC:
+            case EncryptionAlgorithm.ARIA_256_CBC:
+            case EncryptionAlgorithm.CAMELLIA_256_CBC:
+                return CreateCipher_Cbc(cryptoParams, encryptionAlgorithm, 32, macAlgorithm);
+
+            case EncryptionAlgorithm.AES_128_CCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Ccm(cryptoParams, 16, 16);
+            case EncryptionAlgorithm.AES_128_CCM_8:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Ccm(cryptoParams, 16, 8);
+            case EncryptionAlgorithm.AES_128_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Gcm(cryptoParams, 16, 16);
+            case EncryptionAlgorithm.AES_256_CCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Ccm(cryptoParams, 32, 16);
+            case EncryptionAlgorithm.AES_256_CCM_8:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Ccm(cryptoParams, 32, 8);
+            case EncryptionAlgorithm.AES_256_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aes_Gcm(cryptoParams, 32, 16);
+            case EncryptionAlgorithm.ARIA_128_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aria_Gcm(cryptoParams, 16, 16);
+            case EncryptionAlgorithm.ARIA_256_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Aria_Gcm(cryptoParams, 32, 16);
+            case EncryptionAlgorithm.CAMELLIA_128_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Camellia_Gcm(cryptoParams, 16, 16);
+            case EncryptionAlgorithm.CAMELLIA_256_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_Camellia_Gcm(cryptoParams, 32, 16);
+            case EncryptionAlgorithm.CHACHA20_POLY1305:
+                // NOTE: Ignores macAlgorithm
+                return CreateChaCha20Poly1305(cryptoParams);
+            case EncryptionAlgorithm.NULL:
+                return CreateNullCipher(cryptoParams, macAlgorithm);
+            case EncryptionAlgorithm.SM4_CCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_SM4_Ccm(cryptoParams);
+            case EncryptionAlgorithm.SM4_GCM:
+                // NOTE: Ignores macAlgorithm
+                return CreateCipher_SM4_Gcm(cryptoParams);
+
+            case EncryptionAlgorithm.DES40_CBC:
+            case EncryptionAlgorithm.DES_CBC:
+            case EncryptionAlgorithm.IDEA_CBC:
+            case EncryptionAlgorithm.RC2_CBC_40:
+            case EncryptionAlgorithm.RC4_128:
+            case EncryptionAlgorithm.RC4_40:
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        public override TlsDHDomain CreateDHDomain(TlsDHConfig dhConfig)
+        {
+            return new BcTlsDHDomain(this, dhConfig);
+        }
+
+        public override TlsECDomain CreateECDomain(TlsECConfig ecConfig)
+        {
+            switch (ecConfig.NamedGroup)
+            {
+            case NamedGroup.x25519:
+                return new BcX25519Domain(this);
+            case NamedGroup.x448:
+                return new BcX448Domain(this);
+            default:
+                return new BcTlsECDomain(this, ecConfig);
+            }
+        }
+
+        public override TlsEncryptor CreateEncryptor(TlsCertificate certificate)
+        {
+            BcTlsCertificate bcCert = BcTlsCertificate.Convert(this, certificate);
+            bcCert.ValidateKeyUsage(KeyUsage.KeyEncipherment);
+
+            RsaKeyParameters pubKeyRsa = bcCert.GetPubKeyRsa();
+
+            return new BcTlsRsaEncryptor(this, pubKeyRsa);
+        }
+
+        public override TlsNonceGenerator CreateNonceGenerator(byte[] additionalSeedMaterial)
+        {
+            IDigest digest = CreateDigest(CryptoHashAlgorithm.sha256);
+
+            byte[] seed = new byte[digest.GetDigestSize()];
+            SecureRandom.NextBytes(seed);
+
+            DigestRandomGenerator randomGenerator = new DigestRandomGenerator(digest);
+            randomGenerator.AddSeedMaterial(additionalSeedMaterial);
+            randomGenerator.AddSeedMaterial(seed);
+
+            return new BcTlsNonceGenerator(randomGenerator);
+        }
+
+        public override bool HasAllRawSignatureAlgorithms()
+        {
+            // TODO[RFC 8422] Revisit the need to buffer the handshake for "Intrinsic" hash signatures
+            return !HasSignatureAlgorithm(SignatureAlgorithm.ed25519)
+                && !HasSignatureAlgorithm(SignatureAlgorithm.ed448);
+        }
+
+        public override bool HasDHAgreement()
+        {
+            return true;
+        }
+
+        public override bool HasECDHAgreement()
+        {
+            return true;
+        }
+
+        public override bool HasEncryptionAlgorithm(int encryptionAlgorithm)
+        {
+            switch (encryptionAlgorithm)
+            {
+            case EncryptionAlgorithm.DES40_CBC:
+            case EncryptionAlgorithm.DES_CBC:
+            case EncryptionAlgorithm.IDEA_CBC:
+            case EncryptionAlgorithm.RC2_CBC_40:
+            case EncryptionAlgorithm.RC4_128:
+            case EncryptionAlgorithm.RC4_40:
+                return false;
+
+            default:
+                return true;
+            }
+        }
+
+        public override bool HasCryptoHashAlgorithm(int cryptoHashAlgorithm)
+        {
+            return true;
+        }
+
+        public override bool HasCryptoSignatureAlgorithm(int cryptoSignatureAlgorithm)
+        {
+            switch (cryptoSignatureAlgorithm)
+            {
+            case CryptoSignatureAlgorithm.rsa:
+            case CryptoSignatureAlgorithm.dsa:
+            case CryptoSignatureAlgorithm.ecdsa:
+            case CryptoSignatureAlgorithm.rsa_pss_rsae_sha256:
+            case CryptoSignatureAlgorithm.rsa_pss_rsae_sha384:
+            case CryptoSignatureAlgorithm.rsa_pss_rsae_sha512:
+            case CryptoSignatureAlgorithm.ed25519:
+            case CryptoSignatureAlgorithm.ed448:
+            case CryptoSignatureAlgorithm.rsa_pss_pss_sha256:
+            case CryptoSignatureAlgorithm.rsa_pss_pss_sha384:
+            case CryptoSignatureAlgorithm.rsa_pss_pss_sha512:
+                return true;
+
+            // TODO[draft-smyshlyaev-tls12-gost-suites-10]
+            case CryptoSignatureAlgorithm.gostr34102012_256:
+            case CryptoSignatureAlgorithm.gostr34102012_512:
+
+            // TODO[RFC 8998]
+            case CryptoSignatureAlgorithm.sm2:
+
+            default:
+                return false;
+            }
+        }
+
+        public override bool HasMacAlgorithm(int macAlgorithm)
+        {
+            return true;
+        }
+
+        public override bool HasNamedGroup(int namedGroup)
+        {
+            return NamedGroup.RefersToASpecificGroup(namedGroup);
+        }
+
+        public override bool HasRsaEncryption()
+        {
+            return true;
+        }
+
+        public override bool HasSignatureAlgorithm(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+            case SignatureAlgorithm.dsa:
+            case SignatureAlgorithm.ecdsa:
+            case SignatureAlgorithm.ed25519:
+            case SignatureAlgorithm.ed448:
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+            case SignatureAlgorithm.ecdsa_brainpoolP256r1tls13_sha256:
+            case SignatureAlgorithm.ecdsa_brainpoolP384r1tls13_sha384:
+            case SignatureAlgorithm.ecdsa_brainpoolP512r1tls13_sha512:
+                return true;
+
+            // TODO[draft-smyshlyaev-tls12-gost-suites-10]
+            case SignatureAlgorithm.gostr34102012_256:
+            case SignatureAlgorithm.gostr34102012_512:
+            // TODO[RFC 8998]
+            //case SignatureAlgorithm.sm2:
+            default:
+                return false;
+            }
+        }
+
+        public override bool HasSignatureAndHashAlgorithm(SignatureAndHashAlgorithm sigAndHashAlgorithm)
+        {
+            short signature = sigAndHashAlgorithm.Signature;
+
+            switch (sigAndHashAlgorithm.Hash)
+            {
+                case HashAlgorithm.md5:
+                    return SignatureAlgorithm.rsa == signature && HasSignatureAlgorithm(signature);
+                default:
+                    return HasSignatureAlgorithm(signature);
+            }
+        }
+
+        public override bool HasSignatureScheme(int signatureScheme)
+        {
+            switch (signatureScheme)
+            {
+            case SignatureScheme.sm2sig_sm3:
+                return false;
+            default:
+            {
+                short signature = SignatureScheme.GetSignatureAlgorithm(signatureScheme);
+
+                switch(SignatureScheme.GetCryptoHashAlgorithm(signatureScheme))
+                {
+                case CryptoHashAlgorithm.md5:
+                    return SignatureAlgorithm.rsa == signature && HasSignatureAlgorithm(signature);
+                default:
+                    return HasSignatureAlgorithm(signature);
+                }
+            }
+            }
+        }
+
+        public override bool HasSrpAuthentication()
+        {
+            return true;
+        }
+
+        public override TlsSecret CreateSecret(byte[] data)
+        {
+            try
+            {
+                return AdoptLocalSecret(Arrays.Clone(data));
+            }
+            finally
+            {
+                // TODO[tls-ops] Add this after checking all callers
+                //if (data != null)
+                //{
+                //    Array.Clear(data, 0, data.Length);
+                //}
+            }
+        }
+
+        public override TlsSecret GenerateRsaPreMasterSecret(ProtocolVersion version)
+        {
+            byte[] data = new byte[48];
+            SecureRandom.NextBytes(data);
+            TlsUtilities.WriteVersion(version, data, 0);
+            return AdoptLocalSecret(data);
+        }
+
+        public virtual IDigest CloneDigest(int cryptoHashAlgorithm, IDigest digest)
+        {
+            switch (cryptoHashAlgorithm)
+            {
+            case CryptoHashAlgorithm.md5:
+                return new MD5Digest((MD5Digest)digest);
+            case CryptoHashAlgorithm.sha1:
+                return new Sha1Digest((Sha1Digest)digest);
+            case CryptoHashAlgorithm.sha224:
+                return new Sha224Digest((Sha224Digest)digest);
+            case CryptoHashAlgorithm.sha256:
+                return new Sha256Digest((Sha256Digest)digest);
+            case CryptoHashAlgorithm.sha384:
+                return new Sha384Digest((Sha384Digest)digest);
+            case CryptoHashAlgorithm.sha512:
+                return new Sha512Digest((Sha512Digest)digest);
+            case CryptoHashAlgorithm.sm3:
+                return new SM3Digest((SM3Digest)digest);
+            default:
+                throw new ArgumentException("invalid CryptoHashAlgorithm: " + cryptoHashAlgorithm);
+            }
+        }
+
+        public virtual IDigest CreateDigest(int cryptoHashAlgorithm)
+        {
+            switch (cryptoHashAlgorithm)
+            {
+            case CryptoHashAlgorithm.md5:
+                return new MD5Digest();
+            case CryptoHashAlgorithm.sha1:
+                return new Sha1Digest();
+            case CryptoHashAlgorithm.sha224:
+                return new Sha224Digest();
+            case CryptoHashAlgorithm.sha256:
+                return new Sha256Digest();
+            case CryptoHashAlgorithm.sha384:
+                return new Sha384Digest();
+            case CryptoHashAlgorithm.sha512:
+                return new Sha512Digest();
+            case CryptoHashAlgorithm.sm3:
+                return new SM3Digest();
+            default:
+                throw new ArgumentException("invalid CryptoHashAlgorithm: " + cryptoHashAlgorithm);
+            }
+        }
+
+        public override TlsHash CreateHash(int cryptoHashAlgorithm)
+        {
+            return new BcTlsHash(this, cryptoHashAlgorithm);
+        }
+
+        protected virtual IBlockCipher CreateBlockCipher(int encryptionAlgorithm)
+        {
+            switch (encryptionAlgorithm)
+            {
+            case EncryptionAlgorithm.cls_3DES_EDE_CBC:
+                return CreateDesEdeEngine();
+            case EncryptionAlgorithm.AES_128_CBC:
+            case EncryptionAlgorithm.AES_256_CBC:
+                return CreateAesEngine();
+            case EncryptionAlgorithm.ARIA_128_CBC:
+            case EncryptionAlgorithm.ARIA_256_CBC:
+                return CreateAriaEngine();
+            case EncryptionAlgorithm.CAMELLIA_128_CBC:
+            case EncryptionAlgorithm.CAMELLIA_256_CBC:
+                return CreateCamelliaEngine();
+            case EncryptionAlgorithm.SEED_CBC:
+                return CreateSeedEngine();
+            case EncryptionAlgorithm.SM4_CBC:
+                return CreateSM4Engine();
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        protected virtual IBlockCipher CreateCbcBlockCipher(IBlockCipher blockCipher)
+        {
+            return new CbcBlockCipher(blockCipher);
+        }
+
+        protected virtual IBlockCipher CreateCbcBlockCipher(int encryptionAlgorithm)
+        {
+            return CreateCbcBlockCipher(CreateBlockCipher(encryptionAlgorithm));
+        }
+
+        protected virtual TlsCipher CreateChaCha20Poly1305(TlsCryptoParameters cryptoParams)
+        {
+            BcChaCha20Poly1305 encrypt = new BcChaCha20Poly1305(true);
+            BcChaCha20Poly1305 decrypt = new BcChaCha20Poly1305(false);
+
+            return new TlsAeadCipher(cryptoParams, encrypt, decrypt, 32, 16, TlsAeadCipher.AEAD_CHACHA20_POLY1305);
+        }
+
+        protected virtual TlsAeadCipher CreateCipher_Aes_Ccm(TlsCryptoParameters cryptoParams, int cipherKeySize,
+            int macSize)
+        {
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aes_Ccm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aes_Ccm(), false);
+
+            return new TlsAeadCipher(cryptoParams, encrypt, decrypt, cipherKeySize, macSize, TlsAeadCipher.AEAD_CCM);
+        }
+
+        protected virtual TlsAeadCipher CreateCipher_Aes_Gcm(TlsCryptoParameters cryptoParams, int cipherKeySize,
+            int macSize)
+        {
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aes_Gcm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aes_Gcm(), false);
+
+            return new TlsAeadCipher(cryptoParams, encrypt, decrypt, cipherKeySize, macSize, TlsAeadCipher.AEAD_GCM);
+        }
+
+        protected virtual TlsAeadCipher CreateCipher_Aria_Gcm(TlsCryptoParameters cryptoParams, int cipherKeySize,
+            int macSize)
+        {
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aria_Gcm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Aria_Gcm(), false);
+
+            return new TlsAeadCipher(cryptoParams, encrypt, decrypt, cipherKeySize, macSize, TlsAeadCipher.AEAD_GCM);
+        }
+
+        protected virtual TlsAeadCipher CreateCipher_Camellia_Gcm(TlsCryptoParameters cryptoParams, int cipherKeySize,
+            int macSize)
+        {
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Camellia_Gcm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_Camellia_Gcm(), false);
+
+            return new TlsAeadCipher(cryptoParams, encrypt, decrypt, cipherKeySize, macSize, TlsAeadCipher.AEAD_GCM);
+        }
+
+        protected virtual TlsCipher CreateCipher_Cbc(TlsCryptoParameters cryptoParams, int encryptionAlgorithm,
+            int cipherKeySize, int macAlgorithm)
+        {
+            BcTlsBlockCipherImpl encrypt = new BcTlsBlockCipherImpl(CreateCbcBlockCipher(encryptionAlgorithm), true);
+            BcTlsBlockCipherImpl decrypt = new BcTlsBlockCipherImpl(CreateCbcBlockCipher(encryptionAlgorithm), false);
+
+            TlsHmac clientMac = CreateMac(cryptoParams, macAlgorithm);
+            TlsHmac serverMac = CreateMac(cryptoParams, macAlgorithm);
+
+            return new TlsBlockCipher(cryptoParams, encrypt, decrypt, clientMac, serverMac, cipherKeySize);
+        }
+
+        protected virtual TlsAeadCipher CreateCipher_SM4_Ccm(TlsCryptoParameters cryptoParams)
+        {
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_SM4_Ccm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_SM4_Ccm(), false);
+
+            return new TlsAeadCipher(cryptoParams, encrypt, decrypt, 16, 16, TlsAeadCipher.AEAD_CCM);
+        }
+
+        protected virtual TlsAeadCipher CreateCipher_SM4_Gcm(TlsCryptoParameters cryptoParams)
+        {
+            BcTlsAeadCipherImpl encrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_SM4_Gcm(), true);
+            BcTlsAeadCipherImpl decrypt = new BcTlsAeadCipherImpl(CreateAeadBlockCipher_SM4_Gcm(), false);
+
+            return new TlsAeadCipher(cryptoParams, encrypt, decrypt, 16, 16, TlsAeadCipher.AEAD_GCM);
+        }
+
+        protected virtual TlsNullCipher CreateNullCipher(TlsCryptoParameters cryptoParams, int macAlgorithm)
+        {
+            return new TlsNullCipher(cryptoParams, CreateMac(cryptoParams, macAlgorithm),
+                CreateMac(cryptoParams, macAlgorithm));
+        }
+
+        protected virtual IBlockCipher CreateAesEngine()
+        {
+            return new AesEngine();
+        }
+
+        protected virtual IBlockCipher CreateAriaEngine()
+        {
+            return new AriaEngine();
+        }
+
+        protected virtual IBlockCipher CreateCamelliaEngine()
+        {
+            return new CamelliaEngine();
+        }
+
+        protected virtual IBlockCipher CreateDesEdeEngine()
+        {
+            return new DesEdeEngine();
+        }
+
+        protected virtual IBlockCipher CreateSeedEngine()
+        {
+            return new SeedEngine();
+        }
+
+        protected virtual IBlockCipher CreateSM4Engine()
+        {
+            return new SM4Engine();
+        }
+
+        protected virtual IAeadBlockCipher CreateCcmMode(IBlockCipher engine)
+        {
+            return new CcmBlockCipher(engine);
+        }
+
+        protected virtual IAeadBlockCipher CreateGcmMode(IBlockCipher engine)
+        {
+            // TODO Consider allowing custom configuration of multiplier
+            return new GcmBlockCipher(engine);
+        }
+
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Ccm()
+        {
+            return CreateCcmMode(CreateAesEngine());
+        }
+
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aes_Gcm()
+        {
+            return CreateGcmMode(CreateAesEngine());
+        }
+
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Aria_Gcm()
+        {
+            return CreateGcmMode(CreateAriaEngine());
+        }
+
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_Camellia_Gcm()
+        {
+            return CreateGcmMode(CreateCamelliaEngine());
+        }
+
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_SM4_Ccm()
+        {
+            return CreateCcmMode(CreateSM4Engine());
+        }
+
+        protected virtual IAeadBlockCipher CreateAeadBlockCipher_SM4_Gcm()
+        {
+            return CreateGcmMode(CreateSM4Engine());
+        }
+
+        public override TlsHmac CreateHmac(int macAlgorithm)
+        {
+            return CreateHmacForHash(TlsCryptoUtilities.GetHashForHmac(macAlgorithm));
+        }
+
+        public override TlsHmac CreateHmacForHash(int cryptoHashAlgorithm)
+        {
+            return new BcTlsHmac(new HMac(CreateDigest(cryptoHashAlgorithm)));
+        }
+
+        protected virtual TlsHmac CreateHmac_Ssl(int macAlgorithm)
+        {
+            switch (macAlgorithm)
+            {
+            case MacAlgorithm.hmac_md5:
+                return new BcSsl3Hmac(CreateDigest(CryptoHashAlgorithm.md5));
+            case MacAlgorithm.hmac_sha1:
+                return new BcSsl3Hmac(CreateDigest(CryptoHashAlgorithm.sha1));
+            case MacAlgorithm.hmac_sha256:
+                return new BcSsl3Hmac(CreateDigest(CryptoHashAlgorithm.sha256));
+            case MacAlgorithm.hmac_sha384:
+                return new BcSsl3Hmac(CreateDigest(CryptoHashAlgorithm.sha384));
+            case MacAlgorithm.hmac_sha512:
+                return new BcSsl3Hmac(CreateDigest(CryptoHashAlgorithm.sha512));
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        protected virtual TlsHmac CreateMac(TlsCryptoParameters cryptoParams, int macAlgorithm)
+        {
+            if (TlsImplUtilities.IsSsl(cryptoParams))
+            {
+                return CreateHmac_Ssl(macAlgorithm);
+            }
+            else
+            {
+                return CreateHmac(macAlgorithm);
+            }
+        }
+
+        public override TlsSrp6Client CreateSrp6Client(TlsSrpConfig srpConfig)
+        {
+            BigInteger[] ng = srpConfig.GetExplicitNG();
+            Srp6GroupParameters srpGroup = new Srp6GroupParameters(ng[0], ng[1]);
+
+            Srp6Client srp6Client = new Srp6Client();
+            srp6Client.Init(srpGroup, CreateDigest(CryptoHashAlgorithm.sha1), SecureRandom);
+
+            return new BcTlsSrp6Client(srp6Client);
+        }
+
+        public override TlsSrp6Server CreateSrp6Server(TlsSrpConfig srpConfig, BigInteger srpVerifier)
+        {
+            BigInteger[] ng = srpConfig.GetExplicitNG();
+            Srp6GroupParameters srpGroup = new Srp6GroupParameters(ng[0], ng[1]);
+
+            Srp6Server srp6Server = new Srp6Server();
+            srp6Server.Init(srpGroup, srpVerifier, CreateDigest(CryptoHashAlgorithm.sha1), SecureRandom);
+
+            return new BcTlsSrp6Server(srp6Server);
+        }
+
+        public override TlsSrp6VerifierGenerator CreateSrp6VerifierGenerator(TlsSrpConfig srpConfig)
+        {
+            BigInteger[] ng = srpConfig.GetExplicitNG();
+
+            Srp6VerifierGenerator srp6VerifierGenerator = new Srp6VerifierGenerator();
+            srp6VerifierGenerator.Init(ng[0], ng[1], CreateDigest(CryptoHashAlgorithm.sha1));
+
+            return new BcTlsSrp6VerifierGenerator(srp6VerifierGenerator);
+        }
+
+        public override TlsSecret HkdfInit(int cryptoHashAlgorithm)
+        {
+            return AdoptLocalSecret(new byte[TlsCryptoUtilities.GetHashOutputSize(cryptoHashAlgorithm)]);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsDH.cs b/crypto/src/tls/crypto/impl/bc/BcTlsDH.cs
new file mode 100644
index 000000000..8af94f7c6
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsDH.cs
@@ -0,0 +1,43 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Support class for ephemeral Diffie-Hellman using the BC light-weight library.</summary>
+    public class BcTlsDH
+        : TlsAgreement
+    {
+        protected readonly BcTlsDHDomain m_domain;
+
+        protected AsymmetricCipherKeyPair m_localKeyPair;
+        protected DHPublicKeyParameters m_peerPublicKey;
+
+        public BcTlsDH(BcTlsDHDomain domain)
+        {
+            this.m_domain = domain;
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual byte[] GenerateEphemeral()
+        {
+            this.m_localKeyPair = m_domain.GenerateKeyPair();
+
+            return m_domain.EncodePublicKey((DHPublicKeyParameters)m_localKeyPair.Public);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual void ReceivePeerValue(byte[] peerValue)
+        {
+            this.m_peerPublicKey = m_domain.DecodePublicKey(peerValue);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual TlsSecret CalculateSecret()
+        {
+            return m_domain.CalculateDHAgreement((DHPrivateKeyParameters)m_localKeyPair.Private, m_peerPublicKey);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs b/crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs
new file mode 100644
index 000000000..90b8ce94f
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs
@@ -0,0 +1,119 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>BC light-weight support class for Diffie-Hellman key pair generation and key agreement over a
+    /// specified Diffie-Hellman configuration.</summary>
+    public class BcTlsDHDomain
+        : TlsDHDomain
+    {
+        private static byte[] EncodeValue(DHParameters dh, bool padded, BigInteger x)
+        {
+            return padded
+                ? BigIntegers.AsUnsignedByteArray(GetValueLength(dh), x)
+                : BigIntegers.AsUnsignedByteArray(x);
+        }
+
+        private static int GetValueLength(DHParameters dh)
+        {
+            return (dh.P.BitLength + 7) / 8;
+        }
+
+        public static BcTlsSecret CalculateDHAgreement(BcTlsCrypto crypto, DHPrivateKeyParameters privateKey,
+            DHPublicKeyParameters publicKey, bool padded)
+        {
+            DHBasicAgreement basicAgreement = new DHBasicAgreement();
+            basicAgreement.Init(privateKey);
+            BigInteger agreementValue = basicAgreement.CalculateAgreement(publicKey);
+            byte[] secret = EncodeValue(privateKey.Parameters, padded, agreementValue);
+            return crypto.AdoptLocalSecret(secret);
+        }
+
+        public static DHParameters GetParameters(TlsDHConfig dhConfig)
+        {
+            DHGroup dhGroup = TlsDHUtilities.GetDHGroup(dhConfig);
+            if (dhGroup == null)
+                throw new ArgumentException("No DH configuration provided");
+
+            return new DHParameters(dhGroup.P, dhGroup.G, dhGroup.Q, dhGroup.L);
+        }
+
+        protected readonly BcTlsCrypto crypto;
+        protected readonly TlsDHConfig dhConfig;
+        protected readonly DHParameters dhParameters;
+
+        public BcTlsDHDomain(BcTlsCrypto crypto, TlsDHConfig dhConfig)
+        {
+            this.crypto = crypto;
+            this.dhConfig = dhConfig;
+            this.dhParameters = GetParameters(dhConfig);
+        }
+
+        public virtual BcTlsSecret CalculateDHAgreement(DHPrivateKeyParameters privateKey,
+            DHPublicKeyParameters publicKey)
+        {
+            return CalculateDHAgreement(crypto, privateKey, publicKey, dhConfig.IsPadded);
+        }
+
+        public virtual TlsAgreement CreateDH()
+        {
+            return new BcTlsDH(this);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual BigInteger DecodeParameter(byte[] encoding)
+        {
+            if (dhConfig.IsPadded && GetValueLength(dhParameters) != encoding.Length)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            return new BigInteger(1, encoding);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual DHPublicKeyParameters DecodePublicKey(byte[] encoding)
+        {
+            /*
+             * RFC 7919 3. [..] the client MUST verify that dh_Ys is in the range 1 < dh_Ys < dh_p - 1.
+             * If dh_Ys is not in this range, the client MUST terminate the connection with a fatal
+             * handshake_failure(40) alert.
+             */
+            try
+            {
+                BigInteger y = DecodeParameter(encoding);
+
+                return new DHPublicKeyParameters(y, dhParameters);
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.handshake_failure, e);
+            }
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual byte[] EncodeParameter(BigInteger x)
+        {
+            return EncodeValue(dhParameters, dhConfig.IsPadded, x);
+        }
+
+        /// <exception cref="IOException"/>
+        public virtual byte[] EncodePublicKey(DHPublicKeyParameters publicKey)
+        {
+            return EncodeValue(dhParameters, true, publicKey.Y);
+        }
+
+        public virtual AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            DHBasicKeyPairGenerator keyPairGenerator = new DHBasicKeyPairGenerator();
+            keyPairGenerator.Init(new DHKeyGenerationParameters(crypto.SecureRandom, dhParameters));
+            return keyPairGenerator.GenerateKeyPair();
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsDsaSigner.cs b/crypto/src/tls/crypto/impl/bc/BcTlsDsaSigner.cs
new file mode 100644
index 000000000..a1040284c
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsDsaSigner.cs
@@ -0,0 +1,29 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Implementation class for generation of the raw DSA signature type using the BC light-weight API.
+    /// </summary>
+    public class BcTlsDsaSigner
+        : BcTlsDssSigner
+    {
+        public BcTlsDsaSigner(BcTlsCrypto crypto, DsaPrivateKeyParameters privateKey)
+            : base(crypto, privateKey)
+        {
+        }
+
+        protected override IDsa CreateDsaImpl(int cryptoHashAlgorithm)
+        {
+            return new DsaSigner(new HMacDsaKCalculator(m_crypto.CreateDigest(cryptoHashAlgorithm)));
+        }
+
+        protected override short SignatureAlgorithm
+        {
+            get { return Tls.SignatureAlgorithm.dsa; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsDsaVerifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsDsaVerifier.cs
new file mode 100644
index 000000000..bc8395ac5
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsDsaVerifier.cs
@@ -0,0 +1,29 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Implementation class for the verification of the raw DSA signature type using the BC light-weight API.
+    /// </summary>
+    public class BcTlsDsaVerifier
+        : BcTlsDssVerifier
+    {
+        public BcTlsDsaVerifier(BcTlsCrypto crypto, DsaPublicKeyParameters publicKey)
+            : base(crypto, publicKey)
+        {
+        }
+
+        protected override IDsa CreateDsaImpl(int cryptoHashAlgorithm)
+        {
+            return new DsaSigner(new HMacDsaKCalculator(m_crypto.CreateDigest(cryptoHashAlgorithm)));
+        }
+
+        protected override short SignatureAlgorithm
+        {
+            get { return Tls.SignatureAlgorithm.dsa; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsDssSigner.cs b/crypto/src/tls/crypto/impl/bc/BcTlsDssSigner.cs
new file mode 100644
index 000000000..8a687d56c
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsDssSigner.cs
@@ -0,0 +1,54 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>BC light-weight base class for the signers implementing the two DSA style algorithms from FIPS PUB
+    /// 186-4: DSA and ECDSA.</summary>
+    public abstract class BcTlsDssSigner
+        : BcTlsSigner
+    {
+        protected BcTlsDssSigner(BcTlsCrypto crypto, AsymmetricKeyParameter privateKey)
+            : base(crypto, privateKey)
+        {
+        }
+
+        protected abstract IDsa CreateDsaImpl(int cryptoHashAlgorithm);
+
+        protected abstract short SignatureAlgorithm { get; }
+
+        public override byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash)
+        {
+            if (algorithm != null && algorithm.Signature != SignatureAlgorithm)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            int cryptoHashAlgorithm = (null == algorithm)
+                ? CryptoHashAlgorithm.sha1
+                : TlsCryptoUtilities.GetHash(algorithm.Hash);
+
+            ISigner signer = new DsaDigestSigner(CreateDsaImpl(cryptoHashAlgorithm), new NullDigest());
+            signer.Init(true, new ParametersWithRandom(m_privateKey, m_crypto.SecureRandom));
+            if (algorithm == null)
+            {
+                // Note: Only use the SHA1 part of the (MD5/SHA1) hash
+                signer.BlockUpdate(hash, 16, 20);
+            }
+            else
+            {
+                signer.BlockUpdate(hash, 0, hash.Length);
+            }
+            try
+            {
+                return signer.GenerateSignature();
+            }
+            catch (CryptoException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsDssVerifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsDssVerifier.cs
new file mode 100644
index 000000000..f2e371067
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsDssVerifier.cs
@@ -0,0 +1,47 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>BC light-weight base class for the verifiers supporting the two DSA style algorithms from FIPS PUB
+    /// 186-4: DSA and ECDSA.</summary>
+    public abstract class BcTlsDssVerifier
+        : BcTlsVerifier
+    {
+        protected BcTlsDssVerifier(BcTlsCrypto crypto, AsymmetricKeyParameter publicKey)
+            : base(crypto, publicKey)
+        {
+        }
+
+        protected abstract IDsa CreateDsaImpl(int cryptoHashAlgorithm);
+
+        protected abstract short SignatureAlgorithm { get; }
+
+        public override bool VerifyRawSignature(DigitallySigned signedParams, byte[] hash)
+        {
+            SignatureAndHashAlgorithm algorithm = signedParams.Algorithm;
+            if (algorithm != null && algorithm.Signature != SignatureAlgorithm)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            int cryptoHashAlgorithm = (null == algorithm)
+                ? CryptoHashAlgorithm.sha1
+                : TlsCryptoUtilities.GetHash(algorithm.Hash);
+
+            ISigner signer = new DsaDigestSigner(CreateDsaImpl(cryptoHashAlgorithm), new NullDigest());
+            signer.Init(false, m_publicKey);
+            if (algorithm == null)
+            {
+                // Note: Only use the SHA1 part of the (MD5/SHA1) hash
+                signer.BlockUpdate(hash, 16, 20);
+            }
+            else
+            {
+                signer.BlockUpdate(hash, 0, hash.Length);
+            }
+            return signer.VerifySignature(signedParams.Signature);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsECDH.cs b/crypto/src/tls/crypto/impl/bc/BcTlsECDH.cs
new file mode 100644
index 000000000..55b8ed60a
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsECDH.cs
@@ -0,0 +1,39 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Support class for ephemeral Elliptic Curve Diffie-Hellman using the BC light-weight library.</summary>
+    public class BcTlsECDH
+        : TlsAgreement
+    {
+        protected readonly BcTlsECDomain m_domain;
+
+        protected AsymmetricCipherKeyPair m_localKeyPair;
+        protected ECPublicKeyParameters m_peerPublicKey;
+
+        public BcTlsECDH(BcTlsECDomain domain)
+        {
+            this.m_domain = domain;
+        }
+
+        public virtual byte[] GenerateEphemeral()
+        {
+            this.m_localKeyPair = m_domain.GenerateKeyPair();
+
+            return m_domain.EncodePublicKey((ECPublicKeyParameters)m_localKeyPair.Public);
+        }
+
+        public virtual void ReceivePeerValue(byte[] peerValue)
+        {
+            this.m_peerPublicKey = m_domain.DecodePublicKey(peerValue);
+        }
+
+        public virtual TlsSecret CalculateSecret()
+        {
+            return m_domain.CalculateECDHAgreement((ECPrivateKeyParameters)m_localKeyPair.Private, m_peerPublicKey);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsECDomain.cs b/crypto/src/tls/crypto/impl/bc/BcTlsECDomain.cs
new file mode 100644
index 000000000..f4c5cfc5d
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsECDomain.cs
@@ -0,0 +1,125 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Agreement;
+using Org.BouncyCastle.Crypto.EC;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Math.EC;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /**
+     * EC domain class for generating key pairs and performing key agreement.
+     */
+    public class BcTlsECDomain
+        : TlsECDomain
+    {
+        public static BcTlsSecret CalculateBasicAgreement(BcTlsCrypto crypto, ECPrivateKeyParameters privateKey,
+            ECPublicKeyParameters publicKey)
+        {
+            ECDHBasicAgreement basicAgreement = new ECDHBasicAgreement();
+            basicAgreement.Init(privateKey);
+            BigInteger agreementValue = basicAgreement.CalculateAgreement(publicKey);
+
+            /*
+             * RFC 4492 5.10. Note that this octet string (Z in IEEE 1363 terminology) as output by
+             * FE2OSP, the Field Element to Octet String Conversion Primitive, has constant length for
+             * any given field; leading zeros found in this octet string MUST NOT be truncated.
+             */
+            byte[] secret = BigIntegers.AsUnsignedByteArray(basicAgreement.GetFieldSize(), agreementValue);
+            return crypto.AdoptLocalSecret(secret);
+        }
+
+        public static ECDomainParameters GetDomainParameters(TlsECConfig ecConfig)
+        {
+            return GetDomainParameters(ecConfig.NamedGroup);
+        }
+
+        public static ECDomainParameters GetDomainParameters(int namedGroup)
+        {
+            if (!NamedGroup.RefersToASpecificCurve(namedGroup))
+                return null;
+
+            // Parameters are lazily created the first time a particular curve is accessed
+
+            string curveName = NamedGroup.GetCurveName(namedGroup);
+            X9ECParameters ecP = CustomNamedCurves.GetByName(curveName);
+            if (ecP == null)
+            {
+                ecP = ECNamedCurveTable.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());
+        }
+
+        protected readonly BcTlsCrypto m_crypto;
+        protected readonly TlsECConfig m_ecConfig;
+        protected readonly ECDomainParameters m_ecDomainParameters;
+
+        public BcTlsECDomain(BcTlsCrypto crypto, TlsECConfig ecConfig)
+        {
+            this.m_crypto = crypto;
+            this.m_ecConfig = ecConfig;
+            this.m_ecDomainParameters = GetDomainParameters(ecConfig);
+        }
+
+        public virtual BcTlsSecret CalculateECDHAgreement(ECPrivateKeyParameters privateKey,
+            ECPublicKeyParameters publicKey)
+        {
+            return CalculateBasicAgreement(m_crypto, privateKey, publicKey);
+        }
+
+        public virtual TlsAgreement CreateECDH()
+        {
+            return new BcTlsECDH(this);
+        }
+
+        public virtual ECPoint DecodePoint(byte[] encoding)
+        {
+            return m_ecDomainParameters.Curve.DecodePoint(encoding);
+        }
+
+        public virtual ECPublicKeyParameters DecodePublicKey(byte[] encoding)
+        {
+            try
+            {
+                ECPoint point = DecodePoint(encoding);
+
+                return new ECPublicKeyParameters(point, m_ecDomainParameters);
+            }
+            catch (IOException e)
+            {
+                throw e;
+            }
+            catch (Exception e)
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter, e);
+            }
+        }
+
+        public virtual byte[] EncodePoint(ECPoint point)
+        {
+            return point.GetEncoded(false);
+        }
+
+        public virtual byte[] EncodePublicKey(ECPublicKeyParameters publicKey)
+        {
+            return EncodePoint(publicKey.Q);
+        }
+
+        public virtual AsymmetricCipherKeyPair GenerateKeyPair()
+        {
+            ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
+            keyPairGenerator.Init(new ECKeyGenerationParameters(m_ecDomainParameters, m_crypto.SecureRandom));
+            return keyPairGenerator.GenerateKeyPair();
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Signer.cs b/crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Signer.cs
new file mode 100644
index 000000000..590176772
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Signer.cs
@@ -0,0 +1,47 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Implementation class for generation of ECDSA signatures in TLS 1.3+ using the BC light-weight API.
+    /// </summary>
+    public class BcTlsECDsa13Signer
+        : BcTlsSigner
+    {
+        private readonly int m_signatureScheme;
+
+        public BcTlsECDsa13Signer(BcTlsCrypto crypto, ECPrivateKeyParameters privateKey, int signatureScheme)
+            : base(crypto, privateKey)
+        {
+            if (!SignatureScheme.IsECDsa(signatureScheme))
+                throw new ArgumentException("signatureScheme");
+
+            this.m_signatureScheme = signatureScheme;
+        }
+
+        public override byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash)
+        {
+            if (algorithm == null || SignatureScheme.From(algorithm) != m_signatureScheme)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(m_signatureScheme);
+            IDsa dsa = new ECDsaSigner(new HMacDsaKCalculator(m_crypto.CreateDigest(cryptoHashAlgorithm)));
+
+            ISigner signer = new DsaDigestSigner(dsa, new NullDigest());
+            signer.Init(true, new ParametersWithRandom(m_privateKey, m_crypto.SecureRandom));
+            signer.BlockUpdate(hash, 0, hash.Length);
+            try
+            {
+                return signer.GenerateSignature();
+            }
+            catch (CryptoException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Verifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Verifier.cs
new file mode 100644
index 000000000..8488dc8d2
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Verifier.cs
@@ -0,0 +1,41 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Implementation class for verification of ECDSA signatures in TLS 1.3+ using the BC light-weight API.
+    /// </summary>
+    public class BcTlsECDsa13Verifier
+        : BcTlsVerifier
+    {
+        private readonly int m_signatureScheme;
+
+        public BcTlsECDsa13Verifier(BcTlsCrypto crypto, ECPublicKeyParameters publicKey, int signatureScheme)
+            : base(crypto, publicKey)
+        {
+            if (!SignatureScheme.IsECDsa(signatureScheme))
+                throw new ArgumentException("signatureScheme");
+
+            this.m_signatureScheme = signatureScheme;
+        }
+
+        public override bool VerifyRawSignature(DigitallySigned signature, byte[] hash)
+        {
+            SignatureAndHashAlgorithm algorithm = signature.Algorithm;
+            if (algorithm == null || SignatureScheme.From(algorithm) != m_signatureScheme)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(m_signatureScheme);
+            IDsa dsa = new ECDsaSigner(new HMacDsaKCalculator(m_crypto.CreateDigest(cryptoHashAlgorithm)));
+
+            ISigner signer = new DsaDigestSigner(dsa, new NullDigest());
+            signer.Init(false, m_publicKey);
+            signer.BlockUpdate(hash, 0, hash.Length);
+            return signer.VerifySignature(signature.Signature);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsECDsaSigner.cs b/crypto/src/tls/crypto/impl/bc/BcTlsECDsaSigner.cs
new file mode 100644
index 000000000..8b29912af
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsECDsaSigner.cs
@@ -0,0 +1,29 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Implementation class for generation of the raw ECDSA signature type using the BC light-weight API.
+    /// </summary>
+    public class BcTlsECDsaSigner
+        : BcTlsDssSigner
+    {
+        public BcTlsECDsaSigner(BcTlsCrypto crypto, ECPrivateKeyParameters privateKey)
+            : base(crypto, privateKey)
+        {
+        }
+
+        protected override IDsa CreateDsaImpl(int cryptoHashAlgorithm)
+        {
+            return new ECDsaSigner(new HMacDsaKCalculator(m_crypto.CreateDigest(cryptoHashAlgorithm)));
+        }
+
+        protected override short SignatureAlgorithm
+        {
+            get { return Tls.SignatureAlgorithm.ecdsa; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsECDsaVerifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsECDsaVerifier.cs
new file mode 100644
index 000000000..1cd6ff1ea
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsECDsaVerifier.cs
@@ -0,0 +1,29 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Implementation class for the verification of the raw ECDSA signature type using the BC light-weight
+    /// API.</summary>
+    public class BcTlsECDsaVerifier
+        : BcTlsDssVerifier
+    {
+        public BcTlsECDsaVerifier(BcTlsCrypto crypto, ECPublicKeyParameters publicKey)
+            : base(crypto, publicKey)
+        {
+        }
+
+        protected override IDsa CreateDsaImpl(int cryptoHashAlgorithm)
+        {
+            return new ECDsaSigner(new HMacDsaKCalculator(m_crypto.CreateDigest(cryptoHashAlgorithm)));
+        }
+
+        protected override short SignatureAlgorithm
+        {
+            get { return Tls.SignatureAlgorithm.ecdsa; }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsEd25519Signer.cs b/crypto/src/tls/crypto/impl/bc/BcTlsEd25519Signer.cs
new file mode 100644
index 000000000..c8fc99e18
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsEd25519Signer.cs
@@ -0,0 +1,32 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public class BcTlsEd25519Signer
+        : BcTlsSigner
+    {
+        public BcTlsEd25519Signer(BcTlsCrypto crypto, Ed25519PrivateKeyParameters privateKey)
+            : base(crypto, privateKey)
+        {
+        }
+
+        public override byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override TlsStreamSigner GetStreamSigner(SignatureAndHashAlgorithm algorithm)
+        {
+            if (algorithm == null || SignatureScheme.From(algorithm) != SignatureScheme.ed25519)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            Ed25519Signer signer = new Ed25519Signer();
+            signer.Init(true, m_privateKey);
+
+            return new BcTlsStreamSigner(signer);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsEd25519Verifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsEd25519Verifier.cs
new file mode 100644
index 000000000..fb91442d9
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsEd25519Verifier.cs
@@ -0,0 +1,33 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public class BcTlsEd25519Verifier
+        : BcTlsVerifier
+    {
+        public BcTlsEd25519Verifier(BcTlsCrypto crypto, Ed25519PublicKeyParameters publicKey)
+            : base(crypto, publicKey)
+        {
+        }
+
+        public override bool VerifyRawSignature(DigitallySigned signature, byte[] hash)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override TlsStreamVerifier GetStreamVerifier(DigitallySigned signature)
+        {
+            SignatureAndHashAlgorithm algorithm = signature.Algorithm;
+            if (algorithm == null || SignatureScheme.From(algorithm) != SignatureScheme.ed25519)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            Ed25519Signer verifier = new Ed25519Signer();
+            verifier.Init(false, m_publicKey);
+
+            return new BcTlsStreamVerifier(verifier, signature.Signature);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsEd448Signer.cs b/crypto/src/tls/crypto/impl/bc/BcTlsEd448Signer.cs
new file mode 100644
index 000000000..06d4d7ad9
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsEd448Signer.cs
@@ -0,0 +1,32 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public class BcTlsEd448Signer
+        : BcTlsSigner
+    {
+        public BcTlsEd448Signer(BcTlsCrypto crypto, Ed448PrivateKeyParameters privateKey)
+            : base(crypto, privateKey)
+        {
+        }
+
+        public override byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override TlsStreamSigner GetStreamSigner(SignatureAndHashAlgorithm algorithm)
+        {
+            if (algorithm == null || SignatureScheme.From(algorithm) != SignatureScheme.ed448)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            Ed448Signer signer = new Ed448Signer(TlsUtilities.EmptyBytes);
+            signer.Init(true, m_privateKey);
+
+            return new BcTlsStreamSigner(signer);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsEd448Verifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsEd448Verifier.cs
new file mode 100644
index 000000000..4492fdd60
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsEd448Verifier.cs
@@ -0,0 +1,33 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public class BcTlsEd448Verifier
+        : BcTlsVerifier
+    {
+        public BcTlsEd448Verifier(BcTlsCrypto crypto, Ed448PublicKeyParameters publicKey)
+            : base(crypto, publicKey)
+        {
+        }
+
+        public override bool VerifyRawSignature(DigitallySigned signature, byte[] hash)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override TlsStreamVerifier GetStreamVerifier(DigitallySigned signature)
+        {
+            SignatureAndHashAlgorithm algorithm = signature.Algorithm;
+            if (algorithm == null || SignatureScheme.From(algorithm) != SignatureScheme.ed448)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            Ed448Signer verifier = new Ed448Signer(TlsUtilities.EmptyBytes);
+            verifier.Init(false, m_publicKey);
+
+            return new BcTlsStreamVerifier(verifier, signature.Signature);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsHash.cs b/crypto/src/tls/crypto/impl/bc/BcTlsHash.cs
new file mode 100644
index 000000000..0b35831f3
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsHash.cs
@@ -0,0 +1,49 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsHash
+        : TlsHash
+    {
+        private readonly BcTlsCrypto m_crypto;
+        private readonly int m_cryptoHashAlgorithm;
+        private readonly IDigest m_digest;
+
+        internal BcTlsHash(BcTlsCrypto crypto, int cryptoHashAlgorithm)
+            : this(crypto, cryptoHashAlgorithm, crypto.CreateDigest(cryptoHashAlgorithm))
+        {
+        }
+
+        private BcTlsHash(BcTlsCrypto crypto, int cryptoHashAlgorithm, IDigest digest)
+        {
+            this.m_crypto = crypto;
+            this.m_cryptoHashAlgorithm = cryptoHashAlgorithm;
+            this.m_digest = digest;
+        }
+
+        public void Update(byte[] data, int offSet, int length)
+        {
+            m_digest.BlockUpdate(data, offSet, length);
+        }
+
+        public byte[] CalculateHash()
+        {
+            byte[] rv = new byte[m_digest.GetDigestSize()];
+            m_digest.DoFinal(rv, 0);
+            return rv;
+        }
+
+        public TlsHash CloneHash()
+        {
+            IDigest clone = m_crypto.CloneDigest(m_cryptoHashAlgorithm, m_digest);
+            return new BcTlsHash(m_crypto, m_cryptoHashAlgorithm, clone);
+        }
+
+        public void Reset()
+        {
+            m_digest.Reset();
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs b/crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs
new file mode 100644
index 000000000..485a3f744
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs
@@ -0,0 +1,55 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsHmac
+        : TlsHmac
+    {
+        private readonly HMac m_hmac;
+
+        internal BcTlsHmac(HMac hmac)
+        {
+            this.m_hmac = hmac;
+        }
+
+        public void SetKey(byte[] key, int keyOff, int keyLen)
+        {
+            m_hmac.Init(new KeyParameter(key, keyOff, keyLen));
+        }
+
+        public void Update(byte[] input, int inOff, int length)
+        {
+            m_hmac.BlockUpdate(input, inOff, length);
+        }
+
+        public byte[] CalculateMac()
+        {
+            byte[] rv = new byte[m_hmac.GetMacSize()];
+            m_hmac.DoFinal(rv, 0);
+            return rv;
+        }
+
+        public void CalculateMac(byte[] output, int outOff)
+        {
+            m_hmac.DoFinal(output, outOff);
+        }
+
+        public int InternalBlockSize
+        {
+            get { return m_hmac.GetUnderlyingDigest().GetByteLength(); }
+        }
+
+        public int MacLength
+        {
+            get { return m_hmac.GetMacSize(); }
+        }
+
+        public void Reset()
+        {
+            m_hmac.Reset();
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsNonceGenerator.cs b/crypto/src/tls/crypto/impl/bc/BcTlsNonceGenerator.cs
new file mode 100644
index 000000000..d33c622a6
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsNonceGenerator.cs
@@ -0,0 +1,24 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Prng;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsNonceGenerator
+        : TlsNonceGenerator
+    {
+        private readonly IRandomGenerator m_randomGenerator;
+
+        internal BcTlsNonceGenerator(IRandomGenerator randomGenerator)
+        {
+            this.m_randomGenerator = randomGenerator;
+        }
+
+        public byte[] GenerateNonce(int size)
+        {
+            byte[] nonce = new byte[size];
+            m_randomGenerator.NextBytes(nonce);
+            return nonce;
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsRsaEncryptor.cs b/crypto/src/tls/crypto/impl/bc/BcTlsRsaEncryptor.cs
new file mode 100644
index 000000000..1582ff638
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsRsaEncryptor.cs
@@ -0,0 +1,47 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Encodings;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Parameters;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsRsaEncryptor
+        : TlsEncryptor
+    {
+        private static RsaKeyParameters CheckPublicKey(RsaKeyParameters pubKeyRsa)
+        {
+            if (null == pubKeyRsa || pubKeyRsa.IsPrivate)
+                throw new ArgumentException("No public RSA key provided", "pubKeyRsa");
+
+            return pubKeyRsa;
+        }
+
+        private readonly BcTlsCrypto m_crypto;
+        private readonly RsaKeyParameters m_pubKeyRsa;
+
+        internal BcTlsRsaEncryptor(BcTlsCrypto crypto, RsaKeyParameters pubKeyRsa)
+        {
+            this.m_crypto = crypto;
+            this.m_pubKeyRsa = CheckPublicKey(pubKeyRsa);
+        }
+
+        public byte[] Encrypt(byte[] input, int inOff, int length)
+        {
+            try
+            {
+                Pkcs1Encoding encoding = new Pkcs1Encoding(new RsaBlindedEngine());
+                encoding.Init(true, new ParametersWithRandom(m_pubKeyRsa, m_crypto.SecureRandom));
+                return encoding.ProcessBlock(input, inOff, length);
+            }
+            catch (InvalidCipherTextException e)
+            {
+                /*
+                 * This should never happen, only during decryption.
+                 */
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsRsaPssSigner.cs b/crypto/src/tls/crypto/impl/bc/BcTlsRsaPssSigner.cs
new file mode 100644
index 000000000..2c1b41120
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsRsaPssSigner.cs
@@ -0,0 +1,44 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Operator supporting the generation of RSASSA-PSS signatures using the BC light-weight API.</summary>
+    public class BcTlsRsaPssSigner
+        : BcTlsSigner
+    {
+        private readonly int m_signatureScheme;
+
+        public BcTlsRsaPssSigner(BcTlsCrypto crypto, RsaKeyParameters privateKey, int signatureScheme)
+            : base(crypto, privateKey)
+        {
+            if (!SignatureScheme.IsRsaPss(signatureScheme))
+                throw new ArgumentException("signatureScheme");
+
+            this.m_signatureScheme = signatureScheme;
+        }
+
+        public override byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override TlsStreamSigner GetStreamSigner(SignatureAndHashAlgorithm algorithm)
+        {
+            if (algorithm == null || SignatureScheme.From(algorithm) != m_signatureScheme)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(m_signatureScheme);
+            IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
+
+            PssSigner signer = new PssSigner(new RsaBlindedEngine(), digest, digest.GetDigestSize());
+            signer.Init(true, new ParametersWithRandom(m_privateKey, m_crypto.SecureRandom));
+
+            return new BcTlsStreamSigner(signer);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsRsaPssVerifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsRsaPssVerifier.cs
new file mode 100644
index 000000000..1b14d1ab4
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsRsaPssVerifier.cs
@@ -0,0 +1,45 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Signers;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Operator supporting the verification of RSASSA-PSS signatures using the BC light-weight API.</summary>
+    public class BcTlsRsaPssVerifier
+        : BcTlsVerifier
+    {
+        private readonly int m_signatureScheme;
+
+        public BcTlsRsaPssVerifier(BcTlsCrypto crypto, RsaKeyParameters publicKey, int signatureScheme)
+            : base(crypto, publicKey)
+        {
+            if (!SignatureScheme.IsRsaPss(signatureScheme))
+                throw new ArgumentException("signatureScheme");
+
+            this.m_signatureScheme = signatureScheme;
+        }
+
+        public override bool VerifyRawSignature(DigitallySigned signature, byte[] hash)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override TlsStreamVerifier GetStreamVerifier(DigitallySigned signature)
+        {
+            SignatureAndHashAlgorithm algorithm = signature.Algorithm;
+            if (algorithm == null || SignatureScheme.From(algorithm) != m_signatureScheme)
+                throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+            int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(m_signatureScheme);
+            IDigest digest = m_crypto.CreateDigest(cryptoHashAlgorithm);
+
+            PssSigner verifier = new PssSigner(new RsaEngine(), digest, digest.GetDigestSize());
+            verifier.Init(false, m_publicKey);
+
+            return new BcTlsStreamVerifier(verifier, signature.Signature);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsRsaSigner.cs b/crypto/src/tls/crypto/impl/bc/BcTlsRsaSigner.cs
new file mode 100644
index 000000000..0e741a57a
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsRsaSigner.cs
@@ -0,0 +1,71 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+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;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Operator supporting the generation of RSASSA-PKCS1-v1_5 signatures using the BC light-weight API.
+    /// </summary>
+    public class BcTlsRsaSigner
+        : BcTlsSigner
+    {
+        private readonly RsaKeyParameters m_publicKey;
+
+        public BcTlsRsaSigner(BcTlsCrypto crypto, RsaKeyParameters privateKey, RsaKeyParameters publicKey)
+            : base(crypto, privateKey)
+        {
+            this.m_publicKey = publicKey;
+        }
+
+        public override byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash)
+        {
+            IDigest nullDigest = new NullDigest();
+
+            ISigner signer;
+            if (algorithm != null)
+            {
+                if (algorithm.Signature != SignatureAlgorithm.rsa)
+                    throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+                /*
+                 * RFC 5246 4.7. In RSA signing, the opaque vector contains the signature generated
+                 * using the RSASSA-PKCS1-v1_5 signature scheme defined in [PKCS1].
+                 */
+                signer = new RsaDigestSigner(nullDigest, TlsUtilities.GetOidForHashAlgorithm(algorithm.Hash));
+            }
+            else
+            {
+                /*
+                 * RFC 5246 4.7. Note that earlier versions of TLS used a different RSA signature scheme
+                 * that did not include a DigestInfo encoding.
+                 */
+                signer = new GenericSigner(new Pkcs1Encoding(new RsaBlindedEngine()), nullDigest);
+            }
+            signer.Init(true, new ParametersWithRandom(m_privateKey, m_crypto.SecureRandom));
+            signer.BlockUpdate(hash, 0, hash.Length);
+            try
+            {
+                byte[] signature = signer.GenerateSignature();
+
+                signer.Init(false, m_publicKey);
+                signer.BlockUpdate(hash, 0, hash.Length);
+
+                if (signer.VerifySignature(signature))
+                {
+                    return signature;
+                }
+            }
+            catch (CryptoException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsRsaVerifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsRsaVerifier.cs
new file mode 100644
index 000000000..41f1bb1be
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsRsaVerifier.cs
@@ -0,0 +1,52 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+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;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Operator supporting the verification of RSASSA-PKCS1-v1_5 signatures using the BC light-weight API.
+    /// </summary>
+    public class BcTlsRsaVerifier
+        : BcTlsVerifier
+    {
+        public BcTlsRsaVerifier(BcTlsCrypto crypto, RsaKeyParameters publicKey)
+            : base(crypto, publicKey)
+        {
+        }
+
+        public override bool VerifyRawSignature(DigitallySigned signedParams, byte[] hash)
+        {
+            IDigest nullDigest = new NullDigest();
+
+            SignatureAndHashAlgorithm algorithm = signedParams.Algorithm;
+            ISigner signer;
+            if (algorithm != null)
+            {
+                if (algorithm.Signature != SignatureAlgorithm.rsa)
+                    throw new InvalidOperationException("Invalid algorithm: " + algorithm);
+
+                /*
+                 * RFC 5246 4.7. In RSA signing, the opaque vector contains the signature generated
+                 * using the RSASSA-PKCS1-v1_5 signature scheme defined in [PKCS1].
+                 */
+                signer = new RsaDigestSigner(nullDigest, TlsUtilities.GetOidForHashAlgorithm(algorithm.Hash));
+            }
+            else
+            {
+                /*
+                 * RFC 5246 4.7. Note that earlier versions of TLS used a different RSA signature scheme
+                 * that did not include a DigestInfo encoding.
+                 */
+                signer = new GenericSigner(new Pkcs1Encoding(new RsaBlindedEngine()), nullDigest);
+            }
+            signer.Init(false, m_publicKey);
+            signer.BlockUpdate(hash, 0, hash.Length);
+            return signer.VerifySignature(signedParams.Signature);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs b/crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs
new file mode 100644
index 000000000..b76c1612a
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs
@@ -0,0 +1,242 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Macs;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>BC light-weight support class for handling TLS secrets and deriving key material and other secrets
+    /// from them.</summary>
+    public class BcTlsSecret
+        : AbstractTlsSecret
+    {
+        // SSL3 magic mix constants ("A", "BB", "CCC", ...)
+        private static readonly byte[] Ssl3Const = GenerateSsl3Constants();
+
+        private static byte[] GenerateSsl3Constants()
+        {
+            int n = 15;
+            byte[] result = new byte[n * (n + 1) / 2];
+            int pos = 0;
+            for (int i = 0; i < n; ++i)
+            {
+                byte b = (byte)('A' + i);
+                for (int j = 0; j <= i; ++j)
+                {
+                    result[pos++] = b;
+                }
+            }
+            return result;
+        }
+
+        protected readonly BcTlsCrypto m_crypto;
+
+        public BcTlsSecret(BcTlsCrypto crypto, byte[] data)
+            : base(data)
+        {
+            this.m_crypto = crypto;
+        }
+
+        public override TlsSecret DeriveUsingPrf(int prfAlgorithm, string label, byte[] seed, int length)
+        {
+            lock (this)
+            {
+                CheckAlive();
+
+                switch (prfAlgorithm)
+                {
+                case PrfAlgorithm.tls13_hkdf_sha256:
+                    return TlsCryptoUtilities.HkdfExpandLabel(this, CryptoHashAlgorithm.sha256, label, seed, length);
+                case PrfAlgorithm.tls13_hkdf_sha384:
+                    return TlsCryptoUtilities.HkdfExpandLabel(this, CryptoHashAlgorithm.sha384, label, seed, length);
+                case PrfAlgorithm.tls13_hkdf_sm3:
+                    return TlsCryptoUtilities.HkdfExpandLabel(this, CryptoHashAlgorithm.sm3, label, seed, length);
+                default:
+                    return m_crypto.AdoptLocalSecret(Prf(prfAlgorithm, label, seed, length));
+                }
+            }
+        }
+
+        public override TlsSecret HkdfExpand(int cryptoHashAlgorithm, byte[] info, int length)
+        {
+            lock (this)
+            {
+                if (length < 1)
+                    return m_crypto.AdoptLocalSecret(TlsUtilities.EmptyBytes);
+
+                int hashLen = TlsCryptoUtilities.GetHashOutputSize(cryptoHashAlgorithm);
+                if (length > (255 * hashLen))
+                    throw new ArgumentException("must be <= 255 * (output size of 'hashAlgorithm')", "length");
+
+                CheckAlive();
+
+                byte[] prk = m_data;
+
+                HMac hmac = new HMac(m_crypto.CreateDigest(cryptoHashAlgorithm));
+                hmac.Init(new KeyParameter(prk));
+
+                byte[] okm = new byte[length];
+
+                byte[] t = new byte[hashLen];
+                byte counter = 0x00;
+
+                int pos = 0;
+                for (; ; )
+                {
+                    hmac.BlockUpdate(info, 0, info.Length);
+                    hmac.Update((byte)++counter);
+                    hmac.DoFinal(t, 0);
+
+                    int remaining = length - pos;
+                    if (remaining <= hashLen)
+                    {
+                        Array.Copy(t, 0, okm, pos, remaining);
+                        break;
+                    }
+
+                    Array.Copy(t, 0, okm, pos, hashLen);
+                    pos += hashLen;
+                    hmac.BlockUpdate(t, 0, t.Length);
+                }
+
+                return m_crypto.AdoptLocalSecret(okm);
+            }
+        }
+
+        public override TlsSecret HkdfExtract(int cryptoHashAlgorithm, byte[] ikm)
+        {
+            lock (this)
+            {
+                CheckAlive();
+
+                byte[] salt = m_data;
+                this.m_data = null;
+
+                HMac hmac = new HMac(m_crypto.CreateDigest(cryptoHashAlgorithm));
+                hmac.Init(new KeyParameter(salt));
+
+                hmac.BlockUpdate(ikm, 0, ikm.Length);
+
+                byte[] prk = new byte[hmac.GetMacSize()];
+                hmac.DoFinal(prk, 0);
+
+                return m_crypto.AdoptLocalSecret(prk);
+            }
+        }
+
+        protected override AbstractTlsCrypto Crypto
+        {
+            get { return m_crypto; }
+        }
+
+        protected virtual void HmacHash(IDigest digest, byte[] secret, int secretOff, int secretLen, byte[] seed,
+            byte[] output)
+        {
+            HMac mac = new HMac(digest);
+            mac.Init(new KeyParameter(secret, secretOff, secretLen));
+
+            byte[] a = seed;
+
+            int macSize = mac.GetMacSize();
+
+            byte[] b1 = new byte[macSize];
+            byte[] b2 = new byte[macSize];
+
+            int pos = 0;
+            while (pos < output.Length)
+            {
+                mac.BlockUpdate(a, 0, a.Length);
+                mac.DoFinal(b1, 0);
+                a = b1;
+                mac.BlockUpdate(a, 0, a.Length);
+                mac.BlockUpdate(seed, 0, seed.Length);
+                mac.DoFinal(b2, 0);
+                Array.Copy(b2, 0, output, pos, System.Math.Min(macSize, output.Length - pos));
+                pos += macSize;
+            }
+        }
+
+        protected virtual byte[] Prf(int prfAlgorithm, string label, byte[] seed, int length)
+        {
+            if (PrfAlgorithm.ssl_prf_legacy == prfAlgorithm)
+                return Prf_Ssl(seed, length);
+
+            byte[] labelSeed = Arrays.Concatenate(Strings.ToByteArray(label), seed);
+
+            if (PrfAlgorithm.tls_prf_legacy == prfAlgorithm)
+                return Prf_1_0(labelSeed, length);
+
+            return Prf_1_2(prfAlgorithm, labelSeed, length);
+        }
+
+        protected virtual byte[] Prf_Ssl(byte[] seed, int length)
+        {
+            IDigest md5 = m_crypto.CreateDigest(CryptoHashAlgorithm.md5);
+            IDigest sha1 = m_crypto.CreateDigest(CryptoHashAlgorithm.sha1);
+
+            int md5Size = md5.GetDigestSize();
+            int sha1Size = sha1.GetDigestSize();
+
+            byte[] tmp = new byte[System.Math.Max(md5Size, sha1Size)];
+            byte[] result = new byte[length];
+
+            int constLen = 1, constPos = 0, resultPos = 0;
+            while (resultPos < length)
+            {
+                sha1.BlockUpdate(Ssl3Const, constPos, constLen);
+                constPos += constLen++;
+
+                sha1.BlockUpdate(m_data, 0, m_data.Length);
+                sha1.BlockUpdate(seed, 0, seed.Length);
+                sha1.DoFinal(tmp, 0);
+
+                md5.BlockUpdate(m_data, 0, m_data.Length);
+                md5.BlockUpdate(tmp, 0, sha1Size);
+
+                int remaining = length - resultPos;
+                if (remaining < md5Size)
+                {
+                    md5.DoFinal(tmp, 0);
+                    Array.Copy(tmp, 0, result, resultPos, remaining);
+                    resultPos += remaining;
+                }
+                else
+                {
+                    md5.DoFinal(result, resultPos);
+                    resultPos += md5Size;
+                }
+            }
+
+            return result;
+        }
+
+        protected virtual byte[] Prf_1_0(byte[] labelSeed, int length)
+        {
+            int s_half = (m_data.Length + 1) / 2;
+
+            IDigest md5 = m_crypto.CreateDigest(CryptoHashAlgorithm.md5);
+            byte[] b1 = new byte[length];
+            HmacHash(md5, m_data, 0, s_half, labelSeed, b1);
+
+            IDigest sha1 = m_crypto.CreateDigest(CryptoHashAlgorithm.sha1);
+            byte[] b2 = new byte[length];
+            HmacHash(sha1, m_data, m_data.Length - s_half, s_half, labelSeed, b2);
+
+            for (int i = 0; i < length; i++)
+            {
+                b1[i] ^= b2[i];
+            }
+            return b1;
+        }
+
+        protected virtual byte[] Prf_1_2(int prfAlgorithm, byte[] labelSeed, int length)
+        {
+            IDigest digest = m_crypto.CreateDigest(TlsCryptoUtilities.GetHashForPrf(prfAlgorithm));
+            byte[] result = new byte[length];
+            HmacHash(digest, m_data, 0, m_data.Length, labelSeed, result);
+            return result;
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsSigner.cs b/crypto/src/tls/crypto/impl/bc/BcTlsSigner.cs
new file mode 100644
index 000000000..841803043
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsSigner.cs
@@ -0,0 +1,33 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public abstract class BcTlsSigner
+        : TlsSigner
+    {
+        protected readonly BcTlsCrypto m_crypto;
+        protected readonly AsymmetricKeyParameter m_privateKey;
+
+        protected BcTlsSigner(BcTlsCrypto crypto, AsymmetricKeyParameter privateKey)
+        {
+            if (crypto == null)
+                throw new ArgumentNullException("crypto");
+            if (privateKey == null)
+                throw new ArgumentNullException("privateKey");
+            if (!privateKey.IsPrivate)
+                throw new ArgumentException("must be private", "privateKey");
+
+            this.m_crypto = crypto;
+            this.m_privateKey = privateKey;
+        }
+
+        public abstract byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash);
+
+        public virtual TlsStreamSigner GetStreamSigner(SignatureAndHashAlgorithm algorithm)
+        {
+            return null;
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsSrp6Client.cs b/crypto/src/tls/crypto/impl/bc/BcTlsSrp6Client.cs
new file mode 100644
index 000000000..de4b9bd50
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsSrp6Client.cs
@@ -0,0 +1,36 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Agreement.Srp;
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsSrp6Client
+        : TlsSrp6Client
+    {
+        private readonly Srp6Client m_srp6Client;
+
+        internal BcTlsSrp6Client(Srp6Client srpClient)
+        {
+            this.m_srp6Client = srpClient;
+        }
+
+        public BigInteger CalculateSecret(BigInteger serverB)
+        {
+            try
+            {
+                return m_srp6Client.CalculateSecret(serverB);
+            }
+            catch (CryptoException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter, e);
+            }
+        }
+
+        public BigInteger GenerateClientCredentials(byte[] srpSalt, byte[] identity, byte[] password)
+        {
+            return m_srp6Client.GenerateClientCredentials(srpSalt, identity, password);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsSrp6Server.cs b/crypto/src/tls/crypto/impl/bc/BcTlsSrp6Server.cs
new file mode 100644
index 000000000..8ee79f627
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsSrp6Server.cs
@@ -0,0 +1,36 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Agreement.Srp;
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsSrp6Server
+        : TlsSrp6Server
+    {
+        private readonly Srp6Server m_srp6Server;
+
+        internal BcTlsSrp6Server(Srp6Server srp6Server)
+        {
+            this.m_srp6Server = srp6Server;
+        }
+
+        public BigInteger GenerateServerCredentials()
+        {
+            return m_srp6Server.GenerateServerCredentials();
+        }
+
+        public BigInteger CalculateSecret(BigInteger clientA)
+        {
+            try
+            {
+                return m_srp6Server.CalculateSecret(clientA);
+            }
+            catch (CryptoException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter, e);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsSrp6VerifierGenerator.cs b/crypto/src/tls/crypto/impl/bc/BcTlsSrp6VerifierGenerator.cs
new file mode 100644
index 000000000..3d9e23a29
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsSrp6VerifierGenerator.cs
@@ -0,0 +1,23 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Agreement.Srp;
+using Org.BouncyCastle.Math;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsSrp6VerifierGenerator
+        : TlsSrp6VerifierGenerator
+    {
+        private readonly Srp6VerifierGenerator m_srp6VerifierGenerator;
+
+        internal BcTlsSrp6VerifierGenerator(Srp6VerifierGenerator srp6VerifierGenerator)
+        {
+            this.m_srp6VerifierGenerator = srp6VerifierGenerator;
+        }
+
+        public BigInteger GenerateVerifier(byte[] salt, byte[] identity, byte[] password)
+        {
+            return m_srp6VerifierGenerator.GenerateVerifier(salt, identity, password);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsStreamSigner.cs b/crypto/src/tls/crypto/impl/bc/BcTlsStreamSigner.cs
new file mode 100644
index 000000000..158fb053e
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsStreamSigner.cs
@@ -0,0 +1,36 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsStreamSigner
+        : TlsStreamSigner
+    {
+        private readonly SignerSink m_output;
+
+        internal BcTlsStreamSigner(ISigner signer)
+        {
+            this.m_output = new SignerSink(signer);
+        }
+
+        public Stream GetOutputStream()
+        {
+            return m_output;
+        }
+
+        public byte[] GetSignature()
+        {
+            try
+            {
+                return m_output.Signer.GenerateSignature();
+            }
+            catch (CryptoException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsStreamVerifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsStreamVerifier.cs
new file mode 100644
index 000000000..0848a30dd
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsStreamVerifier.cs
@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcTlsStreamVerifier
+        : TlsStreamVerifier
+    {
+        private readonly SignerSink m_output;
+        private readonly byte[] m_signature;
+
+        internal BcTlsStreamVerifier(ISigner verifier, byte[] signature)
+        {
+            this.m_output = new SignerSink(verifier);
+            this.m_signature = signature;
+        }
+
+        public Stream GetOutputStream()
+        {
+            return m_output;
+        }
+
+        public bool IsVerified()
+        {
+            return m_output.Signer.VerifySignature(m_signature);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcTlsVerifier.cs b/crypto/src/tls/crypto/impl/bc/BcTlsVerifier.cs
new file mode 100644
index 000000000..dc8d21d74
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcTlsVerifier.cs
@@ -0,0 +1,33 @@
+using System;
+
+using Org.BouncyCastle.Crypto;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public abstract class BcTlsVerifier
+        : TlsVerifier
+    {
+        protected readonly BcTlsCrypto m_crypto;
+        protected readonly AsymmetricKeyParameter m_publicKey;
+
+        protected BcTlsVerifier(BcTlsCrypto crypto, AsymmetricKeyParameter publicKey)
+        {
+            if (crypto == null)
+                throw new ArgumentNullException("crypto");
+            if (publicKey == null)
+                throw new ArgumentNullException("publicKey");
+            if (publicKey.IsPrivate)
+                throw new ArgumentException("must be public", "publicKey");
+
+            this.m_crypto = crypto;
+            this.m_publicKey = publicKey;
+        }
+
+        public virtual TlsStreamVerifier GetStreamVerifier(DigitallySigned signature)
+        {
+            return null;
+        }
+
+        public abstract bool VerifyRawSignature(DigitallySigned signature, byte[] hash);
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcVerifyingStreamSigner.cs b/crypto/src/tls/crypto/impl/bc/BcVerifyingStreamSigner.cs
new file mode 100644
index 000000000..4d9506857
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcVerifyingStreamSigner.cs
@@ -0,0 +1,48 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.IO;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    internal sealed class BcVerifyingStreamSigner
+        : TlsStreamSigner
+    {
+        private readonly ISigner m_signer;
+        private readonly ISigner m_verifier;
+        private readonly TeeOutputStream m_output;
+
+        internal BcVerifyingStreamSigner(ISigner signer, ISigner verifier)
+        {
+            Stream outputSigner = new SignerSink(signer);
+            Stream outputVerifier = new SignerSink(verifier);
+
+            this.m_signer = signer;
+            this.m_verifier = verifier;
+            this.m_output = new TeeOutputStream(outputSigner, outputVerifier);
+        }
+
+        public Stream GetOutputStream()
+        {
+            return m_output;
+        }
+
+        public byte[] GetSignature()
+        {
+            try
+            {
+                byte[] signature = m_signer.GenerateSignature();
+                if (m_verifier.VerifySignature(signature))
+                    return signature;
+            }
+            catch (CryptoException e)
+            {
+                throw new TlsFatalAlert(AlertDescription.internal_error, e);
+            }
+
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcX25519.cs b/crypto/src/tls/crypto/impl/bc/BcX25519.cs
new file mode 100644
index 000000000..c26bc5155
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcX25519.cs
@@ -0,0 +1,54 @@
+using System;
+
+using Org.BouncyCastle.Math.EC.Rfc7748;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Support class for X25519 using the BC light-weight library.</summary>
+    public class BcX25519
+        : TlsAgreement
+    {
+        protected readonly BcTlsCrypto m_crypto;
+        protected readonly byte[] m_privateKey = new byte[X25519.ScalarSize];
+        protected readonly byte[] m_peerPublicKey = new byte[X25519.PointSize];
+
+        public BcX25519(BcTlsCrypto crypto)
+        {
+            this.m_crypto = crypto;
+        }
+
+        public virtual byte[] GenerateEphemeral()
+        {
+            m_crypto.SecureRandom.NextBytes(m_privateKey);
+
+            byte[] publicKey = new byte[X25519.PointSize];
+            X25519.ScalarMultBase(m_privateKey, 0, publicKey, 0);
+            return publicKey;
+        }
+
+        public virtual void ReceivePeerValue(byte[] peerValue)
+        {
+            if (peerValue == null || peerValue.Length != X25519.PointSize)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            Array.Copy(peerValue, 0, m_peerPublicKey, 0, X25519.PointSize);
+        }
+
+        public virtual TlsSecret CalculateSecret()
+        {
+            try
+            {
+                byte[] secret = new byte[X25519.PointSize];
+                if (!X25519.CalculateAgreement(m_privateKey, 0, m_peerPublicKey, 0, secret, 0))
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                return m_crypto.AdoptLocalSecret(secret);
+            }
+            finally
+            {
+                Array.Clear(m_privateKey, 0, m_privateKey.Length);
+                Array.Clear(m_peerPublicKey, 0, m_peerPublicKey.Length);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcX25519Domain.cs b/crypto/src/tls/crypto/impl/bc/BcX25519Domain.cs
new file mode 100644
index 000000000..58767cfce
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcX25519Domain.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public class BcX25519Domain
+        : TlsECDomain
+    {
+        protected readonly BcTlsCrypto m_crypto;
+
+        public BcX25519Domain(BcTlsCrypto crypto)
+        {
+            this.m_crypto = crypto;
+        }
+
+        public virtual TlsAgreement CreateECDH()
+        {
+            return new BcX25519(m_crypto);
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcX448.cs b/crypto/src/tls/crypto/impl/bc/BcX448.cs
new file mode 100644
index 000000000..ebeba4e8f
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcX448.cs
@@ -0,0 +1,54 @@
+using System;
+
+using Org.BouncyCastle.Math.EC.Rfc7748;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    /// <summary>Support class for X448 using the BC light-weight library.</summary>
+    public class BcX448
+        : TlsAgreement
+    {
+        protected readonly BcTlsCrypto m_crypto;
+        protected readonly byte[] m_privateKey = new byte[X448.ScalarSize];
+        protected readonly byte[] m_peerPublicKey = new byte[X448.PointSize];
+
+        public BcX448(BcTlsCrypto crypto)
+        {
+            this.m_crypto = crypto;
+        }
+
+        public virtual byte[] GenerateEphemeral()
+        {
+            m_crypto.SecureRandom.NextBytes(m_privateKey);
+
+            byte[] publicKey = new byte[X448.PointSize];
+            X448.ScalarMultBase(m_privateKey, 0, publicKey, 0);
+            return publicKey;
+        }
+
+        public virtual void ReceivePeerValue(byte[] peerValue)
+        {
+            if (peerValue == null || peerValue.Length != X448.PointSize)
+                throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+
+            Array.Copy(peerValue, 0, m_peerPublicKey, 0, X448.PointSize);
+        }
+
+        public virtual TlsSecret CalculateSecret()
+        {
+            try
+            {
+                byte[] secret = new byte[X448.PointSize];
+                if (!X448.CalculateAgreement(m_privateKey, 0, m_peerPublicKey, 0, secret, 0))
+                    throw new TlsFatalAlert(AlertDescription.handshake_failure);
+
+                return m_crypto.AdoptLocalSecret(secret);
+            }
+            finally
+            {
+                Array.Clear(m_privateKey, 0, m_privateKey.Length);
+                Array.Clear(m_peerPublicKey, 0, m_peerPublicKey.Length);
+            }
+        }
+    }
+}
diff --git a/crypto/src/tls/crypto/impl/bc/BcX448Domain.cs b/crypto/src/tls/crypto/impl/bc/BcX448Domain.cs
new file mode 100644
index 000000000..757fa8fcc
--- /dev/null
+++ b/crypto/src/tls/crypto/impl/bc/BcX448Domain.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Crypto.Impl.BC
+{
+    public class BcX448Domain
+        : TlsECDomain
+    {
+        protected readonly BcTlsCrypto m_crypto;
+
+        public BcX448Domain(BcTlsCrypto crypto)
+        {
+            this.m_crypto = crypto;
+        }
+
+        public virtual TlsAgreement CreateECDH()
+        {
+            return new BcX448(m_crypto);
+        }
+    }
+}
diff --git a/crypto/test/UnitTests.csproj b/crypto/test/UnitTests.csproj
index 0c0184d42..c5fdecd54 100644
--- a/crypto/test/UnitTests.csproj
+++ b/crypto/test/UnitTests.csproj
@@ -467,6 +467,48 @@
     <Compile Include="src\test\nist\NistCertPathTest.cs" />
     <Compile Include="src\test\nist\NistCertPathTest2.cs" />
     <Compile Include="src\test\rsa3\RSA3CertTest.cs" />
+    <Compile Include="src\tls\crypto\test\BcTlsCryptoTest.cs" />
+    <Compile Include="src\tls\test\ByteQueueInputStreamTest.cs" />
+    <Compile Include="src\tls\test\CertChainUtilities.cs" />
+    <Compile Include="src\tls\test\DtlsProtocolTest.cs" />
+    <Compile Include="src\tls\test\DtlsPskProtocolTest.cs" />
+    <Compile Include="src\tls\test\DtlsTestCase.cs" />
+    <Compile Include="src\tls\test\DtlsTestClientProtocol.cs" />
+    <Compile Include="src\tls\test\DtlsTestServerProtocol.cs" />
+    <Compile Include="src\tls\test\DtlsTestSuite.cs" />
+    <Compile Include="src\tls\test\LoggingDatagramTransport.cs" />
+    <Compile Include="src\tls\test\MockDatagramAssociation.cs" />
+    <Compile Include="src\tls\test\MockDtlsClient.cs" />
+    <Compile Include="src\tls\test\MockDtlsServer.cs" />
+    <Compile Include="src\tls\test\MockPskDtlsClient.cs" />
+    <Compile Include="src\tls\test\MockPskDtlsServer.cs" />
+    <Compile Include="src\tls\test\MockPskTlsClient.cs" />
+    <Compile Include="src\tls\test\MockPskTlsServer.cs" />
+    <Compile Include="src\tls\test\MockSrpTlsClient.cs" />
+    <Compile Include="src\tls\test\MockSrpTlsServer.cs" />
+    <Compile Include="src\tls\test\MockTlsClient.cs" />
+    <Compile Include="src\tls\test\MockTlsServer.cs" />
+    <Compile Include="src\tls\test\NetworkStream.cs" />
+    <Compile Include="src\tls\test\PipedStream.cs" />
+    <Compile Include="src\tls\test\PrfTest.cs" />
+    <Compile Include="src\tls\test\PskTlsClientTest.cs" />
+    <Compile Include="src\tls\test\PskTlsServerTest.cs" />
+    <Compile Include="src\tls\test\TlsClientTest.cs" />
+    <Compile Include="src\tls\test\TlsProtocolNonBlockingTest.cs" />
+    <Compile Include="src\tls\test\TlsProtocolTest.cs" />
+    <Compile Include="src\tls\test\TlsPskProtocolTest.cs" />
+    <Compile Include="src\tls\test\TlsServerTest.cs" />
+    <Compile Include="src\tls\test\TlsSrpProtocolTest.cs" />
+    <Compile Include="src\tls\test\TlsTestCase.cs" />
+    <Compile Include="src\tls\test\TlsTestClientImpl.cs" />
+    <Compile Include="src\tls\test\TlsTestClientProtocol.cs" />
+    <Compile Include="src\tls\test\TlsTestConfig.cs" />
+    <Compile Include="src\tls\test\TlsTestServerImpl.cs" />
+    <Compile Include="src\tls\test\TlsTestServerProtocol.cs" />
+    <Compile Include="src\tls\test\TlsTestSuite.cs" />
+    <Compile Include="src\tls\test\TlsTestUtilities.cs" />
+    <Compile Include="src\tls\test\TlsUtilitiesTest.cs" />
+    <Compile Include="src\tls\test\UnreliableDatagramTransport.cs" />
     <Compile Include="src\tsp\test\AllTests.cs" />
     <Compile Include="src\tsp\test\GenTimeAccuracyTest.cs" />
     <Compile Include="src\tsp\test\ParseTest.cs" />
diff --git a/crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs b/crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs
new file mode 100644
index 000000000..a57212c73
--- /dev/null
+++ b/crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs
@@ -0,0 +1,820 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Tls.Tests;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Crypto.Tests
+{
+    [TestFixture]
+    public class BcTlsCryptoTest
+    {
+        private static readonly byte[] ClientHello = Hex("01 00 00 c0 03 03 cb 34 ec b1 e7 81 63"
+            + "ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12 ec 18 a2 ef 62 83"
+            + "02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00 00 91 00 00 00 0b"
+            + "00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00"
+            + "12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 23"
+            + "00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d e5 60 e4 bd 43 d2"
+            + "3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d 54 13 69 1e 52 9a"
+            + "af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03"
+            + "02 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06"
+            + "02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01");
+        private static readonly byte[] ServerHello = Hex("02 00 00 56 03 03 a6 af 06 a4 12 18 60"
+            + "dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14 34 da c1 55 77 2e"
+            + "d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00 1d 00 20 c9 82 88"
+            + "76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6 cc 25 3b 83 3d f1"
+            + "dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04");
+        private static readonly byte[] EncryptedExtensions = Hex("08 00 00 24 00 22 00 0a 00 14 00"
+            + "12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 1c"
+            + "00 02 40 01 00 00 00 00");
+        private static readonly byte[] Certificate = Hex("0b 00 01 b9 00 00 01 b5 00 01 b0 30 82"
+            + "01 ac 30 82 01 15 a0 03 02 01 02 02 01 02 30 0d 06 09 2a 86 48"
+            + "86 f7 0d 01 01 0b 05 00 30 0e 31 0c 30 0a 06 03 55 04 03 13 03"
+            + "72 73 61 30 1e 17 0d 31 36 30 37 33 30 30 31 32 33 35 39 5a 17"
+            + "0d 32 36 30 37 33 30 30 31 32 33 35 39 5a 30 0e 31 0c 30 0a 06"
+            + "03 55 04 03 13 03 72 73 61 30 81 9f 30 0d 06 09 2a 86 48 86 f7"
+            + "0d 01 01 01 05 00 03 81 8d 00 30 81 89 02 81 81 00 b4 bb 49 8f"
+            + "82 79 30 3d 98 08 36 39 9b 36 c6 98 8c 0c 68 de 55 e1 bd b8 26"
+            + "d3 90 1a 24 61 ea fd 2d e4 9a 91 d0 15 ab bc 9a 95 13 7a ce 6c"
+            + "1a f1 9e aa 6a f9 8c 7c ed 43 12 09 98 e1 87 a8 0e e0 cc b0 52"
+            + "4b 1b 01 8c 3e 0b 63 26 4d 44 9a 6d 38 e2 2a 5f da 43 08 46 74"
+            + "80 30 53 0e f0 46 1c 8c a9 d9 ef bf ae 8e a6 d1 d0 3e 2b d1 93"
+            + "ef f0 ab 9a 80 02 c4 74 28 a6 d3 5a 8d 88 d7 9f 7f 1e 3f 02 03"
+            + "01 00 01 a3 1a 30 18 30 09 06 03 55 1d 13 04 02 30 00 30 0b 06"
+            + "03 55 1d 0f 04 04 03 02 05 a0 30 0d 06 09 2a 86 48 86 f7 0d 01"
+            + "01 0b 05 00 03 81 81 00 85 aa d2 a0 e5 b9 27 6b 90 8c 65 f7 3a"
+            + "72 67 17 06 18 a5 4c 5f 8a 7b 33 7d 2d f7 a5 94 36 54 17 f2 ea"
+            + "e8 f8 a5 8c 8f 81 72 f9 31 9c f3 6b 7f d6 c5 5b 80 f2 1a 03 01"
+            + "51 56 72 60 96 fd 33 5e 5e 67 f2 db f1 02 70 2e 60 8c ca e6 be"
+            + "c1 fc 63 a4 2a 99 be 5c 3e b7 10 7c 3c 54 e9 b9 eb 2b d5 20 3b"
+            + "1c 3b 84 e0 a8 b2 f7 59 40 9b a3 ea c9 d9 1d 40 2d cc 0c c8 f8"
+            + "96 12 29 ac 91 87 b4 2b 4d e1 00 00");
+        private static readonly byte[] CertificateVerify = Hex("0f 00 00 84 08 04 00 80 5a 74 7c"
+            + "5d 88 fa 9b d2 e5 5a b0 85 a6 10 15 b7 21 1f 82 4c d4 84 14 5a"
+            + "b3 ff 52 f1 fd a8 47 7b 0b 7a bc 90 db 78 e2 d3 3a 5c 14 1a 07"
+            + "86 53 fa 6b ef 78 0c 5e a2 48 ee aa a7 85 c4 f3 94 ca b6 d3 0b"
+            + "be 8d 48 59 ee 51 1f 60 29 57 b1 54 11 ac 02 76 71 45 9e 46 44"
+            + "5c 9e a5 8c 18 1e 81 8e 95 b8 c3 fb 0b f3 27 84 09 d3 be 15 2a"
+            + "3d a5 04 3e 06 3d da 65 cd f5 ae a2 0d 53 df ac d4 2f 74 f3");
+        private static readonly byte[] ServerFinished = Hex("14 00 00 20 9b 9b 14 1d 90 63 37 fb d2 cb"
+            + "dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07"
+            + "18");
+        private static readonly byte[] ClientFinished = Hex("14 00 00 20 a8 ec 43 6d 67 76 34 ae 52 5a"
+            + "c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce"
+            + "61");
+
+        private readonly TlsCrypto m_crypto = new BcTlsCrypto(new SecureRandom());
+
+        protected TlsCredentialedSigner LoadCredentialedSigner(TlsCryptoParameters cryptoParams, string resource,
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            return TlsTestUtilities.LoadSignerCredentials(cryptoParams, m_crypto,
+                new string[]{ "x509-server-" + resource + ".pem" }, "x509-server-key-" + resource + ".pem",
+                signatureAndHashAlgorithm);
+        }
+
+        protected TlsCredentialedSigner LoadCredentialedSignerLegacy(TlsCryptoParameters cryptoParams,
+            short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.dsa:
+                return LoadCredentialedSigner(cryptoParams, "dsa", null);
+            case SignatureAlgorithm.ecdsa:
+                return LoadCredentialedSigner(cryptoParams, "ecdsa", null);
+            case SignatureAlgorithm.rsa:
+                return LoadCredentialedSigner(cryptoParams, "rsa-sign", null);
+            default:
+                return null;
+            }
+        }
+
+        protected TlsCredentialedSigner LoadCredentialedSigner12(TlsCryptoParameters cryptoParams,
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            switch (signatureAndHashAlgorithm.Signature)
+            {
+            case SignatureAlgorithm.dsa:
+                return LoadCredentialedSigner(cryptoParams, "dsa", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.ecdsa:
+                return LoadCredentialedSigner(cryptoParams, "ecdsa", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.ed25519:
+                return LoadCredentialedSigner(cryptoParams, "ed25519", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.ed448:
+                return LoadCredentialedSigner(cryptoParams, "ed448", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_256", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_384", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_512", signatureAndHashAlgorithm);
+            case SignatureAlgorithm.rsa:
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                return LoadCredentialedSigner(cryptoParams, "rsa-sign", signatureAndHashAlgorithm);
+
+            // TODO[draft-smyshlyaev-tls12-gost-suites-10] Add test resources for these
+            case SignatureAlgorithm.gostr34102012_256:
+            case SignatureAlgorithm.gostr34102012_512:
+
+            default:
+                return null;
+            }
+        }
+
+        protected TlsCredentialedSigner LoadCredentialedSigner13(TlsCryptoParameters cryptoParams, int signatureScheme)
+        {
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm = SignatureScheme.GetSignatureAndHashAlgorithm(
+                signatureScheme);
+
+            switch (signatureScheme)
+            {
+            case SignatureScheme.ecdsa_secp256r1_sha256:
+                return LoadCredentialedSigner(cryptoParams, "ecdsa", signatureAndHashAlgorithm);
+            case SignatureScheme.ed25519:
+                return LoadCredentialedSigner(cryptoParams, "ed25519", signatureAndHashAlgorithm);
+            case SignatureScheme.ed448:
+                return LoadCredentialedSigner(cryptoParams, "ed448", signatureAndHashAlgorithm);
+            case SignatureScheme.rsa_pss_pss_sha256:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_256", signatureAndHashAlgorithm);
+            case SignatureScheme.rsa_pss_pss_sha384:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_384", signatureAndHashAlgorithm);
+            case SignatureScheme.rsa_pss_pss_sha512:
+                return LoadCredentialedSigner(cryptoParams, "rsa_pss_512", signatureAndHashAlgorithm);
+            case SignatureScheme.rsa_pss_rsae_sha256:
+            case SignatureScheme.rsa_pss_rsae_sha384:
+            case SignatureScheme.rsa_pss_rsae_sha512:
+                return LoadCredentialedSigner(cryptoParams, "rsa-sign", signatureAndHashAlgorithm);
+
+            // TODO[tls] Add test resources for these
+            case SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256:
+            case SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384:
+            case SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512:
+            case SignatureScheme.ecdsa_secp384r1_sha384:
+            case SignatureScheme.ecdsa_secp521r1_sha512:
+            case SignatureScheme.sm2sig_sm3:
+
+            default:
+                return null;
+            }
+        }
+
+        [Test]
+        public void TestDHDomain()
+        {
+            if (!m_crypto.HasDHAgreement())
+            {
+                return;
+            }
+
+            for (int namedGroup = 256; namedGroup < 512; ++namedGroup)
+            {
+                if (!NamedGroup.RefersToASpecificFiniteField(namedGroup) || !m_crypto.HasNamedGroup(namedGroup))
+                {
+                    continue;
+                }
+
+                ImplTestDHDomain(new TlsDHConfig(namedGroup, false));
+                ImplTestDHDomain(new TlsDHConfig(namedGroup, true));
+            }
+
+            IList groups = new TestTlsDHGroupVerifier().Groups;
+            foreach (DHGroup dhGroup in groups)
+            {
+                int namedGroup = TlsDHUtilities.GetNamedGroupForDHParameters(dhGroup.P, dhGroup.G);
+                if (NamedGroup.RefersToASpecificFiniteField(namedGroup))
+                {
+                    // Already tested the named groups
+                    continue;
+                }
+
+                ImplTestDHDomain(new TlsDHConfig(dhGroup));
+            }
+        }
+
+        [Test]
+        public void TestECDomain()
+        {
+            if (!m_crypto.HasECDHAgreement())
+            {
+                return;
+            }
+
+            for (int namedGroup = 0; namedGroup < 256; ++namedGroup)
+            {
+                if (!NamedGroup.RefersToAnECDHCurve(namedGroup) || !m_crypto.HasNamedGroup(namedGroup))
+                {
+                    continue;
+                }
+
+                ImplTestECDomain(new TlsECConfig(namedGroup));
+            }
+        }
+
+        [Test]
+        public void TestHkdf()
+        {
+            /*
+             * Test vectors drawn from the server-side calculations of example handshake trace in RFC 8448, section 3.
+             */
+
+            int hash = CryptoHashAlgorithm.sha256;
+            int hashLen = TlsCryptoUtilities.GetHashOutputSize(hash);
+
+            TlsSecret init = m_crypto.HkdfInit(hash), early, handshake, master, c_hs_t, s_hs_t, c_ap_t, s_ap_t, exp_master, res_master;
+
+            TlsHash prfHash = m_crypto.CreateHash(hash);
+
+            byte[] emptyTranscriptHash = GetCurrentHash(prfHash);
+            Expect(emptyTranscriptHash, "e3 b0 c4 42 98 fc 1c 14 9a fb f4 c8 99 6f b9 24 27 ae 41 e4 64 9b 93 4c a4 95 99 1b 78 52 b8 55");
+
+            // {server}  extract secret "early":
+            {
+                byte[] ikm = new byte[32];
+                early = init.HkdfExtract(hash, ikm);
+                Expect(early, "33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a");
+            }
+
+            // {server}  derive secret for handshake "tls13 derived":
+            {
+                string label = "derived";
+                handshake = TlsCryptoUtilities.HkdfExpandLabel(early, hash, label, emptyTranscriptHash, hashLen);
+                Expect(handshake, "6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba");
+            }
+
+            // {server}  extract secret "handshake":
+            {
+                byte[] ikm = Hex("8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d");
+                handshake = handshake.HkdfExtract(hash, ikm);
+                Expect(handshake, "1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac");
+            }
+
+            prfHash.Update(ClientHello, 0, ClientHello.Length);
+            prfHash.Update(ServerHello, 0, ServerHello.Length);
+
+            byte[] serverHelloTranscriptHash = GetCurrentHash(prfHash);
+            Expect(serverHelloTranscriptHash, "86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8");
+
+            // {server}  derive secret "tls13 c hs traffic":
+            {
+                string label = "c hs traffic";
+                c_hs_t = TlsCryptoUtilities.HkdfExpandLabel(handshake, hash, label, serverHelloTranscriptHash, hashLen);
+                Expect(c_hs_t, "b3 ed db 12 6e 06 7f 35 a7 80 b3 ab f4 5e 2d 8f 3b 1a 95 07 38 f5 2e 96 00 74 6a 0e 27 a5 5a 21");
+            }
+
+            // {server}  derive secret "tls13 s hs traffic":
+            {
+                string label = "s hs traffic";
+                s_hs_t = TlsCryptoUtilities.HkdfExpandLabel(handshake, hash, label, serverHelloTranscriptHash, hashLen);
+                Expect(s_hs_t, "b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 e9 c9 12 bc de d9 10 5d 42 be fd 59 d3 91 ad 38");
+            }
+
+            // {server}  derive secret for master "tls13 derived":
+            {
+                string label = "derived";
+                master = TlsCryptoUtilities.HkdfExpandLabel(handshake, hash, label, emptyTranscriptHash, hashLen);
+                Expect(master, "43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4");
+            }
+
+            // {server}  extract secret "master":
+            {
+                byte[] ikm = new byte[32];
+                master = master.HkdfExtract(hash, ikm);
+                Expect(master, "18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19");
+            }
+
+            // {server}  derive write traffic keys for handshake data:
+            {
+                TlsSecret key = TlsCryptoUtilities.HkdfExpandLabel(s_hs_t, hash, "key", TlsUtilities.EmptyBytes, 16);
+                Expect(key, "3f ce 51 60 09 c2 17 27 d0 f2 e4 e8 6e e4 03 bc");
+
+                TlsSecret iv = TlsCryptoUtilities.HkdfExpandLabel(s_hs_t, hash, "iv", TlsUtilities.EmptyBytes, 12);
+                Expect(iv, "5d 31 3e b2 67 12 76 ee 13 00 0b 30");
+            }
+
+            prfHash.Update(EncryptedExtensions, 0, EncryptedExtensions.Length);
+            prfHash.Update(Certificate, 0, Certificate.Length);
+            prfHash.Update(CertificateVerify, 0, CertificateVerify.Length);
+
+            // {server}  calculate (server) finished "tls13 finished":
+            {
+                TlsSecret expanded = TlsCryptoUtilities.HkdfExpandLabel(s_hs_t, hash, "finished", TlsUtilities.EmptyBytes, hashLen);
+                Expect(expanded, "00 8d 3b 66 f8 16 ea 55 9f 96 b5 37 e8 85 c3 1f c0 68 bf 49 2c 65 2f 01 f2 88 a1 d8 cd c1 9f c8");
+
+                // TODO Mention this transcript hash in RFC 8448 data?
+                byte[] transcriptHash = GetCurrentHash(prfHash);
+                Expect(transcriptHash, "ed b7 72 5f a7 a3 47 3b 03 1e c8 ef 65 a2 48 54 93 90 01 38 a2 b9 12 91 40 7d 79 51 a0 61 10 ed");
+
+                byte[] finished = CalculateHmac(hash, expanded, transcriptHash);
+                Expect(finished, Hex("9b 9b 14 1d 90 63 37 fb d2 cb dc e7 1d f4 de da 4a b4 2c 30 95 72 cb 7f ff ee 54 54 b7 8f 07 18"));
+            }
+
+            prfHash.Update(ServerFinished, 0, ServerFinished.Length);
+
+            byte[] serverFinishedTranscriptHash = GetCurrentHash(prfHash);
+            Expect(serverFinishedTranscriptHash, "96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13");
+
+            // {server}  derive secret "tls13 c ap traffic":
+            {
+                string label = "c ap traffic";
+                c_ap_t = TlsCryptoUtilities.HkdfExpandLabel(master, hash, label, serverFinishedTranscriptHash, hashLen);
+                Expect(c_ap_t, "9e 40 64 6c e7 9a 7f 9d c0 5a f8 88 9b ce 65 52 87 5a fa 0b 06 df 00 87 f7 92 eb b7 c1 75 04 a5");
+            }
+
+            // {server}  derive secret "tls13 s ap traffic":
+            {
+                string label = "s ap traffic";
+                s_ap_t = TlsCryptoUtilities.HkdfExpandLabel(master, hash, label, serverFinishedTranscriptHash, hashLen);
+                Expect(s_ap_t, "a1 1a f9 f0 55 31 f8 56 ad 47 11 6b 45 a9 50 32 82 04 b4 f4 4b fb 6b 3a 4b 4f 1f 3f cb 63 16 43");
+            }
+
+            // {server}  derive secret "tls13 exp master":
+            {
+                string label = "exp master";
+                exp_master = TlsCryptoUtilities.HkdfExpandLabel(master, hash, label, serverFinishedTranscriptHash, hashLen);
+                Expect(exp_master, "fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50");
+            }
+
+            // {server}  derive write traffic keys for application data:
+            {
+                TlsSecret key = TlsCryptoUtilities.HkdfExpandLabel(s_ap_t, hash, "key", TlsUtilities.EmptyBytes, 16);
+                Expect(key, "9f 02 28 3b 6c 9c 07 ef c2 6b b9 f2 ac 92 e3 56");
+
+                TlsSecret iv = TlsCryptoUtilities.HkdfExpandLabel(s_ap_t, hash, "iv", TlsUtilities.EmptyBytes, 12);
+                Expect(iv, "cf 78 2b 88 dd 83 54 9a ad f1 e9 84");
+            }
+
+            // {server}  derive read traffic keys for handshake data:
+            {
+                TlsSecret key = TlsCryptoUtilities.HkdfExpandLabel(c_hs_t, hash, "key", TlsUtilities.EmptyBytes, 16);
+                Expect(key, "db fa a6 93 d1 76 2c 5b 66 6a f5 d9 50 25 8d 01");
+
+                TlsSecret iv = TlsCryptoUtilities.HkdfExpandLabel(c_hs_t, hash, "iv", TlsUtilities.EmptyBytes, 12);
+                Expect(iv, "5b d3 c7 1b 83 6e 0b 76 bb 73 26 5f");
+            }
+
+            // {server}  calculate (client) finished "tls13 finished":
+            {
+                TlsSecret expanded = TlsCryptoUtilities.HkdfExpandLabel(c_hs_t, hash, "finished", TlsUtilities.EmptyBytes, hashLen);
+                Expect(expanded, "b8 0a d0 10 15 fb 2f 0b d6 5f f7 d4 da 5d 6b f8 3f 84 82 1d 1f 87 fd c7 d3 c7 5b 5a 7b 42 d9 c4");
+
+                // TODO Mention this transcript hash in RFC 8448 data?
+                byte[] finished = CalculateHmac(hash, expanded, serverFinishedTranscriptHash);
+                Expect(finished, Hex("a8 ec 43 6d 67 76 34 ae 52 5a c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61"));
+            }
+
+            prfHash.Update(ClientFinished, 0, ClientFinished.Length);
+
+            byte[] clientFinishedTranscriptHash = GetCurrentHash(prfHash);
+            Expect(clientFinishedTranscriptHash, "20 91 45 a9 6e e8 e2 a1 22 ff 81 00 47 cc 95 26 84 65 8d 60 49 e8 64 29 42 6d b8 7c 54 ad 14 3d");
+
+            // {server}  derive read traffic keys for application data:
+            {
+                TlsSecret key = TlsCryptoUtilities.HkdfExpandLabel(c_ap_t, hash, "key", TlsUtilities.EmptyBytes, 16);
+                Expect(key, "17 42 2d da 59 6e d5 d9 ac d8 90 e3 c6 3f 50 51");
+
+                TlsSecret iv = TlsCryptoUtilities.HkdfExpandLabel(c_ap_t, hash, "iv", TlsUtilities.EmptyBytes, 12);
+                Expect(iv, "5b 78 92 3d ee 08 57 90 33 e5 23 d9");
+            }
+
+            // {server}  derive secret "tls13 res master":
+            {
+                res_master = TlsCryptoUtilities.HkdfExpandLabel(master, hash, "res master", clientFinishedTranscriptHash, hashLen);
+                Expect(res_master, "7d f2 35 f2 03 1d 2a 05 12 87 d0 2b 02 41 b0 bf da f8 6c c8 56 23 1f 2d 5a ba 46 c4 34 ec 19 6c");
+            }
+
+            // {server}  generate resumption secret "tls13 resumption":
+            {
+                byte[] context = Hex("00 00");
+                TlsSecret expanded = TlsCryptoUtilities.HkdfExpandLabel(res_master, hash, "resumption", context, hashLen);
+                Expect(expanded, "4e cd 0e b6 ec 3b 4d 87 f5 d6 02 8f 92 2c a4 c5 85 1a 27 7f d4 13 11 c9 e6 2d 2c 94 92 e1 c4 f3");
+            }
+        }
+
+        [Test]
+        public void TestHkdfExpandLimit()
+        {
+            int[] hashes = new int[] { CryptoHashAlgorithm.md5, CryptoHashAlgorithm.sha1, CryptoHashAlgorithm.sha224,
+                CryptoHashAlgorithm.sha256, CryptoHashAlgorithm.sha384, CryptoHashAlgorithm.sha512,
+                CryptoHashAlgorithm.sm3 };
+
+            for (int i = 0; i < hashes.Length; ++i)
+            {
+                int hash = hashes[i];
+                int hashLen = TlsCryptoUtilities.GetHashOutputSize(hash);
+                byte[] zeroes = new byte[hashLen];
+
+                int limit = 255 * hashLen;
+
+                TlsSecret secret = m_crypto.HkdfInit(hash).HkdfExtract(hash, zeroes);
+
+                try
+                {
+                    secret.HkdfExpand(hash, TlsUtilities.EmptyBytes, limit);
+                }
+                catch (Exception e)
+                {
+                    Assert.Fail("Unexpected exception: " + e.Message);
+                }
+
+                try
+                {
+                    secret.HkdfExpand(hash, TlsUtilities.EmptyBytes, limit + 1);
+                    Assert.Fail("Expected an exception!");
+                }
+                catch (ArgumentException)
+                {
+                    // Expected
+                }
+                catch (Exception e)
+                {
+                    Assert.Fail("Unexpected exception: " + e.Message);
+                }
+            }
+        }
+
+        [Test]
+        public void TestSignaturesLegacy()
+        {
+            short[] signatureAlgorithms = new short[]{ SignatureAlgorithm.dsa, SignatureAlgorithm.ecdsa,
+                SignatureAlgorithm.rsa };
+
+            TlsCryptoParameters cryptoParams = new TestTlsCryptoParameters(ProtocolVersion.TLSv11);
+
+            for (int i = 0; i < signatureAlgorithms.Length; ++i)
+            {
+                short signatureAlgorithm = signatureAlgorithms[i];
+                if (!m_crypto.HasSignatureAlgorithm(signatureAlgorithm))
+                    continue;
+
+                TlsCredentialedSigner credentialedSigner = LoadCredentialedSignerLegacy(cryptoParams,
+                    signatureAlgorithm);
+                if (null != credentialedSigner)
+                {
+                    ImplTestSignatureLegacy(credentialedSigner);
+                }
+            }
+        }
+
+        [Test]
+        public void TestSignatures12()
+        {
+            short[] hashAlgorithms = new short[]{ HashAlgorithm.md5, HashAlgorithm.sha1, HashAlgorithm.sha224,
+                HashAlgorithm.sha256, HashAlgorithm.sha384, HashAlgorithm.sha512 };
+            short[] signatureAlgorithms = new short[]{ SignatureAlgorithm.dsa, SignatureAlgorithm.ecdsa,
+                SignatureAlgorithm.rsa };
+
+            TlsCryptoParameters cryptoParams = new TestTlsCryptoParameters(ProtocolVersion.TLSv12);
+
+            for (int i = 0; i < signatureAlgorithms.Length; ++i)
+            {
+                short signatureAlgorithm = signatureAlgorithms[i];
+
+                for (int j = 0; j < hashAlgorithms.Length; ++j)
+                {
+                    SignatureAndHashAlgorithm signatureAndHashAlgorithm = SignatureAndHashAlgorithm.GetInstance(
+                        hashAlgorithms[j], signatureAlgorithm);
+                    if (!m_crypto.HasSignatureAndHashAlgorithm(signatureAndHashAlgorithm))
+                        continue;
+
+                    TlsCredentialedSigner credentialedSigner = LoadCredentialedSigner12(cryptoParams,
+                        signatureAndHashAlgorithm);
+                    if (null != credentialedSigner)
+                    {
+                        ImplTestSignature12(credentialedSigner, signatureAndHashAlgorithm);
+                    }
+                }
+            }
+
+            // Signature algorithms usable with HashAlgorithm.Intrinsic in TLS 1.2
+            short[] intrinsicSignatureAlgorithms = new short[] { SignatureAlgorithm.ed25519, SignatureAlgorithm.ed448,
+                SignatureAlgorithm.gostr34102012_256, SignatureAlgorithm.gostr34102012_512,
+                SignatureAlgorithm.rsa_pss_pss_sha256, SignatureAlgorithm.rsa_pss_pss_sha384,
+                SignatureAlgorithm.rsa_pss_pss_sha512, SignatureAlgorithm.rsa_pss_rsae_sha256,
+                SignatureAlgorithm.rsa_pss_rsae_sha384, SignatureAlgorithm.rsa_pss_rsae_sha512, };
+
+            for (int i = 0; i < intrinsicSignatureAlgorithms.Length; ++i)
+            {
+                SignatureAndHashAlgorithm signatureAndHashAlgorithm = SignatureAndHashAlgorithm.GetInstance(
+                    HashAlgorithm.Intrinsic, intrinsicSignatureAlgorithms[i]);
+                if (!m_crypto.HasSignatureAndHashAlgorithm(signatureAndHashAlgorithm))
+                    continue;
+
+                TlsCredentialedSigner credentialedSigner = LoadCredentialedSigner12(cryptoParams,
+                    signatureAndHashAlgorithm);
+                if (null != credentialedSigner)
+                {
+                    ImplTestSignature12(credentialedSigner, signatureAndHashAlgorithm);
+                }
+            }
+        }
+
+        [Test]
+        public void TestSignatures13()
+        {
+            int[] signatureSchemes = new int[] { SignatureScheme.ecdsa_brainpoolP256r1tls13_sha256,
+                SignatureScheme.ecdsa_brainpoolP384r1tls13_sha384, SignatureScheme.ecdsa_brainpoolP512r1tls13_sha512,
+                SignatureScheme.ecdsa_secp256r1_sha256, SignatureScheme.ecdsa_secp384r1_sha384,
+                SignatureScheme.ecdsa_secp521r1_sha512, SignatureScheme.ed25519, SignatureScheme.ed448,
+                SignatureScheme.rsa_pss_pss_sha256, SignatureScheme.rsa_pss_pss_sha384, SignatureScheme.rsa_pss_pss_sha512,
+                SignatureScheme.rsa_pss_rsae_sha256, SignatureScheme.rsa_pss_rsae_sha384,
+                SignatureScheme.rsa_pss_rsae_sha512, SignatureScheme.sm2sig_sm3,
+                // These are only used for certs in 1.3 (cert verification is not done by TlsCrypto)
+                //SignatureScheme.ecdsa_sha1, SignatureScheme.rsa_pkcs1_sha1, SignatureScheme.rsa_pkcs1_sha256,
+                //SignatureScheme.rsa_pkcs1_sha384, SignatureScheme.rsa_pkcs1_sha512,
+            };
+
+            TlsCryptoParameters cryptoParams = new TestTlsCryptoParameters(ProtocolVersion.TLSv13);
+
+            for (int i = 0; i < signatureSchemes.Length; ++i)
+            {
+                int signatureScheme = signatureSchemes[i];
+                if (!m_crypto.HasSignatureScheme(signatureScheme))
+                    continue;
+
+                TlsCredentialedSigner credentialedSigner = LoadCredentialedSigner13(cryptoParams, signatureScheme);
+                if (null != credentialedSigner)
+                {
+                    ImplTestSignature13(credentialedSigner, signatureScheme);
+                }
+            }
+        }
+
+        private byte[] CalculateHmac(int cryptoHashAlgorithm, TlsSecret hmacKey, byte[] hmacInput)
+        {
+            byte[] keyBytes = Extract(hmacKey);
+
+            TlsHmac hmac = m_crypto.CreateHmacForHash(cryptoHashAlgorithm);
+            hmac.SetKey(keyBytes, 0, keyBytes.Length);
+            hmac.Update(hmacInput, 0, hmacInput.Length);
+            return hmac.CalculateMac();
+        }
+
+        private void Expect(TlsSecret secret, string expectedHex)
+        {
+            Expect(Extract(secret), Hex(expectedHex));
+        }
+
+        private void Expect(byte[] actualOctets, string expectedHex)
+        {
+            Expect(actualOctets, Hex(expectedHex));
+        }
+
+        private void Expect(byte[] actualOctets, byte[] expectedOctets)
+        {
+            AssertArrayEquals(actualOctets, expectedOctets);
+        }
+
+        private byte[] Extract(TlsSecret secret)
+        {
+            return m_crypto.AdoptSecret(secret).Extract();
+        }
+
+        private byte[] GetCurrentHash(TlsHash hash)
+        {
+            return hash.CloneHash().CalculateHash();
+        }
+
+        private static void AssertArrayEquals(byte[] a, byte[] b)
+        {
+            Assert.IsTrue(Arrays.AreEqual(a, b));
+        }
+
+        private static byte[] Hex(string s)
+        {
+            return Utilities.Encoders.Hex.Decode(s.Replace(" ", ""));
+        }
+
+        private void ImplTestAgreement(TlsAgreement aA, TlsAgreement aB)
+        {
+            byte[] pA = aA.GenerateEphemeral();
+            byte[] pB = aB.GenerateEphemeral();
+
+            aA.ReceivePeerValue(pB);
+            aB.ReceivePeerValue(pA);
+
+            TlsSecret sA = aA.CalculateSecret();
+            TlsSecret sB = aB.CalculateSecret();
+
+            AssertArrayEquals(Extract(sA), Extract(sB));
+        }
+
+        private void ImplTestDHDomain(TlsDHConfig dhConfig)
+        {
+            int namedGroup = dhConfig.NamedGroup;
+            int bits = namedGroup >= 0
+                ? NamedGroup.GetFiniteFieldBits(namedGroup)
+                : dhConfig.ExplicitGroup.P.BitLength;
+
+            int rounds = System.Math.Max(2, 11 - (bits >> 10));
+
+            TlsDHDomain d = m_crypto.CreateDHDomain(dhConfig);
+
+            for (int i = 0; i < rounds; ++i)
+            {
+                TlsAgreement aA = d.CreateDH();
+                TlsAgreement aB = d.CreateDH();
+
+                ImplTestAgreement(aA, aB);
+            }
+        }
+
+        private void ImplTestECDomain(TlsECConfig ecConfig)
+        {
+            int bits = NamedGroup.GetCurveBits(ecConfig.NamedGroup);
+            int rounds = System.Math.Max(2, 12 - (bits >> 6));
+
+            TlsECDomain d = m_crypto.CreateECDomain(ecConfig);
+
+            for (int i = 0; i < rounds; ++i)
+            {
+                TlsAgreement aA = d.CreateECDH();
+                TlsAgreement aB = d.CreateECDH();
+
+                ImplTestAgreement(aA, aB);
+            }
+        }
+
+        private void ImplTestSignatureLegacy(TlsCredentialedSigner credentialedSigner)
+        {
+            byte[] message = m_crypto.CreateNonceGenerator(TlsUtilities.EmptyBytes).GenerateNonce(100);
+
+            byte[] signature;
+            TlsStreamSigner tlsStreamSigner = credentialedSigner.GetStreamSigner();
+            if (null != tlsStreamSigner)
+            {
+                Stream output = tlsStreamSigner.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                signature = tlsStreamSigner.GetSignature();
+            }
+            else
+            {
+                TlsHash tlsHash = new CombinedHash(m_crypto);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                signature = credentialedSigner.GenerateRawSignature(hash);
+            }
+
+            DigitallySigned digitallySigned = new DigitallySigned(null, signature);
+
+            TlsCertificate tlsCertificate = credentialedSigner.Certificate.GetCertificateAt(0);
+            TlsVerifier tlsVerifier = tlsCertificate.CreateVerifier(tlsCertificate.GetLegacySignatureAlgorithm());
+
+            bool verified;
+            TlsStreamVerifier tlsStreamVerifier = tlsVerifier.GetStreamVerifier(digitallySigned);
+            if (null != tlsStreamVerifier)
+            {
+                Stream output = tlsStreamVerifier.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                verified = tlsStreamVerifier.IsVerified();
+            }
+            else
+            {
+                TlsHash tlsHash = new CombinedHash(m_crypto);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                verified = tlsVerifier.VerifyRawSignature(digitallySigned, hash);
+            }
+
+            Assert.IsTrue(verified);
+        }
+
+        private void ImplTestSignature12(TlsCredentialedSigner credentialedSigner,
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            short hashAlgorithm = signatureAndHashAlgorithm.Hash;
+
+            byte[] message = m_crypto.CreateNonceGenerator(TlsUtilities.EmptyBytes).GenerateNonce(100);
+
+            byte[] signature;
+            TlsStreamSigner tlsStreamSigner = credentialedSigner.GetStreamSigner();
+            if (null != tlsStreamSigner)
+            {
+                Stream output = tlsStreamSigner.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                signature = tlsStreamSigner.GetSignature();
+            }
+            else
+            {
+                // Currently 1.2 relies on these being handled by stream signers 
+                Assert.IsTrue(HashAlgorithm.Intrinsic != hashAlgorithm);
+
+                int cryptoHashAlgorithm = TlsCryptoUtilities.GetHash(hashAlgorithm);
+
+                TlsHash tlsHash = m_crypto.CreateHash(cryptoHashAlgorithm);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                signature = credentialedSigner.GenerateRawSignature(hash);
+            }
+
+            DigitallySigned digitallySigned = new DigitallySigned(signatureAndHashAlgorithm, signature);
+
+            TlsCertificate tlsCertificate = credentialedSigner.Certificate.GetCertificateAt(0);
+            TlsVerifier tlsVerifier = tlsCertificate.CreateVerifier(signatureAndHashAlgorithm.Signature);
+
+            bool verified;
+            TlsStreamVerifier tlsStreamVerifier = tlsVerifier.GetStreamVerifier(digitallySigned);
+            if (null != tlsStreamVerifier)
+            {
+                Stream output = tlsStreamVerifier.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                verified = tlsStreamVerifier.IsVerified();
+            }
+            else
+            {
+                // Currently 1.2 relies on these being handled by stream verifiers 
+                Assert.IsTrue(HashAlgorithm.Intrinsic != hashAlgorithm);
+
+                int cryptoHashAlgorithm = TlsCryptoUtilities.GetHash(hashAlgorithm);
+
+                TlsHash tlsHash = m_crypto.CreateHash(cryptoHashAlgorithm);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                verified = tlsVerifier.VerifyRawSignature(digitallySigned, hash);
+            }
+
+            Assert.IsTrue(verified);
+        }
+
+        private void ImplTestSignature13(TlsCredentialedSigner credentialedSigner, int signatureScheme)
+        {
+            byte[] message = m_crypto.CreateNonceGenerator(TlsUtilities.EmptyBytes).GenerateNonce(100);
+
+            byte[] signature;
+            TlsStreamSigner tlsStreamSigner = credentialedSigner.GetStreamSigner();
+            if (null != tlsStreamSigner)
+            {
+                Stream output = tlsStreamSigner.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                signature = tlsStreamSigner.GetSignature();
+            }
+            else
+            {
+                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+                TlsHash tlsHash = m_crypto.CreateHash(cryptoHashAlgorithm);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                signature = credentialedSigner.GenerateRawSignature(hash);
+            }
+
+            DigitallySigned digitallySigned = new DigitallySigned(
+                SignatureScheme.GetSignatureAndHashAlgorithm(signatureScheme), signature);
+
+            TlsCertificate tlsCertificate = credentialedSigner.Certificate.GetCertificateAt(0);
+            TlsVerifier tlsVerifier = tlsCertificate.CreateVerifier(signatureScheme);
+
+            bool verified;
+            TlsStreamVerifier tlsStreamVerifier = tlsVerifier.GetStreamVerifier(digitallySigned);
+            if (null != tlsStreamVerifier)
+            {
+                Stream output = tlsStreamVerifier.GetOutputStream();
+                output.Write(message, 0, message.Length);
+                verified = tlsStreamVerifier.IsVerified();
+            }
+            else
+            {
+                int cryptoHashAlgorithm = SignatureScheme.GetCryptoHashAlgorithm(signatureScheme);
+
+                TlsHash tlsHash = m_crypto.CreateHash(cryptoHashAlgorithm);
+                tlsHash.Update(message, 0, message.Length);
+                byte[] hash = tlsHash.CalculateHash();
+                verified = tlsVerifier.VerifyRawSignature(digitallySigned, hash);
+            }
+
+            Assert.IsTrue(verified);
+        }
+
+        private class TestTlsCryptoParameters
+            : TlsCryptoParameters
+        {
+            private readonly ProtocolVersion m_serverVersion;
+
+            internal TestTlsCryptoParameters(ProtocolVersion serverVersion)
+                : base(null)
+            {
+                this.m_serverVersion = serverVersion;
+            }
+
+            public override ProtocolVersion ServerVersion
+            {
+                get { return m_serverVersion; }
+            }
+        }
+
+        private class TestTlsDHGroupVerifier
+            : DefaultTlsDHGroupVerifier
+        {
+            internal IList Groups
+            {
+                get { return m_groups; }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/ByteQueueInputStreamTest.cs b/crypto/test/src/tls/test/ByteQueueInputStreamTest.cs
new file mode 100644
index 000000000..9edb3109b
--- /dev/null
+++ b/crypto/test/src/tls/test/ByteQueueInputStreamTest.cs
@@ -0,0 +1,134 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class ByteQueueInputStreamTest
+    {
+        [Test]
+        public void TestAvailable()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+
+            // buffer is empty
+            Assert.AreEqual(0, input.Available);
+
+            // after adding once
+            input.AddBytes(new byte[10]);
+            Assert.AreEqual(10, input.Available);
+
+            // after adding more than once
+            input.AddBytes(new byte[5]);
+            Assert.AreEqual(15, input.Available);
+
+            // after reading a single byte
+            input.ReadByte();
+            Assert.AreEqual(14, input.Available);
+
+            // after reading into a byte array
+            input.Read(new byte[4], 0, 4);
+            Assert.AreEqual(10, input.Available);
+
+            input.Close();
+        }
+
+        [Test]
+        public void TestSkip()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+
+            // skip when buffer is empty
+            Assert.AreEqual(0, input.Skip(10));
+
+            // skip equal to available
+            input.AddBytes(new byte[2]);
+            Assert.AreEqual(2, input.Skip(2));
+            Assert.AreEqual(0, input.Available);
+
+            // skip less than available
+            input.AddBytes(new byte[10]);
+            Assert.AreEqual(5, input.Skip(5));
+            Assert.AreEqual(5, input.Available);
+
+            // skip more than available
+            Assert.AreEqual(5, input.Skip(20));
+            Assert.AreEqual(0, input.Available);
+
+            input.Close();
+        }
+
+        [Test]
+        public void TestRead()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+            input.AddBytes(new byte[]{ 0x01, 0x02 });
+            input.AddBytes(new byte[]{ 0x03 });
+
+            Assert.AreEqual(0x01, input.ReadByte());
+            Assert.AreEqual(0x02, input.ReadByte());
+            Assert.AreEqual(0x03, input.ReadByte());
+            Assert.AreEqual(-1, input.ReadByte());
+
+            input.Close();
+        }
+
+        [Test]
+        public void TestReadArray()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+            input.AddBytes(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+
+            byte[] buffer = new byte[5];
+
+            // read less than available into specified position
+            Assert.AreEqual(1, input.Read(buffer, 2, 1));
+            AssertArrayEquals(new byte[]{ 0x00, 0x00, 0x01, 0x00, 0x00 }, buffer);
+
+            // read equal to available
+            Assert.AreEqual(5, input.Read(buffer, 0, buffer.Length));
+            AssertArrayEquals(new byte[]{ 0x02, 0x03, 0x04, 0x05, 0x06 }, buffer);
+
+            // read more than available
+            input.AddBytes(new byte[]{ 0x01, 0x02, 0x03 });
+            Assert.AreEqual(3, input.Read(buffer, 0, buffer.Length));
+            AssertArrayEquals(new byte[]{ 0x01, 0x02, 0x03, 0x05, 0x06 }, buffer);
+
+            input.Close();
+        }
+
+        [Test]
+        public void TestPeek()
+        {
+            ByteQueueInputStream input = new ByteQueueInputStream();
+
+            byte[] buffer = new byte[5];
+
+            // peek more than available
+            Assert.AreEqual(0, input.Peek(buffer));
+            AssertArrayEquals(new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00 }, buffer);
+
+            // peek less than available
+            input.AddBytes(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 });
+            Assert.AreEqual(5, input.Peek(buffer));
+            AssertArrayEquals(new byte[]{ 0x01, 0x02, 0x03, 0x04, 0x05 }, buffer);
+            Assert.AreEqual(6, input.Available);
+
+            // peek equal to available
+            input.ReadByte();
+            Assert.AreEqual(5, input.Peek(buffer));
+            AssertArrayEquals(new byte[]{ 0x02, 0x03, 0x04, 0x05, 0x06 }, buffer);
+            Assert.AreEqual(5, input.Available);
+
+            input.Close();
+        }
+
+        private static void AssertArrayEquals(byte[] a, byte[] b)
+        {
+            Assert.IsTrue(Arrays.AreEqual(a, b));
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/CertChainUtilities.cs b/crypto/test/src/tls/test/CertChainUtilities.cs
new file mode 100644
index 000000000..8df8e48bc
--- /dev/null
+++ b/crypto/test/src/tls/test/CertChainUtilities.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Threading;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Operators;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.X509.Extension;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class CertChainUtilities
+    {
+        private static readonly SecureRandom Random = new SecureRandom();
+
+        private static long serialNumber = 1L;
+
+        /// <summary>We generate the CA's certificate.</summary>
+        public static X509Certificate CreateMasterCert(string rootDN, AsymmetricCipherKeyPair keyPair)
+        {
+            //
+            // create the certificate - version 1
+            //
+            X509V1CertificateGenerator gen = new X509V1CertificateGenerator();
+            gen.SetIssuerDN(new X509Name(rootDN));
+            gen.SetSerialNumber(BigInteger.ValueOf(Interlocked.Increment(ref serialNumber)));
+            gen.SetNotBefore(DateTime.UtcNow.AddDays(-30));
+            gen.SetNotAfter(DateTime.UtcNow.AddDays(30));
+            gen.SetSubjectDN(new X509Name(rootDN));
+            gen.SetPublicKey(keyPair.Public);
+
+            return SignV1(gen, keyPair.Private);
+        }
+
+        /// <summary>We generate an intermediate certificate signed by our CA.</summary>
+        public static X509Certificate CreateIntermediateCert(string interDN, AsymmetricKeyParameter pubKey,
+            AsymmetricKeyParameter caPrivKey, X509Certificate caCert)
+        {
+            //
+            // create the certificate - version 3
+            //
+            X509V3CertificateGenerator gen = new X509V3CertificateGenerator();
+            gen.SetIssuerDN(caCert.SubjectDN);
+            gen.SetSerialNumber(BigInteger.ValueOf(Interlocked.Increment(ref serialNumber)));
+            gen.SetNotBefore(DateTime.UtcNow.AddDays(-30));
+            gen.SetNotAfter(DateTime.UtcNow.AddDays(30));
+            gen.SetSubjectDN(new X509Name(interDN));
+            gen.SetPublicKey(pubKey);
+
+            //
+            // extensions
+            //
+            gen.AddExtension(X509Extensions.SubjectKeyIdentifier, false,
+                new SubjectKeyIdentifierStructure(pubKey));
+            gen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false,
+                new AuthorityKeyIdentifierStructure(caCert));
+            gen.AddExtension(X509Extensions.BasicConstraints, true,
+                new BasicConstraints(0));
+
+            return SignV3(gen, caPrivKey);
+        }
+
+        /// <summary>We generate a certificate signed by our CA's intermediate certificate.</summary>
+        public static X509Certificate CreateEndEntityCert(string endEntityDN, AsymmetricKeyParameter pubKey,
+            AsymmetricKeyParameter caPrivKey, X509Certificate caCert)
+        {
+            X509V3CertificateGenerator gen = CreateBaseEndEntityGenerator(endEntityDN, pubKey, caCert);
+
+            return SignV3(gen, caPrivKey);
+        }
+
+        /// <summary>We generate a certificate signed by our CA's intermediate certificate with ExtendedKeyUsage
+        /// extension.</summary>
+        public static X509Certificate CreateEndEntityCert(string endEntityDN, AsymmetricKeyParameter pubKey,
+            AsymmetricKeyParameter caPrivKey, X509Certificate caCert, KeyPurposeID keyPurposeID)
+        {
+            X509V3CertificateGenerator gen = CreateBaseEndEntityGenerator(endEntityDN, pubKey, caCert);
+
+            gen.AddExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(keyPurposeID));
+
+            return SignV3(gen, caPrivKey);
+        }
+
+        private static X509V3CertificateGenerator CreateBaseEndEntityGenerator(string endEntityDN,
+            AsymmetricKeyParameter pubKey, X509Certificate caCert)
+        {
+            //
+            // create the certificate - version 3
+            //
+            X509V3CertificateGenerator gen = new X509V3CertificateGenerator();
+            gen.SetIssuerDN(caCert.SubjectDN);
+            gen.SetSerialNumber(BigInteger.ValueOf(Interlocked.Increment(ref serialNumber)));
+            gen.SetNotBefore(DateTime.UtcNow.AddDays(-30));
+            gen.SetNotAfter(DateTime.UtcNow.AddDays(30));
+            gen.SetSubjectDN(new X509Name(endEntityDN));
+            gen.SetPublicKey(pubKey);
+
+            //
+            // add the extensions
+            //
+            gen.AddExtension(X509Extensions.SubjectKeyIdentifier, false,
+                new SubjectKeyIdentifierStructure(pubKey));
+            gen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false,
+                new AuthorityKeyIdentifierStructure(caCert.GetPublicKey()));
+            gen.AddExtension(X509Extensions.BasicConstraints, true,
+                new BasicConstraints(false));
+
+            return gen;
+        }
+
+        private static X509Certificate SignV1(X509V1CertificateGenerator gen, AsymmetricKeyParameter caPrivKey)
+        {
+            return gen.Generate(new Asn1SignatureFactory("SHA256withRSA", caPrivKey, Random));
+        }
+
+        private static X509Certificate SignV3(X509V3CertificateGenerator gen, AsymmetricKeyParameter caPrivKey)
+        {
+            return gen.Generate(new Asn1SignatureFactory("SHA256withRSA", caPrivKey, Random));
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsProtocolTest.cs b/crypto/test/src/tls/test/DtlsProtocolTest.cs
new file mode 100644
index 000000000..388003666
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsProtocolTest.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class DtlsProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            SecureRandom secureRandom = new SecureRandom();
+
+            DtlsClientProtocol clientProtocol = new DtlsClientProtocol();
+            DtlsServerProtocol serverProtocol = new DtlsServerProtocol();
+
+            MockDatagramAssociation network = new MockDatagramAssociation(1500);
+
+            Server server = new Server(serverProtocol, network.Server);
+
+            Thread serverThread = new Thread(new ThreadStart(server.Run));
+            serverThread.Start();
+
+            DatagramTransport clientTransport = network.Client;
+
+            clientTransport = new UnreliableDatagramTransport(clientTransport, secureRandom, 0, 0);
+
+            clientTransport = new LoggingDatagramTransport(clientTransport, Console.Out);
+
+            MockDtlsClient client = new MockDtlsClient(null);
+
+            DtlsTransport dtlsClient = clientProtocol.Connect(client, clientTransport);
+
+            for (int i = 1; i <= 10; ++i)
+            {
+                byte[] data = new byte[i];
+                Arrays.Fill(data, (byte)i);
+                dtlsClient.Send(data, 0, data.Length);
+            }
+
+            byte[] buf = new byte[dtlsClient.GetReceiveLimit()];
+            while (dtlsClient.Receive(buf, 0, buf.Length, 100) >= 0)
+            {
+            }
+
+            dtlsClient.Close();
+
+            server.Shutdown(serverThread);
+        }
+
+        internal class Server
+        {
+            private readonly DtlsServerProtocol m_serverProtocol;
+            private readonly DatagramTransport m_serverTransport;
+            private volatile bool m_isShutdown = false;
+
+            internal Server(DtlsServerProtocol serverProtocol, DatagramTransport serverTransport)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_serverTransport = serverTransport;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockDtlsServer server = new MockDtlsServer();
+                    DtlsTransport dtlsServer = m_serverProtocol.Accept(server, m_serverTransport);
+                    byte[] buf = new byte[dtlsServer.GetReceiveLimit()];
+                    while (!m_isShutdown)
+                    {
+                        int length = dtlsServer.Receive(buf, 0, buf.Length, 1000);
+                        if (length >= 0)
+                        {
+                            dtlsServer.Send(buf, 0, length);
+                        }
+                    }
+                    dtlsServer.Close();
+                }
+                catch (Exception e)
+                {
+                    Console.Error.WriteLine(e);
+                    Console.Error.Flush();
+                }
+            }
+
+            internal void Shutdown(Thread serverThread)
+            {
+                if (!m_isShutdown)
+                {
+                    this.m_isShutdown = true;
+                    serverThread.Join();
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsPskProtocolTest.cs b/crypto/test/src/tls/test/DtlsPskProtocolTest.cs
new file mode 100644
index 000000000..0da6cb661
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsPskProtocolTest.cs
@@ -0,0 +1,102 @@
+using System;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class DlsPskProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            SecureRandom secureRandom = new SecureRandom();
+
+            DtlsClientProtocol clientProtocol = new DtlsClientProtocol();
+            DtlsServerProtocol serverProtocol = new DtlsServerProtocol();
+
+            MockDatagramAssociation network = new MockDatagramAssociation(1500);
+
+            Server server = new Server(serverProtocol, network.Server);
+
+            Thread serverThread = new Thread(new ThreadStart(server.Run));
+            serverThread.Start();
+
+            DatagramTransport clientTransport = network.Client;
+
+            clientTransport = new UnreliableDatagramTransport(clientTransport, secureRandom, 0, 0);
+
+            clientTransport = new LoggingDatagramTransport(clientTransport, Console.Out);
+
+            MockPskDtlsClient client = new MockPskDtlsClient(null);
+
+            DtlsTransport dtlsClient = clientProtocol.Connect(client, clientTransport);
+
+            for (int i = 1; i <= 10; ++i)
+            {
+                byte[] data = new byte[i];
+                Arrays.Fill(data, (byte)i);
+                dtlsClient.Send(data, 0, data.Length);
+            }
+
+            byte[] buf = new byte[dtlsClient.GetReceiveLimit()];
+            while (dtlsClient.Receive(buf, 0, buf.Length, 100) >= 0)
+            {
+            }
+
+            dtlsClient.Close();
+
+            server.Shutdown(serverThread);
+        }
+
+        internal class Server
+        {
+            private readonly DtlsServerProtocol m_serverProtocol;
+            private readonly DatagramTransport m_serverTransport;
+            private volatile bool m_isShutdown = false;
+
+            internal Server(DtlsServerProtocol serverProtocol, DatagramTransport serverTransport)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_serverTransport = serverTransport;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockPskDtlsServer server = new MockPskDtlsServer();
+                    DtlsTransport dtlsServer = m_serverProtocol.Accept(server, m_serverTransport);
+                    byte[] buf = new byte[dtlsServer.GetReceiveLimit()];
+                    while (!m_isShutdown)
+                    {
+                        int length = dtlsServer.Receive(buf, 0, buf.Length, 1000);
+                        if (length >= 0)
+                        {
+                            dtlsServer.Send(buf, 0, length);
+                        }
+                    }
+                    dtlsServer.Close();
+                }
+                catch (Exception e)
+                {
+                    Console.Error.WriteLine(e);
+                    Console.Error.Flush();
+                }
+            }
+
+            internal void Shutdown(Thread serverThread)
+            {
+                if (!m_isShutdown)
+                {
+                    this.m_isShutdown = true;
+                    serverThread.Join();
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsTestCase.cs b/crypto/test/src/tls/test/DtlsTestCase.cs
new file mode 100644
index 000000000..d93f17c27
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsTestCase.cs
@@ -0,0 +1,164 @@
+using System;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class DtlsTestCase
+    {
+        private static void CheckDtlsVersions(ProtocolVersion[] versions)
+        {
+            if (versions != null)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    if (!versions[i].IsDtls)
+                        throw new InvalidOperationException("Non-DTLS version");
+                }
+            }
+        }
+
+        [Test, TestCaseSource(typeof(DtlsTestSuite), "Suite")]
+        public void RunTest(TlsTestConfig config)
+        {
+            CheckDtlsVersions(config.clientSupportedVersions);
+            CheckDtlsVersions(config.serverSupportedVersions);
+
+            DtlsTestClientProtocol clientProtocol = new DtlsTestClientProtocol(config);
+            DtlsTestServerProtocol serverProtocol = new DtlsTestServerProtocol(config);
+
+            MockDatagramAssociation network = new MockDatagramAssociation(1500);
+
+            TlsTestClientImpl clientImpl = new TlsTestClientImpl(config);
+            TlsTestServerImpl serverImpl = new TlsTestServerImpl(config);
+
+            Server server = new Server(this, serverProtocol, network.Server, serverImpl);
+
+            Thread serverThread = new Thread(new ThreadStart(server.Run));
+            serverThread.Start();
+
+            Exception caught = null;
+            try
+            {
+                DatagramTransport clientTransport = network.Client;
+
+                if (TlsTestConfig.Debug)
+                {
+                    clientTransport = new LoggingDatagramTransport(clientTransport, Console.Out);
+                }
+
+                DtlsTransport dtlsClient = clientProtocol.Connect(clientImpl, clientTransport);
+
+                for (int i = 1; i <= 10; ++i)
+                {
+                    byte[] data = new byte[i];
+                    Arrays.Fill(data, (byte)i);
+                    dtlsClient.Send(data, 0, data.Length);
+                }
+
+                byte[] buf = new byte[dtlsClient.GetReceiveLimit()];
+                while (dtlsClient.Receive(buf, 0, buf.Length, 100) >= 0)
+                {
+                }
+
+                dtlsClient.Close();
+            }
+            catch (Exception e)
+            {
+                caught = e;
+                LogException(caught);
+            }
+
+            server.Shutdown(serverThread);
+
+            // TODO Add checks that the various streams were closed
+
+            Assert.AreEqual(config.expectFatalAlertConnectionEnd, clientImpl.FirstFatalAlertConnectionEnd,
+                "Client fatal alert connection end");
+            Assert.AreEqual(config.expectFatalAlertConnectionEnd, serverImpl.FirstFatalAlertConnectionEnd,
+                "Server fatal alert connection end");
+
+            Assert.AreEqual(config.expectFatalAlertDescription, clientImpl.FirstFatalAlertDescription,
+                "Client fatal alert description");
+            Assert.AreEqual(config.expectFatalAlertDescription, serverImpl.FirstFatalAlertDescription,
+                "Server fatal alert description");
+
+            if (config.expectFatalAlertConnectionEnd == -1)
+            {
+                Assert.IsNull(caught, "Unexpected client exception");
+                Assert.IsNull(server.Caught, "Unexpected server exception");
+            }
+        }
+
+        protected void LogException(Exception e)
+        {
+            if (TlsTestConfig.Debug)
+            {
+                Console.Error.WriteLine(e);
+                Console.Error.Flush();
+            }
+        }
+
+        internal class Server
+        {
+            private readonly DtlsTestCase m_outer;
+            private readonly DtlsTestServerProtocol m_serverProtocol;
+            private readonly DatagramTransport m_serverTransport;
+            private readonly TlsTestServerImpl m_serverImpl;
+
+            private volatile bool m_isShutdown = false;
+            private Exception m_caught = null;
+
+            internal Server(DtlsTestCase outer, DtlsTestServerProtocol serverProtocol,
+                DatagramTransport serverTransport, TlsTestServerImpl serverImpl)
+            {
+                this.m_outer = outer;
+                this.m_serverProtocol = serverProtocol;
+                this.m_serverTransport = serverTransport;
+                this.m_serverImpl = serverImpl;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    DtlsTransport dtlsServer = m_serverProtocol.Accept(m_serverImpl, m_serverTransport);
+                    byte[] buf = new byte[dtlsServer.GetReceiveLimit()];
+                    while (!m_isShutdown)
+                    {
+                        int length = dtlsServer.Receive(buf, 0, buf.Length, 100);
+                        if (length >= 0)
+                        {
+                            dtlsServer.Send(buf, 0, length);
+                        }
+                    }
+                    dtlsServer.Close();
+                }
+                catch (Exception e)
+                {
+                    this.m_caught = e;
+                    m_outer.LogException(m_caught);
+                }
+            }
+
+            internal void Shutdown(Thread serverThread)
+            {
+                if (!m_isShutdown)
+                {
+                    this.m_isShutdown = true;
+                    //serverThread.Interrupt();
+                    serverThread.Join();
+                }
+            }
+
+            internal Exception Caught
+            {
+                get { return m_caught; }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsTestClientProtocol.cs b/crypto/test/src/tls/test/DtlsTestClientProtocol.cs
new file mode 100644
index 000000000..99a7fa07b
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsTestClientProtocol.cs
@@ -0,0 +1,27 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class DtlsTestClientProtocol
+        : DtlsClientProtocol
+    {
+        protected readonly TlsTestConfig m_config;
+
+        public DtlsTestClientProtocol(TlsTestConfig config)
+            : base()
+        {
+            this.m_config = config;
+        }
+
+        protected override byte[] GenerateCertificateVerify(ClientHandshakeState state,
+            DigitallySigned certificateVerify)
+        {
+            if (certificateVerify.Algorithm != null && m_config.clientAuthSigAlgClaimed != null)
+            {
+                certificateVerify = new DigitallySigned(m_config.clientAuthSigAlgClaimed, certificateVerify.Signature);
+            }
+
+            return base.GenerateCertificateVerify(state, certificateVerify);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsTestServerProtocol.cs b/crypto/test/src/tls/test/DtlsTestServerProtocol.cs
new file mode 100644
index 000000000..08fc2b6a9
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsTestServerProtocol.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class DtlsTestServerProtocol
+        : DtlsServerProtocol
+    {
+        protected readonly TlsTestConfig m_config;
+
+        public DtlsTestServerProtocol(TlsTestConfig config)
+            : base()
+        {
+            this.m_config = config;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/DtlsTestSuite.cs b/crypto/test/src/tls/test/DtlsTestSuite.cs
new file mode 100644
index 000000000..0af2be32c
--- /dev/null
+++ b/crypto/test/src/tls/test/DtlsTestSuite.cs
@@ -0,0 +1,260 @@
+using System;
+using System.Collections;
+
+using NUnit.Framework;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class DtlsTestSuite
+    {
+        // Make the access to constants less verbose 
+        internal class C : TlsTestConfig {}
+
+        public DtlsTestSuite()
+        {
+        }
+
+        public static IEnumerable Suite()
+        {
+            IList testSuite = new ArrayList();
+
+            AddFallbackTests(testSuite);
+            AddVersionTests(testSuite, ProtocolVersion.DTLSv10);
+            AddVersionTests(testSuite, ProtocolVersion.DTLSv12);
+
+            return testSuite;
+        }
+
+        private static void AddFallbackTests(IList testSuite)
+        {
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(ProtocolVersion.DTLSv12);
+                c.clientFallback = true;
+
+                AddTestCase(testSuite, c, "FallbackGood");
+            }
+
+            /*
+             * NOTE: Temporarily disabled automatic test runs because of problems getting a clean exit
+             * of the DTLS server after a fatal alert. As of writing, manual runs show the correct
+             * alerts being raised
+             */
+
+#if false
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(ProtocolVersion.DTLSv12);
+                c.clientFallback = true;
+                c.clientSupportedVersions = ProtocolVersion.DTLSv10.Only();
+                c.ExpectServerFatalAlert(AlertDescription.inappropriate_fallback);
+
+                AddTestCase(testSuite, c, "FallbackBad");
+            }
+#endif
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(ProtocolVersion.DTLSv12);
+                c.clientSupportedVersions = ProtocolVersion.DTLSv10.Only();
+
+                AddTestCase(testSuite, c, "FallbackNone");
+            }
+        }
+
+        private static void AddVersionTests(IList testSuite, ProtocolVersion version)
+        {
+            string prefix = version.ToString()
+                .Replace(" ", "")
+                .Replace("\\", "")
+                .Replace(".", "")
+                + "_";
+
+            /*
+             * NOTE: Temporarily disabled automatic test runs because of problems getting a clean exit
+             * of the DTLS server after a fatal alert. As of writing, manual runs show the correct
+             * alerts being raised
+             */
+
+#if false
+            /*
+             * Server only declares support for SHA1/RSA, client selects MD5/RSA. Since the client is
+             * NOT actually tracking MD5 over the handshake, we expect fatal alert from the client.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultRsaSignatureAlgorithms();
+                c.ExpectClientFatalAlert(AlertDescription.internal_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifyHashAlg");
+            }
+
+            /*
+             * Server only declares support for SHA1/ECDSA, client selects SHA1/RSA. Since the client is
+             * actually tracking SHA1 over the handshake, we expect fatal alert to come from the server
+             * when it verifies the selected algorithm against the CertificateRequest supported
+             * algorithms.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultECDsaSignatureAlgorithms();
+                c.serverCheckSigAlgOfClientCerts = false;
+                c.ExpectServerFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySigAlg");
+            }
+
+            /*
+             * Server only declares support for SHA1/ECDSA, client signs with SHA1/RSA, but sends
+             * SHA1/ECDSA in the CertificateVerify. Since the client is actually tracking SHA1 over the
+             * handshake, and the claimed algorithm is in the CertificateRequest supported algorithms,
+             * we expect fatal alert to come from the server when it finds the claimed algorithm
+             * doesn't match the client certificate.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+                c.clientAuthSigAlgClaimed = new SignatureAndHashAlgorithm(HashAlgorithm.sha1,
+                    SignatureAlgorithm.ecdsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultECDsaSignatureAlgorithms();
+                c.ExpectServerFatalAlert(AlertDescription.decrypt_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySigAlgMismatch");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_INVALID_VERIFY;
+                c.ExpectServerFatalAlert(AlertDescription.decrypt_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySignature");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_INVALID_CERT;
+                c.ExpectServerFatalAlert(AlertDescription.bad_certificate);
+
+                AddTestCase(testSuite, c, prefix + "BadClientCertificate");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_NONE;
+                c.serverCertReq = C.SERVER_CERT_REQ_MANDATORY;
+                c.ExpectServerFatalAlert(AlertDescription.handshake_failure);
+
+                AddTestCase(testSuite, c, prefix + "BadMandatoryCertReqDeclined");
+            }
+
+            /*
+             * Server sends SHA-256/RSA certificate, which is not the default {sha1,rsa} implied by the
+             * absent signature_algorithms extension. We expect fatal alert from the client when it
+             * verifies the certificate's 'signatureAlgorithm' against the implicit default signature_algorithms.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientSendSignatureAlgorithms = false;
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.certificate_unknown);
+
+                AddTestCase(testSuite, c, prefix + "BadServerCertSigAlg");
+            }
+
+            /*
+             * Server selects MD5/RSA for ServerKeyExchange signature, which is not in the default
+             * supported signature algorithms that the client sent. We expect fatal alert from the
+             * client when it verifies the selected algorithm against the supported algorithms.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadServerKeyExchangeSigAlg");
+            }
+
+            /*
+             * Server selects MD5/RSA for ServerKeyExchange signature, which is not the default {sha1,rsa}
+             * implied by the absent signature_algorithms extension. We expect fatal alert from the
+             * client when it verifies the selected algorithm against the implicit default.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientCheckSigAlgOfServerCerts = false;
+                c.clientSendSignatureAlgorithms = false;
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadServerKeyExchangeSigAlg2");
+            }
+#endif
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+
+                AddTestCase(testSuite, c, prefix + "GoodDefault");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.serverCertReq = C.SERVER_CERT_REQ_NONE;
+
+                AddTestCase(testSuite, c, prefix + "GoodNoCertReq");
+            }
+
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.clientAuth = C.CLIENT_AUTH_NONE;
+
+                AddTestCase(testSuite, c, prefix + "GoodOptionalCertReqDeclined");
+            }
+
+#if false
+            /*
+             * Server generates downgraded (RFC 8446) ServerHello. We expect fatal alert
+             * (illegal_parameter) from the client.
+             */
+            if (!TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateDtlsTestConfig(version);
+                c.serverNegotiateVersion = version;
+                c.serverSupportedVersions = ProtocolVersion.DTLSv12.DownTo(version);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadDowngrade");
+            }
+#endif
+        }
+
+        private static void AddTestCase(IList testSuite, TlsTestConfig config, string name)
+        {
+            testSuite.Add(new TestCaseData(config).SetName(name));
+        }
+
+        private static TlsTestConfig CreateDtlsTestConfig(ProtocolVersion serverMaxVersion)
+        {
+            TlsTestConfig c = new TlsTestConfig();
+            c.clientSupportedVersions = ProtocolVersion.DTLSv12.DownTo(ProtocolVersion.DTLSv10);
+            c.serverSupportedVersions = serverMaxVersion.DownTo(ProtocolVersion.DTLSv10);
+            return c;
+        }
+
+        public static void RunTests()
+        {
+            foreach (TestCaseData data in Suite())
+            {
+                Console.WriteLine(data.TestName);
+                new DtlsTestCase().RunTest((TlsTestConfig)data.Arguments[0]);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/LoggingDatagramTransport.cs b/crypto/test/src/tls/test/LoggingDatagramTransport.cs
new file mode 100644
index 000000000..f675b72fc
--- /dev/null
+++ b/crypto/test/src/tls/test/LoggingDatagramTransport.cs
@@ -0,0 +1,87 @@
+using System;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class LoggingDatagramTransport
+        : DatagramTransport
+    {
+        private static readonly string HEX_CHARS = "0123456789ABCDEF";
+
+        private readonly DatagramTransport m_transport;
+        private readonly TextWriter m_output;
+        private readonly long m_launchTimestamp;
+
+        public LoggingDatagramTransport(DatagramTransport transport, TextWriter output)
+        {
+            this.m_transport = transport;
+            this.m_output = output;
+            this.m_launchTimestamp = DateTimeUtilities.CurrentUnixMs();
+        }
+
+        public virtual int GetReceiveLimit()
+        {
+            return m_transport.GetReceiveLimit();
+        }
+
+        public virtual int GetSendLimit()
+        {
+            return m_transport.GetSendLimit();
+        }
+
+        public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+        {
+            int length = m_transport.Receive(buf, off, len, waitMillis);
+            if (length >= 0)
+            {
+                DumpDatagram("Received", buf, off, length);
+            }
+            return length;
+        }
+
+        public virtual void Send(byte[] buf, int off, int len)
+        {
+            DumpDatagram("Sending", buf, off, len);
+            m_transport.Send(buf, off, len);
+        }
+
+        public virtual void Close()
+        {
+            m_transport.Close();
+        }
+
+        private void DumpDatagram(string verb, byte[] buf, int off, int len)
+        {
+            long timestamp = DateTimeUtilities.CurrentUnixMs() - m_launchTimestamp;
+            StringBuilder sb = new StringBuilder("(+" + timestamp + "ms) " + verb + " " + len + " byte datagram:");
+            for (int pos = 0; pos < len; ++pos)
+            {
+                if (pos % 16 == 0)
+                {
+                    sb.Append(Environment.NewLine);
+                    sb.Append("    ");
+                }
+                else if (pos % 16 == 8)
+                {
+                    sb.Append('-');
+                }
+                else
+                {
+                    sb.Append(' ');
+                }
+                int val = buf[off + pos] & 0xFF;
+                sb.Append(HEX_CHARS[val >> 4]);
+                sb.Append(HEX_CHARS[val & 0xF]);
+            }
+            Dump(sb.ToString());
+        }
+
+        private void Dump(string s)
+        {
+            lock (this) m_output.WriteLine(s);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockDatagramAssociation.cs b/crypto/test/src/tls/test/MockDatagramAssociation.cs
new file mode 100644
index 000000000..3e0c0f52b
--- /dev/null
+++ b/crypto/test/src/tls/test/MockDatagramAssociation.cs
@@ -0,0 +1,107 @@
+using System;
+using System.Collections;
+using System.Threading;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class MockDatagramAssociation
+    {
+        private int m_mtu;
+        private MockDatagramTransport m_client, m_server;
+
+        public MockDatagramAssociation(int mtu)
+        {
+            this.m_mtu = mtu;
+
+            IList clientQueue = new ArrayList();
+            IList serverQueue = new ArrayList();
+
+            this.m_client = new MockDatagramTransport(this, clientQueue, serverQueue);
+            this.m_server = new MockDatagramTransport(this, serverQueue, clientQueue);
+        }
+
+        public virtual DatagramTransport Client
+        {
+            get { return m_client; }
+        }
+
+        public virtual DatagramTransport Server
+        {
+            get { return m_server; }
+        }
+
+        private class MockDatagramTransport
+            : DatagramTransport
+        {
+            private readonly MockDatagramAssociation m_outer;
+            private IList m_receiveQueue, m_sendQueue;
+
+            internal MockDatagramTransport(MockDatagramAssociation outer, IList receiveQueue, IList sendQueue)
+            {
+                this.m_outer = outer;
+                this.m_receiveQueue = receiveQueue;
+                this.m_sendQueue = sendQueue;
+            }
+
+            public virtual int GetReceiveLimit()
+            {
+                return m_outer.m_mtu;
+            }
+
+            public virtual int GetSendLimit()
+            {
+                return m_outer.m_mtu;
+            }
+
+            public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+            {
+                lock (m_receiveQueue)
+                {
+                    if (m_receiveQueue.Count < 1)
+                    {
+                        try
+                        {
+                            Monitor.Wait(m_receiveQueue, waitMillis);
+                        }
+                        catch (ThreadInterruptedException)
+                        {
+                            // TODO Keep waiting until full wait expired?
+                        }
+
+                        if (m_receiveQueue.Count < 1)
+                            return -1;
+                    }
+
+                    byte[] packet = (byte[])m_receiveQueue[0];
+                    m_receiveQueue.RemoveAt(0);
+                    int copyLength = System.Math.Min(len, packet.Length);
+                    Array.Copy(packet, 0, buf, off, copyLength);
+                    return copyLength;
+                }
+            }
+
+            public virtual void Send(byte[] buf, int off, int len)
+            {
+                if (len > m_outer.m_mtu)
+                {
+                    // TODO Simulate rejection?
+                }
+
+                byte[] packet = Arrays.CopyOfRange(buf, off, off + len);
+
+                lock (m_sendQueue)
+                {
+                    m_sendQueue.Add(packet);
+                    Monitor.PulseAll(m_sendQueue);
+                }
+            }
+
+            public virtual void Close()
+            {
+                // TODO?
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockDtlsClient.cs b/crypto/test/src/tls/test/MockDtlsClient.cs
new file mode 100644
index 000000000..5aa1ebbd3
--- /dev/null
+++ b/crypto/test/src/tls/test/MockDtlsClient.cs
@@ -0,0 +1,170 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockDtlsClient
+        : DefaultTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockDtlsClient(TlsSession session)
+            : base(new BcTlsCrypto(new SecureRandom()))
+        {
+            this.m_session = session;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return this.m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("DTLS client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.DTLSv12.DownTo(ProtocolVersion.DTLSv10);
+        }
+
+        internal class MyTlsAuthentication
+            : TlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("DTLS client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new String[]{ "x509-server-dsa.pem", "x509-server-ecdh.pem",
+                    "x509-server-ecdsa.pem", "x509-server-ed25519.pem", "x509-server-ed448.pem",
+                    "x509-server-rsa_pss_256.pem", "x509-server-rsa_pss_384.pem", "x509-server-rsa_pss_512.pem",
+                    "x509-server-rsa-enc.pem", "x509-server-rsa-sign.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+
+            public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
+            {
+                short[] certificateTypes = certificateRequest.CertificateTypes;
+                if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign))
+                    return null;
+
+                return TlsTestUtilities.LoadSignerCredentials(m_context,
+                    certificateRequest.SupportedSignatureAlgorithms, SignatureAlgorithm.rsa, "x509-client-rsa.pem",
+                    "x509-client-key-rsa.pem");
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockDtlsServer.cs b/crypto/test/src/tls/test/MockDtlsServer.cs
new file mode 100644
index 000000000..18e53628e
--- /dev/null
+++ b/crypto/test/src/tls/test/MockDtlsServer.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockDtlsServer
+        : DefaultTlsServer
+    {
+        internal MockDtlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()))
+        {
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("DTLS server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override CertificateRequest GetCertificateRequest()
+        {
+            short[] certificateTypes = new short[]{ ClientCertificateType.rsa_sign,
+                ClientCertificateType.dss_sign, ClientCertificateType.ecdsa_sign };
+
+            IList serverSigAlgs = null;
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(m_context.ServerVersion))
+            {
+                serverSigAlgs = TlsUtilities.GetDefaultSupportedSignatureAlgorithms(m_context);
+            }
+
+            IList certificateAuthorities = new ArrayList();
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-dsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-ecdsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-rsa.pem").Subject);
+
+            // All the CA certificates are currently configured with this subject
+            certificateAuthorities.Add(new X509Name("CN=BouncyCastle TLS Test CA"));
+
+            return new CertificateRequest(certificateTypes, serverSigAlgs, certificateAuthorities);
+        }
+
+        public override void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            TlsCertificate[] chain = clientCertificate.GetCertificateList();
+
+            Console.WriteLine("DTLS server received client certificate chain of length " + chain.Length);
+            for (int i = 0; i != chain.Length; i++)
+            {
+                X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                // TODO Create fingerprint based on certificate signature algorithm digest
+                Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                    + entry.Subject + ")");
+            }
+
+            bool isEmpty = (clientCertificate == null || clientCertificate.IsEmpty);
+
+            if (isEmpty)
+                return;
+
+            string[] trustedCertResources = new string[]{ "x509-client-dsa.pem", "x509-client-ecdh.pem",
+                "x509-client-ecdsa.pem", "x509-client-ed25519.pem", "x509-client-ed448.pem",
+                "x509-client-rsa_pss_256.pem", "x509-client-rsa_pss_384.pem", "x509-client-rsa_pss_512.pem",
+                "x509-client-rsa.pem" };
+
+            TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                trustedCertResources);
+
+            if (null == certPath)
+                throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+            TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[]{ "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected override TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            IList clientSigAlgs = m_context.SecurityParameters.ClientSigAlgs;
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, clientSigAlgs, SignatureAlgorithm.rsa);
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.DTLSv12.DownTo(ProtocolVersion.DTLSv10);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockPskDtlsClient.cs b/crypto/test/src/tls/test/MockPskDtlsClient.cs
new file mode 100644
index 000000000..c83c9e7fd
--- /dev/null
+++ b/crypto/test/src/tls/test/MockPskDtlsClient.cs
@@ -0,0 +1,161 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockPskDtlsClient
+        : PskTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockPskDtlsClient(TlsSession session)
+            : this(session, new BasicTlsPskIdentity("client", Strings.ToUtf8ByteArray("TLS_TEST_PSK")))
+        {
+        }
+
+        internal MockPskDtlsClient(TlsSession session, TlsPskIdentity pskIdentity)
+            : base(new BcTlsCrypto(new SecureRandom()), pskIdentity)
+        {
+            this.m_session = session;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS-PSK client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS-PSK client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("DTLS-PSK client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.DTLSv12.Only();
+        }
+
+        internal class MyTlsAuthentication
+            : ServerOnlyTlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public override void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("DTLS-PSK client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[] { "x509-server-rsa-enc.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockPskDtlsServer.cs b/crypto/test/src/tls/test/MockPskDtlsServer.cs
new file mode 100644
index 000000000..bb084535a
--- /dev/null
+++ b/crypto/test/src/tls/test/MockPskDtlsServer.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockPskDtlsServer
+        : PskTlsServer
+    {
+        internal MockPskDtlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()), new MyIdentityManager())
+        {
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS-PSK server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("DTLS-PSK server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("DTLS-PSK server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+
+            byte[] pskIdentity = m_context.SecurityParameters.PskIdentity;
+            if (pskIdentity != null)
+            {
+                string name = Strings.FromUtf8ByteArray(pskIdentity);
+                Console.WriteLine("DTLS-PSK server completed handshake for PSK identity: " + name);
+            }
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[] { "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.DTLSv12.Only();
+        }
+
+        internal class MyIdentityManager
+            : TlsPskIdentityManager
+        {
+            public byte[] GetHint()
+            {
+                return Strings.ToUtf8ByteArray("hint");
+            }
+
+            public byte[] GetPsk(byte[] identity)
+            {
+                if (identity != null)
+                {
+                    string name = Strings.FromUtf8ByteArray(identity);
+                    if (name.Equals("client"))
+                    {
+                        return Strings.ToUtf8ByteArray("TLS_TEST_PSK");
+                    }
+                }
+                return null;
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockPskTlsClient.cs b/crypto/test/src/tls/test/MockPskTlsClient.cs
new file mode 100644
index 000000000..46774266b
--- /dev/null
+++ b/crypto/test/src/tls/test/MockPskTlsClient.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockPskTlsClient
+        : PskTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockPskTlsClient(TlsSession session)
+            : this(session, new BasicTlsPskIdentity("client", Strings.ToUtf8ByteArray("TLS_TEST_PSK")))
+        {
+        }
+
+        internal MockPskTlsClient(TlsSession session, TlsPskIdentity pskIdentity)
+            : base(new BcTlsCrypto(new SecureRandom()), pskIdentity)
+        {
+            this.m_session = session;
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_1_1);
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            return protocolNames;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-PSK client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-PSK client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                base.GetClientExtensions());
+
+            {
+                /*
+                 * NOTE: If you are copying test code, do not blindly set these extensions in your own client.
+                 */
+                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
+                TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, m_context.Crypto.SecureRandom.Next(16));
+                TlsExtensionsUtilities.AddTruncatedHmacExtension(clientExtensions);
+            }
+            return clientExtensions;
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("TLS-PSK client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.Only();
+        }
+
+        internal class MyTlsAuthentication
+            : ServerOnlyTlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public override void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("TLS-PSK client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[] { "x509-server-rsa-enc.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/tls/test/MockPskTlsServer.cs b/crypto/test/src/tls/test/MockPskTlsServer.cs
new file mode 100644
index 000000000..743073b04
--- /dev/null
+++ b/crypto/test/src/tls/test/MockPskTlsServer.cs
@@ -0,0 +1,122 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockPskTlsServer
+        : PskTlsServer
+    {
+        internal MockPskTlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()), new MyIdentityManager())
+        {
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            protocolNames.Add(ProtocolName.Http_1_1);
+            return protocolNames;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-PSK server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-PSK server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("TLS-PSK server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+
+            byte[] pskIdentity = m_context.SecurityParameters.PskIdentity;
+            if (pskIdentity != null)
+            {
+                string name = Strings.FromUtf8ByteArray(pskIdentity);
+                Console.WriteLine("TLS-PSK server completed handshake for PSK identity: " + name);
+            }
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[] { "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.Only();
+        }
+
+        internal class MyIdentityManager
+            : TlsPskIdentityManager
+        {
+            public byte[] GetHint()
+            {
+                return Strings.ToUtf8ByteArray("hint");
+            }
+
+            public byte[] GetPsk(byte[] identity)
+            {
+                if (identity != null)
+                {
+                    string name = Strings.FromUtf8ByteArray(identity);
+                    if (name.Equals("client"))
+                    {
+                        return Strings.ToUtf8ByteArray("TLS_TEST_PSK");
+                    }
+                }
+                return null;
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockSrpTlsClient.cs b/crypto/test/src/tls/test/MockSrpTlsClient.cs
new file mode 100644
index 000000000..3d2232893
--- /dev/null
+++ b/crypto/test/src/tls/test/MockSrpTlsClient.cs
@@ -0,0 +1,182 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockSrpTlsClient
+        : SrpTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockSrpTlsClient(TlsSession session, TlsSrpIdentity srpIdentity)
+            : base(new BcTlsCrypto(new SecureRandom()), srpIdentity)
+        {
+            this.m_session = session;
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_1_1);
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            return protocolNames;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-SRP client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-SRP client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                base.GetClientExtensions());
+
+            {
+                /*
+                 * NOTE: If you are copying test code, do not blindly set these extensions in your own client.
+                 */
+                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
+                TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, m_context.Crypto.SecureRandom.Next(16));
+                TlsExtensionsUtilities.AddTruncatedHmacExtension(clientExtensions);
+            }
+            return clientExtensions;
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("TLS-SRP client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            return ProtocolVersion.TLSv12.Only();
+        }
+
+        internal class MyTlsAuthentication
+            : ServerOnlyTlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public override void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("TLS-SRP client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[] { "x509-server-dsa.pem", "x509-server-rsa_pss_256.pem",
+                    "x509-server-rsa_pss_384.pem", "x509-server-rsa_pss_512.pem", "x509-server-rsa-sign.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/tls/test/MockSrpTlsServer.cs b/crypto/test/src/tls/test/MockSrpTlsServer.cs
new file mode 100644
index 000000000..725901811
--- /dev/null
+++ b/crypto/test/src/tls/test/MockSrpTlsServer.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Crypto.Agreement.Srp;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockSrpTlsServer
+        : SrpTlsServer
+    {
+        internal static readonly Srp6Group TEST_GROUP = Tls.Crypto.Srp6StandardGroups.rfc5054_1024;
+        internal static readonly byte[] TEST_IDENTITY = Strings.ToUtf8ByteArray("client");
+        internal static readonly byte[] TEST_PASSWORD = Strings.ToUtf8ByteArray("password");
+        internal static readonly TlsSrpIdentity TEST_SRP_IDENTITY = new BasicTlsSrpIdentity(TEST_IDENTITY,
+            TEST_PASSWORD);
+        internal static readonly byte[] TEST_SALT = Strings.ToUtf8ByteArray("salt");
+        internal static readonly byte[] TEST_SEED_KEY = Strings.ToUtf8ByteArray("seed_key");
+
+        internal MockSrpTlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()), new MyIdentityManager(new BcTlsCrypto(new SecureRandom())))
+        {
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            protocolNames.Add(ProtocolName.Http_1_1);
+            return protocolNames;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-SRP server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS-SRP server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("TLS-SRP server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+
+            byte[] srpIdentity = m_context.SecurityParameters.SrpIdentity;
+            if (srpIdentity != null)
+            {
+                string name = Strings.FromUtf8ByteArray(srpIdentity);
+                Console.WriteLine("TLS-SRP server completed handshake for SRP identity: " + name);
+            }
+        }
+
+        protected override TlsCredentialedSigner GetDsaSignerCredentials()
+        {
+            IList clientSigAlgs = m_context.SecurityParameters.ClientSigAlgs;
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, clientSigAlgs, SignatureAlgorithm.dsa);
+        }
+
+        protected override TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            IList clientSigAlgs = m_context.SecurityParameters.ClientSigAlgs;
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, clientSigAlgs, SignatureAlgorithm.rsa);
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        internal class MyIdentityManager
+            : TlsSrpIdentityManager
+        {
+            protected SimulatedTlsSrpIdentityManager m_unknownIdentityManager;
+
+            internal MyIdentityManager(TlsCrypto crypto)
+            {
+                m_unknownIdentityManager = SimulatedTlsSrpIdentityManager.GetRfc5054Default(crypto, TEST_GROUP,
+                    TEST_SEED_KEY);
+            }
+
+            public TlsSrpLoginParameters GetLoginParameters(byte[] identity)
+            {
+                if (Arrays.ConstantTimeAreEqual(TEST_IDENTITY, identity))
+                {
+                    Srp6VerifierGenerator verifierGenerator = new Srp6VerifierGenerator();
+                    verifierGenerator.Init(TEST_GROUP.N, TEST_GROUP.G, new Sha1Digest());
+
+                    BigInteger verifier = verifierGenerator.GenerateVerifier(TEST_SALT, identity, TEST_PASSWORD);
+
+                    TlsSrpConfig srpConfig = new TlsSrpConfig();
+                    srpConfig.SetExplicitNG(new BigInteger[]{ TEST_GROUP.N, TEST_GROUP.G });
+
+                    return new TlsSrpLoginParameters(identity, srpConfig, verifier, TEST_SALT);
+                }
+
+                return m_unknownIdentityManager.GetLoginParameters(identity);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/MockTlsClient.cs b/crypto/test/src/tls/test/MockTlsClient.cs
new file mode 100644
index 000000000..62b699590
--- /dev/null
+++ b/crypto/test/src/tls/test/MockTlsClient.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockTlsClient
+        : DefaultTlsClient
+    {
+        internal TlsSession m_session;
+
+        internal MockTlsClient(TlsSession session)
+            : base(new BcTlsCrypto(new SecureRandom()))
+        {
+            this.m_session = session;
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_1_1);
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            return protocolNames;
+        }
+
+        public override TlsSession GetSessionToResume()
+        {
+            return m_session;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS client raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS client received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(
+                base.GetClientExtensions());
+
+            {
+                /*
+                 * NOTE: If you are copying test code, do not blindly set these extensions in your own client.
+                 */
+                TlsExtensionsUtilities.AddMaxFragmentLengthExtension(clientExtensions, MaxFragmentLength.pow2_9);
+                TlsExtensionsUtilities.AddPaddingExtension(clientExtensions, m_context.Crypto.SecureRandom.Next(16));
+                TlsExtensionsUtilities.AddTruncatedHmacExtension(clientExtensions);
+            }
+            return clientExtensions;
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            Console.WriteLine("TLS client negotiated " + serverVersion);
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(m_context);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Client ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            TlsSession newSession = m_context.Session;
+            if (newSession != null)
+            {
+                if (newSession.IsResumable)
+                {
+                    byte[] newSessionID = newSession.SessionID;
+                    string hex = ToHexString(newSessionID);
+
+                    if (m_session != null && Arrays.AreEqual(m_session.SessionID, newSessionID))
+                    {
+                        Console.WriteLine("Client resumed session: " + hex);
+                    }
+                    else
+                    {
+                        Console.WriteLine("Client established session: " + hex);
+                    }
+
+                    this.m_session = newSession;
+                }
+
+                byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+                if (null != tlsServerEndPoint)
+                {
+                    Console.WriteLine("Client 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+                }
+
+                byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+                Console.WriteLine("Client 'tls-unique': " + ToHexString(tlsUnique));
+            }
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        internal class MyTlsAuthentication
+            : TlsAuthentication
+        {
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsContext context)
+            {
+                this.m_context = context;
+            }
+
+            public void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                Console.WriteLine("TLS client received server certificate chain of length " + chain.Length);
+                for (int i = 0; i != chain.Length; i++)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[]{ "x509-server-dsa.pem", "x509-server-ecdh.pem",
+                    "x509-server-ecdsa.pem", "x509-server-ed25519.pem", "x509-server-ed448.pem",
+                    "x509-server-rsa_pss_256.pem", "x509-server-rsa_pss_384.pem", "x509-server-rsa_pss_512.pem",
+                    "x509-server-rsa-enc.pem", "x509-server-rsa-sign.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+
+            public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
+            {
+                short[] certificateTypes = certificateRequest.CertificateTypes;
+                if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign))
+                    return null;
+
+                return TlsTestUtilities.LoadSignerCredentials(m_context,
+                    certificateRequest.SupportedSignatureAlgorithms, SignatureAlgorithm.rsa, "x509-client-rsa.pem",
+                    "x509-client-key-rsa.pem");
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/tls/test/MockTlsServer.cs b/crypto/test/src/tls/test/MockTlsServer.cs
new file mode 100644
index 000000000..94d4c7dfd
--- /dev/null
+++ b/crypto/test/src/tls/test/MockTlsServer.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class MockTlsServer
+        : DefaultTlsServer
+    {
+        internal MockTlsServer()
+            : base(new BcTlsCrypto(new SecureRandom()))
+        {
+        }
+
+        protected override IList GetProtocolNames()
+        {
+            IList protocolNames = new ArrayList();
+            protocolNames.Add(ProtocolName.Http_2_Tls);
+            protocolNames.Add(ProtocolName.Http_1_1);
+            return protocolNames;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS server raised alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+            if (message != null)
+            {
+                output.WriteLine("> " + message);
+            }
+            if (cause != null)
+            {
+                output.WriteLine(cause);
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+            output.WriteLine("TLS server received alert: " + AlertLevel.GetText(alertLevel)
+                + ", " + AlertDescription.GetText(alertDescription));
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = base.GetServerVersion();
+
+            Console.WriteLine("TLS server negotiated " + serverVersion);
+
+            return serverVersion;
+        }
+
+        public override CertificateRequest GetCertificateRequest()
+        {
+            short[] certificateTypes = new short[]{ ClientCertificateType.rsa_sign,
+                ClientCertificateType.dss_sign, ClientCertificateType.ecdsa_sign };
+
+            IList serverSigAlgs = null;
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(m_context.ServerVersion))
+            {
+                serverSigAlgs = TlsUtilities.GetDefaultSupportedSignatureAlgorithms(m_context);
+            }
+
+            IList certificateAuthorities = new ArrayList();
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-dsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-ecdsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-rsa.pem").Subject);
+
+            // All the CA certificates are currently configured with this subject
+            certificateAuthorities.Add(new X509Name("CN=BouncyCastle TLS Test CA"));
+
+            return new CertificateRequest(certificateTypes, serverSigAlgs, certificateAuthorities);
+        }
+
+        public override void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            TlsCertificate[] chain = clientCertificate.GetCertificateList();
+
+            Console.WriteLine("TLS server received client certificate chain of length " + chain.Length);
+            for (int i = 0; i < chain.Length; ++i)
+            {
+                X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                // TODO Create fingerprint based on certificate signature algorithm digest
+                Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                    + entry.Subject + ")");
+            }
+
+            bool isEmpty = (clientCertificate == null || clientCertificate.IsEmpty);
+
+            if (isEmpty)
+                return;
+
+            string[] trustedCertResources = new string[]{ "x509-client-dsa.pem", "x509-client-ecdh.pem",
+                "x509-client-ecdsa.pem", "x509-client-ed25519.pem", "x509-client-ed448.pem",
+                "x509-client-rsa_pss_256.pem", "x509-client-rsa_pss_384.pem", "x509-client-rsa_pss_512.pem",
+                "x509-client-rsa.pem" };
+
+            TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                trustedCertResources);
+
+            if (null == certPath)
+                throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+            TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            ProtocolName protocolName = m_context.SecurityParameters.ApplicationProtocol;
+            if (protocolName != null)
+            {
+                Console.WriteLine("Server ALPN: " + protocolName.GetUtf8Decoding());
+            }
+
+            byte[] tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            Console.WriteLine("Server 'tls-server-end-point': " + ToHexString(tlsServerEndPoint));
+
+            byte[] tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+            Console.WriteLine("Server 'tls-unique': " + ToHexString(tlsUnique));
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[]{ "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected override TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            IList clientSigAlgs = m_context.SecurityParameters.ClientSigAlgs;
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, clientSigAlgs, SignatureAlgorithm.rsa);
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/NetworkStream.cs b/crypto/test/src/tls/test/NetworkStream.cs
new file mode 100644
index 000000000..c5f1dfd59
--- /dev/null
+++ b/crypto/test/src/tls/test/NetworkStream.cs
@@ -0,0 +1,101 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class NetworkStream
+        : Stream
+    {
+        private readonly Stream m_inner;
+        private bool m_closed = false;
+
+        internal NetworkStream(Stream inner)
+        {
+            this.m_inner = inner;
+        }
+
+        internal virtual bool IsClosed
+        {
+            get { lock (this) return m_closed; }
+        }
+
+        public override bool CanRead
+        {
+            get { return m_inner.CanRead; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return m_inner.CanSeek; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return m_inner.CanWrite; }
+        }
+
+        public override void Close()
+        {
+            lock (this) m_closed = true;
+        }
+
+        public override void Flush()
+        {
+            m_inner.Flush();
+        }
+
+        public override long Length
+        {
+            get { return m_inner.Length; }
+        }
+
+        public override long Position
+        {
+            get { return m_inner.Position; }
+            set { m_inner.Position = value; }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            return m_inner.Seek(offset, origin);
+        }
+
+        public override void SetLength(long value)
+        {
+            m_inner.SetLength(value);
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            CheckNotClosed();
+            return m_inner.Read(buffer, offset, count);
+        }
+
+        public override int ReadByte()
+        {
+            CheckNotClosed();
+            return m_inner.ReadByte();
+        }
+
+        public override void Write(byte[] buf, int off, int len)
+        {
+            CheckNotClosed();
+            m_inner.Write(buf, off, len);
+        }
+
+        public override void WriteByte(byte value)
+        {
+            CheckNotClosed();
+            m_inner.WriteByte(value);
+        }
+
+        private void CheckNotClosed()
+        {
+            lock (this)
+            {
+                if (m_closed)
+                    throw new ObjectDisposedException(this.GetType().Name);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/PipedStream.cs b/crypto/test/src/tls/test/PipedStream.cs
new file mode 100644
index 000000000..6de5703e1
--- /dev/null
+++ b/crypto/test/src/tls/test/PipedStream.cs
@@ -0,0 +1,134 @@
+using System;
+using System.IO;
+using System.Threading;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class PipedStream
+        : Stream
+    {
+        private readonly MemoryStream m_buf = new MemoryStream();
+        private bool m_closed = false;
+
+        private PipedStream m_other = null;
+        private long m_readPos = 0;
+
+        internal PipedStream()
+        {
+        }
+
+        internal PipedStream(PipedStream other)
+        {
+            lock (other)
+            {
+                this.m_other = other;
+                other.m_other = this;
+            }
+        }
+
+        public override bool CanRead
+        {
+            get { return true; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return false; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return true; }
+        }
+
+        public override void Close()
+        {
+            lock (this)
+            {
+                m_closed = true;
+                Monitor.PulseAll(this);
+            }
+        }
+
+        public override void Flush()
+        {
+        }
+
+        public override long Length
+        {
+            get { throw new NotImplementedException(); }
+        }
+
+        public override long Position
+        {
+            get { throw new NotImplementedException(); }
+            set { throw new NotImplementedException(); }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            lock (m_other)
+            {
+                WaitForData();
+                int len = (int)System.Math.Min(count, m_other.m_buf.Position - m_readPos);
+                Array.Copy(m_other.m_buf.GetBuffer(), m_readPos, buffer, offset, len);
+                m_readPos += len;
+                return len;
+            }
+        }
+
+        public override int ReadByte()
+        {
+            lock (m_other)
+            {
+                WaitForData();
+                bool eof = (m_readPos >= m_other.m_buf.Position);
+                return eof ? -1 : m_other.m_buf.GetBuffer()[m_readPos++];
+            }
+        }
+
+        public override void Write(byte[] buf, int off, int len)
+        {
+            lock (this)
+            {
+                CheckOpen();
+                m_buf.Write(buf, off, len);
+                Monitor.PulseAll(this);
+            }
+        }
+
+        public override void WriteByte(byte value)
+        {
+            lock (this)
+            {
+                CheckOpen();
+                m_buf.WriteByte(value);
+                Monitor.PulseAll(m_buf);
+            }
+        }
+
+        private void CheckOpen()
+        {
+            if (m_closed)
+                throw new ObjectDisposedException(this.GetType().Name);
+        }
+
+        private void WaitForData()
+        {
+            while (m_readPos >= m_other.m_buf.Position && !m_other.m_closed)
+            {
+                Monitor.Wait(m_other);
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/PrfTest.cs b/crypto/test/src/tls/test/PrfTest.cs
new file mode 100644
index 000000000..de00166ed
--- /dev/null
+++ b/crypto/test/src/tls/test/PrfTest.cs
@@ -0,0 +1,97 @@
+using System;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class PrfTest
+    {
+        [Test]
+        public void TestLwTls11()
+        {
+            byte[] pre_master_secret = Hex.Decode("86051948e4d9a0cd273b6cd3a76557fc695e2ad9517cda97081ed009588a20ab48d0b128de8f917da74e711879460b60");
+            byte[] serverHello_random = Hex.Decode("55f1f273d4cdd4abb97f6856ed10f83a799dc42403c3f60c4e504419db4fd727");
+            byte[] clientHello_random = Hex.Decode("0b71e1f7232e675112510cf654a5e6280b3bd8ff078b67ec55276bfaddb92075");
+            byte[] server_random = Hex.Decode("a62615ee7fee41993588b2542735f90910c5a0f9c5dcb64898fdf3e90dc72a5f");
+            byte[] client_random = Hex.Decode("7798a130b732d7789e59a5fc14ad331ae91199f7d122e7fd4a594036b0694873");
+            byte[] master_secret = Hex.Decode("37841ef801f8cbdb49b6a164025de3e0ea8169604ffe80bd98b45cdd34105251cedac7223045ff4c7b67c8a12bf3141c");
+            byte[] key_block = Hex.Decode("c520e2409fa54facd3da01910f50a28f2f50986beb56b0c7b4cee9122e8f7428b7f7b8277bda931c71d35fdc2ea92127a5a143f63fe145275af5bcdab26113deffbb87a67f965b3964ea1ca29df1841c1708e6f42aacd87c12c4471913f61bb994fe3790b735dd11");
+
+            byte[] msSeed = Arrays.Concatenate(clientHello_random, serverHello_random);
+
+            BcTlsCrypto crypto = new BcTlsCrypto(new SecureRandom());
+            TlsSecret masterSecret = new BcTlsSecret(crypto, pre_master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_legacy, ExporterLabel.master_secret, msSeed, master_secret.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(master_secret, masterSecret.Extract()), "master secret wrong");
+
+            byte[] keSeed = Arrays.Concatenate(server_random, client_random);
+
+            TlsSecret keyExpansion = new BcTlsSecret(crypto, master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_legacy, ExporterLabel.key_expansion, keSeed, key_block.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(key_block, keyExpansion.Extract()), "key expansion error");
+        }
+
+        [Test]
+        public void TestLwTls12_Sha256Prf()
+        {
+            byte[] pre_master_secret = Hex.Decode("f8938ecc9edebc5030c0c6a441e213cd24e6f770a50dda07876f8d55da062bcadb386b411fd4fe4313a604fce6c17fbc");
+            byte[] serverHello_random = Hex.Decode("f6c9575ed7ddd73e1f7d16eca115415812a43c2b747daaaae043abfb50053fce");
+            byte[] clientHello_random = Hex.Decode("36c129d01a3200894b9179faac589d9835d58775f9b5ea3587cb8fd0364cae8c");
+            byte[] server_random = Hex.Decode("ae6c806f8ad4d80784549dff28a4b58fd837681a51d928c3e30ee5ff14f39868");
+            byte[] client_random = Hex.Decode("62e1fd91f23f558a605f28478c58cf72637b89784d959df7e946d3f07bd1b616");
+            byte[] master_secret = Hex.Decode("202c88c00f84a17a20027079604787461176455539e705be730890602c289a5001e34eeb3a043e5d52a65e66125188bf");
+            byte[] key_block = Hex.Decode("d06139889fffac1e3a71865f504aa5d0d2a2e89506c6f2279b670c3e1b74f531016a2530c51a3a0f7e1d6590d0f0566b2f387f8d11fd4f731cdd572d2eae927f6f2f81410b25e6960be68985add6c38445ad9f8c64bf8068bf9a6679485d966f1ad6f68b43495b10a683755ea2b858d70ccac7ec8b053c6bd41ca299d4e51928");
+
+            byte[] msSeed = Arrays.Concatenate(clientHello_random, serverHello_random);
+
+            BcTlsCrypto crypto = new BcTlsCrypto(new SecureRandom());
+            TlsSecret masterSecret = new BcTlsSecret(crypto, pre_master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_sha256, ExporterLabel.master_secret, msSeed, master_secret.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(master_secret, masterSecret.Extract()), "master secret wrong");
+
+            byte[] keSeed = Arrays.Concatenate(server_random, client_random);
+
+            TlsSecret keyExpansion = new BcTlsSecret(crypto, master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_sha256, ExporterLabel.key_expansion, keSeed, key_block.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(key_block, keyExpansion.Extract()), "key expansion error");
+        }
+
+        [Test]
+        public void TestLwTls12_Sha384Prf()
+        {
+            byte[] pre_master_secret = Hex.Decode("a5e2642633f5b8c81ad3fe0c2fe3a8e5ef806b06121dd10df4bb0fe857bfdcf522558e05d2682c9a80c741a3aab1716f");
+            byte[] serverHello_random = Hex.Decode("cb6e0b3eb02976b6466dfa9651c2919414f1648fd3a7838d02153e5bd39535b6");
+            byte[] clientHello_random = Hex.Decode("abe4bf5527429ac8eb13574d2709e8012bd1a113c6d3b1d3aa2c3840518778ac");
+            byte[] server_random = Hex.Decode("1b1c8568344a65c30828e7483c0e353e2c68641c9551efae6927d9cd627a107c");
+            byte[] client_random = Hex.Decode("954b5fe1849c2ede177438261f099a2fcd884d001b9fe1de754364b1f6a6dd8e");
+            byte[] master_secret = Hex.Decode("b4d49bfa87747fe815457bc3da15073d6ac73389e703079a3503c09e14bd559a5b3c7c601c7365f6ea8c68d3d9596827");
+            byte[] key_block = Hex.Decode("10fd89ef689c7ef033387b8a8f3e5e8e7c11f680f6bdd71fbac3246a73e98d45d03185dde686e6b2369e4503e9dc5a6d2cee3e2bf2fa3f41d3de57dff3e197c8a9d5f74cc2d277119d894f8584b07a0a5822f0bd68b3433ec6adaf5c9406c5f3ddbb71bbe17ce98f3d4d5893d3179ef369f57aad908e2bf710639100c3ce7e0c");
+
+            byte[] msSeed = Arrays.Concatenate(clientHello_random, serverHello_random);
+
+            BcTlsCrypto crypto = new BcTlsCrypto(new SecureRandom());
+            TlsSecret masterSecret = new BcTlsSecret(crypto, pre_master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_sha384, ExporterLabel.master_secret, msSeed, master_secret.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(master_secret, masterSecret.Extract()), "master secret wrong");
+
+            byte[] keSeed = Arrays.Concatenate(server_random, client_random);
+
+            TlsSecret keyExpansion = new BcTlsSecret(crypto, master_secret)
+                .DeriveUsingPrf(PrfAlgorithm.tls_prf_sha384, ExporterLabel.key_expansion, keSeed, key_block.Length);
+
+            Assert.IsTrue(Arrays.AreEqual(key_block, keyExpansion.Extract()), "key expansion error");
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/PskTlsClientTest.cs b/crypto/test/src/tls/test/PskTlsClientTest.cs
new file mode 100644
index 000000000..62cfc0dc2
--- /dev/null
+++ b/crypto/test/src/tls/test/PskTlsClientTest.cs
@@ -0,0 +1,113 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    /// <summary>A simple test designed to conduct a TLS handshake with an external TLS server.</summary>
+    /// <remarks>
+    /// Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in this package
+    /// (under 'src/test/resources') for help configuring an external TLS server.<br/<br/
+    /// In both cases, extra options are required to enable PSK ciphersuites and configure identities/keys.
+    /// </remarks>
+    [TestFixture]
+    public class PskTlsClientTest
+    {
+        [Test, Ignore]
+        public void TestConnection()
+        {
+            string host = "localhost";
+            int port = 5556;
+
+            long time1 = DateTimeUtilities.CurrentUnixMs();
+
+            /*
+             * Note: This is the default PSK identity for 'openssl s_server' testing, the server must be
+             * started with "-psk 6161616161" to make the keys match, and possibly the "-psk_hint"
+             * option should be present.
+             */
+            //string psk_identity = "Client_identity";
+            //byte[] psk = new byte[] { 0x61, 0x61, 0x61, 0x61, 0x61 };
+
+            // These correspond to the configuration of MockPskTlsServer
+            string psk_identity = "client";
+            byte[] psk = Strings.ToUtf8ByteArray("TLS_TEST_PSK");
+
+            BasicTlsPskIdentity pskIdentity = new BasicTlsPskIdentity(psk_identity, psk);
+
+            MockPskTlsClient client = new MockPskTlsClient(null, pskIdentity);
+            TlsClientProtocol protocol = OpenTlsClientConnection(host, port, client);
+            protocol.Close();
+
+            long time2 = DateTimeUtilities.CurrentUnixMs();
+            Console.WriteLine("Elapsed 1: " + (time2 - time1) + "ms");
+
+            client = new MockPskTlsClient(client.GetSessionToResume(), pskIdentity);
+            protocol = OpenTlsClientConnection(host, port, client);
+
+            long time3 = DateTimeUtilities.CurrentUnixMs();
+            Console.WriteLine("Elapsed 2: " + (time3 - time2) + "ms");
+
+            Http11Get(host, port, protocol.Stream);
+
+            protocol.Close();
+        }
+
+        private static void Http11Get(string host, int port, Stream s)
+        {
+            WriteUtf8Line(s, "GET / HTTP/1.1");
+            //WriteUtf8Line(s, "Host: " + host + ":" + port);
+            WriteUtf8Line(s, "");
+            s.Flush();
+
+            Console.WriteLine("---");
+
+            string[] ends = new string[] { "</HTML>", "HTTP/1.1 3", "HTTP/1.1 4" };
+
+            StreamReader reader = new StreamReader(s);
+
+            bool finished = false;
+            string line;
+            while (!finished && (line = reader.ReadLine()) != null)
+            {
+                Console.WriteLine("<<< " + line);
+
+                string upperLine = TlsTestUtilities.ToUpperInvariant(line);
+
+                // TEST CODE ONLY. This is not a robust way of parsing the result!
+                foreach (string end in ends)
+                {
+                    if (upperLine.Contains(end))
+                    {
+                        finished = true;
+                        break;
+                    }
+                }
+            }
+
+            Console.Out.Flush();
+        }
+
+        private static TlsClientProtocol OpenTlsClientConnection(string hostname, int port, TlsClient client)
+        {
+            TcpClient tcp = new TcpClient(hostname, port);
+
+            TlsClientProtocol protocol = new TlsClientProtocol(tcp.GetStream());
+            protocol.Connect(client);
+            return protocol;
+        }
+
+        private static void WriteUtf8Line(Stream output, string line)
+        {
+            byte[] buf = Encoding.UTF8.GetBytes(line + "\r\n");
+            output.Write(buf, 0, buf.Length);
+            Console.WriteLine(">>> " + line);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/PskTlsServerTest.cs b/crypto/test/src/tls/test/PskTlsServerTest.cs
new file mode 100644
index 000000000..9d87a8d35
--- /dev/null
+++ b/crypto/test/src/tls/test/PskTlsServerTest.cs
@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    /// <summary>A simple test designed to conduct a TLS handshake with an external TLS client.</summary>
+    /// <remarks>
+    /// Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in this package
+    /// (under 'src/test/resources') for help configuring an external TLS client.
+    /// </remarks>
+    [TestFixture]
+    public class PskTlsServerTest
+    {
+        [Test, Ignore]
+        public void TestConnection()
+        {
+            int port = 5556;
+
+            TcpListener ss = new TcpListener(IPAddress.Any, port);
+            ss.Start();
+            Stream stdout = Console.OpenStandardOutput();
+            try
+            {
+                while (true)
+                {
+                    TcpClient s = ss.AcceptTcpClient();
+                    Console.WriteLine("--------------------------------------------------------------------------------");
+                    Console.WriteLine("Accepted " + s);
+                    Server serverRun = new Server(s, stdout);
+                    Thread t = new Thread(new ThreadStart(serverRun.Run));
+                    t.Start();
+                }
+            }
+            finally
+            {
+                ss.Stop();
+            }
+        }
+
+        internal class Server
+        {
+            private readonly TcpClient s;
+            private readonly Stream stdout;
+
+            internal Server(TcpClient s, Stream stdout)
+            {
+                this.s = s;
+                this.stdout = stdout;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockPskTlsServer server = new MockPskTlsServer();
+                    TlsServerProtocol serverProtocol = new TlsServerProtocol(s.GetStream());
+                    serverProtocol.Accept(server);
+                    Stream log = new TeeOutputStream(serverProtocol.Stream, stdout);
+                    Streams.PipeAll(serverProtocol.Stream, log);
+                    serverProtocol.Close();
+                }
+                finally
+                {
+                    try
+                    {
+                        s.Close();
+                    }
+                    catch (IOException)
+                    {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsClientTest.cs b/crypto/test/src/tls/test/TlsClientTest.cs
new file mode 100644
index 000000000..27e5f342b
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsClientTest.cs
@@ -0,0 +1,97 @@
+using System;
+using System.IO;
+using System.Net.Sockets;
+using System.Text;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    /// <summary>A simple test designed to conduct a TLS handshake with an external TLS server.</summary>
+    /// <remarks>
+    /// Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in this package
+    /// (under 'src/test/resources') for help configuring an external TLS server.
+    /// </remarks>
+    [TestFixture]
+    public class TlsClientTest
+    {
+        [Test, Ignore]
+        public void TestConnection()
+        {
+            string host = "localhost";
+            int port = 5556;
+
+            long time1 = DateTimeUtilities.CurrentUnixMs();
+
+            MockTlsClient client = new MockTlsClient(null);
+            TlsClientProtocol protocol = OpenTlsClientConnection(host, port, client);
+            protocol.Close();
+
+            long time2 = DateTimeUtilities.CurrentUnixMs();
+            Console.WriteLine("Elapsed 1: " + (time2 - time1) + "ms");
+
+            client = new MockTlsClient(client.GetSessionToResume());
+            protocol = OpenTlsClientConnection(host, port, client);
+
+            long time3 = DateTimeUtilities.CurrentUnixMs();
+            Console.WriteLine("Elapsed 2: " + (time3 - time2) + "ms");
+
+            Http11Get(host, port, protocol.Stream);
+
+            protocol.Close();
+        }
+
+        private static void Http11Get(string host, int port, Stream s)
+        {
+            WriteUtf8Line(s, "GET / HTTP/1.1");
+            //WriteUtf8Line(s, "Host: " + host + ":" + port);
+            WriteUtf8Line(s, "");
+            s.Flush();
+
+            Console.WriteLine("---");
+
+            string[] ends = new string[] { "</HTML>", "HTTP/1.1 3", "HTTP/1.1 4" };
+
+            StreamReader reader = new StreamReader(s);
+
+            bool finished = false;
+            string line;
+            while (!finished && (line = reader.ReadLine()) != null)
+            {
+                Console.WriteLine("<<< " + line);
+
+                string upperLine = TlsTestUtilities.ToUpperInvariant(line);
+
+                // TEST CODE ONLY. This is not a robust way of parsing the result!
+                foreach (string end in ends)
+                {
+                    if (upperLine.Contains(end))
+                    {
+                        finished = true;
+                        break;
+                    }
+                }
+            }
+
+            Console.Out.Flush();
+        }
+
+        private static TlsClientProtocol OpenTlsClientConnection(string hostname, int port, TlsClient client)
+        {
+            TcpClient tcp = new TcpClient(hostname, port);
+
+            TlsClientProtocol protocol = new TlsClientProtocol(tcp.GetStream());
+            protocol.Connect(client);
+            return protocol;
+        }
+
+        private static void WriteUtf8Line(Stream output, string line)
+        {
+            byte[] buf = Encoding.UTF8.GetBytes(line + "\r\n");
+            output.Write(buf, 0, buf.Length);
+            Console.WriteLine(">>> " + line);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs b/crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs
new file mode 100644
index 000000000..56cd39e87
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs
@@ -0,0 +1,126 @@
+using System;
+using System.IO;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsProtocolNonBlockingTest
+    {
+        [Test]
+        public void TestClientServerFragmented()
+        {
+            // tests if it's really non-blocking when partial records arrive
+            ImplTestClientServer(true);
+        }
+
+        [Test]
+        public void TestClientServerNonFragmented()
+        {
+            ImplTestClientServer(false);
+        }
+
+        private static void ImplTestClientServer(bool fragment)
+        {
+            TlsClientProtocol clientProtocol = new TlsClientProtocol();
+            TlsServerProtocol serverProtocol = new TlsServerProtocol();
+
+            MockTlsClient client = new MockTlsClient(null);
+            MockTlsServer server = new MockTlsServer();
+
+            clientProtocol.Connect(client);
+            serverProtocol.Accept(server);
+
+            // pump handshake
+            bool hadDataFromServer = true;
+            bool hadDataFromClient = true;
+            while (hadDataFromServer || hadDataFromClient)
+            {
+                hadDataFromServer = PumpData(serverProtocol, clientProtocol, fragment);
+                hadDataFromClient = PumpData(clientProtocol, serverProtocol, fragment);
+            }
+
+            // send data in both directions
+            byte[] data = new byte[1024];
+            client.Crypto.SecureRandom.NextBytes(data);
+
+            WriteAndRead(clientProtocol, serverProtocol, data, fragment);
+            WriteAndRead(serverProtocol, clientProtocol, data, fragment);
+
+            // close the connection
+            clientProtocol.Close();
+            PumpData(clientProtocol, serverProtocol, fragment);
+            serverProtocol.CloseInput();
+            CheckClosed(serverProtocol);
+            CheckClosed(clientProtocol);
+        }
+
+        private static void WriteAndRead(TlsProtocol writer, TlsProtocol reader, byte[] data, bool fragment)
+        {
+            int dataSize = data.Length;
+            writer.WriteApplicationData(data, 0, dataSize);
+            PumpData(writer, reader, fragment);
+
+            Assert.AreEqual(dataSize, reader.GetAvailableInputBytes());
+            byte[] readData = new byte[dataSize];
+            reader.ReadInput(readData, 0, dataSize);
+            AssertArrayEquals(data, readData);
+        }
+
+        private static bool PumpData(TlsProtocol from, TlsProtocol to, bool fragment)
+        {
+            int byteCount = from.GetAvailableOutputBytes();
+            if (byteCount == 0)
+                return false;
+
+            if (fragment)
+            {
+                byte[] buffer = new byte[1];
+                while (from.GetAvailableOutputBytes() > 0)
+                {
+                    from.ReadOutput(buffer, 0, 1);
+                    to.OfferInput(buffer);
+                }
+            }
+            else
+            {
+                byte[] buffer = new byte[byteCount];
+                from.ReadOutput(buffer, 0, buffer.Length);
+                to.OfferInput(buffer);
+            }
+
+            return true;
+        }
+
+        private static void CheckClosed(TlsProtocol protocol)
+        {
+            Assert.IsTrue(protocol.IsClosed);
+
+            try
+            {
+                protocol.OfferInput(new byte[10]);
+                Assert.Fail("Input was accepted after close");
+            }
+            catch (IOException e)
+            {
+            }
+
+            try
+            {
+                protocol.WriteApplicationData(new byte[10], 0, 10);
+                Assert.Fail("Output was accepted after close");
+            }
+            catch (IOException e)
+            {
+            }
+        }
+
+        private static void AssertArrayEquals(byte[] a, byte[] b)
+        {
+            Assert.IsTrue(Arrays.AreEqual(a, b));
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsProtocolTest.cs b/crypto/test/src/tls/test/TlsProtocolTest.cs
new file mode 100644
index 000000000..b4f79f9ba
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsProtocolTest.cs
@@ -0,0 +1,75 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            PipedStream clientPipe = new PipedStream();
+            PipedStream serverPipe = new PipedStream(clientPipe);
+
+            TlsClientProtocol clientProtocol = new TlsClientProtocol(clientPipe);
+            TlsServerProtocol serverProtocol = new TlsServerProtocol(serverPipe);
+
+            MockTlsClient client = new MockTlsClient(null);
+            MockTlsServer server = new MockTlsServer();
+
+            Server serverRun = new Server(serverProtocol, server);
+            Thread serverThread = new Thread(new ThreadStart(serverRun.Run));
+            serverThread.Start();
+
+            clientProtocol.Connect(client);
+
+            byte[] data = new byte[1000];
+            client.Crypto.SecureRandom.NextBytes(data);
+
+            Stream output = clientProtocol.Stream;
+            output.Write(data, 0, data.Length);
+
+            byte[] echo = new byte[data.Length];
+            int count = Streams.ReadFully(clientProtocol.Stream, echo);
+
+            Assert.AreEqual(count, data.Length);
+            Assert.IsTrue(Arrays.AreEqual(data, echo));
+
+            output.Close();
+
+            serverThread.Join();
+        }
+
+        internal class Server
+        {
+            private readonly TlsServerProtocol m_serverProtocol;
+            private readonly TlsServer m_server;
+
+            internal Server(TlsServerProtocol serverProtocol, TlsServer server)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_server = server;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    m_serverProtocol.Accept(m_server);
+                    Streams.PipeAll(m_serverProtocol.Stream, m_serverProtocol.Stream);
+                    m_serverProtocol.Close();
+                }
+                catch (Exception e)
+                {
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsPskProtocolTest.cs b/crypto/test/src/tls/test/TlsPskProtocolTest.cs
new file mode 100644
index 000000000..97e6f133b
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsPskProtocolTest.cs
@@ -0,0 +1,75 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsPskProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            PipedStream clientPipe = new PipedStream();
+            PipedStream serverPipe = new PipedStream(clientPipe);
+
+            TlsClientProtocol clientProtocol = new TlsClientProtocol(clientPipe);
+            TlsServerProtocol serverProtocol = new TlsServerProtocol(serverPipe);
+
+            MockPskTlsClient client = new MockPskTlsClient(null);
+            MockPskTlsServer server = new MockPskTlsServer();
+
+            Server serverRun = new Server(serverProtocol, server);
+            Thread serverThread = new Thread(new ThreadStart(serverRun.Run));
+            serverThread.Start();
+
+            clientProtocol.Connect(client);
+
+            byte[] data = new byte[1000];
+            client.Crypto.SecureRandom.NextBytes(data);
+
+            Stream output = clientProtocol.Stream;
+            output.Write(data, 0, data.Length);
+
+            byte[] echo = new byte[data.Length];
+            int count = Streams.ReadFully(clientProtocol.Stream, echo);
+
+            Assert.AreEqual(count, data.Length);
+            Assert.IsTrue(Arrays.AreEqual(data, echo));
+
+            output.Close();
+
+            serverThread.Join();
+        }
+
+        internal class Server
+        {
+            private readonly TlsServerProtocol m_serverProtocol;
+            private readonly TlsServer m_server;
+
+            internal Server(TlsServerProtocol serverProtocol, TlsServer server)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_server = server;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    m_serverProtocol.Accept(m_server);
+                    Streams.PipeAll(m_serverProtocol.Stream, m_serverProtocol.Stream);
+                    m_serverProtocol.Close();
+                }
+                catch (Exception e)
+                {
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsServerTest.cs b/crypto/test/src/tls/test/TlsServerTest.cs
new file mode 100644
index 000000000..333ff5664
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsServerTest.cs
@@ -0,0 +1,82 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    /// <summary>A simple test designed to conduct a TLS handshake with an external TLS client.</summary>
+    /// <remarks>
+    /// Please refer to GnuTLSSetup.html or OpenSSLSetup.html (under 'docs'), and x509-*.pem files in this package
+    /// (under 'src/test/resources') for help configuring an external TLS client.
+    /// </remarks>
+    [TestFixture]
+    public class TlsServerTest
+    {
+        [Test, Ignore]
+        public void TestConnection()
+        {
+            int port = 5556;
+
+            TcpListener ss = new TcpListener(IPAddress.Any, port);
+            ss.Start();
+            Stream stdout = Console.OpenStandardOutput();
+            try
+            {
+                while (true)
+                {
+                    TcpClient s = ss.AcceptTcpClient();
+                    Console.WriteLine("--------------------------------------------------------------------------------");
+                    Console.WriteLine("Accepted " + s);
+                    Server serverRun = new Server(s, stdout);
+                    Thread t = new Thread(new ThreadStart(serverRun.Run));
+                    t.Start();
+                }
+            }
+            finally
+            {
+                ss.Stop();
+            }
+        }
+
+        internal class Server
+        {
+            private readonly TcpClient s;
+            private readonly Stream stdout;
+
+            internal Server(TcpClient s, Stream stdout)
+            {
+                this.s = s;
+                this.stdout = stdout;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    MockTlsServer server = new MockTlsServer();
+                    TlsServerProtocol serverProtocol = new TlsServerProtocol(s.GetStream());
+                    serverProtocol.Accept(server);
+                    Stream log = new TeeOutputStream(serverProtocol.Stream, stdout);
+                    Streams.PipeAll(serverProtocol.Stream, log);
+                    serverProtocol.Close();
+                }
+                finally
+                {
+                    try
+                    {
+                        s.Close();
+                    }
+                    catch (IOException)
+                    {
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsSrpProtocolTest.cs b/crypto/test/src/tls/test/TlsSrpProtocolTest.cs
new file mode 100644
index 000000000..555b1f1f7
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsSrpProtocolTest.cs
@@ -0,0 +1,75 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsSrpProtocolTest
+    {
+        [Test]
+        public void TestClientServer()
+        {
+            PipedStream clientPipe = new PipedStream();
+            PipedStream serverPipe = new PipedStream(clientPipe);
+
+            TlsClientProtocol clientProtocol = new TlsClientProtocol(clientPipe);
+            TlsServerProtocol serverProtocol = new TlsServerProtocol(serverPipe);
+
+            MockSrpTlsClient client = new MockSrpTlsClient(null, MockSrpTlsServer.TEST_SRP_IDENTITY);
+            MockSrpTlsServer server = new MockSrpTlsServer();
+
+            Server serverRun = new Server(serverProtocol, server);
+            Thread serverThread = new Thread(new ThreadStart(serverRun.Run));
+            serverThread.Start();
+
+            clientProtocol.Connect(client);
+
+            byte[] data = new byte[1000];
+            client.Crypto.SecureRandom.NextBytes(data);
+
+            Stream output = clientProtocol.Stream;
+            output.Write(data, 0, data.Length);
+
+            byte[] echo = new byte[data.Length];
+            int count = Streams.ReadFully(clientProtocol.Stream, echo);
+
+            Assert.AreEqual(count, data.Length);
+            Assert.IsTrue(Arrays.AreEqual(data, echo));
+
+            output.Close();
+
+            serverThread.Join();
+        }
+
+        internal class Server
+        {
+            private readonly TlsServerProtocol m_serverProtocol;
+            private readonly TlsServer m_server;
+
+            internal Server(TlsServerProtocol serverProtocol, TlsServer server)
+            {
+                this.m_serverProtocol = serverProtocol;
+                this.m_server = server;
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    m_serverProtocol.Accept(m_server);
+                    Streams.PipeAll(m_serverProtocol.Stream, m_serverProtocol.Stream);
+                    m_serverProtocol.Close();
+                }
+                catch (Exception e)
+                {
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestCase.cs b/crypto/test/src/tls/test/TlsTestCase.cs
new file mode 100644
index 000000000..0489d22c1
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestCase.cs
@@ -0,0 +1,182 @@
+using System;
+using System.IO;
+using System.Threading;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsTestCase
+    {
+        private static void CheckTlsVersions(ProtocolVersion[] versions)
+        {
+            if (versions != null)
+            {
+                for (int i = 0; i < versions.Length; ++i)
+                {
+                    if (!versions[i].IsTls)
+                        throw new InvalidOperationException("Non-TLS version");
+                }
+            }
+        }
+
+        [Test, TestCaseSource(typeof(TlsTestSuite), "Suite")]
+        public void RunTest(TlsTestConfig config)
+        {
+            // Disable the test if it is not being run via TlsTestSuite
+            if (config == null)
+                return;
+
+            CheckTlsVersions(config.clientSupportedVersions);
+            CheckTlsVersions(config.serverSupportedVersions);
+
+            PipedStream clientPipe = new PipedStream();
+            PipedStream serverPipe = new PipedStream(clientPipe);
+
+            NetworkStream clientNet = new NetworkStream(clientPipe);
+            NetworkStream serverNet = new NetworkStream(serverPipe);
+
+            TlsTestClientProtocol clientProtocol = new TlsTestClientProtocol(clientNet, config);
+            TlsTestServerProtocol serverProtocol = new TlsTestServerProtocol(serverNet, config);
+
+            clientProtocol.IsResumableHandshake = true;
+            serverProtocol.IsResumableHandshake = true;
+
+            TlsTestClientImpl clientImpl = new TlsTestClientImpl(config);
+            TlsTestServerImpl serverImpl = new TlsTestServerImpl(config);
+
+            Server serverRun = new Server(this, serverProtocol, serverImpl);
+            Thread serverThread = new Thread(new ThreadStart(serverRun.Run));
+            serverThread.Start();
+
+            Exception caught = null;
+            try
+            {
+                clientProtocol.Connect(clientImpl);
+
+                byte[] data = new byte[1000];
+                clientImpl.Crypto.SecureRandom.NextBytes(data);
+
+                Stream stream = clientProtocol.Stream;
+                stream.Write(data, 0, data.Length);
+
+                byte[] echo = new byte[data.Length];
+                int count = Streams.ReadFully(stream, echo, 0, echo.Length);
+
+                Assert.AreEqual(count, data.Length);
+                Assert.IsTrue(Arrays.AreEqual(data, echo));
+
+                Assert.IsTrue(Arrays.AreEqual(clientImpl.m_tlsServerEndPoint, serverImpl.m_tlsServerEndPoint));
+
+                if (!TlsUtilities.IsTlsV13(clientImpl.m_negotiatedVersion))
+                {
+                    Assert.NotNull(clientImpl.m_tlsUnique);
+                    Assert.NotNull(serverImpl.m_tlsUnique);
+                }
+                Assert.IsTrue(Arrays.AreEqual(clientImpl.m_tlsUnique, serverImpl.m_tlsUnique));
+
+                stream.Close();
+            }
+            catch (Exception e)
+            {
+                caught = e;
+                LogException(caught);
+            }
+
+            serverRun.AllowExit();
+            serverThread.Join();
+
+            Assert.IsTrue(clientNet.IsClosed, "Client Stream not closed");
+            Assert.IsTrue(serverNet.IsClosed, "Server Stream not closed");
+
+            Assert.AreEqual(config.expectFatalAlertConnectionEnd, clientImpl.FirstFatalAlertConnectionEnd,
+                "Client fatal alert connection end");
+            Assert.AreEqual(config.expectFatalAlertConnectionEnd, serverImpl.FirstFatalAlertConnectionEnd,
+                "Server fatal alert connection end");
+
+            Assert.AreEqual(config.expectFatalAlertDescription, clientImpl.FirstFatalAlertDescription,
+                "Client fatal alert description");
+            Assert.AreEqual(config.expectFatalAlertDescription, serverImpl.FirstFatalAlertDescription,
+                "Server fatal alert description");
+
+            if (config.expectFatalAlertConnectionEnd == -1)
+            {
+                Assert.IsNull(caught, "Unexpected client exception");
+                Assert.IsNull(serverRun.m_caught, "Unexpected server exception");
+            }
+        }
+
+        protected virtual void LogException(Exception e)
+        {
+            if (TlsTestConfig.Debug)
+            {
+                Console.Error.WriteLine(e);
+                Console.Error.Flush();
+            }
+        }
+
+        internal class Server
+        {
+            protected readonly TlsTestCase m_outer;
+            protected readonly TlsServerProtocol m_serverProtocol;
+            protected readonly TlsServer m_server;
+
+            internal bool m_canExit = false;
+            internal Exception m_caught = null;
+
+            internal Server(TlsTestCase outer, TlsTestServerProtocol serverProtocol, TlsServer server)
+            {
+                this.m_outer = outer;
+                this.m_serverProtocol = serverProtocol;
+                this.m_server = server;
+            }
+
+            internal void AllowExit()
+            {
+                lock (this)
+                {
+                    m_canExit = true;
+                    Monitor.PulseAll(this);
+                }
+            }
+
+            public void Run()
+            {
+                try
+                {
+                    m_serverProtocol.Accept(m_server);
+                    Streams.PipeAll(m_serverProtocol.Stream, m_serverProtocol.Stream);
+                    m_serverProtocol.Close();
+                }
+                catch (Exception e)
+                {
+                    m_caught = e;
+                    m_outer.LogException(m_caught);
+                }
+
+                WaitExit();
+            }
+
+            protected void WaitExit()
+            {
+                lock (this)
+                {
+                    while (!m_canExit)
+                    {
+                        try
+                        {
+                            Monitor.Wait(this);
+                        }
+                        catch (ThreadInterruptedException)
+                        {
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestClientImpl.cs b/crypto/test/src/tls/test/TlsTestClientImpl.cs
new file mode 100644
index 000000000..d436df3f7
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestClientImpl.cs
@@ -0,0 +1,370 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class TlsTestClientImpl
+        : DefaultTlsClient
+    {
+        private static readonly int[] TestCipherSuites = new int[]
+        {
+            /*
+             * TLS 1.3
+             */
+            CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_AES_128_GCM_SHA256,
+
+            /*
+             * pre-TLS 1.3
+             */
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+        };
+
+        protected readonly TlsTestConfig m_config;
+
+        protected int m_firstFatalAlertConnectionEnd = -1;
+        protected short m_firstFatalAlertDescription = -1;
+
+        internal ProtocolVersion m_negotiatedVersion = null;
+        internal byte[] m_tlsServerEndPoint = null;
+        internal byte[] m_tlsUnique = null;
+
+        internal TlsTestClientImpl(TlsTestConfig config)
+            : base(TlsTestSuite.GetCrypto(config))
+        {
+            this.m_config = config;
+        }
+
+        internal int FirstFatalAlertConnectionEnd
+        {
+            get { return m_firstFatalAlertConnectionEnd; }
+        }
+
+        internal short FirstFatalAlertDescription
+        {
+            get { return m_firstFatalAlertDescription; }
+        }
+
+        public override IDictionary GetClientExtensions()
+        {
+            IDictionary clientExtensions = base.GetClientExtensions();
+            if (clientExtensions != null)
+            {
+                if (!m_config.clientSendSignatureAlgorithms)
+                {
+                    clientExtensions.Remove(ExtensionType.signature_algorithms);
+                    this.m_supportedSignatureAlgorithms = null;
+                }
+                if (!m_config.clientSendSignatureAlgorithmsCert)
+                {
+                    clientExtensions.Remove(ExtensionType.signature_algorithms_cert);
+                    this.m_supportedSignatureAlgorithmsCert = null;
+                }
+            }
+            return clientExtensions;
+        }
+
+        public override IList GetEarlyKeyShareGroups()
+        {
+            if (m_config.clientEmptyKeyShare)
+                return null;
+
+            return base.GetEarlyKeyShareGroups();
+        }
+
+        public override bool IsFallback()
+        {
+            return m_config.clientFallback;
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            if (alertLevel == AlertLevel.fatal && m_firstFatalAlertConnectionEnd == -1)
+            {
+                m_firstFatalAlertConnectionEnd = ConnectionEnd.client;
+                m_firstFatalAlertDescription = alertDescription;
+            }
+
+            if (TlsTestConfig.Debug)
+            {
+                TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+                output.WriteLine("TLS client raised alert: " + AlertLevel.GetText(alertLevel)
+                    + ", " + AlertDescription.GetText(alertDescription));
+                if (message != null)
+                {
+                    output.WriteLine("> " + message);
+                }
+                if (cause != null)
+                {
+                    output.WriteLine(cause);
+                }
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            if (alertLevel == AlertLevel.fatal && m_firstFatalAlertConnectionEnd == -1)
+            {
+                m_firstFatalAlertConnectionEnd = ConnectionEnd.server;
+                m_firstFatalAlertDescription = alertDescription;
+            }
+
+            if (TlsTestConfig.Debug)
+            {
+                TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+                output.WriteLine("TLS client received alert: " + AlertLevel.GetText(alertLevel)
+                    + ", " + AlertDescription.GetText(alertDescription));
+            }
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            m_tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            m_tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS client reports 'tls-server-end-point' = " + ToHexString(m_tlsServerEndPoint));
+                Console.WriteLine("TLS client reports 'tls-unique' = " + ToHexString(m_tlsUnique));
+            }
+        }
+
+        public override void NotifyServerVersion(ProtocolVersion serverVersion)
+        {
+            base.NotifyServerVersion(serverVersion);
+
+            this.m_negotiatedVersion = serverVersion;
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS client negotiated " + serverVersion);
+            }
+        }
+
+        public override TlsAuthentication GetAuthentication()
+        {
+            return new MyTlsAuthentication(this, m_context);
+        }
+
+        protected virtual Certificate CorruptCertificate(Certificate cert)
+        {
+            CertificateEntry[] certEntryList = cert.GetCertificateEntryList();
+            CertificateEntry ee = certEntryList[0];
+            TlsCertificate corruptCert = CorruptCertificateSignature(ee.Certificate);
+            certEntryList[0] = new CertificateEntry(corruptCert, ee.Extensions);
+            return new Certificate(cert.GetCertificateRequestContext(), certEntryList);
+        }
+
+        protected virtual TlsCertificate CorruptCertificateSignature(TlsCertificate tlsCertificate)
+        {
+            X509CertificateStructure cert = X509CertificateStructure.GetInstance(tlsCertificate.GetEncoded());
+
+            Asn1EncodableVector v = new Asn1EncodableVector();
+            v.Add(cert.TbsCertificate);
+            v.Add(cert.SignatureAlgorithm);
+            v.Add(CorruptSignature(cert.Signature));
+
+            cert = X509CertificateStructure.GetInstance(new DerSequence(v));
+
+            return Crypto.CreateCertificate(cert.GetEncoded(Asn1Encodable.Der));
+        }
+
+        protected virtual DerBitString CorruptSignature(DerBitString bs)
+        {
+            return new DerBitString(CorruptBit(bs.GetOctets()));
+        }
+
+        protected virtual byte[] CorruptBit(byte[] bs)
+        {
+            bs = Arrays.Clone(bs);
+
+            // Flip a random bit
+            int bit = m_context.Crypto.SecureRandom.Next(bs.Length << 3);
+            bs[bit >> 3] ^= (byte)(1 << (bit & 7));
+
+            return bs;
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, TestCipherSuites);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            if (null != m_config.clientSupportedVersions)
+            {
+                return m_config.clientSupportedVersions;
+            }
+
+            return base.GetSupportedVersions();
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        internal class MyTlsAuthentication
+            : TlsAuthentication
+        {
+            private readonly TlsTestClientImpl m_outer;
+            private readonly TlsContext m_context;
+
+            internal MyTlsAuthentication(TlsTestClientImpl outer, TlsContext context)
+            {
+                this.m_outer = outer;
+                this.m_context = context;
+            }
+
+            public virtual void NotifyServerCertificate(TlsServerCertificate serverCertificate)
+            {
+                TlsCertificate[] chain = serverCertificate.Certificate.GetCertificateList();
+
+                if (TlsTestConfig.Debug)
+                {
+                    Console.WriteLine("TLS client received server certificate chain of length " + chain.Length);
+                    for (int i = 0; i < chain.Length; ++i)
+                    {
+                        X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[i].GetEncoded());
+                        // TODO Create fingerprint based on certificate signature algorithm digest
+                        Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                            + entry.Subject + ")");
+                    }
+                }
+
+                bool isEmpty = serverCertificate == null || serverCertificate.Certificate == null
+                    || serverCertificate.Certificate.IsEmpty;
+
+                if (isEmpty)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                string[] trustedCertResources = new string[]{ "x509-server-dsa.pem", "x509-server-ecdh.pem",
+                    "x509-server-ecdsa.pem", "x509-server-ed25519.pem", "x509-server-ed448.pem",
+                    "x509-server-rsa_pss_256.pem", "x509-server-rsa_pss_384.pem", "x509-server-rsa_pss_512.pem",
+                    "x509-server-rsa-enc.pem", "x509-server-rsa-sign.pem" };
+
+                TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                    trustedCertResources);
+
+                if (null == certPath)
+                    throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+                if (m_outer.m_config.clientCheckSigAlgOfServerCerts)
+                {
+                    TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+                }
+            }
+
+            public virtual TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
+            {
+                TlsTestConfig config = m_outer.m_config;
+
+                if (config.serverCertReq == TlsTestConfig.SERVER_CERT_REQ_NONE)
+                    throw new InvalidOperationException();
+                if (config.clientAuth == TlsTestConfig.CLIENT_AUTH_NONE)
+                    return null;
+
+                bool isTlsV13 = TlsUtilities.IsTlsV13(m_context);
+
+                if (!isTlsV13)
+                {
+                    short[] certificateTypes = certificateRequest.CertificateTypes;
+                    if (certificateTypes == null || !Arrays.Contains(certificateTypes, ClientCertificateType.rsa_sign))
+                        return null;
+                }
+
+                IList supportedSigAlgs = certificateRequest.SupportedSignatureAlgorithms;
+                if (supportedSigAlgs != null && config.clientAuthSigAlg != null)
+                {
+                    supportedSigAlgs = new ArrayList(1);
+                    supportedSigAlgs.Add(config.clientAuthSigAlg);
+                }
+
+                // TODO[tls13] Check also supportedSigAlgsCert against the chain signature(s)
+
+                TlsCredentialedSigner signerCredentials = TlsTestUtilities.LoadSignerCredentials(m_context,
+                    supportedSigAlgs, SignatureAlgorithm.rsa, "x509-client-rsa.pem", "x509-client-key-rsa.pem");
+
+                if (config.clientAuth == TlsTestConfig.CLIENT_AUTH_VALID)
+                    return signerCredentials;
+
+                return new MyTlsCredentialedSigner(m_outer, signerCredentials);
+            }
+        }
+
+        internal class MyTlsCredentialedSigner
+            : TlsCredentialedSigner
+        {
+            private readonly TlsTestClientImpl m_outer;
+            private readonly TlsCredentialedSigner m_inner;
+
+            internal MyTlsCredentialedSigner(TlsTestClientImpl outer, TlsCredentialedSigner inner)
+            {
+                this.m_outer = outer;
+                this.m_inner = inner;
+            }
+
+            public virtual byte[] GenerateRawSignature(byte[] hash)
+            {
+                byte[] sig = m_inner.GenerateRawSignature(hash);
+
+                if (m_outer.m_config.clientAuth == TlsTestConfig.CLIENT_AUTH_INVALID_VERIFY)
+                {
+                    sig = m_outer.CorruptBit(sig);
+                }
+
+                return sig;
+            }
+
+            public virtual Certificate Certificate
+            {
+                get
+                {
+                    Certificate cert = m_inner.Certificate;
+
+                    if (m_outer.m_config.clientAuth == TlsTestConfig.CLIENT_AUTH_INVALID_CERT)
+                    {
+                        cert = m_outer.CorruptCertificate(cert);
+                    }
+
+                    return cert;
+                }
+            }
+
+            public virtual SignatureAndHashAlgorithm SignatureAndHashAlgorithm
+            {
+                get { return m_inner.SignatureAndHashAlgorithm; }
+            }
+
+            public virtual TlsStreamSigner GetStreamSigner()
+            {
+                return null;
+            }
+        };
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestClientProtocol.cs b/crypto/test/src/tls/test/TlsTestClientProtocol.cs
new file mode 100644
index 000000000..f7e94680a
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestClientProtocol.cs
@@ -0,0 +1,32 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class TlsTestClientProtocol
+        : TlsClientProtocol
+    {
+        protected readonly TlsTestConfig m_config;
+
+        internal TlsTestClientProtocol(Stream stream, TlsTestConfig config)
+            : this(stream, stream, config)
+        {
+        }
+
+        internal TlsTestClientProtocol(Stream input, Stream output, TlsTestConfig config)
+            : base(input, output)
+        {
+            this.m_config = config;
+        }
+
+        protected override void SendCertificateVerifyMessage(DigitallySigned certificateVerify)
+        {
+            if (certificateVerify.Algorithm != null && m_config.clientAuthSigAlgClaimed != null)
+            {
+                certificateVerify = new DigitallySigned(m_config.clientAuthSigAlgClaimed, certificateVerify.Signature);
+            }
+
+            base.SendCertificateVerifyMessage(certificateVerify);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestConfig.cs b/crypto/test/src/tls/test/TlsTestConfig.cs
new file mode 100644
index 000000000..a15d4e535
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestConfig.cs
@@ -0,0 +1,128 @@
+using System;
+using System.Collections;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class TlsTestConfig
+    {
+        // TODO[tls-port]
+        public static readonly bool Debug = true;
+
+        /// <summary>Client does not authenticate, ignores any certificate request.</summary>
+        public const int CLIENT_AUTH_NONE = 0;
+
+        /// <summary>Client will authenticate if it receives a certificate request.</summary>
+        public const int CLIENT_AUTH_VALID = 1;
+
+        /// <summary>Client will authenticate if it receives a certificate request, with an invalid certificate.
+        /// </summary>
+        public const int CLIENT_AUTH_INVALID_CERT = 2;
+
+        /// <summary>Client will authenticate if it receives a certificate request, with an invalid CertificateVerify
+        /// signature.</summary>
+        public const int CLIENT_AUTH_INVALID_VERIFY = 3;
+
+        public const int CRYPTO_BC = 0;
+
+        /// <summary>Server will not request a client certificate.</summary>
+        public const int SERVER_CERT_REQ_NONE = 0;
+
+        /// <summary>Server will request a client certificate but receiving one is optional.</summary>
+        public const int SERVER_CERT_REQ_OPTIONAL = 1;
+
+        /// <summary>Server will request a client certificate and receiving one is mandatory.</summary>
+        public const int SERVER_CERT_REQ_MANDATORY = 2;
+
+        /// <summary>Configures the client authentication behaviour of the test client. Use CLIENT_AUTH_* constants.
+        /// </summary>
+        public int clientAuth = CLIENT_AUTH_VALID;
+
+        /// <summary>If not null, and TLS 1.2 or higher is negotiated, selects a fixed signature/ hash algorithm to be
+        /// used for the CertificateVerify signature(if one is sent).</summary>
+        public SignatureAndHashAlgorithm clientAuthSigAlg = null;
+
+        /// <summary>If not null, and TLS 1.2 or higher is negotiated, selects a fixed signature/ hash algorithm to be
+        /// _claimed_ in the CertificateVerify (if one is sent), independently of what was actually used.</summary>
+        public SignatureAndHashAlgorithm clientAuthSigAlgClaimed = null;
+
+        /// <summary>Control whether the client will call
+        /// <see cref="TlsUtilities.CheckPeerSigAlgs(TlsContext, Crypto.TlsCertificate[])"/> to check the server
+        /// certificate chain.</summary>
+        public bool clientCheckSigAlgOfServerCerts = true;
+
+        public int clientCrypto = CRYPTO_BC;
+
+        /// <summary>Configures whether the client will send an empty key_share extension in initial ClientHello.
+        /// </summary>
+        public bool clientEmptyKeyShare = false;
+
+        /// <summary>Configures whether the client will indicate version fallback via TLS_FALLBACK_SCSV.</summary>
+        public bool clientFallback = false;
+
+        /// <summary>Configures whether a (TLS 1.2+) client may send the signature_algorithms extension in ClientHello.
+        /// </summary>
+        public bool clientSendSignatureAlgorithms = true;
+
+        /// <summary>Configures whether a (TLS 1.2+) client may send the signature_algorithms_cert extension in
+        /// ClientHello.</summary>
+        public bool clientSendSignatureAlgorithmsCert = true;
+
+        /// <summary>Configures the supported protocol versions for the client. If null, uses the library's default.
+        /// </summary>
+        public ProtocolVersion[] clientSupportedVersions = null;
+
+        /// <summary>If not null, and TLS 1.2 or higher is negotiated, selects a fixed signature/ hash algorithm to be
+        /// used for the ServerKeyExchange signature(if one is sent).</summary>
+        public SignatureAndHashAlgorithm serverAuthSigAlg = null;
+
+        /// <summary>Configures whether the test server will send a certificate request.</summary>
+        public int serverCertReq = SERVER_CERT_REQ_OPTIONAL;
+
+        /// <summary>If TLS 1.2 or higher is negotiated, configures the set of supported signature algorithms in the
+        /// CertificateRequest (if one is sent). If null, uses a default set.</summary>
+        public IList serverCertReqSigAlgs = null;
+
+        /// <summary>Control whether the server will call
+        /// <see cref="TlsUtilities.CheckPeerSigAlgs(TlsContext, Crypto.TlsCertificate[])"/> to check the client
+        /// certificate chain.</summary>
+        public bool serverCheckSigAlgOfClientCerts = true;
+
+        public int serverCrypto = CRYPTO_BC;
+
+        /// <summary>Configures a protocol version the server will unconditionally negotiate.</summary>
+        /// <remarks>
+        /// Ignored if null.
+        /// </remarks>
+        public ProtocolVersion serverNegotiateVersion = null;
+
+        /// <summary>Configures the supported protocol versions for the server.</summary>
+        /// <remarks>
+        /// If null, uses the library's default.
+        /// </remarks>
+        public ProtocolVersion[] serverSupportedVersions = null;
+
+        /// <summary>Configures the connection end at which a fatal alert is expected to be raised.</summary>
+        /// <remarks>
+        /// Use <see cref="ConnectionEnd"/> constants.
+        /// </remarks>
+        public int expectFatalAlertConnectionEnd = -1;
+
+        /// <summary>Configures the type of fatal alert expected to be raised.</summary>
+        /// <remarks>
+        /// Use <see cref="AlertDescription"/> constants.
+        /// </remarks>
+        public short expectFatalAlertDescription = -1;
+
+        public virtual void ExpectClientFatalAlert(short alertDescription)
+        {
+            this.expectFatalAlertConnectionEnd = ConnectionEnd.client;
+            this.expectFatalAlertDescription = alertDescription;
+        }
+
+        public virtual void ExpectServerFatalAlert(short alertDescription)
+        {
+            this.expectFatalAlertConnectionEnd = ConnectionEnd.server;
+            this.expectFatalAlertDescription = alertDescription;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestServerImpl.cs b/crypto/test/src/tls/test/TlsTestServerImpl.cs
new file mode 100644
index 000000000..6bc4d315d
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestServerImpl.cs
@@ -0,0 +1,310 @@
+using System;
+using System.Collections;
+using System.IO;
+
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Utilities.Encoders;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class TlsTestServerImpl
+        : DefaultTlsServer
+    {
+        private static readonly int[] TestCipherSuites = new int[]
+        {
+            /*
+             * TLS 1.3
+             */
+            CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_AES_256_GCM_SHA384,
+            CipherSuite.TLS_AES_128_GCM_SHA256,
+
+            /*
+             * pre-TLS 1.3
+             */
+            CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
+            CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
+            CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
+            CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+        };
+
+        protected readonly TlsTestConfig m_config;
+
+        protected int m_firstFatalAlertConnectionEnd = -1;
+        protected short m_firstFatalAlertDescription = -1;
+
+        internal byte[] m_tlsServerEndPoint = null;
+        internal byte[] m_tlsUnique = null;
+
+        internal TlsTestServerImpl(TlsTestConfig config)
+            : base(TlsTestSuite.GetCrypto(config))
+        {
+            this.m_config = config;
+        }
+
+        internal int FirstFatalAlertConnectionEnd
+        {
+            get { return m_firstFatalAlertConnectionEnd; }
+        }
+
+        internal short FirstFatalAlertDescription
+        {
+            get { return m_firstFatalAlertDescription; }
+        }
+
+        public override TlsCredentials GetCredentials()
+        {
+            /*
+             * TODO[tls13] Should really be finding the first client-supported signature scheme that the
+             * server also supports and has credentials for.
+             */
+            if (TlsUtilities.IsTlsV13(m_context))
+            {
+                return GetRsaSignerCredentials();
+            }
+
+            return base.GetCredentials();
+        }
+
+        public override void NotifyAlertRaised(short alertLevel, short alertDescription, string message,
+            Exception cause)
+        {
+            if (alertLevel == AlertLevel.fatal && m_firstFatalAlertConnectionEnd == -1)
+            {
+                m_firstFatalAlertConnectionEnd = ConnectionEnd.server;
+                m_firstFatalAlertDescription = alertDescription;
+            }
+
+            if (TlsTestConfig.Debug)
+            {
+                TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+                output.WriteLine("TLS server raised alert: " + AlertLevel.GetText(alertLevel)
+                    + ", " + AlertDescription.GetText(alertDescription));
+                if (message != null)
+                {
+                    output.WriteLine("> " + message);
+                }
+                if (cause != null)
+                {
+                    output.WriteLine(cause);
+                }
+            }
+        }
+
+        public override void NotifyAlertReceived(short alertLevel, short alertDescription)
+        {
+            if (alertLevel == AlertLevel.fatal && m_firstFatalAlertConnectionEnd == -1)
+            {
+                m_firstFatalAlertConnectionEnd = ConnectionEnd.client;
+                m_firstFatalAlertDescription = alertDescription;
+            }
+
+            if (TlsTestConfig.Debug)
+            {
+                TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out;
+                output.WriteLine("TLS server received alert: " + AlertLevel.GetText(alertLevel)
+                    + ", " + AlertDescription.GetText(alertDescription));
+            }
+        }
+
+        public override void NotifyHandshakeComplete()
+        {
+            base.NotifyHandshakeComplete();
+
+            m_tlsServerEndPoint = m_context.ExportChannelBinding(ChannelBinding.tls_server_end_point);
+            m_tlsUnique = m_context.ExportChannelBinding(ChannelBinding.tls_unique);
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS server reports 'tls-server-end-point' = " + ToHexString(m_tlsServerEndPoint));
+                Console.WriteLine("TLS server reports 'tls-unique' = " + ToHexString(m_tlsUnique));
+            }
+        }
+
+        public override ProtocolVersion GetServerVersion()
+        {
+            ProtocolVersion serverVersion = (null != m_config.serverNegotiateVersion)
+                ?   m_config.serverNegotiateVersion
+                :   base.GetServerVersion();
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS server negotiated " + serverVersion);
+            }
+
+            return serverVersion;
+        }
+
+        public override CertificateRequest GetCertificateRequest()
+        {
+            if (m_config.serverCertReq == TlsTestConfig.SERVER_CERT_REQ_NONE)
+                return null;
+
+            IList serverSigAlgs = null;
+            if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(m_context.ServerVersion))
+            {
+                serverSigAlgs = m_config.serverCertReqSigAlgs;
+                if (serverSigAlgs == null)
+                {
+                    serverSigAlgs = TlsUtilities.GetDefaultSupportedSignatureAlgorithms(m_context);
+                }
+            }
+
+            IList certificateAuthorities = new ArrayList();
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-dsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-ecdsa.pem").Subject);
+            //certificateAuthorities.Add(TlsTestUtilities.LoadBcCertificateResource("x509-ca-rsa.pem").Subject);
+
+            // All the CA certificates are currently configured with this subject
+            certificateAuthorities.Add(new X509Name("CN=BouncyCastle TLS Test CA"));
+
+            if (TlsUtilities.IsTlsV13(m_context))
+            {
+                // TODO[tls13] Support for non-empty request context
+                byte[] certificateRequestContext = TlsUtilities.EmptyBytes;
+
+                // TODO[tls13] Add TlsTestConfig.serverCertReqSigAlgsCert
+                IList serverSigAlgsCert = null;
+
+                return new CertificateRequest(certificateRequestContext, serverSigAlgs, serverSigAlgsCert,
+                    certificateAuthorities);
+            }
+            else
+            {
+                short[] certificateTypes = new short[]{ ClientCertificateType.rsa_sign,
+                    ClientCertificateType.dss_sign, ClientCertificateType.ecdsa_sign };
+
+                return new CertificateRequest(certificateTypes, serverSigAlgs, certificateAuthorities);
+            }
+        }
+
+        public override void NotifyClientCertificate(Certificate clientCertificate)
+        {
+            bool isEmpty = (clientCertificate == null || clientCertificate.IsEmpty);
+
+            if (isEmpty != (m_config.clientAuth == TlsTestConfig.CLIENT_AUTH_NONE))
+                throw new InvalidOperationException();
+
+            if (isEmpty && (m_config.serverCertReq == TlsTestConfig.SERVER_CERT_REQ_MANDATORY))
+            {
+                short alertDescription = TlsUtilities.IsTlsV13(m_context)
+                    ?   AlertDescription.certificate_required
+                    :   AlertDescription.handshake_failure;
+
+                throw new TlsFatalAlert(alertDescription);
+            }
+
+            TlsCertificate[] chain = clientCertificate.GetCertificateList();
+
+            if (TlsTestConfig.Debug)
+            {
+                Console.WriteLine("TLS server received client certificate chain of length " + chain.Length);
+                for (int i = 0; i < chain.Length; ++i)
+                {
+                    X509CertificateStructure entry = X509CertificateStructure.GetInstance(chain[0].GetEncoded());
+                    // TODO Create fingerprint based on certificate signature algorithm digest
+                    Console.WriteLine("    fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " ("
+                        + entry.Subject + ")");
+                }
+            }
+
+            if (isEmpty)
+                return;
+
+            string[] trustedCertResources = new string[]{ "x509-client-dsa.pem", "x509-client-ecdh.pem",
+                "x509-client-ecdsa.pem", "x509-client-ed25519.pem", "x509-client-ed448.pem",
+                "x509-client-rsa_pss_256.pem", "x509-client-rsa_pss_384.pem", "x509-client-rsa_pss_512.pem",
+                "x509-client-rsa.pem" };
+
+            TlsCertificate[] certPath = TlsTestUtilities.GetTrustedCertPath(m_context.Crypto, chain[0],
+                trustedCertResources);
+
+            if (null == certPath)
+                throw new TlsFatalAlert(AlertDescription.bad_certificate);
+
+            if (m_config.serverCheckSigAlgOfClientCerts)
+            {
+                TlsUtilities.CheckPeerSigAlgs(m_context, certPath);
+            }
+        }
+
+        protected virtual IList GetSupportedSignatureAlgorithms()
+        {
+            if (TlsUtilities.IsTlsV12(m_context) && m_config.serverAuthSigAlg != null)
+            {
+                IList signatureAlgorithms = new ArrayList(1);
+                signatureAlgorithms.Add(m_config.serverAuthSigAlg);
+                return signatureAlgorithms;
+            }
+
+            return m_context.SecurityParameters.ClientSigAlgs;
+        }
+
+        protected override TlsCredentialedSigner GetDsaSignerCredentials()
+        {
+            return LoadSignerCredentials(SignatureAlgorithm.dsa);
+        }
+
+        protected override TlsCredentialedSigner GetECDsaSignerCredentials()
+        {
+            // TODO[RFC 8422] Code should choose based on client's supported sig algs?
+            return LoadSignerCredentials(SignatureAlgorithm.ecdsa);
+            //return LoadSignerCredentials(SignatureAlgorithm.ed25519);
+            //return LoadSignerCredentials(SignatureAlgorithm.ed448);
+        }
+
+        protected override TlsCredentialedDecryptor GetRsaEncryptionCredentials()
+        {
+            return TlsTestUtilities.LoadEncryptionCredentials(m_context,
+                new string[]{ "x509-server-rsa-enc.pem", "x509-ca-rsa.pem" }, "x509-server-key-rsa-enc.pem");
+        }
+
+        protected override TlsCredentialedSigner GetRsaSignerCredentials()
+        {
+            return LoadSignerCredentials(SignatureAlgorithm.rsa);
+        }
+
+        protected override int[] GetSupportedCipherSuites()
+        {
+            return TlsUtilities.GetSupportedCipherSuites(Crypto, TestCipherSuites);
+        }
+
+        protected override ProtocolVersion[] GetSupportedVersions()
+        {
+            if (m_config.serverSupportedVersions != null)
+            {
+                return m_config.serverSupportedVersions;
+            }
+
+            return base.GetSupportedVersions();
+        }
+
+        protected virtual string ToHexString(byte[] data)
+        {
+            return data == null ? "(null)" : Hex.ToHexString(data);
+        }
+
+        private TlsCredentialedSigner LoadSignerCredentials(short signatureAlgorithm)
+        {
+            return TlsTestUtilities.LoadSignerCredentialsServer(m_context, GetSupportedSignatureAlgorithms(),
+                signatureAlgorithm);
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestServerProtocol.cs b/crypto/test/src/tls/test/TlsTestServerProtocol.cs
new file mode 100644
index 000000000..632f6b8bf
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestServerProtocol.cs
@@ -0,0 +1,22 @@
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    internal class TlsTestServerProtocol
+        : TlsServerProtocol
+    {
+        protected readonly TlsTestConfig m_config;
+
+        internal TlsTestServerProtocol(Stream stream, TlsTestConfig config)
+            : this(stream, stream, config)
+        {
+        }
+
+        internal TlsTestServerProtocol(Stream input, Stream output, TlsTestConfig config)
+            : base(input, output)
+        {
+            this.m_config = config;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestSuite.cs b/crypto/test/src/tls/test/TlsTestSuite.cs
new file mode 100644
index 000000000..adedd8249
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestSuite.cs
@@ -0,0 +1,300 @@
+using System;
+using System.Collections;
+
+using NUnit.Framework;
+
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class TlsTestSuite
+    {
+        internal static TlsCrypto BC_CRYPTO = new BcTlsCrypto(new SecureRandom());
+
+        internal static TlsCrypto GetCrypto(TlsTestConfig config)
+        {
+            switch (config.clientCrypto)
+            {
+                case TlsTestConfig.CRYPTO_BC:
+                default:
+                    return BC_CRYPTO;
+            }
+        }
+
+        // Make the access to constants less verbose 
+        internal abstract class C : TlsTestConfig {}
+
+        public TlsTestSuite()
+        {
+        }
+
+        public static IEnumerable Suite()
+        {
+            IList testSuite = new ArrayList();
+            AddAllTests(testSuite, TlsTestConfig.CRYPTO_BC, TlsTestConfig.CRYPTO_BC);
+            return testSuite;
+        }
+
+        private static void AddAllTests(IList testSuite, int clientCrypto, int serverCrypto)
+        {
+            AddFallbackTests(testSuite, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.SSLv3, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.TLSv10, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.TLSv11, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.TLSv12, clientCrypto, serverCrypto);
+            AddVersionTests(testSuite, ProtocolVersion.TLSv13, clientCrypto, serverCrypto);
+        }
+
+        private static void AddFallbackTests(IList testSuite, int clientCrypto, int serverCrypto)
+        {
+            string prefix = GetCryptoName(clientCrypto) + "_" + GetCryptoName(serverCrypto) + "_";
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(ProtocolVersion.TLSv12, clientCrypto, serverCrypto);
+                c.clientFallback = true;
+
+                AddTestCase(testSuite, c, prefix + "FallbackGood");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(ProtocolVersion.TLSv12, clientCrypto, serverCrypto);
+                c.clientFallback = true;
+                c.clientSupportedVersions = ProtocolVersion.TLSv11.DownTo(ProtocolVersion.TLSv10);
+                c.ExpectServerFatalAlert(AlertDescription.inappropriate_fallback);
+
+                AddTestCase(testSuite, c, prefix + "FallbackBad");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(ProtocolVersion.TLSv12, clientCrypto, serverCrypto);
+                c.clientSupportedVersions = ProtocolVersion.TLSv11.DownTo(ProtocolVersion.TLSv10);
+
+                AddTestCase(testSuite, c, prefix + "FallbackNone");
+            }
+        }
+
+        private static void AddVersionTests(IList testSuite, ProtocolVersion version, int clientCrypto,
+            int serverCrypto)
+        {
+            string prefix = GetCryptoName(clientCrypto) + "_" + GetCryptoName(serverCrypto) + "_"
+                + version.ToString().Replace(" ", "").Replace(".", "") + "_";
+
+            bool isTlsV12 = TlsUtilities.IsTlsV12(version);
+            bool isTlsV13 = TlsUtilities.IsTlsV13(version);
+            bool isTlsV12Exactly = isTlsV12 && !isTlsV13;
+
+            short certReqDeclinedAlert = TlsUtilities.IsTlsV13(version)
+                ?   AlertDescription.certificate_required
+                :   AlertDescription.handshake_failure;
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+
+                AddTestCase(testSuite, c, prefix + "GoodDefault");
+            }
+
+            if (isTlsV13)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientEmptyKeyShare = true;
+
+                AddTestCase(testSuite, c, prefix + "GoodEmptyKeyShare");
+            }
+
+            /*
+             * Server only declares support for SHA1/RSA, client selects MD5/RSA. Since the client is
+             * NOT actually tracking MD5 over the handshake, we expect fatal alert from the client.
+             */
+            if (isTlsV12Exactly)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultRsaSignatureAlgorithms();
+                c.serverCheckSigAlgOfClientCerts = false;
+                c.ExpectClientFatalAlert(AlertDescription.internal_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifyHashAlg");
+            }
+
+            /*
+             * Server only declares support for SHA1/ECDSA, client selects SHA1/RSA. Since the client is
+             * actually tracking SHA1 over the handshake, we expect fatal alert to come from the server
+             * when it verifies the selected algorithm against the CertificateRequest supported
+             * algorithms.
+             */
+            if (isTlsV12)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultECDsaSignatureAlgorithms();
+                c.serverCheckSigAlgOfClientCerts = false;
+                c.ExpectServerFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySigAlg");
+            }
+
+            /*
+             * Server only declares support for SHA1/ECDSA, client signs with SHA1/RSA, but sends
+             * SHA1/ECDSA in the CertificateVerify. Since the client is actually tracking SHA1 over the
+             * handshake, and the claimed algorithm is in the CertificateRequest supported algorithms,
+             * we expect fatal alert to come from the server when it finds the claimed algorithm
+             * doesn't match the client certificate.
+             */
+            if (isTlsV12)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_VALID;
+                c.clientAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa);
+                c.clientAuthSigAlgClaimed = new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.ecdsa);
+                c.serverCertReqSigAlgs = TlsUtilities.GetDefaultECDsaSignatureAlgorithms();
+                c.ExpectServerFatalAlert(AlertDescription.bad_certificate);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySigAlgMismatch");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_INVALID_VERIFY;
+                c.ExpectServerFatalAlert(AlertDescription.decrypt_error);
+
+                AddTestCase(testSuite, c, prefix + "BadCertificateVerifySignature");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_INVALID_CERT;
+                c.ExpectServerFatalAlert(AlertDescription.bad_certificate);
+
+                AddTestCase(testSuite, c, prefix + "BadClientCertificate");
+            }
+
+            if (isTlsV13)
+            {
+                /*
+                 * For TLS 1.3 the supported_algorithms extension is required in ClientHello when the
+                 * server authenticates via a certificate.
+                 */
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientSendSignatureAlgorithms = false;
+                c.clientSendSignatureAlgorithmsCert = false;
+                c.ExpectServerFatalAlert(AlertDescription.missing_extension);
+
+                AddTestCase(testSuite, c, prefix + "BadClientSigAlgs");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_NONE;
+                c.serverCertReq = C.SERVER_CERT_REQ_MANDATORY;
+                c.ExpectServerFatalAlert(certReqDeclinedAlert);
+
+                AddTestCase(testSuite, c, prefix + "BadMandatoryCertReqDeclined");
+            }
+
+            /*
+             * Server sends SHA-256/RSA certificate, which is not the default {sha1,rsa} implied by the
+             * absent signature_algorithms extension. We expect fatal alert from the client when it
+             * verifies the certificate's 'signatureAlgorithm' against the implicit default signature_algorithms.
+             */
+            if (isTlsV12Exactly)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientSendSignatureAlgorithms = false;
+                c.clientSendSignatureAlgorithmsCert = false;
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.sha256, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.bad_certificate);
+
+                AddTestCase(testSuite, c, prefix + "BadServerCertSigAlg");
+            }
+
+            /*
+             * Server selects MD5/RSA for ServerKeyExchange signature, which is not in the default
+             * supported signature algorithms that the client sent. We expect fatal alert from the
+             * client when it verifies the selected algorithm against the supported algorithms.
+             */
+            if (TlsUtilities.IsTlsV12(version))
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadServerKeyExchangeSigAlg");
+            }
+
+            /*
+             * Server selects MD5/RSA for ServerKeyExchange signature, which is not the default {sha1,rsa}
+             * implied by the absent signature_algorithms extension. We expect fatal alert from the
+             * client when it verifies the selected algorithm against the implicit default.
+             */
+            if (isTlsV12Exactly)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientCheckSigAlgOfServerCerts = false;
+                c.clientSendSignatureAlgorithms = false;
+                c.clientSendSignatureAlgorithmsCert = false;
+                c.serverAuthSigAlg = new SignatureAndHashAlgorithm(HashAlgorithm.md5, SignatureAlgorithm.rsa);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadServerKeyExchangeSigAlg2");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.serverCertReq = C.SERVER_CERT_REQ_NONE;
+
+                AddTestCase(testSuite, c, prefix + "GoodNoCertReq");
+            }
+
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.clientAuth = C.CLIENT_AUTH_NONE;
+
+                AddTestCase(testSuite, c, prefix + "GoodOptionalCertReqDeclined");
+            }
+
+            /*
+             * Server generates downgraded (RFC 8446) 1.1 ServerHello. We expect fatal alert
+             * (illegal_parameter) from the client.
+             */
+            if (!isTlsV13)
+            {
+                TlsTestConfig c = CreateTlsTestConfig(version, clientCrypto, serverCrypto);
+                c.serverNegotiateVersion = version;
+                c.serverSupportedVersions = ProtocolVersion.TLSv13.DownTo(version);
+                c.ExpectClientFatalAlert(AlertDescription.illegal_parameter);
+
+                AddTestCase(testSuite, c, prefix + "BadDowngrade");
+            }
+        }
+
+        private static void AddTestCase(IList testSuite, TlsTestConfig config, string name)
+        {
+            testSuite.Add(new TestCaseData(config).SetName(name));
+        }
+
+        private static TlsTestConfig CreateTlsTestConfig(ProtocolVersion serverMaxVersion, int clientCrypto,
+            int serverCrypto)
+        {
+            TlsTestConfig c = new TlsTestConfig();
+            c.clientCrypto = clientCrypto;
+            c.clientSupportedVersions = ProtocolVersion.TLSv13.DownTo(ProtocolVersion.SSLv3);
+            c.serverCrypto = serverCrypto;
+            c.serverSupportedVersions = serverMaxVersion.DownTo(ProtocolVersion.SSLv3);
+            return c;
+        }
+
+        private static string GetCryptoName(int crypto)
+        {
+            switch (crypto)
+            {
+            case TlsTestConfig.CRYPTO_BC:
+            default:
+                return "BC";
+            }
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsTestUtilities.cs b/crypto/test/src/tls/test/TlsTestUtilities.cs
new file mode 100644
index 000000000..3ecacb0f0
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsTestUtilities.cs
@@ -0,0 +1,412 @@
+using System;
+using System.Collections;
+using System.Globalization;
+using System.IO;
+using System.Text;
+
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.Sec;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Asn1.X9;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.Tls.Crypto;
+using Org.BouncyCastle.Tls.Crypto.Impl.BC;
+using Org.BouncyCastle.Utilities;
+using Org.BouncyCastle.Utilities.Encoders;
+using Org.BouncyCastle.Utilities.IO.Pem;
+using Org.BouncyCastle.Utilities.Test;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class TlsTestUtilities
+    {
+        internal static readonly byte[] RsaCertData = Base64.Decode(
+            "MIICUzCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEBhMCQVUxKDAmBgNVBAoMH1RoZSBMZWdpb2" +
+            "4gb2YgdGhlIEJvdW5jeSBDYXN0bGUxEjAQBgNVBAcMCU1lbGJvdXJuZTERMA8GA1UECAwIVmljdG9yaWExLzAtBgkq" +
+            "hkiG9w0BCQEWIGZlZWRiYWNrLWNyeXB0b0Bib3VuY3ljYXN0bGUub3JnMB4XDTEzMDIyNTA2MDIwNVoXDTEzMDIyNT" +
+            "A2MDM0NVowgY8xCzAJBgNVBAYTAkFVMSgwJgYDVQQKDB9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIw" +
+            "EAYDVQQHDAlNZWxib3VybmUxETAPBgNVBAgMCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWVkYmFjay1jcnlwdG" +
+            "9AYm91bmN5Y2FzdGxlLm9yZzBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr5YtqKmKXmEGb4Shy" +
+            "pL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERo0QwQjAOBgNVHQ8BAf8EBAMCBSAwEgYDVR" +
+            "0lAQH/BAgwBgYEVR0lADAcBgNVHREBAf8EEjAQgQ50ZXN0QHRlc3QudGVzdDANBgkqhkiG9w0BAQQFAANBAHU55Ncz" +
+            "eglREcTg54YLUlGWu2WOYWhit/iM1eeq8Kivro7q98eW52jTuMI3CI5ulqd0hYzshQKQaZ5GDzErMyM=");
+
+        internal static readonly byte[] DudRsaCertData = Base64.Decode(
+            "MIICUzCCAf2gAwIBAgIBATANBgkqhkiG9w0BAQQFADCBjzELMAkGA1UEBhMCQVUxKDAmBgNVBAoMH1RoZSBMZWdpb2" +
+            "4gb2YgdGhlIEJvdW5jeSBDYXN0bGUxEjAQBgNVBAcMCU1lbGJvdXJuZTERMA8GA1UECAwIVmljdG9yaWExLzAtBgkq" +
+            "hkiG9w0BCQEWIGZlZWRiYWNrLWNyeXB0b0Bib3VuY3ljYXN0bGUub3JnMB4XDTEzMDIyNTA1NDcyOFoXDTEzMDIyNT" +
+            "A1NDkwOFowgY8xCzAJBgNVBAYTAkFVMSgwJgYDVQQKDB9UaGUgTGVnaW9uIG9mIHRoZSBCb3VuY3kgQ2FzdGxlMRIw" +
+            "EAYDVQQHDAlNZWxib3VybmUxETAPBgNVBAgMCFZpY3RvcmlhMS8wLQYJKoZIhvcNAQkBFiBmZWVkYmFjay1jcnlwdG" +
+            "9AYm91bmN5Y2FzdGxlLm9yZzBaMA0GCSqGSIb3DQEBAQUAA0kAMEYCQQC0p+RhcFdPFqlwgrIr5YtqKmKXmEGb4Shy" +
+            "pL26Ymz66ZAPdqv7EhOdzl3lZWT6srZUMWWgQMYGiHQg4z2R7X7XAgERo0QwQjAOBgNVHQ8BAf8EBAMCAAEwEgYDVR" +
+            "0lAQH/BAgwBgYEVR0lADAcBgNVHREBAf8EEjAQgQ50ZXN0QHRlc3QudGVzdDANBgkqhkiG9w0BAQQFAANBAJg55PBS" +
+            "weg6obRUKF4FF6fCrWFi6oCYSQ99LWcAeupc5BofW5MstFMhCOaEucuGVqunwT5G7/DweazzCIrSzB0=");
+
+        internal static bool EqualsIgnoreCase(string a, string b)
+        {
+            return ToUpperInvariant(a) == ToUpperInvariant(b);
+        }
+
+        internal static string ToUpperInvariant(string s)
+        {
+            return s.ToUpper(CultureInfo.InvariantCulture);
+        }
+
+        internal static string Fingerprint(X509CertificateStructure c)
+        {
+            byte[] der = c.GetEncoded();
+            byte[] hash = Sha256DigestOf(der);
+            byte[] hexBytes = Hex.Encode(hash);
+            string hex = ToUpperInvariant(Encoding.ASCII.GetString(hexBytes));
+
+            StringBuilder fp = new StringBuilder();
+            int i = 0;
+            fp.Append(hex.Substring(i, 2));
+            while ((i += 2) < hex.Length)
+            {
+                fp.Append(':');
+                fp.Append(hex.Substring(i, 2));
+            }
+            return fp.ToString();
+        }
+
+        internal static byte[] Sha256DigestOf(byte[] input)
+        {
+            return DigestUtilities.CalculateDigest("SHA256", input);
+        }
+
+        internal static string GetCACertResource(short signatureAlgorithm)
+        {
+            return "x509-ca-" + GetResourceName(signatureAlgorithm) + ".pem";
+        }
+
+        internal static string GetCACertResource(string eeCertResource)
+        {
+            if (eeCertResource.StartsWith("x509-client-"))
+            {
+                eeCertResource = eeCertResource.Substring("x509-client-".Length);
+            }
+            if (eeCertResource.StartsWith("x509-server-"))
+            {
+                eeCertResource = eeCertResource.Substring("x509-server-".Length);
+            }
+            if (eeCertResource.EndsWith(".pem"))
+            {
+                eeCertResource = eeCertResource.Substring(0, eeCertResource.Length - ".pem".Length);
+            }
+
+            if (EqualsIgnoreCase("dsa", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.dsa);
+            }
+
+            if (EqualsIgnoreCase("ecdh", eeCertResource)
+                || EqualsIgnoreCase("ecdsa", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.ecdsa);
+            }
+
+            if (EqualsIgnoreCase("ed25519", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.ed25519);
+            }
+
+            if (EqualsIgnoreCase("ed448", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.ed448);
+            }
+
+            if (EqualsIgnoreCase("rsa", eeCertResource)
+                || EqualsIgnoreCase("rsa-enc", eeCertResource)
+                || EqualsIgnoreCase("rsa-sign", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.rsa);
+            }
+
+            if (EqualsIgnoreCase("rsa_pss_256", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.rsa_pss_pss_sha256);
+            }
+            if (EqualsIgnoreCase("rsa_pss_384", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.rsa_pss_pss_sha384);
+            }
+            if (EqualsIgnoreCase("rsa_pss_512", eeCertResource))
+            {
+                return GetCACertResource(SignatureAlgorithm.rsa_pss_pss_sha512);
+            }
+
+            throw new TlsFatalAlert(AlertDescription.internal_error);
+        }
+
+        internal static string GetResourceName(short signatureAlgorithm)
+        {
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                return "rsa";
+            case SignatureAlgorithm.dsa:
+                return "dsa";
+            case SignatureAlgorithm.ecdsa:
+                return "ecdsa";
+            case SignatureAlgorithm.ed25519:
+                return "ed25519";
+            case SignatureAlgorithm.ed448:
+                return "ed448";
+            case SignatureAlgorithm.rsa_pss_pss_sha256:
+                return "rsa_pss_256";
+            case SignatureAlgorithm.rsa_pss_pss_sha384:
+                return "rsa_pss_384";
+            case SignatureAlgorithm.rsa_pss_pss_sha512:
+                return "rsa_pss_512";
+            default:
+                throw new TlsFatalAlert(AlertDescription.internal_error);
+            }
+        }
+
+        internal static TlsCredentialedAgreement LoadAgreementCredentials(TlsContext context, string[] certResources,
+            string keyResource)
+        {
+            TlsCrypto crypto = context.Crypto;
+            Certificate certificate = LoadCertificateChain(context, certResources);
+
+            // TODO[tls-ops] Need to have TlsCrypto construct the credentials from the certs/key (as raw data)
+            if (crypto is BcTlsCrypto)
+            {
+                AsymmetricKeyParameter privateKey = LoadBcPrivateKeyResource(keyResource);
+
+                return new BcDefaultTlsCredentialedAgreement((BcTlsCrypto)crypto, certificate, privateKey);
+            }
+            else
+            {
+                throw new NotSupportedException();
+            }
+        }
+
+        internal static TlsCredentialedDecryptor LoadEncryptionCredentials(TlsContext context, string[] certResources,
+            string keyResource)
+        {
+            TlsCrypto crypto = context.Crypto;
+            Certificate certificate = LoadCertificateChain(context, certResources);
+
+            // TODO[tls-ops] Need to have TlsCrypto construct the credentials from the certs/key (as raw data)
+            if (crypto is BcTlsCrypto)
+            {
+                AsymmetricKeyParameter privateKey = LoadBcPrivateKeyResource(keyResource);
+
+                return new BcDefaultTlsCredentialedDecryptor((BcTlsCrypto)crypto, certificate, privateKey);
+            }
+            else
+            {
+                throw new NotSupportedException();
+            }
+        }
+
+        public static TlsCredentialedSigner LoadSignerCredentials(TlsCryptoParameters cryptoParams, TlsCrypto crypto,
+            string[] certResources, string keyResource, SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            Certificate certificate = LoadCertificateChain(cryptoParams.ServerVersion, crypto, certResources);
+
+            // TODO[tls-ops] Need to have TlsCrypto construct the credentials from the certs/key (as raw data)
+            if (crypto is BcTlsCrypto)
+            {
+                AsymmetricKeyParameter privateKey = LoadBcPrivateKeyResource(keyResource);
+
+                return new BcDefaultTlsCredentialedSigner(cryptoParams, (BcTlsCrypto)crypto, privateKey, certificate, signatureAndHashAlgorithm);
+            }
+            else
+            {
+                throw new NotSupportedException();
+            }
+        }
+
+        internal static TlsCredentialedSigner LoadSignerCredentials(TlsContext context, string[] certResources,
+            string keyResource, SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+        {
+            TlsCrypto crypto = context.Crypto;
+            TlsCryptoParameters cryptoParams = new TlsCryptoParameters(context);
+
+            return LoadSignerCredentials(cryptoParams, crypto, certResources, keyResource, signatureAndHashAlgorithm);
+        }
+
+        internal static TlsCredentialedSigner LoadSignerCredentials(TlsContext context,
+            IList supportedSignatureAlgorithms, short signatureAlgorithm, string certResource, string keyResource)
+        {
+            if (supportedSignatureAlgorithms == null)
+            {
+                supportedSignatureAlgorithms = TlsUtilities.GetDefaultSignatureAlgorithms(signatureAlgorithm);
+            }
+
+            SignatureAndHashAlgorithm signatureAndHashAlgorithm = null;
+
+            foreach (SignatureAndHashAlgorithm alg in supportedSignatureAlgorithms)
+            {
+                if (alg.Signature == signatureAlgorithm)
+                {
+                    // Just grab the first one we find
+                    signatureAndHashAlgorithm = alg;
+                    break;
+                }
+            }
+
+            if (signatureAndHashAlgorithm == null)
+                return null;
+
+            return LoadSignerCredentials(context, new string[]{ certResource }, keyResource,
+                signatureAndHashAlgorithm);
+        }
+
+        internal static TlsCredentialedSigner LoadSignerCredentialsServer(TlsContext context,
+            IList supportedSignatureAlgorithms, short signatureAlgorithm)
+        {
+            string sigName = GetResourceName(signatureAlgorithm);
+
+            switch (signatureAlgorithm)
+            {
+            case SignatureAlgorithm.rsa:
+            case SignatureAlgorithm.rsa_pss_rsae_sha256:
+            case SignatureAlgorithm.rsa_pss_rsae_sha384:
+            case SignatureAlgorithm.rsa_pss_rsae_sha512:
+                sigName += "-sign";
+                break;
+            }
+
+            string certResource = "x509-server-" + sigName + ".pem";
+            string keyResource = "x509-server-key-" + sigName + ".pem";
+
+            return LoadSignerCredentials(context, supportedSignatureAlgorithms, signatureAlgorithm, certResource,
+                keyResource);
+        }
+
+        internal static Certificate LoadCertificateChain(ProtocolVersion protocolVersion, TlsCrypto crypto,
+            string[] resources)
+        {
+            if (TlsUtilities.IsTlsV13(protocolVersion))
+            {
+                CertificateEntry[] certificateEntryList = new CertificateEntry[resources.Length];
+                for (int i = 0; i < resources.Length; ++i)
+                {
+                    TlsCertificate certificate = LoadCertificateResource(crypto, resources[i]);
+
+                    // TODO[tls13] Add possibility of specifying e.g. CertificateStatus 
+                    IDictionary extensions = null;
+
+                    certificateEntryList[i] = new CertificateEntry(certificate, extensions);
+                }
+
+                // TODO[tls13] Support for non-empty request context
+                byte[] certificateRequestContext = TlsUtilities.EmptyBytes;
+
+                return new Certificate(certificateRequestContext, certificateEntryList);
+            }
+            else
+            {
+                TlsCertificate[] chain = new TlsCertificate[resources.Length];
+                for (int i = 0; i < resources.Length; ++i)
+                {
+                    chain[i] = LoadCertificateResource(crypto, resources[i]);
+                }
+                return new Certificate(chain);
+            }
+        }
+
+        internal static Certificate LoadCertificateChain(TlsContext context, string[] resources)
+        {
+            return LoadCertificateChain(context.ServerVersion, context.Crypto, resources);
+        }
+
+        internal static X509CertificateStructure LoadBcCertificateResource(string resource)
+        {
+            PemObject pem = LoadPemResource(resource);
+            if (pem.Type.EndsWith("CERTIFICATE"))
+            {
+                return X509CertificateStructure.GetInstance(pem.Content);
+            }
+            throw new ArgumentException("doesn't specify a valid certificate", "resource");
+        }
+
+        internal static TlsCertificate LoadCertificateResource(TlsCrypto crypto, string resource)
+        {
+            PemObject pem = LoadPemResource(resource);
+            if (pem.Type.EndsWith("CERTIFICATE"))
+            {
+                return crypto.CreateCertificate(pem.Content);
+            }
+            throw new ArgumentException("doesn't specify a valid certificate", "resource");
+        }
+
+        internal static AsymmetricKeyParameter LoadBcPrivateKeyResource(string resource)
+        {
+            PemObject pem = LoadPemResource(resource);
+            if (pem.Type.Equals("PRIVATE KEY"))
+            {
+                return PrivateKeyFactory.CreateKey(pem.Content);
+            }
+            if (pem.Type.Equals("ENCRYPTED PRIVATE KEY"))
+            {
+                throw new NotSupportedException("Encrypted PKCS#8 keys not supported");
+            }
+            if (pem.Type.Equals("RSA PRIVATE KEY"))
+            {
+                RsaPrivateKeyStructure rsa = RsaPrivateKeyStructure.GetInstance(pem.Content);
+                return new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent,
+                    rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1,
+                    rsa.Exponent2, rsa.Coefficient);
+            }
+            if (pem.Type.Equals("EC PRIVATE KEY"))
+            {
+                ECPrivateKeyStructure pKey = ECPrivateKeyStructure.GetInstance(pem.Content);
+                AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.IdECPublicKey,
+                    pKey.GetParameters());
+                PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey);
+                return PrivateKeyFactory.CreateKey(privInfo);
+            }
+            throw new ArgumentException("doesn't specify a valid private key", "resource");
+        }
+
+        internal static PemObject LoadPemResource(string resource)
+           
+        {
+            Stream s = SimpleTest.GetTestDataAsStream("tls." + resource);
+            PemReader p = new PemReader(new StreamReader(s));
+            PemObject o = p.ReadPemObject();
+            p.Reader.Close();
+            return o;
+        }
+
+        internal static bool AreSameCertificate(TlsCrypto crypto, TlsCertificate cert, string resource)
+        {
+            // TODO Cache test resources?
+            return AreSameCertificate(cert, LoadCertificateResource(crypto, resource));
+        }
+
+        internal static bool AreSameCertificate(TlsCertificate a, TlsCertificate b)
+        {
+            // TODO[tls-ops] Support equals on TlsCertificate?
+            return Arrays.AreEqual(a.GetEncoded(), b.GetEncoded());
+        }
+
+        internal static TlsCertificate[] GetTrustedCertPath(TlsCrypto crypto, TlsCertificate cert, string[] resources)
+        {
+            foreach (string eeCertResource in resources)
+            {
+                TlsCertificate eeCert = LoadCertificateResource(crypto, eeCertResource);
+                if (AreSameCertificate(cert, eeCert))
+                {
+                    string caCertResource = GetCACertResource(eeCertResource);
+                    TlsCertificate caCert = LoadCertificateResource(crypto, caCertResource);
+                    if (null != caCert)
+                    {
+                        return new TlsCertificate[]{ eeCert, caCert };
+                    }
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/TlsUtilitiesTest.cs b/crypto/test/src/tls/test/TlsUtilitiesTest.cs
new file mode 100644
index 000000000..702c40082
--- /dev/null
+++ b/crypto/test/src/tls/test/TlsUtilitiesTest.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections;
+
+using NUnit.Framework;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    [TestFixture]
+    public class TlsUtilitiesTest
+    {
+        [Test]
+        public void TestChooseSignatureAndHash()
+        {
+            int keyExchangeAlgorithm = KeyExchangeAlgorithm.ECDHE_RSA;
+            short signatureAlgorithm = TlsUtilities.GetLegacySignatureAlgorithmServer(keyExchangeAlgorithm);
+
+            IList supportedSignatureAlgorithms = GetSignatureAlgorithms(false);
+            SignatureAndHashAlgorithm sigAlg = TlsUtilities.ChooseSignatureAndHashAlgorithm(ProtocolVersion.TLSv12,
+                supportedSignatureAlgorithms, signatureAlgorithm);
+            Assert.AreEqual(HashAlgorithm.sha256, sigAlg.Hash);
+
+            for (int count = 0; count < 10; ++count)
+            {
+                supportedSignatureAlgorithms = GetSignatureAlgorithms(true);
+                sigAlg = TlsUtilities.ChooseSignatureAndHashAlgorithm(ProtocolVersion.TLSv12,
+                    supportedSignatureAlgorithms, signatureAlgorithm);
+                Assert.AreEqual(HashAlgorithm.sha256, sigAlg.Hash);
+            }
+        }
+
+        private static IList GetSignatureAlgorithms(bool randomise)
+        {
+            short[] hashAlgorithms = new short[]{ HashAlgorithm.sha1, HashAlgorithm.sha224, HashAlgorithm.sha256,
+                HashAlgorithm.sha384, HashAlgorithm.sha512, HashAlgorithm.md5 };
+            short[] signatureAlgorithms = new short[]{ SignatureAlgorithm.rsa, SignatureAlgorithm.dsa,
+                SignatureAlgorithm.ecdsa };
+
+            IList result = new ArrayList();
+            for (int i = 0; i < signatureAlgorithms.Length; ++i)
+            {
+                for (int j = 0; j < hashAlgorithms.Length; ++j)
+                {
+                    result.Add(SignatureAndHashAlgorithm.GetInstance(hashAlgorithms[j], signatureAlgorithms[i]));
+                }
+            }
+
+            if (randomise)
+            {
+                Random r = new Random();
+                int count = result.Count;
+                for (int src = 0; src < count; ++src)
+                {
+                    int dst = r.Next(count);
+                    if (src != dst)
+                    {
+                        object a = result[src], b = result[dst];
+                        result[dst] = a;
+                        result[src] = b;
+                    }
+                }
+            }
+
+            return result;
+        }
+    }
+}
diff --git a/crypto/test/src/tls/test/UnreliableDatagramTransport.cs b/crypto/test/src/tls/test/UnreliableDatagramTransport.cs
new file mode 100644
index 000000000..bdbfd6e67
--- /dev/null
+++ b/crypto/test/src/tls/test/UnreliableDatagramTransport.cs
@@ -0,0 +1,79 @@
+using System;
+
+using Org.BouncyCastle.Utilities.Date;
+
+namespace Org.BouncyCastle.Tls.Tests
+{
+    public class UnreliableDatagramTransport
+        : DatagramTransport
+    {
+        private readonly DatagramTransport m_transport;
+        private readonly Random m_random;
+        private readonly int m_percentPacketLossReceiving, m_percentPacketLossSending;
+
+        public UnreliableDatagramTransport(DatagramTransport transport, Random random,
+            int percentPacketLossReceiving, int percentPacketLossSending)
+        {
+            if (percentPacketLossReceiving < 0 || percentPacketLossReceiving > 100)
+                throw new ArgumentException("out of range", "percentPacketLossReceiving");
+            if (percentPacketLossSending < 0 || percentPacketLossSending > 100)
+                throw new ArgumentException("out of range", "percentPacketLossSending");
+
+            this.m_transport = transport;
+            this.m_random = random;
+            this.m_percentPacketLossReceiving = percentPacketLossReceiving;
+            this.m_percentPacketLossSending = percentPacketLossSending;
+        }
+
+        public virtual int GetReceiveLimit()
+        {
+            return m_transport.GetReceiveLimit();
+        }
+
+        public virtual int GetSendLimit()
+        {
+            return m_transport.GetSendLimit();
+        }
+
+        public virtual int Receive(byte[] buf, int off, int len, int waitMillis)
+        {
+            long endMillis = DateTimeUtilities.CurrentUnixMs() + waitMillis;
+            for (;;)
+            {
+                int length = m_transport.Receive(buf, off, len, waitMillis);
+                if (length < 0 || !LostPacket(m_percentPacketLossReceiving))
+                    return length;
+
+                Console.WriteLine("PACKET LOSS (" + length + " byte packet not received)");
+
+                long now = DateTimeUtilities.CurrentUnixMs();
+                if (now >= endMillis)
+                    return -1;
+
+                waitMillis = (int)(endMillis - now);
+            }
+        }
+
+        public virtual void Send(byte[] buf, int off, int len)
+        {
+            if (LostPacket(m_percentPacketLossSending))
+            {
+                Console.WriteLine("PACKET LOSS (" + len + " byte packet not sent)");
+            }
+            else
+            {
+                m_transport.Send(buf, off, len);
+            }
+        }
+
+        public virtual void Close()
+        {
+            m_transport.Close();
+        }
+
+        private bool LostPacket(int percentPacketLoss)
+        {
+            return percentPacketLoss > 0 && m_random.Next(100) < percentPacketLoss;
+        }
+    }
+}