diff options
author | Peter Dettman <peter.dettman@bouncycastle.org> | 2014-08-25 10:31:54 +0700 |
---|---|---|
committer | Peter Dettman <peter.dettman@bouncycastle.org> | 2014-08-25 10:31:54 +0700 |
commit | 24f37b51eafe7cbbf42b633be133ecca3abcb46e (patch) | |
tree | 6ee7123e825d546ec7c53225620eb3d962b20c9f /crypto | |
parent | Fix NAnt build issue (diff) | |
download | BouncyCastle.NET-ed25519-24f37b51eafe7cbbf42b633be133ecca3abcb46e.tar.xz |
Port TLS server impl/test from Java API
Diffstat (limited to 'crypto')
-rw-r--r-- | crypto/crypto.csproj | 15 | ||||
-rw-r--r-- | crypto/src/crypto/tls/TlsProtocol.cs | 4 | ||||
-rw-r--r-- | crypto/src/crypto/tls/TlsServerProtocol.cs | 744 | ||||
-rw-r--r-- | crypto/test/src/crypto/tls/test/MockTlsServer.cs | 137 | ||||
-rw-r--r-- | crypto/test/src/crypto/tls/test/TlsServerTest.cs | 78 |
5 files changed, 976 insertions, 2 deletions
diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj index cb4710669..d65021a68 100644 --- a/crypto/crypto.csproj +++ b/crypto/crypto.csproj @@ -4874,6 +4874,11 @@ BuildAction = "Compile" /> <File + RelPath = "src\crypto\tls\TlsServerProtocol.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "src\crypto\tls\TlsSession.cs" SubType = "Code" BuildAction = "Compile" @@ -10916,11 +10921,21 @@ BuildAction = "Compile" /> <File + RelPath = "test\src\crypto\tls\test\MockTlsServer.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "test\src\crypto\tls\test\TlsClientTest.cs" SubType = "Code" BuildAction = "Compile" /> <File + RelPath = "test\src\crypto\tls\test\TlsServerTest.cs" + SubType = "Code" + BuildAction = "Compile" + /> + <File RelPath = "test\src\crypto\tls\test\TlsTestUtilities.cs" SubType = "Code" BuildAction = "Compile" diff --git a/crypto/src/crypto/tls/TlsProtocol.cs b/crypto/src/crypto/tls/TlsProtocol.cs index 2e2ef9214..8ba156952 100644 --- a/crypto/src/crypto/tls/TlsProtocol.cs +++ b/crypto/src/crypto/tls/TlsProtocol.cs @@ -533,7 +533,7 @@ namespace Org.BouncyCastle.Crypto.Tls } } - protected void WriteHandshakeMessage(byte[] buf, int off, int len) + protected virtual void WriteHandshakeMessage(byte[] buf, int off, int len) { while (len > 0) { @@ -698,7 +698,7 @@ namespace Org.BouncyCastle.Crypto.Tls message.WriteToRecordStream(this); } - protected byte[] CreateVerifyData(bool isServer) + protected virtual byte[] CreateVerifyData(bool isServer) { TlsContext context = Context; string asciiLabel = isServer ? ExporterLabel.server_finished : ExporterLabel.client_finished; diff --git a/crypto/src/crypto/tls/TlsServerProtocol.cs b/crypto/src/crypto/tls/TlsServerProtocol.cs new file mode 100644 index 000000000..589ede802 --- /dev/null +++ b/crypto/src/crypto/tls/TlsServerProtocol.cs @@ -0,0 +1,744 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls +{ + public class TlsServerProtocol + : TlsProtocol + { + protected TlsServer mTlsServer = null; + internal TlsServerContextImpl mTlsServerContext = null; + + protected TlsKeyExchange mKeyExchange = null; + protected TlsCredentials mServerCredentials = null; + protected CertificateRequest mCertificateRequest = null; + + protected short mClientCertificateType = -1; + protected TlsHandshakeHash mPrepareFinishHash = null; + + public TlsServerProtocol(Stream stream, SecureRandom secureRandom) + : base(stream, secureRandom) + { + } + + public TlsServerProtocol(Stream input, Stream output, SecureRandom secureRandom) + : base(input, output, secureRandom) + { + } + + /** + * Receives a TLS handshake in the role of server + * + * @param mTlsServer + * @throws IOException If handshake was not successful. + */ + public virtual void Accept(TlsServer tlsServer) + { + if (tlsServer == null) + throw new ArgumentNullException("tlsServer"); + if (this.mTlsServer != null) + throw new InvalidOperationException("'Accept' can only be called once"); + + this.mTlsServer = tlsServer; + + this.mSecurityParameters = new SecurityParameters(); + this.mSecurityParameters.entity = ConnectionEnd.server; + + this.mTlsServerContext = new TlsServerContextImpl(mSecureRandom, mSecurityParameters); + + this.mSecurityParameters.serverRandom = CreateRandomBlock(tlsServer.ShouldUseGmtUnixTime(), + mTlsServerContext.NonceRandomGenerator); + + this.mTlsServer.Init(mTlsServerContext); + this.mRecordStream.Init(mTlsServerContext); + + this.mRecordStream.SetRestrictReadVersion(false); + + CompleteHandshake(); + } + + protected override void CleanupHandshake() + { + base.CleanupHandshake(); + + this.mKeyExchange = null; + this.mServerCredentials = null; + this.mCertificateRequest = null; + this.mPrepareFinishHash = null; + } + + protected override TlsContext Context + { + get { return mTlsServerContext; } + } + + internal override AbstractTlsContext ContextAdmin + { + get { return mTlsServerContext; } + } + + protected override TlsPeer Peer + { + get { return mTlsServer; } + } + + protected override void HandleHandshakeMessage(byte type, byte[] data) + { + MemoryStream buf = new MemoryStream(data); + + switch (type) + { + case HandshakeType.client_hello: + { + switch (this.mConnectionState) + { + case CS_START: + { + ReceiveClientHelloMessage(buf); + this.mConnectionState = CS_CLIENT_HELLO; + + SendServerHelloMessage(); + this.mConnectionState = CS_SERVER_HELLO; + + IList serverSupplementalData = mTlsServer.GetServerSupplementalData(); + if (serverSupplementalData != null) + { + SendSupplementalDataMessage(serverSupplementalData); + } + this.mConnectionState = CS_SERVER_SUPPLEMENTAL_DATA; + + this.mKeyExchange = mTlsServer.GetKeyExchange(); + this.mKeyExchange.Init(Context); + + this.mServerCredentials = mTlsServer.GetCredentials(); + + Certificate serverCertificate = null; + + if (this.mServerCredentials == null) + { + this.mKeyExchange.SkipServerCredentials(); + } + else + { + this.mKeyExchange.ProcessServerCredentials(this.mServerCredentials); + + serverCertificate = this.mServerCredentials.Certificate; + SendCertificateMessage(serverCertificate); + } + this.mConnectionState = CS_SERVER_CERTIFICATE; + + // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus + if (serverCertificate == null || serverCertificate.IsEmpty) + { + this.mAllowCertificateStatus = false; + } + + if (this.mAllowCertificateStatus) + { + CertificateStatus certificateStatus = mTlsServer.GetCertificateStatus(); + if (certificateStatus != null) + { + SendCertificateStatusMessage(certificateStatus); + } + } + + this.mConnectionState = CS_CERTIFICATE_STATUS; + + byte[] serverKeyExchange = this.mKeyExchange.GenerateServerKeyExchange(); + if (serverKeyExchange != null) + { + SendServerKeyExchangeMessage(serverKeyExchange); + } + this.mConnectionState = CS_SERVER_KEY_EXCHANGE; + + if (this.mServerCredentials != null) + { + this.mCertificateRequest = mTlsServer.GetCertificateRequest(); + if (this.mCertificateRequest != null) + { + this.mKeyExchange.ValidateCertificateRequest(mCertificateRequest); + + SendCertificateRequestMessage(mCertificateRequest); + + TlsUtilities.TrackHashAlgorithms(this.mRecordStream.HandshakeHash, + this.mCertificateRequest.SupportedSignatureAlgorithms); + } + } + this.mConnectionState = CS_CERTIFICATE_REQUEST; + + SendServerHelloDoneMessage(); + this.mConnectionState = CS_SERVER_HELLO_DONE; + + this.mRecordStream.HandshakeHash.SealHashAlgorithms(); + + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.supplemental_data: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO_DONE: + { + mTlsServer.ProcessClientSupplementalData(ReadSupplementalDataMessage(buf)); + this.mConnectionState = CS_CLIENT_SUPPLEMENTAL_DATA; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.certificate: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO_DONE: + case CS_CLIENT_SUPPLEMENTAL_DATA: + { + if (mConnectionState < CS_CLIENT_SUPPLEMENTAL_DATA) + { + mTlsServer.ProcessClientSupplementalData(null); + } + + if (this.mCertificateRequest == null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + ReceiveCertificateMessage(buf); + this.mConnectionState = CS_CLIENT_CERTIFICATE; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.client_key_exchange: + { + switch (this.mConnectionState) + { + case CS_SERVER_HELLO_DONE: + case CS_CLIENT_SUPPLEMENTAL_DATA: + case CS_CLIENT_CERTIFICATE: + { + if (mConnectionState < CS_CLIENT_SUPPLEMENTAL_DATA) + { + mTlsServer.ProcessClientSupplementalData(null); + } + + if (mConnectionState < CS_CLIENT_CERTIFICATE) + { + if (this.mCertificateRequest == null) + { + this.mKeyExchange.SkipClientCredentials(); + } + else + { + if (TlsUtilities.IsTlsV12(Context)) + { + /* + * 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(Context)) + { + if (this.mPeerCertificate == null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + else + { + NotifyClientCertificate(Certificate.EmptyChain); + } + } + } + + ReceiveClientKeyExchangeMessage(buf); + this.mConnectionState = CS_CLIENT_KEY_EXCHANGE; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.certificate_verify: + { + switch (this.mConnectionState) + { + 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); + this.mConnectionState = CS_CERTIFICATE_VERIFY; + + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.finished: + { + switch (this.mConnectionState) + { + case CS_CLIENT_KEY_EXCHANGE: + case CS_CERTIFICATE_VERIFY: + { + if (mConnectionState < CS_CERTIFICATE_VERIFY && ExpectCertificateVerifyMessage()) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + ProcessFinishedMessage(buf); + this.mConnectionState = CS_CLIENT_FINISHED; + + if (this.mExpectSessionTicket) + { + SendNewSessionTicketMessage(mTlsServer.GetNewSessionTicket()); + SendChangeCipherSpecMessage(); + } + this.mConnectionState = CS_SERVER_SESSION_TICKET; + + SendFinishedMessage(); + this.mConnectionState = CS_SERVER_FINISHED; + this.mConnectionState = CS_END; + break; + } + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + break; + } + case HandshakeType.hello_request: + case HandshakeType.hello_verify_request: + case HandshakeType.server_hello: + case HandshakeType.server_key_exchange: + case HandshakeType.certificate_request: + case HandshakeType.server_hello_done: + case HandshakeType.session_ticket: + default: + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + } + + protected override void HandleWarningMessage(byte description) + { + switch (description) + { + case AlertDescription.no_certificate: + { + /* + * 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 (TlsUtilities.IsSsl(Context) && mCertificateRequest != null) + { + NotifyClientCertificate(Certificate.EmptyChain); + } + break; + } + default: + { + base.HandleWarningMessage(description); + break; + } + } + } + + protected virtual void NotifyClientCertificate(Certificate clientCertificate) + { + if (mCertificateRequest == null) + throw new InvalidOperationException(); + if (mPeerCertificate != null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + this.mPeerCertificate = clientCertificate; + + if (clientCertificate.IsEmpty) + { + this.mKeyExchange.SkipClientCredentials(); + } + else + { + + /* + * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request + * message was non-empty, one of the certificates in the certificate chain SHOULD be + * issued by one of the listed CAs. + */ + + this.mClientCertificateType = TlsUtilities.GetClientCertificateType(clientCertificate, + this.mServerCredentials.Certificate); + + this.mKeyExchange.ProcessClientCertificate(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. + */ + this.mTlsServer.NotifyClientCertificate(clientCertificate); + } + + protected virtual void ReceiveCertificateMessage(MemoryStream buf) + { + Certificate clientCertificate = Certificate.Parse(buf); + + AssertEmpty(buf); + + NotifyClientCertificate(clientCertificate); + } + + protected virtual void ReceiveCertificateVerifyMessage(MemoryStream buf) + { + DigitallySigned clientCertificateVerify = DigitallySigned.Parse(Context, buf); + + AssertEmpty(buf); + + // Verify the CertificateVerify message contains a correct signature. + try + { + byte[] certificateVerifyHash; + if (TlsUtilities.IsTlsV12(Context)) + { + certificateVerifyHash = mPrepareFinishHash.GetFinalHash(clientCertificateVerify.Algorithm.Hash); + } + else + { + certificateVerifyHash = TlsProtocol.GetCurrentPrfHash(Context, mPrepareFinishHash, null); + } + + X509CertificateStructure x509Cert = mPeerCertificate.GetCertificateAt(0); + SubjectPublicKeyInfo keyInfo = x509Cert.SubjectPublicKeyInfo; + AsymmetricKeyParameter publicKey = PublicKeyFactory.CreateKey(keyInfo); + + TlsSigner tlsSigner = TlsUtilities.CreateTlsSigner((byte)mClientCertificateType); + tlsSigner.Init(Context); + if (!tlsSigner.VerifyRawSignature(clientCertificateVerify.Algorithm, + clientCertificateVerify.Signature, publicKey, certificateVerifyHash)) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error); + } + } + catch (Exception e) + { + throw new TlsFatalAlert(AlertDescription.decrypt_error, e); + } + } + + protected virtual void ReceiveClientHelloMessage(MemoryStream buf) + { + ProtocolVersion client_version = TlsUtilities.ReadVersion(buf); + if (client_version.IsDtls) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + byte[] client_random = TlsUtilities.ReadFully(32, buf); + + /* + * TODO RFC 5077 3.4. If a ticket is presented by the client, the server MUST NOT attempt to + * use the Session ID in the ClientHello for stateful session resumption. + */ + byte[] sessionID = TlsUtilities.ReadOpaque8(buf); + if (sessionID.Length > 32) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + /* + * TODO RFC 5246 7.4.1.2. 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 cipher_suites_length = TlsUtilities.ReadUint16(buf); + if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0) + throw new TlsFatalAlert(AlertDescription.decode_error); + + this.mOfferedCipherSuites = TlsUtilities.ReadUint16Array(cipher_suites_length / 2, buf); + + /* + * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session + * resumption request), it MUST include the compression_method from that session. + */ + int compression_methods_length = TlsUtilities.ReadUint8(buf); + if (compression_methods_length < 1) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + this.mOfferedCompressionMethods = TlsUtilities.ReadUint8Array(compression_methods_length, buf); + + /* + * 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. + */ + this.mClientExtensions = ReadExtensions(buf); + + ContextAdmin.SetClientVersion(client_version); + + mTlsServer.NotifyClientVersion(client_version); + + mSecurityParameters.clientRandom = client_random; + + mTlsServer.NotifyOfferedCipherSuites(mOfferedCipherSuites); + mTlsServer.NotifyOfferedCompressionMethods(mOfferedCompressionMethods); + + /* + * 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(mOfferedCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) + { + this.mSecureRenegotiation = true; + } + + /* + * The server MUST check if the "renegotiation_info" extension is included in the + * ClientHello. + */ + byte[] renegExtData = TlsUtilities.GetExtensionData(mClientExtensions, 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. + */ + this.mSecureRenegotiation = true; + + if (!Arrays.ConstantTimeAreEqual(renegExtData, CreateRenegotiationInfo(TlsUtilities.EmptyBytes))) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + } + + mTlsServer.NotifySecureRenegotiation(this.mSecureRenegotiation); + + if (mClientExtensions != null) + { + mTlsServer.ProcessClientExtensions(mClientExtensions); + } + } + + protected virtual void ReceiveClientKeyExchangeMessage(MemoryStream buf) + { + mKeyExchange.ProcessClientKeyExchange(buf); + + AssertEmpty(buf); + + EstablishMasterSecret(Context, mKeyExchange); + mRecordStream.SetPendingConnectionState(Peer.GetCompression(), Peer.GetCipher()); + + this.mPrepareFinishHash = mRecordStream.PrepareToFinish(); + + if (!mExpectSessionTicket) + { + SendChangeCipherSpecMessage(); + } + } + + protected virtual void SendCertificateRequestMessage(CertificateRequest certificateRequest) + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_request); + + certificateRequest.Encode(message); + + message.WriteToRecordStream(this); + } + + protected virtual void SendCertificateStatusMessage(CertificateStatus certificateStatus) + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_status); + + certificateStatus.Encode(message); + + message.WriteToRecordStream(this); + } + + protected virtual void SendNewSessionTicketMessage(NewSessionTicket newSessionTicket) + { + if (newSessionTicket == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + HandshakeMessage message = new HandshakeMessage(HandshakeType.session_ticket); + + newSessionTicket.Encode(message); + + message.WriteToRecordStream(this); + } + + protected virtual void SendServerHelloMessage() + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.server_hello); + + ProtocolVersion server_version = mTlsServer.GetServerVersion(); + if (!server_version.IsEqualOrEarlierVersionOf(Context.ClientVersion)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + mRecordStream.ReadVersion = server_version; + mRecordStream.SetWriteVersion(server_version); + mRecordStream.SetRestrictReadVersion(true); + ContextAdmin.SetServerVersion(server_version); + + TlsUtilities.WriteVersion(server_version, message); + + message.Write(this.mSecurityParameters.serverRandom); + + /* + * The server may return an empty session_id to indicate that the session will not be cached + * and therefore cannot be resumed. + */ + TlsUtilities.WriteOpaque8(TlsUtilities.EmptyBytes, message); + + int selectedCipherSuite = mTlsServer.GetSelectedCipherSuite(); + if (!Arrays.Contains(mOfferedCipherSuites, selectedCipherSuite) + || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL + || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV + || !TlsUtilities.IsValidCipherSuiteForVersion(selectedCipherSuite, server_version)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + mSecurityParameters.cipherSuite = selectedCipherSuite; + + byte selectedCompressionMethod = mTlsServer.GetSelectedCompressionMethod(); + if (!Arrays.Contains(mOfferedCompressionMethods, selectedCompressionMethod)) + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + mSecurityParameters.compressionAlgorithm = selectedCompressionMethod; + + TlsUtilities.WriteUint16(selectedCipherSuite, message); + TlsUtilities.WriteUint8(selectedCompressionMethod, message); + + this.mServerExtensions = mTlsServer.GetServerExtensions(); + + /* + * RFC 5746 3.6. Server Behavior: Initial Handshake + */ + if (this.mSecureRenegotiation) + { + byte[] renegExtData = TlsUtilities.GetExtensionData(this.mServerExtensions, 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. + */ + this.mServerExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised(mServerExtensions); + this.mServerExtensions[ExtensionType.renegotiation_info] = CreateRenegotiationInfo(TlsUtilities.EmptyBytes); + } + } + + /* + * 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 (this.mServerExtensions != null) + { + this.mSecurityParameters.encryptThenMac = TlsExtensionsUtilities.HasEncryptThenMacExtension(mServerExtensions); + + this.mSecurityParameters.maxFragmentLength = ProcessMaxFragmentLengthExtension(mClientExtensions, + mServerExtensions, AlertDescription.internal_error); + + this.mSecurityParameters.truncatedHMac = TlsExtensionsUtilities.HasTruncatedHMacExtension(mServerExtensions); + + /* + * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in + * a session resumption handshake. + */ + this.mAllowCertificateStatus = !mResumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(mServerExtensions, ExtensionType.status_request, + AlertDescription.internal_error); + + this.mExpectSessionTicket = !mResumedSession + && TlsUtilities.HasExpectedEmptyExtensionData(mServerExtensions, ExtensionType.session_ticket, + AlertDescription.internal_error); + + WriteExtensions(message, this.mServerExtensions); + } + + if (mSecurityParameters.maxFragmentLength >= 0) + { + int plainTextLimit = 1 << (8 + mSecurityParameters.maxFragmentLength); + mRecordStream.SetPlaintextLimit(plainTextLimit); + } + + mSecurityParameters.prfAlgorithm = GetPrfAlgorithm(Context, mSecurityParameters.CipherSuite); + + /* + * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has + * a verify_data_length equal to 12. This includes all existing cipher suites. + */ + mSecurityParameters.verifyDataLength = 12; + + message.WriteToRecordStream(this); + + this.mRecordStream.NotifyHelloComplete(); + } + + protected virtual void SendServerHelloDoneMessage() + { + byte[] message = new byte[4]; + TlsUtilities.WriteUint8(HandshakeType.server_hello_done, message, 0); + TlsUtilities.WriteUint24(0, message, 1); + + WriteHandshakeMessage(message, 0, message.Length); + } + + protected virtual void SendServerKeyExchangeMessage(byte[] serverKeyExchange) + { + HandshakeMessage message = new HandshakeMessage(HandshakeType.server_key_exchange, serverKeyExchange.Length); + + message.Write(serverKeyExchange); + + message.WriteToRecordStream(this); + } + + protected virtual bool ExpectCertificateVerifyMessage() + { + return mClientCertificateType >= 0 && TlsUtilities.HasSigningCapability((byte)mClientCertificateType); + } + } +} diff --git a/crypto/test/src/crypto/tls/test/MockTlsServer.cs b/crypto/test/src/crypto/tls/test/MockTlsServer.cs new file mode 100644 index 000000000..54a645d96 --- /dev/null +++ b/crypto/test/src/crypto/tls/test/MockTlsServer.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections; +using System.IO; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Tls.Tests +{ + internal class MockTlsServer + : DefaultTlsServer + { + public override void NotifyAlertRaised(byte alertLevel, byte alertDescription, string message, Exception cause) + { + TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out; + output.WriteLine("TLS client raised alert (AlertLevel." + alertLevel + ", AlertDescription." + alertDescription + + ")"); + if (message != null) + { + output.WriteLine("> " + message); + } + if (cause != null) + { + output.WriteLine(cause); + } + } + + public override void NotifyAlertReceived(byte alertLevel, byte alertDescription) + { + TextWriter output = (alertLevel == AlertLevel.fatal) ? Console.Error : Console.Out; + output.WriteLine("TLS client received alert (AlertLevel." + alertLevel + ", AlertDescription." + + alertDescription + ")"); + } + + protected override int[] GetCipherSuites() + { + return Arrays.Concatenate(base.GetCipherSuites(), + new int[] + { + CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1, + CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1, + CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1, + CipherSuite.TLS_RSA_WITH_SALSA20_SHA1, + }); + } + + protected override ProtocolVersion MaximumVersion + { + get { return ProtocolVersion.TLSv12; } + } + + public override ProtocolVersion GetServerVersion() + { + ProtocolVersion serverVersion = base.GetServerVersion(); + + Console.WriteLine("TLS server negotiated " + serverVersion); + + return serverVersion; + } + + public override CertificateRequest GetCertificateRequest() + { + IList serverSigAlgs = null; + + if (TlsUtilities.IsSignatureAlgorithmsExtensionAllowed(mServerVersion)) + { + byte[] hashAlgorithms = new byte[]{ HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256, + HashAlgorithm.sha224, HashAlgorithm.sha1 }; + byte[] signatureAlgorithms = new byte[]{ SignatureAlgorithm.rsa }; + + serverSigAlgs = new ArrayList(); + for (int i = 0; i < hashAlgorithms.Length; ++i) + { + for (int j = 0; j < signatureAlgorithms.Length; ++j) + { + serverSigAlgs.Add(new SignatureAndHashAlgorithm(hashAlgorithms[i], + signatureAlgorithms[j])); + } + } + } + + IList certificateAuthorities = new ArrayList(); + certificateAuthorities.Add(TlsTestUtilities.LoadCertificateResource("x509-ca.pem").Subject); + + return new CertificateRequest(new byte[]{ ClientCertificateType.rsa_sign }, serverSigAlgs, certificateAuthorities); + } + + public override void NotifyClientCertificate(Certificate clientCertificate) + { + X509CertificateStructure[] 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 = chain[i]; + // TODO Create fingerprint based on certificate signature algorithm digest + Console.WriteLine(" fingerprint:SHA-256 " + TlsTestUtilities.Fingerprint(entry) + " (" + + entry.Subject + ")"); + } + } + + protected override TlsEncryptionCredentials GetRsaEncryptionCredentials() + { + return TlsTestUtilities.LoadEncryptionCredentials(mContext, new string[]{"x509-server.pem", "x509-ca.pem"}, + "x509-server-key.pem"); + } + + protected override TlsSignerCredentials GetRsaSignerCredentials() + { + /* + * TODO Note that this code fails to provide default value for the client supported + * algorithms if it wasn't sent. + */ + SignatureAndHashAlgorithm signatureAndHashAlgorithm = null; + IList sigAlgs = mSupportedSignatureAlgorithms; + if (sigAlgs != null) + { + foreach (SignatureAndHashAlgorithm sigAlg in sigAlgs) + { + if (sigAlg.Signature == SignatureAlgorithm.rsa) + { + signatureAndHashAlgorithm = sigAlg; + break; + } + } + + if (signatureAndHashAlgorithm == null) + { + return null; + } + } + + return TlsTestUtilities.LoadSignerCredentials(mContext, new string[]{"x509-server.pem", "x509-ca.pem"}, + "x509-server-key.pem", signatureAndHashAlgorithm); + } + } +} diff --git a/crypto/test/src/crypto/tls/test/TlsServerTest.cs b/crypto/test/src/crypto/tls/test/TlsServerTest.cs new file mode 100644 index 000000000..77adf22f7 --- /dev/null +++ b/crypto/test/src/crypto/tls/test/TlsServerTest.cs @@ -0,0 +1,78 @@ +using System; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.IO; + +namespace Org.BouncyCastle.Crypto.Tls.Tests +{ + /** + * A simple test designed to conduct a TLS handshake with an external TLS client. + * <p/> + * 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. + */ + public class TlsServerTest + { + private static readonly SecureRandom secureRandom = new SecureRandom(); + + public static void Main(string[] args) + { + int port = 5556; + + TcpListener ss = new TcpListener(IPAddress.Any, port); + ss.Start(); + Stream stdout = Console.OpenStandardOutput(); + while (true) + { + TcpClient s = ss.AcceptTcpClient(); + Console.WriteLine("--------------------------------------------------------------------------------"); + Console.WriteLine("Accepted " + s); + ServerThread st = new ServerThread(s, stdout); + Thread t = new Thread(new ThreadStart(st.Run)); + t.Start(); + } + } + + internal class ServerThread + { + private readonly TcpClient s; + private readonly Stream stdout; + + internal ServerThread(TcpClient s, Stream stdout) + { + this.s = s; + this.stdout = stdout; + } + + public void Run() + { + try + { + MockTlsServer server = new MockTlsServer(); + TlsServerProtocol serverProtocol = new TlsServerProtocol(s.GetStream(), secureRandom); + serverProtocol.Accept(server); + Stream log = new TeeOutputStream(serverProtocol.Stream, stdout); + Streams.PipeAll(serverProtocol.Stream, log); + serverProtocol.Close(); + } + finally + { + try + { + s.Close(); + } + catch (IOException) + { + } + finally + { + } + } + } + } + } +} |