From 68c795fe81277f73aeb90d8ad4c6f4305f32c906 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Mon, 12 Jul 2021 15:15:36 +0700 Subject: Port of new TLS API from bc-java --- crypto/BouncyCastle.Android.csproj | 266 + crypto/BouncyCastle.csproj | 266 + crypto/BouncyCastle.iOS.csproj | 266 + crypto/crypto.csproj | 1540 ++++++ crypto/src/tls/AbstractTlsClient.cs | 435 ++ crypto/src/tls/AbstractTlsContext.cs | 282 ++ crypto/src/tls/AbstractTlsKeyExchange.cs | 90 + crypto/src/tls/AbstractTlsKeyExchangeFactory.cs | 90 + crypto/src/tls/AbstractTlsPeer.cs | 157 + crypto/src/tls/AbstractTlsServer.cs | 578 +++ crypto/src/tls/AlertDescription.cs | 323 ++ crypto/src/tls/AlertLevel.cs | 29 + crypto/src/tls/BasicTlsPskIdentity.cs | 44 + crypto/src/tls/BasicTlsSrpIdentity.cs | 36 + crypto/src/tls/ByteQueue.cs | 195 + crypto/src/tls/ByteQueueInputStream.cs | 72 + crypto/src/tls/ByteQueueOutputStream.cs | 33 + crypto/src/tls/CachedInformationType.cs | 28 + crypto/src/tls/CertChainType.cs | 34 + crypto/src/tls/Certificate.cs | 294 ++ crypto/src/tls/CertificateEntry.cs | 32 + crypto/src/tls/CertificateRequest.cs | 276 + crypto/src/tls/CertificateStatus.cs | 220 + crypto/src/tls/CertificateStatusRequest.cs | 91 + crypto/src/tls/CertificateStatusRequestItemV2.cs | 100 + crypto/src/tls/CertificateStatusType.cs | 17 + crypto/src/tls/CertificateType.cs | 16 + crypto/src/tls/CertificateUrl.cs | 116 + crypto/src/tls/ChangeCipherSpec.cs | 9 + crypto/src/tls/ChannelBinding.cs | 19 + crypto/src/tls/CipherSuite.cs | 461 ++ crypto/src/tls/CipherType.cs | 20 + crypto/src/tls/ClientAuthenticationType.cs | 14 + crypto/src/tls/ClientCertificateType.cs | 69 + crypto/src/tls/ClientHello.cs | 167 + crypto/src/tls/CombinedHash.cs | 67 + crypto/src/tls/CompressionMethod.cs | 20 + crypto/src/tls/ConnectionEnd.cs | 15 + crypto/src/tls/ContentType.cs | 38 + crypto/src/tls/DatagramReceiver.cs | 14 + crypto/src/tls/DatagramSender.cs | 14 + crypto/src/tls/DatagramTransport.cs | 10 + crypto/src/tls/DefaultTlsClient.cs | 48 + crypto/src/tls/DefaultTlsCredentialedSigner.cs | 66 + crypto/src/tls/DefaultTlsDHGroupVerifier.cs | 107 + crypto/src/tls/DefaultTlsHeartbeat.cs | 44 + crypto/src/tls/DefaultTlsKeyExchangeFactory.cs | 89 + crypto/src/tls/DefaultTlsServer.cs | 108 + crypto/src/tls/DefaultTlsSrpConfigVerifier.cs | 63 + crypto/src/tls/DeferredHash.cs | 249 + crypto/src/tls/DigestInputBuffer.cs | 26 + crypto/src/tls/DigitallySigned.cs | 62 + crypto/src/tls/DtlsClientProtocol.cs | 976 ++++ crypto/src/tls/DtlsEpoch.cs | 61 + crypto/src/tls/DtlsHandshakeRetransmit.cs | 11 + crypto/src/tls/DtlsProtocol.cs | 107 + crypto/src/tls/DtlsReassembler.cs | 120 + crypto/src/tls/DtlsRecordLayer.cs | 817 +++ crypto/src/tls/DtlsReliableHandshake.cs | 558 ++ crypto/src/tls/DtlsReplayWindow.cs | 84 + crypto/src/tls/DtlsRequest.cs | 38 + crypto/src/tls/DtlsServerProtocol.cs | 835 +++ crypto/src/tls/DtlsTransport.cs | 139 + crypto/src/tls/DtlsVerifier.cs | 89 + crypto/src/tls/ECCurveType.cs | 29 + crypto/src/tls/ECPointFormat.cs | 16 + crypto/src/tls/EncryptionAlgorithm.cs | 82 + crypto/src/tls/ExporterLabel.cs | 42 + crypto/src/tls/ExtensionType.cs | 279 + crypto/src/tls/HandshakeMessageInput.cs | 22 + crypto/src/tls/HandshakeMessageOutput.cs | 62 + crypto/src/tls/HandshakeType.cs | 131 + crypto/src/tls/HashAlgorithm.cs | 94 + crypto/src/tls/HeartbeatExtension.cs | 44 + crypto/src/tls/HeartbeatMessage.cs | 118 + crypto/src/tls/HeartbeatMessageType.cs | 34 + crypto/src/tls/HeartbeatMode.cs | 36 + crypto/src/tls/IdentifierType.cs | 35 + crypto/src/tls/KeyExchangeAlgorithm.cs | 63 + crypto/src/tls/KeyShareEntry.cs | 62 + crypto/src/tls/KeyUpdateRequest.cs | 34 + crypto/src/tls/MacAlgorithm.cs | 66 + crypto/src/tls/MaxFragmentLength.cs | 20 + crypto/src/tls/NameType.cs | 38 + crypto/src/tls/NamedGroup.cs | 416 ++ crypto/src/tls/NamedGroupRole.cs | 15 + crypto/src/tls/NewSessionTicket.cs | 47 + crypto/src/tls/OcspStatusRequest.cs | 111 + crypto/src/tls/OfferedPsks.cs | 111 + crypto/src/tls/PrfAlgorithm.cs | 49 + crypto/src/tls/ProtocolName.cs | 88 + crypto/src/tls/ProtocolVersion.cs | 406 ++ crypto/src/tls/PskIdentity.cs | 47 + crypto/src/tls/PskKeyExchangeMode.cs | 32 + crypto/src/tls/PskTlsClient.cs | 60 + crypto/src/tls/PskTlsServer.cs | 76 + crypto/src/tls/RecordFormat.cs | 12 + crypto/src/tls/RecordPreview.cs | 36 + crypto/src/tls/RecordStream.cs | 533 ++ crypto/src/tls/SecurityParameters.cs | 334 ++ crypto/src/tls/ServerHello.cs | 108 + crypto/src/tls/ServerName.cs | 63 + crypto/src/tls/ServerNameList.cs | 87 + crypto/src/tls/ServerOnlyTlsAuthentication.cs | 15 + crypto/src/tls/ServerSrpParams.cs | 67 + crypto/src/tls/SessionParameters.cs | 195 + crypto/src/tls/SignatureAlgorithm.cs | 125 + crypto/src/tls/SignatureAndHashAlgorithm.cs | 171 + crypto/src/tls/SignatureScheme.cs | 235 + crypto/src/tls/SimulatedTlsSrpIdentityManager.cs | 69 + crypto/src/tls/SrpTlsClient.cs | 83 + crypto/src/tls/SrpTlsServer.cs | 106 + crypto/src/tls/SrtpProtectionProfile.cs | 21 + crypto/src/tls/Ssl3Utilities.cs | 69 + crypto/src/tls/SupplementalDataEntry.cs | 26 + crypto/src/tls/SupplementalDataType.cs | 13 + crypto/src/tls/Timeout.cs | 119 + crypto/src/tls/TlsAuthentication.cs | 29 + crypto/src/tls/TlsClient.cs | 101 + crypto/src/tls/TlsClientContext.cs | 10 + crypto/src/tls/TlsClientContextImpl.cs | 20 + crypto/src/tls/TlsClientProtocol.cs | 1715 +++++++ crypto/src/tls/TlsCloseable.cs | 11 + crypto/src/tls/TlsContext.cs | 79 + crypto/src/tls/TlsCredentialedAgreement.cs | 19 + crypto/src/tls/TlsCredentialedDecryptor.cs | 19 + crypto/src/tls/TlsCredentialedSigner.cs | 26 + crypto/src/tls/TlsCredentials.cs | 12 + crypto/src/tls/TlsDHGroupVerifier.cs | 15 + crypto/src/tls/TlsDHKeyExchange.cs | 93 + crypto/src/tls/TlsDHUtilities.cs | 159 + crypto/src/tls/TlsDHanonKeyExchange.cs | 124 + crypto/src/tls/TlsDheKeyExchange.cs | 129 + crypto/src/tls/TlsECDHKeyExchange.cs | 95 + crypto/src/tls/TlsECDHanonKeyExchange.cs | 127 + crypto/src/tls/TlsECDheKeyExchange.cs | 141 + crypto/src/tls/TlsEccUtilities.cs | 117 + crypto/src/tls/TlsException.cs | 24 + crypto/src/tls/TlsExtensionsUtilities.cs | 1377 +++++ crypto/src/tls/TlsFatalAlert.cs | 46 + crypto/src/tls/TlsFatalAlertReceived.cs | 21 + crypto/src/tls/TlsHandshakeHash.cs | 29 + crypto/src/tls/TlsHeartbeat.cs | 13 + crypto/src/tls/TlsKeyExchange.cs | 55 + crypto/src/tls/TlsKeyExchangeFactory.cs | 59 + crypto/src/tls/TlsNoCloseNotifyException.cs | 21 + crypto/src/tls/TlsObjectIdentifiers.cs | 14 + crypto/src/tls/TlsPeer.cs | 121 + crypto/src/tls/TlsProtocol.cs | 1867 +++++++ crypto/src/tls/TlsPskIdentity.cs | 16 + crypto/src/tls/TlsPskIdentityManager.cs | 12 + crypto/src/tls/TlsPskKeyExchange.cs | 305 ++ crypto/src/tls/TlsRsaKeyExchange.cs | 80 + crypto/src/tls/TlsRsaUtilities.cs | 24 + crypto/src/tls/TlsServer.cs | 119 + crypto/src/tls/TlsServerCertificate.cs | 12 + crypto/src/tls/TlsServerCertificateImpl.cs | 27 + crypto/src/tls/TlsServerContext.cs | 10 + crypto/src/tls/TlsServerContextImpl.cs | 20 + crypto/src/tls/TlsServerProtocol.cs | 1471 ++++++ crypto/src/tls/TlsSession.cs | 16 + crypto/src/tls/TlsSessionImpl.cs | 52 + crypto/src/tls/TlsSrpConfigVerifier.cs | 15 + crypto/src/tls/TlsSrpIdentity.cs | 12 + crypto/src/tls/TlsSrpIdentityManager.cs | 18 + crypto/src/tls/TlsSrpKeyExchange.cs | 186 + crypto/src/tls/TlsSrpLoginParameters.cs | 44 + crypto/src/tls/TlsSrpUtilities.cs | 69 + crypto/src/tls/TlsSrtpUtilities.cs | 63 + crypto/src/tls/TlsStream.cs | 96 + crypto/src/tls/TlsTimeoutException.cs | 24 + crypto/src/tls/TlsUtilities.cs | 5305 ++++++++++++++++++++ crypto/src/tls/TrustedAuthority.cs | 149 + crypto/src/tls/UrlAndHash.cs | 83 + crypto/src/tls/UseSrtpData.cs | 43 + crypto/src/tls/UserMappingType.cs | 13 + crypto/src/tls/crypto/CryptoHashAlgorithm.cs | 15 + crypto/src/tls/crypto/CryptoSignatureAlgorithm.cs | 22 + crypto/src/tls/crypto/DHGroup.cs | 46 + crypto/src/tls/crypto/DHStandardGroups.cs | 248 + crypto/src/tls/crypto/Srp6Group.cs | 31 + crypto/src/tls/crypto/Srp6StandardGroups.cs | 159 + crypto/src/tls/crypto/TlsAgreement.cs | 24 + crypto/src/tls/crypto/TlsCertificate.cs | 52 + crypto/src/tls/crypto/TlsCertificateRole.cs | 12 + crypto/src/tls/crypto/TlsCipher.cs | 61 + crypto/src/tls/crypto/TlsCrypto.cs | 181 + crypto/src/tls/crypto/TlsCryptoException.cs | 19 + crypto/src/tls/crypto/TlsCryptoParameters.cs | 49 + crypto/src/tls/crypto/TlsCryptoUtilities.cs | 180 + crypto/src/tls/crypto/TlsDHConfig.cs | 41 + crypto/src/tls/crypto/TlsDHDomain.cs | 12 + crypto/src/tls/crypto/TlsDecodeResult.cs | 19 + crypto/src/tls/crypto/TlsECConfig.cs | 22 + crypto/src/tls/crypto/TlsECDomain.cs | 12 + crypto/src/tls/crypto/TlsEncodeResult.cs | 19 + crypto/src/tls/crypto/TlsHash.cs | 25 + crypto/src/tls/crypto/TlsHashSink.cs | 35 + crypto/src/tls/crypto/TlsHmac.cs | 13 + crypto/src/tls/crypto/TlsMac.cs | 36 + crypto/src/tls/crypto/TlsMacSink.cs | 35 + crypto/src/tls/crypto/TlsNonceGenerator.cs | 12 + crypto/src/tls/crypto/TlsNullNullCipher.cs | 55 + crypto/src/tls/crypto/TlsSecret.cs | 61 + crypto/src/tls/crypto/TlsSigner.cs | 19 + crypto/src/tls/crypto/TlsSrp6Client.cs | 24 + crypto/src/tls/crypto/TlsSrp6Server.cs | 22 + crypto/src/tls/crypto/TlsSrp6VerifierGenerator.cs | 17 + crypto/src/tls/crypto/TlsSrpConfig.cs | 26 + crypto/src/tls/crypto/TlsStreamSigner.cs | 14 + crypto/src/tls/crypto/TlsStreamVerifier.cs | 14 + crypto/src/tls/crypto/TlsVerifier.cs | 20 + crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs | 90 + crypto/src/tls/crypto/impl/AbstractTlsSecret.cs | 87 + crypto/src/tls/crypto/impl/RsaUtilities.cs | 136 + crypto/src/tls/crypto/impl/TlsAeadCipher.cs | 377 ++ crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs | 41 + crypto/src/tls/crypto/impl/TlsBlockCipher.cs | 423 ++ crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs | 40 + crypto/src/tls/crypto/impl/TlsEncryptor.cs | 17 + crypto/src/tls/crypto/impl/TlsImplUtilities.cs | 75 + crypto/src/tls/crypto/impl/TlsNullCipher.cs | 104 + crypto/src/tls/crypto/impl/TlsSuiteHmac.cs | 121 + crypto/src/tls/crypto/impl/TlsSuiteMac.cs | 33 + .../src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs | 124 + .../impl/bc/BcDefaultTlsCredentialedAgreement.cs | 112 + .../impl/bc/BcDefaultTlsCredentialedDecryptor.cs | 139 + .../impl/bc/BcDefaultTlsCredentialedSigner.cs | 85 + crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs | 112 + .../src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs | 54 + .../src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs | 49 + crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs | 484 ++ crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs | 655 +++ crypto/src/tls/crypto/impl/bc/BcTlsDH.cs | 43 + crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs | 119 + crypto/src/tls/crypto/impl/bc/BcTlsDsaSigner.cs | 29 + crypto/src/tls/crypto/impl/bc/BcTlsDsaVerifier.cs | 29 + crypto/src/tls/crypto/impl/bc/BcTlsDssSigner.cs | 54 + crypto/src/tls/crypto/impl/bc/BcTlsDssVerifier.cs | 47 + crypto/src/tls/crypto/impl/bc/BcTlsECDH.cs | 39 + crypto/src/tls/crypto/impl/bc/BcTlsECDomain.cs | 125 + .../src/tls/crypto/impl/bc/BcTlsECDsa13Signer.cs | 47 + .../src/tls/crypto/impl/bc/BcTlsECDsa13Verifier.cs | 41 + crypto/src/tls/crypto/impl/bc/BcTlsECDsaSigner.cs | 29 + .../src/tls/crypto/impl/bc/BcTlsECDsaVerifier.cs | 29 + .../src/tls/crypto/impl/bc/BcTlsEd25519Signer.cs | 32 + .../src/tls/crypto/impl/bc/BcTlsEd25519Verifier.cs | 33 + crypto/src/tls/crypto/impl/bc/BcTlsEd448Signer.cs | 32 + .../src/tls/crypto/impl/bc/BcTlsEd448Verifier.cs | 33 + crypto/src/tls/crypto/impl/bc/BcTlsHash.cs | 49 + crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs | 55 + .../src/tls/crypto/impl/bc/BcTlsNonceGenerator.cs | 24 + crypto/src/tls/crypto/impl/bc/BcTlsRsaEncryptor.cs | 47 + crypto/src/tls/crypto/impl/bc/BcTlsRsaPssSigner.cs | 44 + .../src/tls/crypto/impl/bc/BcTlsRsaPssVerifier.cs | 45 + crypto/src/tls/crypto/impl/bc/BcTlsRsaSigner.cs | 71 + crypto/src/tls/crypto/impl/bc/BcTlsRsaVerifier.cs | 52 + crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs | 242 + crypto/src/tls/crypto/impl/bc/BcTlsSigner.cs | 33 + crypto/src/tls/crypto/impl/bc/BcTlsSrp6Client.cs | 36 + crypto/src/tls/crypto/impl/bc/BcTlsSrp6Server.cs | 36 + .../crypto/impl/bc/BcTlsSrp6VerifierGenerator.cs | 23 + crypto/src/tls/crypto/impl/bc/BcTlsStreamSigner.cs | 36 + .../src/tls/crypto/impl/bc/BcTlsStreamVerifier.cs | 31 + crypto/src/tls/crypto/impl/bc/BcTlsVerifier.cs | 33 + .../tls/crypto/impl/bc/BcVerifyingStreamSigner.cs | 48 + crypto/src/tls/crypto/impl/bc/BcX25519.cs | 54 + crypto/src/tls/crypto/impl/bc/BcX25519Domain.cs | 20 + crypto/src/tls/crypto/impl/bc/BcX448.cs | 54 + crypto/src/tls/crypto/impl/bc/BcX448Domain.cs | 20 + crypto/test/UnitTests.csproj | 42 + crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs | 820 +++ .../test/src/tls/test/ByteQueueInputStreamTest.cs | 134 + crypto/test/src/tls/test/CertChainUtilities.cs | 123 + crypto/test/src/tls/test/DtlsProtocolTest.cs | 102 + crypto/test/src/tls/test/DtlsPskProtocolTest.cs | 102 + crypto/test/src/tls/test/DtlsTestCase.cs | 164 + crypto/test/src/tls/test/DtlsTestClientProtocol.cs | 27 + crypto/test/src/tls/test/DtlsTestServerProtocol.cs | 16 + crypto/test/src/tls/test/DtlsTestSuite.cs | 260 + .../test/src/tls/test/LoggingDatagramTransport.cs | 87 + .../test/src/tls/test/MockDatagramAssociation.cs | 107 + crypto/test/src/tls/test/MockDtlsClient.cs | 170 + crypto/test/src/tls/test/MockDtlsServer.cs | 146 + crypto/test/src/tls/test/MockPskDtlsClient.cs | 161 + crypto/test/src/tls/test/MockPskDtlsServer.cs | 113 + crypto/test/src/tls/test/MockPskTlsClient.cs | 186 + crypto/test/src/tls/test/MockPskTlsServer.cs | 122 + crypto/test/src/tls/test/MockSrpTlsClient.cs | 182 + crypto/test/src/tls/test/MockSrpTlsServer.cs | 143 + crypto/test/src/tls/test/MockTlsClient.cs | 190 + crypto/test/src/tls/test/MockTlsServer.cs | 149 + crypto/test/src/tls/test/NetworkStream.cs | 101 + crypto/test/src/tls/test/PipedStream.cs | 134 + crypto/test/src/tls/test/PrfTest.cs | 97 + crypto/test/src/tls/test/PskTlsClientTest.cs | 113 + crypto/test/src/tls/test/PskTlsServerTest.cs | 82 + crypto/test/src/tls/test/TlsClientTest.cs | 97 + .../src/tls/test/TlsProtocolNonBlockingTest.cs | 126 + crypto/test/src/tls/test/TlsProtocolTest.cs | 75 + crypto/test/src/tls/test/TlsPskProtocolTest.cs | 75 + crypto/test/src/tls/test/TlsServerTest.cs | 82 + crypto/test/src/tls/test/TlsSrpProtocolTest.cs | 75 + crypto/test/src/tls/test/TlsTestCase.cs | 182 + crypto/test/src/tls/test/TlsTestClientImpl.cs | 370 ++ crypto/test/src/tls/test/TlsTestClientProtocol.cs | 32 + crypto/test/src/tls/test/TlsTestConfig.cs | 128 + crypto/test/src/tls/test/TlsTestServerImpl.cs | 310 ++ crypto/test/src/tls/test/TlsTestServerProtocol.cs | 22 + crypto/test/src/tls/test/TlsTestSuite.cs | 300 ++ crypto/test/src/tls/test/TlsTestUtilities.cs | 412 ++ crypto/test/src/tls/test/TlsUtilitiesTest.cs | 66 + .../src/tls/test/UnreliableDatagramTransport.cs | 79 + 313 files changed, 45112 insertions(+) create mode 100644 crypto/src/tls/AbstractTlsClient.cs create mode 100644 crypto/src/tls/AbstractTlsContext.cs create mode 100644 crypto/src/tls/AbstractTlsKeyExchange.cs create mode 100644 crypto/src/tls/AbstractTlsKeyExchangeFactory.cs create mode 100644 crypto/src/tls/AbstractTlsPeer.cs create mode 100644 crypto/src/tls/AbstractTlsServer.cs create mode 100644 crypto/src/tls/AlertDescription.cs create mode 100644 crypto/src/tls/AlertLevel.cs create mode 100644 crypto/src/tls/BasicTlsPskIdentity.cs create mode 100644 crypto/src/tls/BasicTlsSrpIdentity.cs create mode 100644 crypto/src/tls/ByteQueue.cs create mode 100644 crypto/src/tls/ByteQueueInputStream.cs create mode 100644 crypto/src/tls/ByteQueueOutputStream.cs create mode 100644 crypto/src/tls/CachedInformationType.cs create mode 100644 crypto/src/tls/CertChainType.cs create mode 100644 crypto/src/tls/Certificate.cs create mode 100644 crypto/src/tls/CertificateEntry.cs create mode 100644 crypto/src/tls/CertificateRequest.cs create mode 100644 crypto/src/tls/CertificateStatus.cs create mode 100644 crypto/src/tls/CertificateStatusRequest.cs create mode 100644 crypto/src/tls/CertificateStatusRequestItemV2.cs create mode 100644 crypto/src/tls/CertificateStatusType.cs create mode 100644 crypto/src/tls/CertificateType.cs create mode 100644 crypto/src/tls/CertificateUrl.cs create mode 100644 crypto/src/tls/ChangeCipherSpec.cs create mode 100644 crypto/src/tls/ChannelBinding.cs create mode 100644 crypto/src/tls/CipherSuite.cs create mode 100644 crypto/src/tls/CipherType.cs create mode 100644 crypto/src/tls/ClientAuthenticationType.cs create mode 100644 crypto/src/tls/ClientCertificateType.cs create mode 100644 crypto/src/tls/ClientHello.cs create mode 100644 crypto/src/tls/CombinedHash.cs create mode 100644 crypto/src/tls/CompressionMethod.cs create mode 100644 crypto/src/tls/ConnectionEnd.cs create mode 100644 crypto/src/tls/ContentType.cs create mode 100644 crypto/src/tls/DatagramReceiver.cs create mode 100644 crypto/src/tls/DatagramSender.cs create mode 100644 crypto/src/tls/DatagramTransport.cs create mode 100644 crypto/src/tls/DefaultTlsClient.cs create mode 100644 crypto/src/tls/DefaultTlsCredentialedSigner.cs create mode 100644 crypto/src/tls/DefaultTlsDHGroupVerifier.cs create mode 100644 crypto/src/tls/DefaultTlsHeartbeat.cs create mode 100644 crypto/src/tls/DefaultTlsKeyExchangeFactory.cs create mode 100644 crypto/src/tls/DefaultTlsServer.cs create mode 100644 crypto/src/tls/DefaultTlsSrpConfigVerifier.cs create mode 100644 crypto/src/tls/DeferredHash.cs create mode 100644 crypto/src/tls/DigestInputBuffer.cs create mode 100644 crypto/src/tls/DigitallySigned.cs create mode 100644 crypto/src/tls/DtlsClientProtocol.cs create mode 100644 crypto/src/tls/DtlsEpoch.cs create mode 100644 crypto/src/tls/DtlsHandshakeRetransmit.cs create mode 100644 crypto/src/tls/DtlsProtocol.cs create mode 100644 crypto/src/tls/DtlsReassembler.cs create mode 100644 crypto/src/tls/DtlsRecordLayer.cs create mode 100644 crypto/src/tls/DtlsReliableHandshake.cs create mode 100644 crypto/src/tls/DtlsReplayWindow.cs create mode 100644 crypto/src/tls/DtlsRequest.cs create mode 100644 crypto/src/tls/DtlsServerProtocol.cs create mode 100644 crypto/src/tls/DtlsTransport.cs create mode 100644 crypto/src/tls/DtlsVerifier.cs create mode 100644 crypto/src/tls/ECCurveType.cs create mode 100644 crypto/src/tls/ECPointFormat.cs create mode 100644 crypto/src/tls/EncryptionAlgorithm.cs create mode 100644 crypto/src/tls/ExporterLabel.cs create mode 100644 crypto/src/tls/ExtensionType.cs create mode 100644 crypto/src/tls/HandshakeMessageInput.cs create mode 100644 crypto/src/tls/HandshakeMessageOutput.cs create mode 100644 crypto/src/tls/HandshakeType.cs create mode 100644 crypto/src/tls/HashAlgorithm.cs create mode 100644 crypto/src/tls/HeartbeatExtension.cs create mode 100644 crypto/src/tls/HeartbeatMessage.cs create mode 100644 crypto/src/tls/HeartbeatMessageType.cs create mode 100644 crypto/src/tls/HeartbeatMode.cs create mode 100644 crypto/src/tls/IdentifierType.cs create mode 100644 crypto/src/tls/KeyExchangeAlgorithm.cs create mode 100644 crypto/src/tls/KeyShareEntry.cs create mode 100644 crypto/src/tls/KeyUpdateRequest.cs create mode 100644 crypto/src/tls/MacAlgorithm.cs create mode 100644 crypto/src/tls/MaxFragmentLength.cs create mode 100644 crypto/src/tls/NameType.cs create mode 100644 crypto/src/tls/NamedGroup.cs create mode 100644 crypto/src/tls/NamedGroupRole.cs create mode 100644 crypto/src/tls/NewSessionTicket.cs create mode 100644 crypto/src/tls/OcspStatusRequest.cs create mode 100644 crypto/src/tls/OfferedPsks.cs create mode 100644 crypto/src/tls/PrfAlgorithm.cs create mode 100644 crypto/src/tls/ProtocolName.cs create mode 100644 crypto/src/tls/ProtocolVersion.cs create mode 100644 crypto/src/tls/PskIdentity.cs create mode 100644 crypto/src/tls/PskKeyExchangeMode.cs create mode 100644 crypto/src/tls/PskTlsClient.cs create mode 100644 crypto/src/tls/PskTlsServer.cs create mode 100644 crypto/src/tls/RecordFormat.cs create mode 100644 crypto/src/tls/RecordPreview.cs create mode 100644 crypto/src/tls/RecordStream.cs create mode 100644 crypto/src/tls/SecurityParameters.cs create mode 100644 crypto/src/tls/ServerHello.cs create mode 100644 crypto/src/tls/ServerName.cs create mode 100644 crypto/src/tls/ServerNameList.cs create mode 100644 crypto/src/tls/ServerOnlyTlsAuthentication.cs create mode 100644 crypto/src/tls/ServerSrpParams.cs create mode 100644 crypto/src/tls/SessionParameters.cs create mode 100644 crypto/src/tls/SignatureAlgorithm.cs create mode 100644 crypto/src/tls/SignatureAndHashAlgorithm.cs create mode 100644 crypto/src/tls/SignatureScheme.cs create mode 100644 crypto/src/tls/SimulatedTlsSrpIdentityManager.cs create mode 100644 crypto/src/tls/SrpTlsClient.cs create mode 100644 crypto/src/tls/SrpTlsServer.cs create mode 100644 crypto/src/tls/SrtpProtectionProfile.cs create mode 100644 crypto/src/tls/Ssl3Utilities.cs create mode 100644 crypto/src/tls/SupplementalDataEntry.cs create mode 100644 crypto/src/tls/SupplementalDataType.cs create mode 100644 crypto/src/tls/Timeout.cs create mode 100644 crypto/src/tls/TlsAuthentication.cs create mode 100644 crypto/src/tls/TlsClient.cs create mode 100644 crypto/src/tls/TlsClientContext.cs create mode 100644 crypto/src/tls/TlsClientContextImpl.cs create mode 100644 crypto/src/tls/TlsClientProtocol.cs create mode 100644 crypto/src/tls/TlsCloseable.cs create mode 100644 crypto/src/tls/TlsContext.cs create mode 100644 crypto/src/tls/TlsCredentialedAgreement.cs create mode 100644 crypto/src/tls/TlsCredentialedDecryptor.cs create mode 100644 crypto/src/tls/TlsCredentialedSigner.cs create mode 100644 crypto/src/tls/TlsCredentials.cs create mode 100644 crypto/src/tls/TlsDHGroupVerifier.cs create mode 100644 crypto/src/tls/TlsDHKeyExchange.cs create mode 100644 crypto/src/tls/TlsDHUtilities.cs create mode 100644 crypto/src/tls/TlsDHanonKeyExchange.cs create mode 100644 crypto/src/tls/TlsDheKeyExchange.cs create mode 100644 crypto/src/tls/TlsECDHKeyExchange.cs create mode 100644 crypto/src/tls/TlsECDHanonKeyExchange.cs create mode 100644 crypto/src/tls/TlsECDheKeyExchange.cs create mode 100644 crypto/src/tls/TlsEccUtilities.cs create mode 100644 crypto/src/tls/TlsException.cs create mode 100644 crypto/src/tls/TlsExtensionsUtilities.cs create mode 100644 crypto/src/tls/TlsFatalAlert.cs create mode 100644 crypto/src/tls/TlsFatalAlertReceived.cs create mode 100644 crypto/src/tls/TlsHandshakeHash.cs create mode 100644 crypto/src/tls/TlsHeartbeat.cs create mode 100644 crypto/src/tls/TlsKeyExchange.cs create mode 100644 crypto/src/tls/TlsKeyExchangeFactory.cs create mode 100644 crypto/src/tls/TlsNoCloseNotifyException.cs create mode 100644 crypto/src/tls/TlsObjectIdentifiers.cs create mode 100644 crypto/src/tls/TlsPeer.cs create mode 100644 crypto/src/tls/TlsProtocol.cs create mode 100644 crypto/src/tls/TlsPskIdentity.cs create mode 100644 crypto/src/tls/TlsPskIdentityManager.cs create mode 100644 crypto/src/tls/TlsPskKeyExchange.cs create mode 100644 crypto/src/tls/TlsRsaKeyExchange.cs create mode 100644 crypto/src/tls/TlsRsaUtilities.cs create mode 100644 crypto/src/tls/TlsServer.cs create mode 100644 crypto/src/tls/TlsServerCertificate.cs create mode 100644 crypto/src/tls/TlsServerCertificateImpl.cs create mode 100644 crypto/src/tls/TlsServerContext.cs create mode 100644 crypto/src/tls/TlsServerContextImpl.cs create mode 100644 crypto/src/tls/TlsServerProtocol.cs create mode 100644 crypto/src/tls/TlsSession.cs create mode 100644 crypto/src/tls/TlsSessionImpl.cs create mode 100644 crypto/src/tls/TlsSrpConfigVerifier.cs create mode 100644 crypto/src/tls/TlsSrpIdentity.cs create mode 100644 crypto/src/tls/TlsSrpIdentityManager.cs create mode 100644 crypto/src/tls/TlsSrpKeyExchange.cs create mode 100644 crypto/src/tls/TlsSrpLoginParameters.cs create mode 100644 crypto/src/tls/TlsSrpUtilities.cs create mode 100644 crypto/src/tls/TlsSrtpUtilities.cs create mode 100644 crypto/src/tls/TlsStream.cs create mode 100644 crypto/src/tls/TlsTimeoutException.cs create mode 100644 crypto/src/tls/TlsUtilities.cs create mode 100644 crypto/src/tls/TrustedAuthority.cs create mode 100644 crypto/src/tls/UrlAndHash.cs create mode 100644 crypto/src/tls/UseSrtpData.cs create mode 100644 crypto/src/tls/UserMappingType.cs create mode 100644 crypto/src/tls/crypto/CryptoHashAlgorithm.cs create mode 100644 crypto/src/tls/crypto/CryptoSignatureAlgorithm.cs create mode 100644 crypto/src/tls/crypto/DHGroup.cs create mode 100644 crypto/src/tls/crypto/DHStandardGroups.cs create mode 100644 crypto/src/tls/crypto/Srp6Group.cs create mode 100644 crypto/src/tls/crypto/Srp6StandardGroups.cs create mode 100644 crypto/src/tls/crypto/TlsAgreement.cs create mode 100644 crypto/src/tls/crypto/TlsCertificate.cs create mode 100644 crypto/src/tls/crypto/TlsCertificateRole.cs create mode 100644 crypto/src/tls/crypto/TlsCipher.cs create mode 100644 crypto/src/tls/crypto/TlsCrypto.cs create mode 100644 crypto/src/tls/crypto/TlsCryptoException.cs create mode 100644 crypto/src/tls/crypto/TlsCryptoParameters.cs create mode 100644 crypto/src/tls/crypto/TlsCryptoUtilities.cs create mode 100644 crypto/src/tls/crypto/TlsDHConfig.cs create mode 100644 crypto/src/tls/crypto/TlsDHDomain.cs create mode 100644 crypto/src/tls/crypto/TlsDecodeResult.cs create mode 100644 crypto/src/tls/crypto/TlsECConfig.cs create mode 100644 crypto/src/tls/crypto/TlsECDomain.cs create mode 100644 crypto/src/tls/crypto/TlsEncodeResult.cs create mode 100644 crypto/src/tls/crypto/TlsHash.cs create mode 100644 crypto/src/tls/crypto/TlsHashSink.cs create mode 100644 crypto/src/tls/crypto/TlsHmac.cs create mode 100644 crypto/src/tls/crypto/TlsMac.cs create mode 100644 crypto/src/tls/crypto/TlsMacSink.cs create mode 100644 crypto/src/tls/crypto/TlsNonceGenerator.cs create mode 100644 crypto/src/tls/crypto/TlsNullNullCipher.cs create mode 100644 crypto/src/tls/crypto/TlsSecret.cs create mode 100644 crypto/src/tls/crypto/TlsSigner.cs create mode 100644 crypto/src/tls/crypto/TlsSrp6Client.cs create mode 100644 crypto/src/tls/crypto/TlsSrp6Server.cs create mode 100644 crypto/src/tls/crypto/TlsSrp6VerifierGenerator.cs create mode 100644 crypto/src/tls/crypto/TlsSrpConfig.cs create mode 100644 crypto/src/tls/crypto/TlsStreamSigner.cs create mode 100644 crypto/src/tls/crypto/TlsStreamVerifier.cs create mode 100644 crypto/src/tls/crypto/TlsVerifier.cs create mode 100644 crypto/src/tls/crypto/impl/AbstractTlsCrypto.cs create mode 100644 crypto/src/tls/crypto/impl/AbstractTlsSecret.cs create mode 100644 crypto/src/tls/crypto/impl/RsaUtilities.cs create mode 100644 crypto/src/tls/crypto/impl/TlsAeadCipher.cs create mode 100644 crypto/src/tls/crypto/impl/TlsAeadCipherImpl.cs create mode 100644 crypto/src/tls/crypto/impl/TlsBlockCipher.cs create mode 100644 crypto/src/tls/crypto/impl/TlsBlockCipherImpl.cs create mode 100644 crypto/src/tls/crypto/impl/TlsEncryptor.cs create mode 100644 crypto/src/tls/crypto/impl/TlsImplUtilities.cs create mode 100644 crypto/src/tls/crypto/impl/TlsNullCipher.cs create mode 100644 crypto/src/tls/crypto/impl/TlsSuiteHmac.cs create mode 100644 crypto/src/tls/crypto/impl/TlsSuiteMac.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcChaCha20Poly1305.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedAgreement.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedDecryptor.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcDefaultTlsCredentialedSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcSsl3Hmac.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsAeadCipherImpl.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsBlockCipherImpl.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsCertificate.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsCrypto.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsDH.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsDHDomain.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsDsaSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsDsaVerifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsDssSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsDssVerifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsECDH.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsECDomain.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Signer.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsECDsa13Verifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsECDsaSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsECDsaVerifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsEd25519Signer.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsEd25519Verifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsEd448Signer.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsEd448Verifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsHash.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsHmac.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsNonceGenerator.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsRsaEncryptor.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsRsaPssSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsRsaPssVerifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsRsaSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsRsaVerifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsSecret.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsSrp6Client.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsSrp6Server.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsSrp6VerifierGenerator.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsStreamSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsStreamVerifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcTlsVerifier.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcVerifyingStreamSigner.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcX25519.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcX25519Domain.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcX448.cs create mode 100644 crypto/src/tls/crypto/impl/bc/BcX448Domain.cs create mode 100644 crypto/test/src/tls/crypto/test/BcTlsCryptoTest.cs create mode 100644 crypto/test/src/tls/test/ByteQueueInputStreamTest.cs create mode 100644 crypto/test/src/tls/test/CertChainUtilities.cs create mode 100644 crypto/test/src/tls/test/DtlsProtocolTest.cs create mode 100644 crypto/test/src/tls/test/DtlsPskProtocolTest.cs create mode 100644 crypto/test/src/tls/test/DtlsTestCase.cs create mode 100644 crypto/test/src/tls/test/DtlsTestClientProtocol.cs create mode 100644 crypto/test/src/tls/test/DtlsTestServerProtocol.cs create mode 100644 crypto/test/src/tls/test/DtlsTestSuite.cs create mode 100644 crypto/test/src/tls/test/LoggingDatagramTransport.cs create mode 100644 crypto/test/src/tls/test/MockDatagramAssociation.cs create mode 100644 crypto/test/src/tls/test/MockDtlsClient.cs create mode 100644 crypto/test/src/tls/test/MockDtlsServer.cs create mode 100644 crypto/test/src/tls/test/MockPskDtlsClient.cs create mode 100644 crypto/test/src/tls/test/MockPskDtlsServer.cs create mode 100644 crypto/test/src/tls/test/MockPskTlsClient.cs create mode 100644 crypto/test/src/tls/test/MockPskTlsServer.cs create mode 100644 crypto/test/src/tls/test/MockSrpTlsClient.cs create mode 100644 crypto/test/src/tls/test/MockSrpTlsServer.cs create mode 100644 crypto/test/src/tls/test/MockTlsClient.cs create mode 100644 crypto/test/src/tls/test/MockTlsServer.cs create mode 100644 crypto/test/src/tls/test/NetworkStream.cs create mode 100644 crypto/test/src/tls/test/PipedStream.cs create mode 100644 crypto/test/src/tls/test/PrfTest.cs create mode 100644 crypto/test/src/tls/test/PskTlsClientTest.cs create mode 100644 crypto/test/src/tls/test/PskTlsServerTest.cs create mode 100644 crypto/test/src/tls/test/TlsClientTest.cs create mode 100644 crypto/test/src/tls/test/TlsProtocolNonBlockingTest.cs create mode 100644 crypto/test/src/tls/test/TlsProtocolTest.cs create mode 100644 crypto/test/src/tls/test/TlsPskProtocolTest.cs create mode 100644 crypto/test/src/tls/test/TlsServerTest.cs create mode 100644 crypto/test/src/tls/test/TlsSrpProtocolTest.cs create mode 100644 crypto/test/src/tls/test/TlsTestCase.cs create mode 100644 crypto/test/src/tls/test/TlsTestClientImpl.cs create mode 100644 crypto/test/src/tls/test/TlsTestClientProtocol.cs create mode 100644 crypto/test/src/tls/test/TlsTestConfig.cs create mode 100644 crypto/test/src/tls/test/TlsTestServerImpl.cs create mode 100644 crypto/test/src/tls/test/TlsTestServerProtocol.cs create mode 100644 crypto/test/src/tls/test/TlsTestSuite.cs create mode 100644 crypto/test/src/tls/test/TlsTestUtilities.cs create mode 100644 crypto/test/src/tls/test/TlsUtilitiesTest.cs create mode 100644 crypto/test/src/tls/test/UnreliableDatagramTransport.cs 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj index 1ba78a83c..97fcb3a5b 100644 --- a/crypto/crypto.csproj +++ b/crypto/crypto.csproj @@ -7658,6 +7658,1336 @@ SubType = "Code" BuildAction = "Compile" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Base class for a TLS client. + 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) + { + } + + /// + 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; + } + + /// + 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); + } + + /// + public virtual TlsPskIdentity GetPskIdentity() + { + return null; + } + + /// + 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)); + } + + /// an of (or null). + protected virtual IList GetMultiCertStatusRequest() + { + return null; + } + + protected virtual IList GetSniServerNames() + { + return null; + } + + /// The default implementation calls this to determine which named + /// groups to include in the supported_groups extension for the ClientHello. + /// The named group roles for which there should + /// be at least one supported group. By default this is inferred from the offered cipher suites and signature + /// algorithms. + /// an of . See for group constants. + /// + 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; + } + + /// + 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; + } + + /// + 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]); + } + + /// + public virtual void NotifyServerVersion(ProtocolVersion serverVersion) + { + } + + public virtual void NotifySessionToResume(TlsSession session) + { + } + + public virtual void NotifySessionID(byte[] sessionID) + { + } + + public virtual void NotifySelectedCipherSuite(int selectedCipherSuite) + { + } + + /// + 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); + } + } + + /// + public virtual void ProcessServerSupplementalData(IList serverSupplementalData) + { + if (serverSupplementalData != null) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + public abstract TlsAuthentication GetAuthentication(); + + /// + public virtual IList GetClientSupplementalData() + { + return null; + } + + /// + 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); + } + + /// + 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(); + } + + /// + 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 +{ + /// Base class for supporting a TLS key exchange implementation. + 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 +{ + /// Base class for supporting a TLS key exchange factory implementation. + 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 +{ + /// Base class for a TLS client or server. + public abstract class AbstractTlsPeer + : TlsPeer + { + private readonly TlsCrypto m_crypto; + + private volatile TlsCloseable m_closeHandle; + + protected AbstractTlsPeer(TlsCrypto crypto) + { + this.m_crypto = crypto; + } + + /// Get the values that are supported by this peer. + /// + /// WARNING: Mixing DTLS and TLS versions in the returned array is currently NOT supported. Use a separate + /// (sub-)class for each case. + /// + /// an array of supported values. + protected virtual ProtocolVersion[] GetSupportedVersions() + { + // TODO[tls13] Enable TLSv13 by default in due course + return ProtocolVersion.TLSv12.DownTo(ProtocolVersion.TLSv10); + } + + protected abstract int[] GetSupportedCipherSuites(); + + /// + 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(); + + /// + 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; + } + + /// + public virtual void NotifySecureRenegotiation(bool secureRenegotiation) + { + if (!secureRenegotiation) + throw new TlsFatalAlert(AlertDescription.handshake_failure); + } + + /// + 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) + { + } + + /// + 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 +{ + /// Base class for a TLS server. + 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; + } + + /// + 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 +{ + /// RFC 5246 7.2. + public abstract class AlertDescription + { + /// This message notifies the recipient that the sender will not send any more messages on this + /// connection. + /// + /// 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. + /// + public const short close_notify = 0; + + /// An inappropriate message was received. + /// + /// This alert is always fatal and should never be observed in communication between proper implementations. + /// + public const short unexpected_message = 10; + + /// This alert is returned if a record is received with an incorrect MAC. + /// + /// 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). + /// + public const short bad_record_mac = 20; + + /// + /// 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. + /// + public const short decryption_failed = 21; + + /// 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. + /// + /// This message is always fatal and should never be observed in communication between proper implementations + /// (except when messages were corrupted in the network). + /// + public const short record_overflow = 22; + + /// The decompression function received improper input (e.g., data that would expand to excessive + /// length). + /// + /// This message is always fatal and should never be observed in communication between proper implementations. + /// + public const short decompression_failure = 30; + + /// 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. + /// + /// This is a fatal error. + /// + public const short handshake_failure = 40; + + /// + /// This alert was used in SSLv3 but not any version of TLS. It MUST NOT be sent by compliant implementations. + /// + public const short no_certificate = 41; + + /// A certificate was corrupt, contained signatures that did not verify correctly, etc. + public const short bad_certificate = 42; + + /// A certificate was of an unsupported type. + public const short unsupported_certificate = 43; + + /// A certificate was revoked by its signer. + public const short certificate_revoked = 44; + + /// A certificate has expired or is not currently valid. + public const short certificate_expired = 45; + + /// Some other (unspecified) issue arose in processing the certificate, rendering it unacceptable. + /// + public const short certificate_unknown = 46; + + /// A field in the handshake was out of range or inconsistent with other fields. + /// + /// This message is always fatal. + /// + public const short illegal_parameter = 47; + + /// 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. + /// + /// This message is always fatal. + /// + public const short unknown_ca = 48; + + /// A valid certificate was received, but when access control was applied, the sender decided not to + /// proceed with negotiation. + /// + /// This message is always fatal. + /// + public const short access_denied = 49; + + /// A message could not be decoded because some field was out of the specified range or the length of + /// the message was incorrect. + /// + /// This message is always fatal and should never be observed in communication between proper + /// implementations (except when messages were corrupted in the network). + /// + public const short decode_error = 50; + + /// A handshake cryptographic operation failed, including being unable to correctly verify a signature + /// or validate a Finished message. + /// + /// This message is always fatal. + /// + public const short decrypt_error = 51; + + /// + /// This alert was used in some earlier versions of TLS. It MUST NOT be sent by compliant implementations. + /// + public const short export_restriction = 60; + + /// The protocol version the client has attempted to negotiate is recognized but not supported. + /// + /// + /// (For example, old protocol versions might be avoided for security reasons.) This message is always fatal. + /// + public const short protocol_version = 70; + + /// Returned instead of handshake_failure when a negotiation has failed specifically because the + /// server requires ciphers more secure than those supported by the client. + /// + /// This message is always fatal. + /// + public const short insufficient_security = 71; + + /// An internal error unrelated to the peer or the correctness of the protocol (such as a memory + /// allocation failure) makes it impossible to continue. + /// + /// This message is always fatal. + /// + public const short internal_error = 80; + + /// This handshake is being canceled for some reason unrelated to a protocol failure. + /// + /// 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. + /// + public const short user_canceled = 90; + + /// Sent by the client in response to a hello request or by the server in response to a client hello + /// after initial handshaking. + /// + /// 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. + /// + public const short no_renegotiation = 100; + + /// Sent by clients that receive an extended server hello containing an extension that they did not + /// put in the corresponding client hello. + /// + /// This message is always fatal. + /// + public const short unsupported_extension = 110; + + /* + * RFC 3546 + */ + + /// 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). + /// + /// 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. + /// + public const short certificate_unobtainable = 111; + + /// This alert is sent by servers that receive a server_name extension request, but do not recognize + /// the server name. + /// + /// This message MAY be fatal. + /// + public const short unrecognized_name = 112; + + /// This alert is sent by clients that receive an invalid certificate status response (see Section 3.6 + /// ). + /// + /// This message is always fatal. + /// + public const short bad_certificate_status_response = 113; + + /// This alert is sent by servers when a certificate hash does not match a client provided + /// certificate_hash. + /// + /// This message is always fatal. + /// + public const short bad_certificate_hash_value = 114; + + /* + * RFC 4279 + */ + + /// If the server does not recognize the PSK identity, it MAY respond with an "unknown_psk_identity" + /// alert message. + public const short unknown_psk_identity = 115; + + /* + * RFC 7301 + */ + + /// 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. + public const short no_application_protocol = 120; + + /* + * RFC 7507 + */ + + /// 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[..]. + public const short inappropriate_fallback = 86; + + /* + * RFC 8446 + */ + + /// 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. + public const short missing_extension = 109; + + /// Sent by servers when a client certificate is desired but none was provided by the client. + /// + 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 +{ + /// RFC 5246 7.2 + 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 +{ + /// A basic PSK Identity holder. + 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 +{ + /// A basic SRP Identity holder. + 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 +{ + /// A queue for bytes. This file could be more optimized. + public sealed class ByteQueue + { + /// The smallest number which can be written as 2^x which is bigger than i. + public static int NextTwoPow(int i) + { + /* + * This code is based of a lot of code I found on the Internet which mostly + * referenced a book called "Hacking delight". + */ + i |= i >> 1; + i |= i >> 2; + i |= i >> 4; + i |= i >> 8; + i |= i >> 16; + return i + 1; + } + + /// The buffer where we store our data. + private byte[] m_databuf; + + /// How many bytes at the beginning of the buffer are skipped. + private int m_skipped = 0; + + /// How many bytes in the buffer are valid data. + 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; + } + + /// Add some data to our buffer. + /// A byte-array to read data from. + /// How many bytes to skip at the beginning of the array. + /// How many bytes to read from the array. + 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; + } + + /// The number of bytes which are available in this buffer. + public int Available + { + get { return m_available; } + } + + /// Copy some bytes from the beginning of the data to the provided . + /// The to copy the bytes to. + /// How many bytes to copy. + 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); + } + + /// Read data from the buffer. + /// The buffer where the read data will be copied to. + /// How many bytes to skip at the beginning of buf. + /// How many bytes to read at all. + /// How many bytes from our data to skip. + 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); + } + + /// Return a over some bytes at the beginning of the data. + /// + /// How many bytes will be readable. + /// A over the data. + 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); + } + + /// Remove some bytes from our data from the beginning. + /// How many bytes to remove. + 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; + } + + /// Remove data from the buffer. + /// The buffer where the removed data will be copied to. + /// How many bytes to skip at the beginning of buf. + /// How many bytes to read at all. + /// How many bytes from our data to skip. + 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 +{ + /// OutputStream based on a ByteQueue implementation. + 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 +{ + /// Implementation of the RFC 3546 3.3. CertChainType. + 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 +{ + /// Parsing and encoding of a Certificate struct from RFC 4346. + /// + ///
+    /// opaque ASN.1Cert<2^24-1>;
+    /// struct {
+    ///   ASN.1Cert certificate_list<0..2^24-1>;
+    /// } Certificate;
+    /// 
+ ///
+ 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); + } + + /// an array of representing a certificate chain. + 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; } + } + + /// true if this certificate chain contains no certificates, or false otherwise. + /// + public bool IsEmpty + { + get { return m_certificateEntryList.Length == 0; } + } + + /// Encode this to a , and optionally calculate the + /// "end point hash" (per RFC 5929's tls-server-end-point binding). + /// the of the current connection. + /// the to encode to. + /// the to write the "end point hash" to (or null). + /// + /// + 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); + } + } + } + + /// Parse a from a . + /// the to apply during parsing. + /// the of the current connection. + /// the to parse from. + /// the to write the "end point hash" to (or null). + /// + /// a object. + /// + 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 +{ + /// Parsing and encoding of a CertificateRequest struct from RFC 4346. + /// + ///
+    /// struct {
+    ///   ClientCertificateType certificate_types<1..2^8-1>;
+    ///   DistinguishedName certificate_authorities<3..2^16-1>;
+    /// } CertificateRequest;
+    /// 
+ /// Updated for RFC 5246: + ///
+    /// struct {
+    ///   ClientCertificateType certificate_types <1..2 ^ 8 - 1>;
+    ///   SignatureAndHashAlgorithm supported_signature_algorithms <2 ^ 16 - 1>;
+    ///   DistinguishedName certificate_authorities <0..2 ^ 16 - 1>;
+    /// } CertificateRequest;
+    /// 
+ /// Revised for RFC 8446: + ///
+    /// struct {
+    ///   opaque certificate_request_context <0..2 ^ 8 - 1>;
+    ///   Extension extensions <2..2 ^ 16 - 1>;
+    /// } CertificateRequest;
+    /// 
+ ///
+ /// + /// + public sealed class CertificateRequest + { + /// + 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; + + /// see for valid constants. + /// + /// an of . + public CertificateRequest(short[] certificateTypes, IList supportedSignatureAlgorithms, + IList certificateAuthorities) + : this(null, certificateTypes, supportedSignatureAlgorithms, null, certificateAuthorities) + { + } + + // TODO[tls13] Prefer to manage the certificateRequestContext internally only? + /// + 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); + } + + /// an array of certificate types + /// + public short[] CertificateTypes + { + get { return m_certificateTypes; } + } + + /// an of (or null before TLS 1.2). + /// + public IList SupportedSignatureAlgorithms + { + get { return m_supportedSignatureAlgorithms; } + } + + /// an optional of . May be non-null from + /// TLS 1.3 onwards. + public IList SupportedSignatureAlgorithmsCert + { + get { return m_supportedSignatureAlgorithmsCert; } + } + + /// an of . + public IList CertificateAuthorities + { + get { return m_certificateAuthorities; } + } + + public bool HasCertificateRequestContext(byte[] certificateRequestContext) + { + return Arrays.AreEqual(m_certificateRequestContext, certificateRequestContext); + } + + /// Encode this to a . + /// the of the current connection. + /// the to encode to. + /// + 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); + } + } + } + + /// Parse a from a + /// the of the current connection. + /// the to parse from. + /// a object. + /// + 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; + } + } + + /// an of (possibly null) . + public IList OcspResponseList + { + get + { + if (!IsCorrectType(CertificateStatusType.ocsp_multi, m_response)) + throw new InvalidOperationException("'response' is not an OCSPResponseList"); + + return (IList)m_response; + } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + } + + /// Parse a from a . + /// the of the current connection. + /// the to parse from. + /// a object. + /// + 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; + } + + /// + 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 +{ + /// Implementation of the RFC 3546 3.6. CertificateStatusRequest. + 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; + } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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 +{ + /// Implementation of the RFC 6961 2.2. CertificateStatusRequestItemV2. + 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; + } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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 +{ + /// RFC 6091 + 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 +{ + /// RFC 3546 3.3 + public sealed class CertificateUrl + { + private readonly short m_type; + private readonly IList m_urlAndHashList; + + /// see for valid constants. + /// an of . + 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; + } + + /// + public short Type + { + get { return m_type; } + } + + /// an of . + public IList UrlAndHashList + { + get { return m_urlAndHashList; } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + + /// Parse a from a . + /// the of the current connection. + /// the to parse from. + /// a object. + /// + 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 +{ + /// RFC 5056 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the + /// particular values (e.g.serialization). + /// + 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 +{ + /// RFC 2246 A.5 + 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 +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the + /// particular values (e.g. serialization). + /// + 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; } + } + + /// Encode this to a . + /// the of the current connection. + /// the to encode to. + /// + 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); + } + + /// Parse a from a . + /// the to parse from. + /// for DTLS this should be non-null; the input is copied to this + /// , minus the cookie field. + /// a object. + /// + 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); + } + } + + /// + 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 +{ + /// A combined hash, which implements md5(m) || sha1(m). + 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 +{ + /// RFC 2246 6.1 + 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 +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the + /// particular values(e.g.serialization). + /// + 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 +{ + /// RFC 2246 6.2.1 + 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 + { + /// + int GetReceiveLimit(); + + /// + 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 + { + /// + int GetSendLimit(); + + /// + 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 +{ + /// Base interface for an object sending and receiving DTLS data. + 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 +{ + /// Container class for generating signatures that carries the signature type, parameters, public key + /// certificate and public key's associated signer object. + 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; + + /// Accept named groups and various standard DH groups with 'P' at least + /// bits. + public DefaultTlsDHGroupVerifier() + : this(DefaultMinimumPrimeBits) + { + } + + /// Accept named groups and various standard DH groups with 'P' at least the specified number of bits. + /// + /// the minimum bitlength of 'P'. + public DefaultTlsDHGroupVerifier(int minimumPrimeBits) + : this(DefaultGroups, minimumPrimeBits) + { + } + + /// Accept named groups and a custom set of group parameters, subject to a minimum bitlength for 'P'. + /// + /// a list of acceptable s. + /// the minimum bitlength of 'P'. + 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) + { + } + + /// + protected virtual TlsCredentialedSigner GetDsaSignerCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + /// + protected virtual TlsCredentialedSigner GetECDsaSignerCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + /// + protected virtual TlsCredentialedDecryptor GetRsaEncryptionCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + /// + 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; + + /// Accept only the group parameters specified in RFC 5054 Appendix A. + public DefaultTlsSrpConfigVerifier() + : this(DefaultGroups) + { + } + + /// Specify a custom set of acceptable group parameters. + /// an of acceptable . + 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 +{ + /// Buffers input until the hash algorithm is determined. + 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; + } + + /// + 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)); + } + + /// + 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; + } + + /// a (or null before TLS 1.2). + public SignatureAndHashAlgorithm Algorithm + { + get { return algorithm; } + } + + public byte[] Signature + { + get { return signature; } + } + + /// Encode this to a . + /// the to encode to. + /// + public void Encode(Stream output) + { + if (algorithm != null) + { + algorithm.Encode(output); + } + TlsUtilities.WriteOpaque16(signature, output); + } + + /// Parse a from a . + /// the of the current connection. + /// the to parse from. + /// a object. + /// + 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() + { + } + + /// + 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); + } + + /// + 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); + } + + /// + protected virtual byte[] GenerateCertificateVerify(ClientHandshakeState state, + DigitallySigned certificateVerify) + { + MemoryStream buf = new MemoryStream(); + certificateVerify.Encode(buf); + return buf.ToArray(); + } + + /// + 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(); + } + + /// + 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; + } + } + + /// + 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); + } + + /// + 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); + } + + /// + 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; + } + + /// + 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); + } + + /// + protected virtual void ProcessServerCertificate(ClientHandshakeState state, byte[] body) + { + state.authentication = TlsUtilities.ReceiveServerCertificate(state.clientContext, state.client, + new MemoryStream(body, false)); + } + + /// + 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); + } + } + + /// + protected virtual void ProcessServerKeyExchange(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + state.keyExchange.ProcessServerKeyExchange(buf); + TlsProtocol.AssertEmpty(buf); + } + + /// + protected virtual void ProcessServerSupplementalData(ClientHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + IList serverSupplementalData = TlsProtocol.ReadSupplementalDataMessage(buf); + state.client.ProcessServerSupplementalData(serverSupplementalData); + } + + /// + 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); + } + + /// + 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; + } + + /// + 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 + { + /// + 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() + { + } + + /// + 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); + } + + /// + 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); + } + } + + /// + 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; + } + + /// + internal static byte[] GenerateCertificate(TlsContext context, Certificate certificate, Stream endPointHash) + { + MemoryStream buf = new MemoryStream(); + certificate.Encode(context, buf, endPointHash); + return buf.ToArray(); + } + + /// + internal static byte[] GenerateSupplementalData(IList supplementalData) + { + MemoryStream buf = new MemoryStream(); + TlsProtocol.WriteSupplementalData(buf, supplementalData); + return buf.ToArray(); + } + + /// + 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; + } + + /// + 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; + + /// + 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); + } + + /// + 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); + } + + /// + 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; + } + } + + /// + public virtual int GetReceiveLimit() + { + return System.Math.Min(m_plaintextLimit, + m_readEpoch.Cipher.GetPlaintextLimit(m_transport.GetReceiveLimit() - RECORD_HEADER_LENGTH)); + } + + /// + public virtual int GetSendLimit() + { + return System.Math.Min(m_plaintextLimit, + m_writeEpoch.Cipher.GetPlaintextLimit(m_transport.GetSendLimit() - RECORD_HEADER_LENGTH)); + } + + /// + 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; + } + + /// + 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); + } + + /// + 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(); + } + } + + /// + 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; + } + } + + /// + 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); + } + + /// + 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 + /// + 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; + } + + /// + 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); + } + + /// + 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). + */ + /// + 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; + + /// + 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); + } + + /// + 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; + } + + /// + 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); + } + + /// + internal byte[] ReceiveMessageBody(short msg_type) + { + Message message = ReceiveMessage(); + if (message.Type != msg_type) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + + return message.Body; + } + + /// + 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? + } + } + } + + /// + 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; + } + + /// + 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); + } + } + + /// + 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); + } + + /// + 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; + } + + /// + 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); + } + + /// + 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 + *

+ * Support fast rejection of duplicate records by maintaining a sliding receive window + *

+ */ + internal sealed class DtlsReplayWindow + { + private const long ValidSeqMask = 0x0000FFFFFFFFFFFFL; + + private const long WindowSize = 64L; + + private long m_latestConfirmedSeq = -1; + private ulong m_bitmap = 0; + + /// Check whether a received record with the given sequence number should be rejected as a duplicate. + /// + /// the 48-bit DTLSPlainText.sequence_number field of a received record. + /// true if the record should be discarded without further processing. + 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; + } + + /// Report that a received record with the given sequence number passed authentication checks. + /// + /// the 48-bit DTLSPlainText.sequence_number field of an authenticated record. + 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; } + } + + /// + public virtual DtlsTransport Accept(TlsServer server, DatagramTransport transport) + { + return Accept(server, transport, null); + } + + /// + 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); + } + + /// + 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); + } + + /// + protected virtual byte[] GenerateCertificateRequest(ServerHandshakeState state, + CertificateRequest certificateRequest) + { + MemoryStream buf = new MemoryStream(); + certificateRequest.Encode(state.serverContext, buf); + return buf.ToArray(); + } + + /// + 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(); + } + + /// + protected virtual byte[] GenerateNewSessionTicket(ServerHandshakeState state, + NewSessionTicket newSessionTicket) + { + MemoryStream buf = new MemoryStream(); + newSessionTicket.Encode(buf); + return buf.ToArray(); + } + + /// + 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; + } + } + + /// + 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); + } + + /// + 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); + } + + /// + 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); + } + + /// + protected virtual void ProcessClientHello(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + ClientHello clientHello = ClientHello.Parse(buf, new NullOutputStream()); + ProcessClientHello(state, clientHello); + } + + /// + 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); + } + } + + /// + protected virtual void ProcessClientKeyExchange(ServerHandshakeState state, byte[] body) + { + MemoryStream buf = new MemoryStream(body, false); + state.keyExchange.ProcessClientKeyExchange(buf); + TlsProtocol.AssertEmpty(buf); + } + + /// + 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; + } + + /// + public virtual int GetReceiveLimit() + { + return m_recordLayer.GetReceiveLimit(); + } + + /// + public virtual int GetSendLimit() + { + return m_recordLayer.GetSendLimit(); + } + + /// + 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); + } + } + + /// + 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); + } + } + + /// + 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 +{ + /// RFC 4492 5.4 + 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 +{ + /// RFC 4492 5.1.2 + 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 +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the + /// particular values (e.g. serialization). + /// + 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 +{ + /// RFC 5705 + 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; + } + + /// + 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); + } + + /// + internal HandshakeMessageOutput(short handshakeType) + : this(handshakeType, 60) + { + } + + /// + internal HandshakeMessageOutput(short handshakeType, int bodyLength) + : base(GetLength(bodyLength)) + { + TlsUtilities.CheckUint8(handshakeType); + TlsUtilities.WriteUint8(handshakeType, this); + // Reserve space for length + Seek(3L, SeekOrigin.Current); + } + + /// + 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 +{ + /// RFC 5246 7.4.1.4.1 + 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; } + } + + /// Encode this to a . + /// the to encode to. + /// + public void Encode(Stream output) + { + TlsUtilities.WriteUint8(m_mode, output); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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; } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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 +{ + /// RFC 6520 3. + 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 +{ + /// RFC 6066 + 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 +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the + /// particular values (e.g. serialization). + /// + 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; + + /// + /// + 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; + } + + /// + public int NamedGroup + { + get { return m_namedGroup; } + } + + public byte[] KeyExchange + { + get { return m_keyExchange; } + } + + /// Encode this to a . + /// the to encode to. + /// + public void Encode(Stream output) + { + TlsUtilities.WriteUint16(NamedGroup, output); + TlsUtilities.WriteOpaque16(KeyExchange, output); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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 +{ + /// RFC 8446 4.6.3 + 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 +{ + /// RFC 2246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the + /// particular values (e.g. serialization). + /// + 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 +{ + /// RFC 7919 + public abstract class NamedGroup + { + /* + * RFC 4492 5.1.1 + *

+ * The named curves defined here are those specified in SEC 2 [13]. Note that many of these curves + * are also recommended in ANSI X9.62 [7] and FIPS 186-2 [11]. Values 0xFE00 through 0xFEFF are + * reserved for private use. Values 0xFF01 and 0xFF02 indicate that the client supports arbitrary + * prime and characteristic-2 curves, respectively (the curve parameters must be encoded explicitly + * in ECParameters). + */ + 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 +{ + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the + /// particular values (e.g. serialization). + /// + 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; } + } + + ///

Encode this to a . + /// the to encode to. + /// + public void Encode(Stream output) + { + TlsUtilities.WriteUint32(TicketLifetimeHint, output); + TlsUtilities.WriteOpaque16(Ticket, output); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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 +{ + /// RFC 3546 3.6 + public sealed class OcspStatusRequest + { + private readonly IList m_responderIDList; + private readonly X509Extensions m_requestExtensions; + + /// an of , 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. + /// OCSP request extensions. A null value means that there are no extensions. + /// + public OcspStatusRequest(IList responderIDList, X509Extensions requestExtensions) + { + this.m_responderIDList = responderIDList; + this.m_requestExtensions = requestExtensions; + } + + /// an of . + public IList ResponderIDList + { + get { return m_responderIDList; } + } + + /// OCSP request extensions. + public X509Extensions RequestExtensions + { + get { return m_requestExtensions; } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + } + + /// Parse an from a . + /// the to parse from. + /// an object. + /// + 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; } + } + + /// + 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); + } + } + } + + /// + 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 +{ + /// RFC 5246 + /// + /// Note that the values here are implementation-specific and arbitrary. It is recommended not to depend on the + /// particular values (e.g. serialization). + /// + 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 +{ + /// RFC 7301 Represents a protocol name for use with ALPN. + 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); + } + + /// Encode this to a . + /// the to encode to. + /// + public void Encode(Stream output) + { + TlsUtilities.WriteOpaque8(m_bytes, output); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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; + } + + /// + 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; + } + + /// + 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 +{ + /// An implementation of the TLS 1.0/1.1/1.2 record layer. + 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; + } + + /// + internal void NotifyChangeCipherSpecReceived() + { + if (m_pendingCipher == null) + throw new TlsFatalAlert(AlertDescription.unexpected_message, "No pending cipher"); + + EnablePendingCipherRead(false); + } + + /// + 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(); + } + } + + /// + internal void EnablePendingCipherWrite() + { + if (m_pendingCipher == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + this.m_writeCipher = this.m_pendingCipher; + m_writeSeqNo.Reset(); + } + + /// + 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); + } + + /// + internal void NotifyKeyUpdateReceived() + { + m_readCipher.RekeyDecoder(); + m_readSeqNo.Reset(); + } + + /// + internal void NotifyKeyUpdateSent() + { + m_writeCipher.RekeyEncoder(); + m_writeSeqNo.Reset(); + } + + /// + 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); + } + + /// + 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; + } + + /// + 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; + } + + /// + 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; + } + + /// + 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(); + } + + /// + 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; + } + + /// + 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)); + } + } + + /// + 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; + } + + /// + 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; + } + + /// + 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; + //} + } + } + + /// + internal void ReadFragment(Stream input, int fragmentLength) + { + int recordLength = RecordFormat.FragmentOffset + fragmentLength; + Resize(recordLength); + FillTo(input, recordLength); + if (m_pos < recordLength) + throw new EndOfStreamException(); + } + + /// + 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; } + } + + /// + 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); + } + + /// Encode this to a . + /// the of the current connection. + /// the to encode to. + /// + 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); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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 +{ + /// RFC 6066 3. Server Name Indication + /// + /// 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. + /// + 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; } + } + + /// Encode this to a . + /// the to encode to. + /// + public void Encode(Stream output) + { + TlsUtilities.WriteUint8(nameType, output); + TlsUtilities.WriteOpaque16(nameData, output); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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; + + /// an of . + public ServerNameList(IList serverNameList) + { + if (null == serverNameList) + throw new ArgumentNullException("serverNameList"); + + this.m_serverNameList = serverNameList; + } + + /// an of . + public IList ServerNames + { + get { return m_serverNameList; } + } + + /// Encode this to a . + /// the to encode to . + /// + 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); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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; } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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; + } + + /// + 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; } + } + + /// + 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 +{ + /// RFC 5246 7.4.1.4.1 + 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; + + /// + /// + 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; + } + + /// + public short Hash + { + get { return m_hash; } + } + + /// + public short Signature + { + get { return m_signature; } + } + + /// Encode this to a . + /// the to encode to. + /// + public void Encode(Stream output) + { + TlsUtilities.WriteUint8(Hash, output); + TlsUtilities.WriteUint8(Signature, output); + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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 +{ + /// An implementation of that simulates the existence of "unknown" + /// identities to obscure the fact that there is no verifier for them. + public class SimulatedTlsSrpIdentityManager + : TlsSrpIdentityManager + { + private static readonly byte[] PrefixPassword = Strings.ToByteArray("password"); + private static readonly byte[] PrefixSalt = Strings.ToByteArray("salt"); + + /// Create a that implements the algorithm from RFC 5054 + /// 2.5.1.3. + /// + /// the defining the group that SRP is operating in. + /// the secret "seed key" referred to in RFC 5054 2.5.1.3. + /// an instance of . + /// + 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; } + } + + /// + public override IDictionary GetClientExtensions() + { + IDictionary clientExtensions = TlsExtensionsUtilities.EnsureExtensionsInitialised( + base.GetClientExtensions()); + TlsSrpUtilities.AddSrpExtension(clientExtensions, m_srpIdentity.GetSrpIdentity()); + return clientExtensions; + } + + /// + 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; + } + + /// + 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; + } + + /// + protected virtual TlsCredentialedSigner GetDsaSignerCredentials() + { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + + /// + 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; + } + + /// + internal static byte[] ReadEncryptedPms(Stream input) + { + return Streams.ReadAll(input); + } + + /// + 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 +{ + /// RFC 4680 + 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 +{ + /// Base interface to provide TLS authentication credentials. + public interface TlsAuthentication + { + /// Called by the protocol handler to report the server certificate. + /// + /// Note: this method is responsible for certificate verification and validation. + /// + /// the server certificate received. + /// + void NotifyServerCertificate(TlsServerCertificate serverCertificate); + + /// Return client credentials in response to server's certificate request. + /// + /// The returned value may be null, or else it MUST implement exactly one of + /// , , or + /// , depending on the key exchange that was negotiated and the details of + /// the . + /// + /// details of the certificate request. + /// a object or null for no client authentication. + /// + 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); + + /// Return the session this client wants to resume, if any. + /// + /// Note that the peer's certificate chain for the session (if any) may need to be periodically revalidated. + /// + /// A representing the resumable session to be used for this connection, or + /// null to use a new session. + /// + TlsSession GetSessionToResume(); + + bool IsFallback(); + + /// (Int32 -> byte[]) + /// + IDictionary GetClientExtensions(); + + /// 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. + /// + /// Groups that were not included in the supported_groups extension (by will + /// be ignored. The protocol will then add a suitable key_share extension to the ClientHello extensions. + /// + /// an of named group values, possibly empty or null. + /// + IList GetEarlyKeyShareGroups(); + + /// + void NotifyServerVersion(ProtocolVersion selectedVersion); + + /// Notifies the client of the session that will be offered in ClientHello for resumption, if any. + /// + /// + /// 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 . + /// + /// The representing the resumable session to be offered for + /// this connection, or null if there is none. + /// + void NotifySessionToResume(TlsSession session); + + /// Notifies the client of the session_id sent in the ServerHello. + /// + /// + void NotifySessionID(byte[] sessionID); + + void NotifySelectedCipherSuite(int selectedCipherSuite); + + /// The protocol implementation validates that any server extensions received correspond to client + /// extensions sent. + /// + /// 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. + /// + /// (Int32 -> byte[]) + /// + void ProcessServerExtensions(IDictionary serverExtensions); + + /// (SupplementalDataEntry) + /// + void ProcessServerSupplementalData(IList serverSupplementalData); + + /// + TlsPskIdentity GetPskIdentity(); + + /// + TlsSrpIdentity GetSrpIdentity(); + + /// + TlsDHGroupVerifier GetDHGroupVerifier(); + + /// + TlsSrpConfigVerifier GetSrpConfigVerifier(); + + /// + TlsAuthentication GetAuthentication(); + + /// (SupplementalDataEntry) + /// + IList GetClientSupplementalData(); + + /// RFC 5077 3.3. NewSessionTicket Handshake Message + /// + /// 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". + /// + /// The ticket. + /// + 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 +{ + /// Marker interface to distinguish a TLS client context. + 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; + + /// Constructor for non-blocking mode. + /// + /// When data is received, use to provide the received ciphertext, + /// then use to read the corresponding cleartext.

+ /// Similarly, when data needs to be sent, use + /// to provide the cleartext, then use to get the + /// corresponding ciphertext. + ///
+ public TlsClientProtocol() + : base() + { + } + + /// Constructor for blocking mode. + /// The of data to/from the server. + public TlsClientProtocol(Stream stream) + : base(stream) + { + } + + /// Constructor for blocking mode. + /// The of data from the server. + /// The of data to the server. + public TlsClientProtocol(Stream input, Stream output) + : base(input, output) + { + } + + /// Initiates a TLS handshake in the role of client. + /// + /// In blocking mode, this will not return until the handshake is complete. In non-blocking mode, use + /// to receive a callback when the handshake is complete. + /// + /// The to use for the handshake. + /// If in blocking mode and handshake was not successful. + 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; } + } + + /// + 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); + } + } + + /// + protected virtual void HandleServerCertificate() + { + TlsUtilities.ProcessServerCertificate(m_tlsClientContext, m_certificateStatus, m_keyExchange, + m_authentication, m_clientExtensions, m_serverExtensions); + } + + /// + 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); + } + + /// + 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; + } + + /// + 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; + } + } + + /// + 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); + } + + /// + 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; + } + } + + /// + 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); + } + + /// + 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); + } + + /// + 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); + } + + /// + 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(); + } + + /// + 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); + } + + /// + protected virtual void Receive13ServerFinished(MemoryStream buf) + { + Process13FinishedMessage(buf); + } + + /// + 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); + } + + /// + protected virtual void ReceiveNewSessionTicket(MemoryStream buf) + { + NewSessionTicket newSessionTicket = NewSessionTicket.Parse(buf); + + AssertEmpty(buf); + + m_tlsClient.NotifyNewSessionTicket(newSessionTicket); + } + + /// + protected virtual ServerHello ReceiveServerHelloMessage(MemoryStream buf) + { + return ServerHello.Parse(buf); + } + + /// + 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(); + } + + /// + protected virtual void SendCertificateVerifyMessage(DigitallySigned certificateVerify) + { + HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_verify); + certificateVerify.Encode(message); + message.Send(this); + } + + /// + 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(); + } + + /// + protected virtual void SendClientHelloMessage() + { + HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.client_hello); + m_clientHello.Encode(m_tlsClientContext, message); + message.Send(this); + } + + /// + protected virtual void SendClientKeyExchange() + { + HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.client_key_exchange); + m_keyExchange.GenerateClientKeyExchange(message); + message.Send(this); + } + + /// + protected virtual void Skip13CertificateRequest() + { + this.m_certificateRequest = null; + } + + /// + 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 + { + /// + 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 +{ + /// Base interface for a TLS context implementation. + public interface TlsContext + { + TlsCrypto Crypto { get; } + + TlsNonceGenerator NonceGenerator { get; } + + SecurityParameters SecurityParameters { get; } + + /// Return true if this context is for a server, false otherwise. + /// true for a server based context, false for a client based one. + bool IsServer { get; } + + ProtocolVersion[] ClientSupportedVersions { get; } + + ProtocolVersion ClientVersion { get; } + + ProtocolVersion RsaPreMasterSecretVersion { get; } + + ProtocolVersion ServerVersion { get; } + + /// Used to get the resumable session, if any, used by this connection. + /// + /// Only available after the handshake has successfully completed. + /// + /// A representing the resumable session used by this connection, or null if + /// no resumable session available. + /// + TlsSession ResumableSession { get; } + + /// Used to get the session information for this connection. + /// + /// Only available after the handshake has successfully completed. Use + /// to find out if the session is resumable. + /// + /// A representing the session used by this connection. + /// + TlsSession Session { get; } + + object UserObject { get; set; } + + /// Export the value of the specified channel binding. + /// + /// Only available after the handshake has successfully completed. + /// + /// A constant specifying the channel binding to + /// export. + /// A copy of the channel binding data as a byte[], or null if the binding could not be + /// determined. + byte[] ExportChannelBinding(int channelBinding); + + /// Export (early data) keying material according to RFC 5705: "Keying Material Exporters for TLS", as + /// updated for TLS 1.3 (RFC 8446). + /// + /// NOTE: for use in settings where an exporter is needed for 0-RTT data. + /// + /// indicates which application will use the exported keys. + /// allows the application using the exporter to mix its own data with the TLS PRF + /// for the exporter output. + /// the number of bytes to generate. + /// a pseudorandom bit string of 'length' bytes generated from the (exporter_)master_secret. + byte[] ExportEarlyKeyingMaterial(string asciiLabel, byte[] context_value, int length); + + /// Export keying material according to RFC 5705: "Keying Material Exporters for TLS", as updated for + /// TLS 1.3 (RFC 8446) when negotiated. + /// indicates which application will use the exported keys. + /// allows the application using the exporter to mix its own data with the TLS PRF + /// for the exporter output. + /// the number of bytes to generate. + /// a pseudorandom bit string of 'length' bytes generated from the (exporter_)master_secret. + 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 +{ + /// Support interface for generating a secret based on the credentials sent by a TLS peer. + public interface TlsCredentialedAgreement + : TlsCredentials + { + /// Calculate an agreed secret based on our credentials and the public key credentials of our peer. + /// + /// public key certificate of our TLS peer. + /// the agreed secret. + /// in case of an exception on generation of the secret. + 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 +{ + /// Base interface for a class that decrypts TLS secrets. + public interface TlsCredentialedDecryptor + : TlsCredentials + { + /// Decrypt the passed in cipher text using the parameters available. + /// the parameters to use for the decryption. + /// the cipher text containing the secret. + /// a TLS secret. + /// on a parsing or decryption error. + 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 +{ + /// Support interface for generating a signature based on our private credentials. + public interface TlsCredentialedSigner + : TlsCredentials + { + /// Generate a signature against the passed in hash. + /// a message digest calculated across the message the signature is to apply to. + /// an encoded signature. + /// if the hash cannot be processed, or there is an issue with the private + /// credentials. + byte[] GenerateRawSignature(byte[] hash); + + /// Return the algorithm IDs for the signature algorithm and the associated hash it uses. + /// the full algorithm details for the signature. + SignatureAndHashAlgorithm SignatureAndHashAlgorithm { get; } + + /// + 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 +{ + /// Base interface for interfaces/classes carrying TLS credentials. + public interface TlsCredentials + { + /// Return the certificate structure representing our identity. + /// our certificate structure. + 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 +{ + /// Interface for verifying explicit Diffie-Hellman group parameters. + public interface TlsDHGroupVerifier + { + /// Check whether the given DH group is acceptable for use. + /// the to check. + /// true if (and only if) the specified group is acceptable. + 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 +{ + /// (D)TLS DH key exchange. + 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; + } + + /// + 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); + } + + /// + public static BigInteger ReadDHParameter(Stream input) + { + return new BigInteger(1, TlsUtilities.ReadOpaque16(input, 1)); + } + + /// + public static void WriteDHConfig(TlsDHConfig dhConfig, Stream output) + { + DHGroup group = GetDHGroup(dhConfig); + WriteDHParameter(group.P, output); + WriteDHParameter(group.G, output); + } + + /// + 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 +{ + /// (D)TLS DH_anon key exchange. + 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 +{ + /// (D)TLS ECDH key exchange (see RFC 4492). + 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 +{ + /// (D)TLS ECDH_anon key exchange (see RFC 4492). + 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 +{ + /// (D)TLS ECDHE key exchange (see RFC 4492). + 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 + { + /// + 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; + } + } + + /// + 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); + } + } + + /// + 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); + } + + /// + public static void WriteECConfig(TlsECConfig ecConfig, Stream output) + { + WriteNamedECParameters(ecConfig.NamedGroup, output); + } + + /// + 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; + } + + /// (Int32 -> byte[]) + /// an of . + /// + public static void AddAlpnExtensionClient(IDictionary extensions, IList protocolNameList) + { + extensions[ExtensionType.application_layer_protocol_negotiation] = CreateAlpnExtensionClient(protocolNameList); + } + + /// + public static void AddAlpnExtensionServer(IDictionary extensions, ProtocolName protocolName) + { + extensions[ExtensionType.application_layer_protocol_negotiation] = CreateAlpnExtensionServer(protocolName); + } + + /// + public static void AddCertificateAuthoritiesExtension(IDictionary extensions, IList authorities) + { + extensions[ExtensionType.certificate_authorities] = CreateCertificateAuthoritiesExtension(authorities); + } + + /// + public static void AddClientCertificateTypeExtensionClient(IDictionary extensions, short[] certificateTypes) + { + extensions[ExtensionType.client_certificate_type] = CreateCertificateTypeExtensionClient(certificateTypes); + } + + /// + 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(); + } + + /// + public static void AddCookieExtension(IDictionary extensions, byte[] cookie) + { + extensions[ExtensionType.cookie] = CreateCookieExtension(cookie); + } + + public static void AddEarlyDataIndication(IDictionary extensions) + { + extensions[ExtensionType.early_data] = CreateEarlyDataIndication(); + } + + /// + 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(); + } + + /// + public static void AddHeartbeatExtension(IDictionary extensions, HeartbeatExtension heartbeatExtension) + { + extensions[ExtensionType.heartbeat] = CreateHeartbeatExtension(heartbeatExtension); + } + + /// + public static void AddKeyShareClientHello(IDictionary extensions, IList clientShares) + { + extensions[ExtensionType.key_share] = CreateKeyShareClientHello(clientShares); + } + + /// + public static void AddKeyShareHelloRetryRequest(IDictionary extensions, int namedGroup) + { + extensions[ExtensionType.key_share] = CreateKeyShareHelloRetryRequest(namedGroup); + } + + /// + public static void AddKeyShareServerHello(IDictionary extensions, KeyShareEntry serverShare) + { + extensions[ExtensionType.key_share] = CreateKeyShareServerHello(serverShare); + } + + /// + public static void AddMaxFragmentLengthExtension(IDictionary extensions, short maxFragmentLength) + { + extensions[ExtensionType.max_fragment_length] = CreateMaxFragmentLengthExtension(maxFragmentLength); + } + + /// + public static void AddOidFiltersExtension(IDictionary extensions, IDictionary filters) + { + extensions[ExtensionType.oid_filters] = CreateOidFiltersExtension(filters); + } + + /// + 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(); + } + + /// + public static void AddPreSharedKeyClientHello(IDictionary extensions, OfferedPsks offeredPsks) + { + extensions[ExtensionType.pre_shared_key] = CreatePreSharedKeyClientHello(offeredPsks); + } + + /// + public static void AddPreSharedKeyServerHello(IDictionary extensions, int selectedIdentity) + { + extensions[ExtensionType.pre_shared_key] = CreatePreSharedKeyServerHello(selectedIdentity); + } + + /// + public static void AddPskKeyExchangeModesExtension(IDictionary extensions, short[] modes) + { + extensions[ExtensionType.psk_key_exchange_modes] = CreatePskKeyExchangeModesExtension(modes); + } + + /// + public static void AddRecordSizeLimitExtension(IDictionary extensions, int recordSizeLimit) + { + extensions[ExtensionType.record_size_limit] = CreateRecordSizeLimitExtension(recordSizeLimit); + } + + /// + public static void AddServerCertificateTypeExtensionClient(IDictionary extensions, short[] certificateTypes) + { + extensions[ExtensionType.server_certificate_type] = CreateCertificateTypeExtensionClient(certificateTypes); + } + + /// + public static void AddServerCertificateTypeExtensionServer(IDictionary extensions, short certificateType) + { + extensions[ExtensionType.server_certificate_type] = CreateCertificateTypeExtensionServer(certificateType); + } + + /// + public static void AddServerNameExtensionClient(IDictionary extensions, IList serverNameList) + { + extensions[ExtensionType.server_name] = CreateServerNameExtensionClient(serverNameList); + } + + /// + public static void AddServerNameExtensionServer(IDictionary extensions) + { + extensions[ExtensionType.server_name] = CreateServerNameExtensionServer(); + } + + /// + public static void AddSignatureAlgorithmsExtension(IDictionary extensions, IList supportedSignatureAlgorithms) + { + extensions[ExtensionType.signature_algorithms] = CreateSignatureAlgorithmsExtension(supportedSignatureAlgorithms); + } + + /// + public static void AddSignatureAlgorithmsCertExtension(IDictionary extensions, IList supportedSignatureAlgorithms) + { + extensions[ExtensionType.signature_algorithms_cert] = CreateSignatureAlgorithmsCertExtension(supportedSignatureAlgorithms); + } + + /// + public static void AddStatusRequestExtension(IDictionary extensions, CertificateStatusRequest statusRequest) + { + extensions[ExtensionType.status_request] = CreateStatusRequestExtension(statusRequest); + } + + /// + public static void AddStatusRequestV2Extension(IDictionary extensions, IList statusRequestV2) + { + extensions[ExtensionType.status_request_v2] = CreateStatusRequestV2Extension(statusRequestV2); + } + + /// + public static void AddSupportedGroupsExtension(IDictionary extensions, IList namedGroups) + { + extensions[ExtensionType.supported_groups] = CreateSupportedGroupsExtension(namedGroups); + } + + /// + public static void AddSupportedPointFormatsExtension(IDictionary extensions, short[] ecPointFormats) + { + extensions[ExtensionType.ec_point_formats] = CreateSupportedPointFormatsExtension(ecPointFormats); + } + + /// + public static void AddSupportedVersionsExtensionClient(IDictionary extensions, ProtocolVersion[] versions) + { + extensions[ExtensionType.supported_versions] = CreateSupportedVersionsExtensionClient(versions); + } + + /// + 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(); + } + + /// + 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(); + } + + /// an of . + /// + public static IList GetAlpnExtensionClient(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.application_layer_protocol_negotiation); + return extensionData == null ? null : ReadAlpnExtensionClient(extensionData); + } + + /// + public static ProtocolName GetAlpnExtensionServer(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.application_layer_protocol_negotiation); + return extensionData == null ? null : ReadAlpnExtensionServer(extensionData); + } + + /// + public static IList GetCertificateAuthoritiesExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.certificate_authorities); + return extensionData == null ? null : ReadCertificateAuthoritiesExtension(extensionData); + } + + /// + public static short[] GetClientCertificateTypeExtensionClient(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_type); + return extensionData == null ? null : ReadCertificateTypeExtensionClient(extensionData); + } + + /// + public static short GetClientCertificateTypeExtensionServer(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_type); + return extensionData == null ? (short)-1 : ReadCertificateTypeExtensionServer(extensionData); + } + + /// + public static byte[] GetCookieExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.cookie); + return extensionData == null ? null : ReadCookieExtension(extensionData); + } + + /// + public static long GetEarlyDataMaxSize(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.early_data); + return extensionData == null ? -1L : ReadEarlyDataMaxSize(extensionData); + } + + /// + public static HeartbeatExtension GetHeartbeatExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.heartbeat); + return extensionData == null ? null : ReadHeartbeatExtension(extensionData); + } + + /// + public static IList GetKeyShareClientHello(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.key_share); + return extensionData == null ? null : ReadKeyShareClientHello(extensionData); + } + + /// + public static int GetKeyShareHelloRetryRequest(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.key_share); + return extensionData == null ? -1 : ReadKeyShareHelloRetryRequest(extensionData); + } + + /// + public static KeyShareEntry GetKeyShareServerHello(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.key_share); + return extensionData == null ? null : ReadKeyShareServerHello(extensionData); + } + + /// + public static short GetMaxFragmentLengthExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.max_fragment_length); + return extensionData == null ? (short)-1 : ReadMaxFragmentLengthExtension(extensionData); + } + + /// + public static IDictionary GetOidFiltersExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.oid_filters); + return extensionData == null ? null : ReadOidFiltersExtension(extensionData); + } + + /// + public static int GetPaddingExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.padding); + return extensionData == null ? -1 : ReadPaddingExtension(extensionData); + } + + /// + public static OfferedPsks GetPreSharedKeyClientHello(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.pre_shared_key); + return extensionData == null ? null : ReadPreSharedKeyClientHello(extensionData); + } + + /// + public static int GetPreSharedKeyServerHello(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.pre_shared_key); + return extensionData == null ? -1 : ReadPreSharedKeyServerHello(extensionData); + } + + /// + public static short[] GetPskKeyExchangeModesExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.psk_key_exchange_modes); + return extensionData == null ? null : ReadPskKeyExchangeModesExtension(extensionData); + } + + /// + public static int GetRecordSizeLimitExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.record_size_limit); + return extensionData == null ? -1 : ReadRecordSizeLimitExtension(extensionData); + } + + /// + public static short[] GetServerCertificateTypeExtensionClient(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_certificate_type); + return extensionData == null ? null : ReadCertificateTypeExtensionClient(extensionData); + } + + /// + public static short GetServerCertificateTypeExtensionServer(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_certificate_type); + return extensionData == null ? (short)-1 : ReadCertificateTypeExtensionServer(extensionData); + } + + /// + public static IList GetServerNameExtensionClient(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_name); + return extensionData == null ? null : ReadServerNameExtensionClient(extensionData); + } + + /// + public static IList GetSignatureAlgorithmsExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.signature_algorithms); + return extensionData == null ? null : ReadSignatureAlgorithmsExtension(extensionData); + } + + /// + public static IList GetSignatureAlgorithmsCertExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.signature_algorithms_cert); + return extensionData == null ? null : ReadSignatureAlgorithmsCertExtension(extensionData); + } + + /// + public static CertificateStatusRequest GetStatusRequestExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.status_request); + return extensionData == null ? null : ReadStatusRequestExtension(extensionData); + } + + /// + public static IList GetStatusRequestV2Extension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.status_request_v2); + return extensionData == null ? null : ReadStatusRequestV2Extension(extensionData); + } + + /// + public static int[] GetSupportedGroupsExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.supported_groups); + return extensionData == null ? null : ReadSupportedGroupsExtension(extensionData); + } + + /// + public static short[] GetSupportedPointFormatsExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.ec_point_formats); + return extensionData == null ? null : ReadSupportedPointFormatsExtension(extensionData); + } + + /// + public static ProtocolVersion[] GetSupportedVersionsExtensionClient(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.supported_versions); + return extensionData == null ? null : ReadSupportedVersionsExtensionClient(extensionData); + } + + /// + public static ProtocolVersion GetSupportedVersionsExtensionServer(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.supported_versions); + return extensionData == null ? null : ReadSupportedVersionsExtensionServer(extensionData); + } + + /// + public static IList GetTrustedCAKeysExtensionClient(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.trusted_ca_keys); + return extensionData == null ? null : ReadTrustedCAKeysExtensionClient(extensionData); + } + + /// + public static bool HasClientCertificateUrlExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.client_certificate_url); + return extensionData == null ? false : ReadClientCertificateUrlExtension(extensionData); + } + + /// + public static bool HasEarlyDataIndication(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.early_data); + return extensionData == null ? false : ReadEarlyDataIndication(extensionData); + } + + /// + public static bool HasEncryptThenMacExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.encrypt_then_mac); + return extensionData == null ? false : ReadEncryptThenMacExtension(extensionData); + } + + /// + public static bool HasExtendedMasterSecretExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.extended_master_secret); + return extensionData == null ? false : ReadExtendedMasterSecretExtension(extensionData); + } + + /// + public static bool HasServerNameExtensionServer(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.server_name); + return extensionData == null ? false : ReadServerNameExtensionServer(extensionData); + } + + /// + public static bool HasPostHandshakeAuthExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.post_handshake_auth); + return extensionData == null ? false : ReadPostHandshakeAuthExtension(extensionData); + } + + /// + public static bool HasTruncatedHmacExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.truncated_hmac); + return extensionData == null ? false : ReadTruncatedHmacExtension(extensionData); + } + + /// + public static bool HasTrustedCAKeysExtensionServer(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.trusted_ca_keys); + return extensionData == null ? false : ReadTrustedCAKeysExtensionServer(extensionData); + } + + /// an of . + /// + 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); + } + + /// + public static byte[] CreateAlpnExtensionServer(ProtocolName protocolName) + { + IList protocol_name_list = Platform.CreateArrayList(); + protocol_name_list.Add(protocolName); + + return CreateAlpnExtensionClient(protocol_name_list); + } + + /// + 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); + } + + /// + public static byte[] CreateCertificateTypeExtensionClient(short[] certificateTypes) + { + if (TlsUtilities.IsNullOrEmpty(certificateTypes) || certificateTypes.Length > 255) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return TlsUtilities.EncodeUint8ArrayWithUint8Length(certificateTypes); + } + + /// + public static byte[] CreateCertificateTypeExtensionServer(short certificateType) + { + return TlsUtilities.EncodeUint8(certificateType); + } + + public static byte[] CreateClientCertificateUrlExtension() + { + return CreateEmptyExtensionData(); + } + + /// + 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(); + } + + /// + 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(); + } + + /// + 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(); + } + + /// + 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); + } + + /// + public static byte[] CreateKeyShareHelloRetryRequest(int namedGroup) + { + return TlsUtilities.EncodeUint16(namedGroup); + } + + /// + 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(); + } + + /// + public static byte[] CreateMaxFragmentLengthExtension(short maxFragmentLength) + { + return TlsUtilities.EncodeUint8(maxFragmentLength); + } + + /// + 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); + } + + /// + public static byte[] CreatePaddingExtension(int dataLength) + { + TlsUtilities.CheckUint16(dataLength); + return new byte[dataLength]; + } + + public static byte[] CreatePostHandshakeAuthExtension() + { + return CreateEmptyExtensionData(); + } + + /// + 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(); + } + + /// + public static byte[] CreatePreSharedKeyServerHello(int selectedIdentity) + { + return TlsUtilities.EncodeUint16(selectedIdentity); + } + + /// + public static byte[] CreatePskKeyExchangeModesExtension(short[] modes) + { + if (TlsUtilities.IsNullOrEmpty(modes) || modes.Length > 255) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return TlsUtilities.EncodeUint8ArrayWithUint8Length(modes); + } + + /// + public static byte[] CreateRecordSizeLimitExtension(int recordSizeLimit) + { + if (recordSizeLimit < 64) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return TlsUtilities.EncodeUint16(recordSizeLimit); + } + + /// + 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(); + } + + /// + public static byte[] CreateSignatureAlgorithmsExtension(IList supportedSignatureAlgorithms) + { + MemoryStream buf = new MemoryStream(); + + TlsUtilities.EncodeSupportedSignatureAlgorithms(supportedSignatureAlgorithms, buf); + + return buf.ToArray(); + } + + /// + public static byte[] CreateSignatureAlgorithmsCertExtension(IList supportedSignatureAlgorithms) + { + return CreateSignatureAlgorithmsExtension(supportedSignatureAlgorithms); + } + + /// + 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(); + } + + /// + 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); + } + + /// + 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); + } + + /// + 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); + } + + /// + 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; + } + + /// + public static byte[] CreateSupportedVersionsExtensionServer(ProtocolVersion selectedVersion) + { + return TlsUtilities.EncodeVersion(selectedVersion); + } + + public static byte[] CreateTruncatedHmacExtension() + { + return CreateEmptyExtensionData(); + } + + /// + 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(); + } + + /// + 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; + } + + /// an of . + /// + 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; + } + + /// + 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]; + } + + /// + 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; + } + + /// + public static short[] ReadCertificateTypeExtensionClient(byte[] extensionData) + { + short[] certificateTypes = TlsUtilities.DecodeUint8ArrayWithUint8Length(extensionData); + if (certificateTypes.Length < 1) + throw new TlsFatalAlert(AlertDescription.decode_error); + + return certificateTypes; + } + + /// + public static short ReadCertificateTypeExtensionServer(byte[] extensionData) + { + return TlsUtilities.DecodeUint8(extensionData); + } + + /// + public static bool ReadClientCertificateUrlExtension(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + public static byte[] ReadCookieExtension(byte[] extensionData) + { + return TlsUtilities.DecodeOpaque16(extensionData, 1); + } + + /// + public static bool ReadEarlyDataIndication(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + public static long ReadEarlyDataMaxSize(byte[] extensionData) + { + return TlsUtilities.DecodeUint32(extensionData); + } + + /// + public static bool ReadEncryptThenMacExtension(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + public static bool ReadExtendedMasterSecretExtension(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + 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; + } + + /// + 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; + } + + /// + public static int ReadKeyShareHelloRetryRequest(byte[] extensionData) + { + return TlsUtilities.DecodeUint16(extensionData); + } + + /// + 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; + } + + /// + public static short ReadMaxFragmentLengthExtension(byte[] extensionData) + { + return TlsUtilities.DecodeUint8(extensionData); + } + + /// + 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; + } + + /// + 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; + } + + /// + public static bool ReadPostHandshakeAuthExtension(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + 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; + } + + /// + public static int ReadPreSharedKeyServerHello(byte[] extensionData) + { + return TlsUtilities.DecodeUint16(extensionData); + } + + /// + public static short[] ReadPskKeyExchangeModesExtension(byte[] extensionData) + { + short[] modes = TlsUtilities.DecodeUint8ArrayWithUint8Length(extensionData); + if (modes.Length < 1) + throw new TlsFatalAlert(AlertDescription.decode_error); + + return modes; + } + + /// + public static int ReadRecordSizeLimitExtension(byte[] extensionData) + { + int recordSizeLimit = TlsUtilities.DecodeUint16(extensionData); + if (recordSizeLimit < 64) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return recordSizeLimit; + } + + /// + 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; + } + + /// + public static bool ReadServerNameExtensionServer(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + 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; + } + + /// + public static IList ReadSignatureAlgorithmsCertExtension(byte[] extensionData) + { + return ReadSignatureAlgorithmsExtension(extensionData); + } + + /// + 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; + } + + /// + 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; + } + + /// + 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; + } + + /// + 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; + } + + /// + 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; + } + + /// + 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); + } + + /// + public static bool ReadTruncatedHmacExtension(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + 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; + } + + /// + public static bool ReadTrustedCAKeysExtensionServer(byte[] extensionData) + { + return ReadEmptyExtensionData(extensionData); + } + + /// + 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 +{ + /// Base interface for an object that can calculate a handshake hash. + public interface TlsHandshakeHash + : TlsHash + { + /// + 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 +{ + /// A generic interface for key exchange implementations in (D)TLS. + public interface TlsKeyExchange + { + void Init(TlsContext context); + + /// + void SkipServerCredentials(); + + /// + void ProcessServerCredentials(TlsCredentials serverCredentials); + + /// + void ProcessServerCertificate(Certificate serverCertificate); + + bool RequiresServerKeyExchange { get; } + + /// + byte[] GenerateServerKeyExchange(); + + /// + void SkipServerKeyExchange(); + + /// + void ProcessServerKeyExchange(Stream input); + + short[] GetClientCertificateTypes(); + + /// + void SkipClientCredentials(); + + /// + void ProcessClientCredentials(TlsCredentials clientCredentials); + + /// + void ProcessClientCertificate(Certificate clientCertificate); + + /// + void GenerateClientKeyExchange(Stream output); + + /// + void ProcessClientKeyExchange(Stream input); + + bool RequiresCertificateVerify { get; } + + /// + 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 +{ + /// Interface for a key exchange factory offering a variety of specific algorithms. + public interface TlsKeyExchangeFactory + { + /// + TlsKeyExchange CreateDHKeyExchange(int keyExchange); + + /// + TlsKeyExchange CreateDHanonKeyExchangeClient(int keyExchange, TlsDHGroupVerifier dhGroupVerifier); + + /// + TlsKeyExchange CreateDHanonKeyExchangeServer(int keyExchange, TlsDHConfig dhConfig); + + /// + TlsKeyExchange CreateDheKeyExchangeClient(int keyExchange, TlsDHGroupVerifier dhGroupVerifier); + + /// + TlsKeyExchange CreateDheKeyExchangeServer(int keyExchange, TlsDHConfig dhConfig); + + /// + TlsKeyExchange CreateECDHKeyExchange(int keyExchange); + + /// + TlsKeyExchange CreateECDHanonKeyExchangeClient(int keyExchange); + + /// + TlsKeyExchange CreateECDHanonKeyExchangeServer(int keyExchange, TlsECConfig ecConfig); + + /// + TlsKeyExchange CreateECDheKeyExchangeClient(int keyExchange); + + /// + TlsKeyExchange CreateECDheKeyExchangeServer(int keyExchange, TlsECConfig ecConfig); + + /// + TlsKeyExchange CreatePskKeyExchangeClient(int keyExchange, TlsPskIdentity pskIdentity, + TlsDHGroupVerifier dhGroupVerifier); + + /// + TlsKeyExchange CreatePskKeyExchangeServer(int keyExchange, TlsPskIdentityManager pskIdentityManager, + TlsDHConfig dhConfig, TlsECConfig ecConfig); + + /// + TlsKeyExchange CreateRsaKeyExchange(int keyExchange); + + /// + TlsKeyExchange CreateSrpKeyExchangeClient(int keyExchange, TlsSrpIdentity srpIdentity, + TlsSrpConfigVerifier srpConfigVerifier); + + /// + 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 +{ + /// This exception will be thrown (only) when the connection is closed by the peer without sending a + /// close_notify warning alert. + /// + /// 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. + /// + 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 +{ + /// Object Identifiers associated with TLS extensions. + public abstract class TlsObjectIdentifiers + { + /// RFC 7633 + 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 +{ + /// Base interface for a (D)TLS endpoint. + public interface TlsPeer + { + TlsCrypto Crypto { get; } + + void NotifyCloseHandle(TlsCloseable closehandle); + + /// + void Cancel(); + + ProtocolVersion[] GetProtocolVersions(); + + int[] GetCipherSuites(); + + /// Notifies the peer that a new handshake is about to begin. + /// + void NotifyHandshakeBeginning(); + + /// Specify the timeout, in milliseconds, to use for the complete handshake process. + /// + /// 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). + /// + /// the handshake timeout, in milliseconds. + int GetHandshakeTimeoutMillis(); + + bool AllowLegacyResumption(); + + int GetMaxCertificateChainLength(); + + int GetMaxHandshakeMessageSize(); + + /// + /// 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. + /// + bool RequiresCloseNotify(); + + /// 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). + /// + /// true if the handshake should be aborted when the peer does not negotiate the + /// extended_master_secret extension, or false to support legacy interoperability. + bool RequiresExtendedMasterSecret(); + + bool ShouldUseExtendedMasterSecret(); + + /// See RFC 5246 6.2.3.2. Controls whether block cipher encryption may randomly add extra padding + /// beyond the minimum. + /// + /// 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. + /// + /// true if random extra padding should be added during block cipher encryption, or + /// false to always use the minimum amount of required padding. + bool ShouldUseExtendedPadding(); + + /// 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.". + /// + /// 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. + /// + /// true if the current time should be used in the gmt_unix_time field of Random, or + /// false if gmt_unix_time should contain a cryptographically random value. + bool ShouldUseGmtUnixTime(); + + /// 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. + /// + /// NOTE: TLS 1.3 forbids renegotiation, so this is never called when TLS 1.3 (or later) was negotiated. + /// + /// + void NotifySecureRenegotiation(bool secureRenegotiation); + + /// + TlsKeyExchangeFactory GetKeyExchangeFactory(); + + /// This method will be called when an alert is raised by the protocol. + /// + /// + /// A human-readable message explaining what caused this alert. May be null. + /// The that caused this alert to be raised. May be null. + void NotifyAlertRaised(short alertLevel, short alertDescription, string message, Exception cause); + + /// This method will be called when an alert is received from the remote peer. + /// + /// + void NotifyAlertReceived(short alertLevel, short alertDescription); + + /// Notifies the peer that the handshake has been successfully completed. + /// + void NotifyHandshakeComplete(); + + /// Return a 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. + /// an instance of . + /// + TlsHeartbeat GetHeartbeat(); + + /// Return the heartbeat mode applicable to the remote peer. Heartbeats are described in RFC 6520. + /// + /// + /// See enumeration class for appropriate return values. + /// + /// the value. + 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); + } + + /// + 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(); + } + + /// + protected virtual void CloseConnection() + { + m_recordStream.Close(); + } + + protected abstract TlsContext Context { get; } + + internal abstract AbstractTlsContext ContextAdmin { get; } + + protected abstract TlsPeer Peer { get; } + + /// + protected virtual void HandleAlertMessage(short alertLevel, short alertDescription) + { + Peer.NotifyAlertReceived(alertLevel, alertDescription); + + if (alertLevel == AlertLevel.warning) + { + HandleAlertWarningMessage(alertDescription); + } + else + { + HandleFailure(); + + throw new TlsFatalAlertReceived(alertDescription); + } + } + + /// + 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); + } + } + } + + /// + protected virtual void HandleChangeCipherSpecMessage() + { + } + + /// + 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(); + } + } + + /// + 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(); + } + } + + /// + 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(); + } + + /// + protected abstract void HandleHandshakeMessage(short type, HandshakeMessageInput buf); + + /// + 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); + } + } + + /// + protected virtual void CheckReceivedChangeCipherSpec(bool expected) + { + if (expected != m_receivedChangeCipherSpec) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + /// + 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(); + } + } + + /// + 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; + } + + /// + 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(); + } + } + + /// + 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); + } + } + + /// + 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. + */ + } + + /// + 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); + } + } + + /// This method is called, when a change cipher spec message is received. + /// If the message has an invalid content or the handshake is not in the correct + /// state. + 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; } + } + + /// Read data from the network. + /// + /// The method will return immediately, if there is still some data left in the buffer, or block until some + /// application data has been read from the network. + /// + /// The buffer where the data will be copied to. + /// The position where the data will be placed in the buffer. + /// The maximum number of bytes to read. + /// The number of bytes read. + /// If something goes wrong during reading data. + 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; + } + + /// + 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); + } + } + + /// + 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(); + } + + /// + 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); + } + } + + /// + 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); + } + } + + /// Write some application data. + /// + /// Fragmentation is handled internally. Usable in both blocking/non-blocking modes.

+ /// In blocking mode, the output will be automatically sent via the underlying transport. In non-blocking mode, + /// call to get the output bytes to send to the peer.

+ /// This method must not be called until after the initial handshake is complete. Attempting to call it earlier + /// will result in an . + ///
+ /// The buffer containing application data to send. + /// The offset at which the application data begins + /// The number of bytes of application data. + /// If called before the initial handshake has completed. + /// + /// If connection is already closed, or for encryption or transport errors. + /// + 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; } + } + + /// + 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); + } + + /// The secure bidirectional stream for this connection + /// Only allowed in blocking mode. + 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; + } + } + + /// Should be called in non-blocking mode when the input data reaches EOF. + /// + 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(); + } + + /// + 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); + } + + /// + 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; + } + } + + /// Equivalent to OfferInput(input, 0, input.Length). + /// The input buffer to offer. + /// + /// + public virtual void OfferInput(byte[] input) + { + OfferInput(input, 0, input.Length); + } + + /// Offer input from an arbitrary source. + /// Only allowed in non-blocking mode.

+ /// 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.

+ /// If any records containing application data were processed, the decrypted data can be obtained using + /// . 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 . + ///
+ /// The input buffer to offer. + /// The offset within the input buffer that input begins. + /// The number of bytes of input being offered. + /// If an error occurs while decrypting or processing a record. + 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; } + } + + /// Gets the amount of received application data. + /// A call to is guaranteed to be able to return at least + /// this much data.

+ /// Only allowed in non-blocking mode. + ///
+ /// The number of bytes of available application data. + public virtual int GetAvailableInputBytes() + { + if (m_blocking) + throw new InvalidOperationException("Cannot use GetAvailableInputBytes() in blocking mode!"); + + return ApplicationDataAvailable; + } + + /// Retrieves received application data. + /// + /// Use to check how much application data is currently available. This + /// method functions similarly to , 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. + ///
+ /// The buffer to hold the application data. + /// The start offset in the buffer at which the data is written. + /// The maximum number of bytes to read. + /// 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. + 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; + } + + /// Gets the amount of encrypted data available to be sent. + /// + /// A call to is guaranteed to be able to return at least this much + /// data. Only allowed in non-blocking mode. + /// + /// The number of bytes of available encrypted data. + public virtual int GetAvailableOutputBytes() + { + if (m_blocking) + throw new InvalidOperationException("Cannot use GetAvailableOutputBytes() in blocking mode! Use Stream instead."); + + return m_outputBuffer.Buffer.Available; + } + + /// Retrieves encrypted data to be sent. + /// + /// Use to check how much encrypted data is currently available. This + /// method functions similarly to , 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. + /// + /// The buffer to hold the encrypted data. + /// The start offset in the buffer at which the data is written. + /// The maximum number of bytes to read. + /// 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. + 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; + } + } + + /// + 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; + } + } + } + + /// + 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; + } + + /// + 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 + } + } + + /// + 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); + } + + + /// + 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; + } + + /// + 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; + } + + /// + 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; + } + + /// + protected virtual void Send13CertificateVerifyMessage(DigitallySigned certificateVerify) + { + HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_verify); + certificateVerify.Encode(message); + message.Send(this); + } + + /// + protected virtual void SendChangeCipherSpec() + { + SendChangeCipherSpecMessage(); + m_recordStream.EnablePendingCipherWrite(); + } + + /// + protected virtual void SendChangeCipherSpecMessage() + { + byte[] message = new byte[]{ 1 }; + SafeWriteRecord(ContentType.change_cipher_spec, message, 0, message.Length); + } + + /// + 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); + } + + /// + 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); + } + + /// + 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; + } + + /// + 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; + } + } + + /// + 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; + } + + /// + 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"); + } + + /// Make sure the 'buf' is now empty. Fail otherwise. + /// The to check. + /// + 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; + } + + /// + internal static byte[] CreateRenegotiationInfo(byte[] renegotiated_connection) + { + return TlsUtilities.EncodeOpaque8(renegotiated_connection); + } + + /// + 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(); + } + } + + /// + internal static IDictionary ReadExtensions(MemoryStream input) + { + if (input.Position >= input.Length) + return null; + + byte[] extBytes = TlsUtilities.ReadOpaque16(input); + + AssertEmpty(input); + + return ReadExtensionsData(extBytes); + } + + /// + 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; + } + + /// + 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; + } + + /// + 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; + } + + /// + 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; + } + + /// + internal static void WriteExtensions(Stream output, IDictionary extensions) + { + if (null == extensions || extensions.Count < 1) + return; + + byte[] extBytes = WriteExtensionsData(extensions); + + TlsUtilities.WriteOpaque16(extBytes, output); + } + + /// + internal static byte[] WriteExtensionsData(IDictionary extensions) + { + MemoryStream buf = new MemoryStream(); + WriteExtensionsData(extensions, buf); + return buf.ToArray(); + } + + /// + 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); + } + + /// + 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); + } + } + } + + /// + 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 +{ + /// Processor interface for a PSK identity. + 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 +{ + /// Base interface for an object that can process a PSK identity. + 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 +{ + /// (D)TLS PSK key exchange (RFC 4279). + 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 +{ + /// (D)TLS RSA key exchange. + 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 +{ + /// RSA Utility methods. + public abstract class TlsRsaUtilities + { + /// Generate a pre_master_secret and send it encrypted to the server. + /// + 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 +{ + /// Interface describing a TLS server endpoint. + public interface TlsServer + : TlsPeer + { + void Init(TlsServerContext context); + + /// Return the specified session, if available. + /// + /// Note that the peer's certificate chain for the session (if any) may need to be periodically revalidated. + /// + /// the ID of the session to resume. + /// A with the specified session ID, or null. + /// + TlsSession GetSessionToResume(byte[] sessionID); + + byte[] GetNewSessionID(); + + void NotifySession(TlsSession session); + + /// + void NotifyClientVersion(ProtocolVersion clientVersion); + + /// + void NotifyFallback(bool isFallback); + + /// + void NotifyOfferedCipherSuites(int[] offeredCipherSuites); + + /// (Int32 -> byte[]) + /// + void ProcessClientExtensions(IDictionary clientExtensions); + + /// + ProtocolVersion GetServerVersion(); + + /// + int[] GetSupportedGroups(); + + /// + int GetSelectedCipherSuite(); + + /// (Int32 -> byte[]) + /// + IDictionary GetServerExtensions(); + + /// (Int32 -> byte[]) + /// + void GetServerExtensionsForConnection(IDictionary serverExtensions); + + /// (SupplementalDataEntry) + /// + IList GetServerSupplementalData(); + + /// Return server credentials to use. + /// + /// The returned value may be null, or else it MUST implement exactly one of + /// , , or + /// , depending on the key exchange that was negotiated. + /// + /// a object or null for anonymous key exchanges. + /// + TlsCredentials GetCredentials(); + + /// + /// 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 RFC 3546 3.6. Certificate Status Request. If a + /// non-null is returned, it is sent to the client as a handshake message of + /// type "certificate_status". + /// + /// A to be sent to the client (or null for none). + /// + CertificateStatus GetCertificateStatus(); + + /// + CertificateRequest GetCertificateRequest(); + + /// + TlsPskIdentityManager GetPskIdentityManager(); + + /// + TlsSrpLoginParameters GetSrpLoginParameters(); + + /// + TlsDHConfig GetDHConfig(); + + /// + TlsECConfig GetECDHConfig(); + + /// (SupplementalDataEntry) + /// + void ProcessClientSupplementalData(IList clientSupplementalData); + + /// Called by the protocol handler to report the client certificate, only if + /// returned non-null. + /// + /// Note: this method is responsible for certificate verification and validation. + /// + /// the effective client certificate (may be an empty chain). + /// + void NotifyClientCertificate(Certificate clientCertificate); + + /// RFC 5077 3.3. NewSessionTicket Handshake Message. + /// + /// This method will be called (only) if a NewSessionTicket extension was sent by the server. See RFC 5077 + /// 4. Recommended Ticket Construction for recommended format and protection. + /// + /// The ticket. + /// + 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 +{ + /// Server certificate carrier interface. + 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 +{ + /// Marker interface to distinguish a TLS server context. + 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; + + /// Constructor for non-blocking mode. + /// + /// When data is received, use to provide the received ciphertext, + /// then use to read the corresponding cleartext.

+ /// Similarly, when data needs to be sent, use + /// to provide the cleartext, then use to get the + /// corresponding ciphertext. + ///
+ public TlsServerProtocol() + : base() + { + } + + /// Constructor for blocking mode. + /// The of data to/from the server. + public TlsServerProtocol(Stream stream) + : base(stream) + { + } + + /// Constructor for blocking mode. + /// The of data from the server. + /// The of data to the server. + public TlsServerProtocol(Stream input, Stream output) + : base(input, output) + { + } + + /// Receives a TLS handshake in the role of server. + /// + /// In blocking mode, this will not return until the handshake is complete. In non-blocking mode, use + /// to receive a callback when the handshake is complete. + /// + /// The to use for the handshake. + /// If in blocking mode and handshake was not successful. + 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); + } + + /// + 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); + } + + /// + 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); + } + + /// + 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; } + } + + /// + 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); + } + + /// + 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); + } + + /// + 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); + } + + /// + 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); + } + + /// + protected virtual void Receive13ClientFinished(MemoryStream buf) + { + Process13FinishedMessage(buf); + } + + /// + 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); + } + + /// + 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(); + } + + /// + protected virtual ClientHello ReceiveClientHelloMessage(MemoryStream buf) + { + return ClientHello.Parse(buf, null); + } + + /// + 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(); + } + } + + /// + 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); + } + + /// + 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(); + } + + /// + protected virtual void SendCertificateRequestMessage(CertificateRequest certificateRequest) + { + HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.certificate_request); + certificateRequest.Encode(m_tlsServerContext, message); + message.Send(this); + } + + /// + 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); + } + + /// + protected virtual void SendHelloRequestMessage() + { + HandshakeMessageOutput.Send(this, HandshakeType.hello_request, TlsUtilities.EmptyBytes); + } + + /// + 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); + } + + /// + protected virtual void SendServerHelloDoneMessage() + { + HandshakeMessageOutput.Send(this, HandshakeType.server_hello_done, TlsUtilities.EmptyBytes); + } + + /// + protected virtual void SendServerHelloMessage(ServerHello serverHello) + { + HandshakeMessageOutput message = new HandshakeMessageOutput(HandshakeType.server_hello); + serverHello.Encode(m_tlsServerContext, message); + message.Send(this); + } + + /// + protected virtual void SendServerKeyExchangeMessage(byte[] serverKeyExchange) + { + HandshakeMessageOutput.Send(this, HandshakeType.server_key_exchange, serverKeyExchange); + } + + /// + protected virtual void Skip13ClientCertificate() + { + if (null != m_certificateRequest) + throw new TlsFatalAlert(AlertDescription.unexpected_message); + } + + /// + 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 +{ + /// Base interface for a carrier object for a TLS session. + 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 +{ + /// Interface for verifying SRP config needs to conform to. + public interface TlsSrpConfigVerifier + { + /// Check whether the given SRP configuration is acceptable for use. + /// the to check. + /// true if (and only if) the specified configuration is acceptable. + 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 +{ + /// Processor interface for an SRP identity. + 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 +{ + /// Base interface for an object that can return login parameters from an SRP identity. + public interface TlsSrpIdentityManager + { + /// Lookup the corresponding to the specified identity. + /// + /// NOTE: To avoid "identity probing", unknown identities SHOULD be handled as recommended in RFC 5054 2.5.1.3. + /// is provided for this purpose. + /// + /// the SRP identity sent by the connecting client. + /// the for the specified identity, or else 'simulated' parameters + /// if the identity is not recognized. A null value is also allowed, but not recommended. + 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 +{ + /// (D)TLS SRP key exchange (RFC 5054). + 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 + { + /// + public static void AddSrpExtension(IDictionary extensions, byte[] identity) + { + extensions[ExtensionType.srp] = CreateSrpExtension(identity); + } + + /// + public static byte[] GetSrpExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.srp); + return extensionData == null ? null : ReadSrpExtension(extensionData); + } + + /// + public static byte[] CreateSrpExtension(byte[] identity) + { + if (identity == null) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return TlsUtilities.EncodeOpaque8(identity); + } + + /// + public static byte[] ReadSrpExtension(byte[] extensionData) + { + if (extensionData == null) + throw new ArgumentNullException("extensionData"); + + return TlsUtilities.DecodeOpaque8(extensionData, 1); + } + + /// + public static BigInteger ReadSrpParameter(Stream input) + { + return new BigInteger(1, TlsUtilities.ReadOpaque16(input, 1)); + } + + /// + 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 +{ + /// RFC 5764 DTLS Extension to Establish Keys for SRTP. + public abstract class TlsSrtpUtilities +{ + /// + public static void AddUseSrtpExtension(IDictionary extensions, UseSrtpData useSrtpData) + { + extensions[ExtensionType.use_srtp] = CreateUseSrtpExtension(useSrtpData); + } + + /// + public static UseSrtpData GetUseSrtpExtension(IDictionary extensions) + { + byte[] extensionData = TlsUtilities.GetExtensionData(extensions, ExtensionType.use_srtp); + return extensionData == null ? null : ReadUseSrtpExtension(extensionData); + } + + /// + 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(); + } + + /// + 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; + } + + /// + 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); + } + } + + /// + 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; + } + + /// + 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); + } + } + + /// + 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; + } + + /// + internal static byte[] CalculateEndPointHash(TlsContext context, TlsCertificate certificate, byte[] enc) + { + return CalculateEndPointHash(context, certificate, enc, 0, enc.Length); + } + + /// + 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; + } + + /// + 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); + } + + /// + 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; + } + + /// + public static bool IsAeadCipherSuite(int cipherSuite) + { + return CipherType.aead == GetCipherType(cipherSuite); + } + + /// + public static bool IsBlockCipherSuite(int cipherSuite) + { + return CipherType.block == GetCipherType(cipherSuite); + } + + /// + public static bool IsStreamCipherSuite(int cipherSuite) + { + return CipherType.stream == GetCipherType(cipherSuite); + } + + /// Whether a server can select the specified cipher suite given the available signature algorithms + /// for ServerKeyExchange. + 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); + } + + /// + public static SignatureAndHashAlgorithm ChooseSignatureAndHashAlgorithm(TlsContext context, IList sigHashAlgs, + short signatureAlgorithm) + { + return ChooseSignatureAndHashAlgorithm(context.ServerVersion, sigHashAlgs, signatureAlgorithm); + } + + /// + 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); + } + + /// + 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); + } + } + + /// + 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); + } + } + + /// + internal static TlsKeyExchange InitKeyExchangeClient(TlsClientContext clientContext, TlsClient client) + { + SecurityParameters securityParameters = clientContext.SecurityParameters; + TlsKeyExchange keyExchange = CreateKeyExchangeClient(client, securityParameters.KeyExchangeAlgorithm); + keyExchange.Init(clientContext); + return keyExchange; + } + + /// + 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); + } + + /// 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. + /// + /// 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). + /// + /// if any certificate in the CertPath (excepting the trust anchor) has a + /// signature algorithm that is not one of the locally supported signature algorithms. + 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; + } + + /// + internal static TlsCredentialedAgreement RequireAgreementCredentials(TlsCredentials credentials) + { + if (!(credentials is TlsCredentialedAgreement)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return (TlsCredentialedAgreement)credentials; + } + + /// + internal static TlsCredentialedDecryptor RequireDecryptorCredentials(TlsCredentials credentials) + { + if (!(credentials is TlsCredentialedDecryptor)) + throw new TlsFatalAlert(AlertDescription.internal_error); + + return (TlsCredentialedDecryptor)credentials; + } + + /// + 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); + } + } + } + + /// + 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; + } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + } + + /// Parse a from a . + /// the to parse from. + /// a object. + /// + 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 +{ + /// RFC 6066 5. + 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; } + } + + /// Encode this to a . + /// the to encode to. + /// + 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); + } + } + + /// Parse a from a . + /// the of the current connection. + /// the to parse from. + /// a object. + /// + 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 +{ + /// RFC 5764 4.1.1 + public sealed class UseSrtpData + { + private readonly int[] m_protectionProfiles; + private readonly byte[] m_mki; + + /// see for valid constants. + /// valid lengths from 0 to 255. + 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; + } + + /// see for valid constants. + public int[] ProtectionProfiles + { + get { return m_protectionProfiles; } + } + + /// valid lengths from 0 to 255. + 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 +{ + /// RFC 4681 + 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 +{ + /// Carrier class for Diffie-Hellman group parameters. + public class DHGroup + { + private readonly BigInteger g, p, q; + private readonly int l; + + /// Base constructor with the prime factor of (p - 1). + /// the prime modulus. + /// specifies the prime factor of (p - 1). + /// the base generator. + /// + 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 +{ + /// Standard Diffie-Hellman groups from various IETF specifications. + 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 +{ + /// Carrier class for SRP-6 group parameters. + public class Srp6Group + { + private readonly BigInteger n, g; + + /// Base constructor. + /// the n value. + /// the g value. + 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 +{ + /// A selection of standard groups for SRP-6. + 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 +{ + /// Base interface for ephemeral key agreement calculator. + public interface TlsAgreement + { + /// Generate an ephemeral key pair, returning the encoding of the public key. + /// a byte encoding of the public key. + /// + byte[] GenerateEphemeral(); + + /// Pass in the public key for the peer to the agreement calculator. + /// a byte encoding of the peer public key. + /// + void ReceivePeerValue(byte[] peerValue); + + /// Calculate the agreed secret based on the calculator's current state. + /// the calculated secret. + /// + 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 +{ + /// Interface providing the functional representation of a single X.509 certificate. + public interface TlsCertificate + { + /// + /// + TlsVerifier CreateVerifier(short signatureAlgorithm); + + /// + /// + TlsVerifier CreateVerifier(int signatureScheme); + + /// + byte[] GetEncoded(); + + /// + byte[] GetExtension(DerObjectIdentifier extensionOid); + + BigInteger SerialNumber { get; } + + /// the OID of this certificate's 'signatureAlgorithm', as a string. + string SigAlgOid { get; } + + /// + Asn1Encodable GetSigAlgParams(); + + /// + /// + short GetLegacySignatureAlgorithm(); + + /// + /// true if (and only if) this certificate can be used to verify the given signature algorithm. + /// + /// + bool SupportsSignatureAlgorithm(short signatureAlgorithm); + + /// + bool SupportsSignatureAlgorithmCA(short signatureAlgorithm); + + /// + /// + /// + 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 +{ + /// Base interface for a TLS bulk cipher. + public interface TlsCipher + { + /// Return the maximum input size for a ciphertext given a maximum output size for the plaintext of + /// plaintextLimit bytes. + /// the maximum output size for the plaintext. + /// the maximum input size of the ciphertext for plaintextlimit bytes of output. + int GetCiphertextDecodeLimit(int plaintextLimit); + + /// 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. + /// the actual input size for the plaintext. + /// the maximum input size for the plaintext. + /// the maximum output size of the ciphertext for plaintextlimit bytes of input. + int GetCiphertextEncodeLimit(int plaintextLength, int plaintextLimit); + + /// Return the maximum size for the plaintext given ciphertextlimit bytes of ciphertext. + /// the maximum number of bytes of ciphertext. + /// the maximum size of the plaintext for ciphertextlimit bytes of input. + int GetPlaintextLimit(int ciphertextLimit); + + /// Encode the passed in plaintext using the current bulk cipher. + /// sequence number of the message represented by plaintext. + /// content type of the message represented by plaintext. + /// used for the record. + /// extra bytes to allocate at start of returned byte array. + /// array holding input plaintext to the cipher. + /// offset into input array the plaintext starts at. + /// length of the plaintext in the array. + /// A containing the result of encoding (after 'headerAllocation' unused + /// bytes). + /// + TlsEncodeResult EncodePlaintext(long seqNo, short contentType, ProtocolVersion recordVersion, + int headerAllocation, byte[] plaintext, int offset, int len); + + /// Decode the passed in ciphertext using the current bulk cipher. + /// sequence number of the message represented by ciphertext. + /// content type used in the record for this message. + /// used for the record. + /// array holding input ciphertext to the cipher. + /// offset into input array the ciphertext starts at. + /// length of the ciphertext in the array. + /// A containing the result of decoding. + /// + TlsDecodeResult DecodeCiphertext(long seqNo, short recordType, ProtocolVersion recordVersion, + byte[] ciphertext, int offset, int len); + + /// + void RekeyDecoder(); + + /// + 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 +{ + /// Service and object creation interface for the primitive types and services that are associated with + /// cryptography in the API. + public interface TlsCrypto + { + /// Return true if this TlsCrypto can perform raw signatures and verifications for all supported + /// algorithms. + /// true if this instance can perform raw signatures and verifications for all supported algorithms, + /// false otherwise. + bool HasAllRawSignatureAlgorithms(); + + /// Return true if this TlsCrypto can support DH key agreement. + /// true if this instance can support DH key agreement, false otherwise. + bool HasDHAgreement(); + + /// Return true if this TlsCrypto can support ECDH key agreement. + /// true if this instance can support ECDH key agreement, false otherwise. + bool HasECDHAgreement(); + + /// Return true if this TlsCrypto can support the passed in block/stream encryption algorithm. + /// + /// the algorithm of interest. + /// true if encryptionAlgorithm is supported, false otherwise. + bool HasEncryptionAlgorithm(int encryptionAlgorithm); + + /// Return true if this TlsCrypto can support the passed in hash algorithm. + /// the algorithm of interest. + /// true if cryptoHashAlgorithm is supported, false otherwise. + bool HasCryptoHashAlgorithm(int cryptoHashAlgorithm); + + /// Return true if this TlsCrypto can support the passed in signature algorithm (not necessarily in + /// combination with EVERY hash algorithm). + /// the algorithm of interest. + /// true if cryptoSignatureAlgorithm is supported, false otherwise. + bool HasCryptoSignatureAlgorithm(int cryptoSignatureAlgorithm); + + /// Return true if this TlsCrypto can support the passed in MAC algorithm. + /// the algorithm of interest. + /// true if macAlgorithm is supported, false otherwise. + bool HasMacAlgorithm(int macAlgorithm); + + /// Return true if this TlsCrypto supports the passed in named group + /// value. + /// true if this instance supports the passed in named group value. + /// + bool HasNamedGroup(int namedGroup); + + /// Return true if this TlsCrypto can support RSA encryption/decryption. + /// true if this instance can support RSA encryption/decryption, false otherwise. + bool HasRsaEncryption(); + + /// Return true if this TlsCrypto can support the passed in signature algorithm (not necessarily in + /// combination with EVERY hash algorithm). + /// true if signatureAlgorithm is supported, false otherwise. + bool HasSignatureAlgorithm(short signatureAlgorithm); + + /// Return true if this TlsCrypto can support the passed in signature algorithm. + /// the algorithm of interest. + /// true if sigAndHashAlgorithm is supported, false otherwise. + bool HasSignatureAndHashAlgorithm(SignatureAndHashAlgorithm sigAndHashAlgorithm); + + /// Return true if this TlsCrypto can support the passed in signature scheme. + /// the scheme of interest. + /// true if signatureScheme is supported, false otherwise. + bool HasSignatureScheme(int signatureScheme); + + /// Return true if this TlsCrypto can support SRP authentication. + /// true if this instance can support SRP authentication, false otherwise. + bool HasSrpAuthentication(); + + /// Create a TlsSecret object based on provided data. + /// the data to base the TlsSecret on. + /// a TlsSecret based on the provided data. + TlsSecret CreateSecret(byte[] data); + + /// Create a TlsSecret object containing a randomly-generated RSA PreMasterSecret + /// the client version to place in the first 2 bytes + /// a TlsSecret containing the PreMasterSecret. + TlsSecret GenerateRsaPreMasterSecret(ProtocolVersion clientVersion); + + /// Return the primary (safest) SecureRandom for this crypto. + /// a SecureRandom suitable for key generation. + SecureRandom SecureRandom { get; } + + /// Create a TlsCertificate from an ASN.1 binary encoding of an X.509 certificate. + /// DER/BER encoding of the certificate of interest. + /// a TlsCertificate. + /// if there is an issue on decoding or constructing the certificate. + TlsCertificate CreateCertificate(byte[] encoding); + + /// Create a cipher for the specified encryption and MAC algorithms. + /// + /// See enumeration classes , for appropriate + /// argument values. + /// + /// context specific parameters. + /// the encryption algorithm to be employed by the cipher. + /// the MAC algorithm to be employed by the cipher. + /// a implementing the encryption and MAC algorithms. + /// + TlsCipher CreateCipher(TlsCryptoParameters cryptoParams, int encryptionAlgorithm, int macAlgorithm); + + /// Create a domain object supporting the domain parameters described in dhConfig. + /// the config describing the DH parameters to use. + /// a TlsDHDomain supporting the parameters in dhConfig. + TlsDHDomain CreateDHDomain(TlsDHConfig dhConfig); + + /// Create a domain object supporting the domain parameters described in ecConfig. + /// the config describing the EC parameters to use. + /// a TlsECDomain supporting the parameters in ecConfig. + TlsECDomain CreateECDomain(TlsECConfig ecConfig); + + /// Adopt the passed in secret, creating a new copy of it. + /// the secret to make a copy of. + /// a TlsSecret based on the original secret. + TlsSecret AdoptSecret(TlsSecret secret); + + /// Create a suitable hash for the hash algorithm identifier passed in. + /// + /// See enumeration class for appropriate argument values. + /// + /// the hash algorithm the hash needs to implement. + /// a . + TlsHash CreateHash(int cryptoHashAlgorithm); + + /// Create a suitable HMAC for the MAC algorithm identifier passed in. + /// + /// See enumeration class for appropriate argument values. + /// + /// the MAC algorithm the HMAC needs to match. + /// a . + TlsHmac CreateHmac(int macAlgorithm); + + /// Create a suitable HMAC using the hash algorithm identifier passed in. + /// + /// See enumeration class for appropriate argument values. + /// + /// the hash algorithm the HMAC should use. + /// a . + TlsHmac CreateHmacForHash(int cryptoHashAlgorithm); + + /// Create a nonce generator. + /// + /// Each call should construct a new generator, and the generator should be returned from this call only after + /// automatically seeding from this 's entropy source, and from the provided additional + /// seed material. The output of each returned generator must be completely independent of the others. + /// + /// context-specific seed material + /// a . + TlsNonceGenerator CreateNonceGenerator(byte[] additionalSeedMaterial); + + /// Create an SRP-6 client. + /// client config. + /// an initialised SRP6 client object. + TlsSrp6Client CreateSrp6Client(TlsSrpConfig srpConfig); + + /// Create an SRP-6 server. + /// server config. + /// the SRP6 verifier value. + /// an initialised SRP6 server object. + TlsSrp6Server CreateSrp6Server(TlsSrpConfig srpConfig, BigInteger srpVerifier); + + /// Create an SRP-6 verifier generator. + /// generator config. + /// an initialized SRP6 verifier generator. + TlsSrp6VerifierGenerator CreateSrp6VerifierGenerator(TlsSrpConfig srpConfig); + + /// Setup an initial "secret" for a chain of HKDF calls (RFC 5869), containing a string of HashLen + /// zeroes. + /// the hash algorithm to instantiate HMAC with. See + /// for values. + 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 +{ + /// Basic exception class for crypto services to pass back a cause. + 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 + /// Carrier class for context-related parameters needed for creating secrets and ciphers. + public class TlsCryptoParameters + { + private readonly TlsContext m_context; + + /// Base constructor. + /// the context for this parameters object. + 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)); + } + } + + /// + 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); + } + + /// + 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 +{ + /// Basic config for Diffie-Hellman. + 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 +{ + /// Domain interface to service factory for creating Diffie-Hellman operators. + public interface TlsDHDomain + { + /// Return an agreement operator suitable for ephemeral Diffie-Hellman. + /// a key agreement operator. + 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 +{ + /// Carrier class for Elliptic Curve parameter configuration. + public class TlsECConfig + { + protected readonly int m_namedGroup; + + public TlsECConfig(int namedGroup) + { + this.m_namedGroup = namedGroup; + } + + /// Return the group used. + /// the named group used. + 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 +{ + /// Domain interface to service factory for creating Elliptic-Curve (EC) based operators. + public interface TlsECDomain + { + /// Return an agreement operator suitable for ephemeral EC Diffie-Hellman. + /// a key agreement operator. + 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 +{ + /// Interface for message digest, or hash, services. + public interface TlsHash + { + /// Update the hash with the passed in input. + /// input array containing the data. + /// offset into the input array the input starts at. + /// the length of the input data. + void Update(byte[] input, int inOff, int length); + + /// Return calculated hash for any input passed in. + /// the hash value. + byte[] CalculateHash(); + + /// Return a clone of this hash object representing its current state. + /// a clone of the current hash. + TlsHash CloneHash(); + + /// Reset the hash underlying this service. + 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 +{ + /// Interface for MAC services based on HMAC. + public interface TlsHmac + : TlsMac + { + /// Return the internal block size for the message digest underlying this HMAC service. + /// the internal block size for the digest (in bytes). + 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 +{ + /// Interface for MAC services. + public interface TlsMac + { + /// Set the key to be used by the MAC implementation supporting this service. + /// array holding the MAC key. + /// offset into the array the key starts at. + /// length of the key in the array. + void SetKey(byte[] key, int keyOff, int keyLen); + + /// Update the MAC with the passed in input. + /// input array containing the data. + /// offset into the input array the input starts at. + /// the length of the input data. + void Update(byte[] input, int inOff, int length); + + /// Return calculated MAC for any input passed in. + /// the MAC value. + byte[] CalculateMac(); + + /// Write the calculated MAC to an output buffer. + /// output array to write the MAC to. + /// offset into the output array to write the MAC to. + void CalculateMac(byte[] output, int outOff); + + /// Return the length of the MAC generated by this service. + /// the MAC length. + int MacLength { get; } + + /// Reset the MAC underlying this service. + 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 + { + /// Generate a nonce byte[] string. + /// the length, in bytes, of the nonce to generate. + /// the nonce value. + 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 +{ + /// The cipher for TLS_NULL_WITH_NULL_NULL. + 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 +{ + /// Interface supporting the generation of key material and other SSL/TLS secret values from PRFs. + /// + public interface TlsSecret + { + /// Return a new secret based on applying a PRF to this one. + /// PRF algorithm to use. + /// the label details. + /// the seed details. + /// the size (in bytes) of the secret to generate. + /// the new secret. + TlsSecret DeriveUsingPrf(int prfAlgorithm, string label, byte[] seed, int length); + + /// Destroy the internal state of the secret. + /// + /// After this call, any attempt to use the will result in an + /// being thrown. + /// + void Destroy(); + + /// Return an encrypted copy of the data this secret is based on. + /// the certificate containing the public key to use for protecting the internal + /// data. + /// an encrypted copy of this secret's internal data. + /// + byte[] Encrypt(TlsCertificate certificate); + + /// Return the internal data from this secret. + /// + /// The does not keep a copy of the data. After this call, any attempt to use the + /// will result in an being thrown. + /// + /// the secret's internal data. + byte[] Extract(); + + /// RFC 5869 HKDF-Expand function, with this secret's data as the pseudo-random key ('prk'). + /// the hash algorithm to instantiate HMAC with. See + /// for values. + /// optional context and application specific information (can be zero-length). + /// length of output keying material in octets. + /// output keying material (of 'length' octets). + TlsSecret HkdfExpand(int cryptoHashAlgorithm, byte[] info, int length); + + /// RFC 5869 HKDF-Extract function, with this secret's data as the 'salt'. + /// + /// The does not keep a copy of the data. After this call, any attempt to use + /// the will result in an being thrown. + /// + /// the hash algorithm to instantiate HMAC with. See + /// for values. + /// input keying material. + /// a pseudo-random key (of HashLen octets). + 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 +{ + /// Base interface for a TLS signer that works on raw message digests. + public interface TlsSigner + { + /// Generate an encoded signature based on the passed in hash. + /// the signature algorithm to use. + /// the hash calculated for the signature. + /// an encoded signature. + /// in case of an exception processing the hash. + byte[] GenerateRawSignature(SignatureAndHashAlgorithm algorithm, byte[] hash); + + /// + 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 +{ + /// Basic interface for an SRP-6 client implementation. + public interface TlsSrp6Client + { + /// Generates the secret S given the server's credentials + /// The server's credentials + /// Client's verification message for the server + /// If server's credentials are invalid + BigInteger CalculateSecret(BigInteger serverB); + + /// Generates client's credentials given the client's salt, identity and password + /// The salt used in the client's verifier. + /// The user's identity (eg. username) + /// The user's password + /// Client's public value to send to server + 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 +{ + /// Basic interface for an SRP-6 server implementation. + public interface TlsSrp6Server + { + /// Generates the server's credentials that are to be sent to the client. + /// The server's public value to the client + BigInteger GenerateServerCredentials(); + + /// Processes the client's credentials. If valid the shared secret is generated and returned. + /// + /// The client's credentials. + /// A shared secret . + /// If client's credentials are invalid. + 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 +{ + /// Base interface for a generator for SRP-6 verifiers. + public interface TlsSrp6VerifierGenerator + { + /// Creates a new SRP-6 verifier value. + /// The salt to use, generally should be large and random + /// The user's identifying information (eg. username) + /// The user's password + /// A new verifier for use in future SRP authentication + 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 +{ + /// Basic config for SRP. + public class TlsSrpConfig + { + protected BigInteger[] m_explicitNG; + + /// Return the (N, g) values used in SRP-6. + /// (N, g) as a BigInteger array (N=[0], g=[1]). + public BigInteger[] GetExplicitNG() + { + return (BigInteger[])m_explicitNG.Clone(); + } + + /// Set the (N, g) values used for SRP-6. + /// (N, g) as a BigInteger array (N=[0], g=[1]). + 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 + { + /// + Stream GetOutputStream(); + + /// + 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 + { + /// + Stream GetOutputStream(); + + /// + 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 +{ + /// Base interface for a TLS verifier that works with signatures and either raw message digests, or entire + /// messages. + public interface TlsVerifier + { + /// + TlsStreamVerifier GetStreamVerifier(DigitallySigned signature); + + /// Return true if the passed in signature and hash represent a real signature. + /// the signature object containing the signature to be verified. + /// the hash calculated for the signature. + /// true if signature verifies, false otherwise. + /// in case of an exception verifying signature. + 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 +{ + /// Base class for a TlsCrypto implementation that provides some needed methods from elsewhere in the impl + /// package. + 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); + + /// Return an encryptor based on the public key in certificate. + /// the certificate carrying the public key. + /// a based on the certificate's public key. + /// + 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 +{ + /// Base class for a TlsSecret implementation which captures common code and fields. + public abstract class AbstractTlsSecret + : TlsSecret + { + protected byte[] m_data; + + /// Base constructor. + /// the byte[] making up the secret value. + 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; + } + } + } + + /// + 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 +{ + /// A generic TLS 1.2 AEAD cipher. + 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; + + /// + 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 +{ + /// Base interface for services supporting AEAD encryption/decryption. + public interface TlsAeadCipherImpl + { + /// Set the key to be used by the AEAD cipher implementation supporting this service. + /// array holding the AEAD cipher key. + /// offset into the array the key starts at. + /// length of the key in the array. + /// + void SetKey(byte[] key, int keyOff, int keyLen); + + /// Initialise the parameters for the AEAD operator. + /// the nonce. + /// MAC size in bytes. + /// any additional data to be included in the MAC calculation. + /// if the parameters are inappropriate. + void Init(byte[] nonce, int macSize, byte[] additionalData); + + /// Return the maximum size of the output for input of inputLength bytes. + /// the length (in bytes) of the proposed input. + /// the maximum size of the output. + int GetOutputSize(int inputLength); + + /// Perform the cipher encryption/decryption returning the output in output. + /// + /// Note: we have to use DoFinal() here as it is the only way to guarantee output from the underlying cipher. + /// + /// array holding input data to the cipher. + /// offset into input array data starts at. + /// length of the input data in the array. + /// array to hold the cipher output. + /// offset into output array to start saving output. + /// the amount of data written to output. + /// in case of failure. + 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 +{ + /// A generic TLS 1.0-1.2 block cipher. This can be used for AES or 3DES for example. + 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; + + /// + 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 +{ + /// Interface for block cipher services. + public interface TlsBlockCipherImpl + { + /// Set the key to be used by the block cipher implementation supporting this service. + /// array holding the block cipher key. + /// offset into the array the key starts at. + /// length of the key in the array. + /// + void SetKey(byte[] key, int keyOff, int keyLen); + + /// Initialise the parameters for operator. + /// array holding the initialization vector (IV). + /// offset into the array the IV starts at. + /// length of the IV in the array. + /// if the parameters are inappropriate. + void Init(byte[] iv, int ivOff, int ivLen); + + /// Perform the cipher encryption/decryption returning the output in output. + /// + /// Note: we have to use DoFinal() here as it is the only way to guarantee output from the underlying cipher. + /// + /// array holding input data to the cipher. + /// offset into input array data starts at. + /// length of the input data in the array. + /// array to hold the cipher output. + /// offset into output array to start saving output. + /// the amount of data written to output. + /// in case of failure. + int DoFinal(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset); + + /// Return the blocksize (in bytes) of the underlying block cipher. + /// the cipher's blocksize. + 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 +{ + /// Base interface for an encryptor based on a public key. + public interface TlsEncryptor + { + /// Encrypt data from the passed in input array. + /// byte array containing the input data. + /// offset into input where the data starts. + /// the length of the data to encrypt. + /// the encrypted data. + /// + 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 +{ + /// Useful utility methods. + 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 +{ + /// The NULL cipher. + public class TlsNullCipher + : TlsCipher + { + protected readonly TlsCryptoParameters m_cryptoParams; + protected readonly TlsSuiteHmac m_readMac, m_writeMac; + + /// + 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 +{ + /// A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest. + 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; + + /// Generate a new instance of a TlsMac. + /// the TLS client context specific crypto parameters. + /// The MAC to use. + 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 +{ + /// Base interface for a generic TLS MAC implementation for use with a bulk cipher. + public interface TlsSuiteMac + { + /// Return the output length (in bytes) of this MAC. + /// The output length of this MAC. + int Size { get; } + + /// Calculate the MAC for some given data. + /// The sequence number of the record. + /// The content type of the message. + /// A byte array containing the message. + /// The number of bytes to skip, before the message starts. + /// The length of the message. + /// A new byte array containing the MAC value. + byte[] CalculateMac(long seqNo, short type, byte[] message, int offset, int length); + + /// Constant time calculation of the MAC for some given data with a given expected length. + /// The sequence number of the record. + /// The content type of the message. + /// A byte array containing the message. + /// The number of bytes to skip, before the message starts. + /// The length of the message. + /// The expected length of the full message. + /// Random data for padding out the MAC calculation if required. + /// A new byte array containing the MAC value. + 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 +{ + /// Credentialed class generating agreed secrets from a peer's public key for our end of the TLS connection + /// using the BC light-weight API. + 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 +{ + /// Credentialed class decrypting RSA encrypted secrets sent from a peer for our end of the TLS connection + /// using the BC light-weight API. + 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 +{ + /// Credentialed class for generating signatures based on the use of primitives from the BC light-weight API. + 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 +{ + /// HMAC implementation based on original internet draft for HMAC (RFC 2104). + /// + /// The difference is that padding is concatenated versus XORed with the key, e.g: + /// H(K + opad, H(K + ipad, text)) + /// + 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; + + /// Base constructor for one of the standard digest algorithms for which the byteLength is known. + /// + /// + /// Behaviour is undefined for digests other than MD5 or SHA1. + /// + /// the digest. + 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 +{ + /// Implementation class for a single X.509 certificate based on the BC light-weight API. + public class BcTlsCertificate + : TlsCertificate + { + /// + public static BcTlsCertificate Convert(BcTlsCrypto crypto, TlsCertificate certificate) + { + if (certificate is BcTlsCertificate) + return (BcTlsCertificate)certificate; + + return new BcTlsCertificate(crypto, certificate.GetEncoded()); + } + + /// + 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; + + /// + public BcTlsCertificate(BcTlsCrypto crypto, byte[] encoding) + : this(crypto, ParseCertificate(encoding)) + { + } + + public BcTlsCertificate(BcTlsCrypto crypto, X509CertificateStructure certificate) + { + this.m_crypto = crypto; + this.m_certificate = certificate; + } + + /// + 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); + } + } + + /// + 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); + } + } + + /// + public virtual byte[] GetEncoded() + { + return m_certificate.GetEncoded(Asn1Encodable.Der); + } + + /// + 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; + } + + /// + 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; + } + + /// + public virtual DHPublicKeyParameters GetPubKeyDH() + { + try + { + return (DHPublicKeyParameters)GetPublicKey(); + } + catch (InvalidCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + } + + /// + public virtual DsaPublicKeyParameters GetPubKeyDss() + { + try + { + return (DsaPublicKeyParameters)GetPublicKey(); + } + catch (InvalidCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + } + + /// + public virtual ECPublicKeyParameters GetPubKeyEC() + { + try + { + return (ECPublicKeyParameters)GetPublicKey(); + } + catch (InvalidCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + } + + /// + public virtual Ed25519PublicKeyParameters GetPubKeyEd25519() + { + try + { + return (Ed25519PublicKeyParameters)GetPublicKey(); + } + catch (InvalidCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + } + + /// + public virtual Ed448PublicKeyParameters GetPubKeyEd448() + { + try + { + return (Ed448PublicKeyParameters)GetPublicKey(); + } + catch (InvalidCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + } + + /// + public virtual RsaKeyParameters GetPubKeyRsa() + { + try + { + return (RsaKeyParameters)GetPublicKey(); + } + catch (InvalidCastException e) + { + throw new TlsFatalAlert(AlertDescription.certificate_unknown, e); + } + } + + /// + public virtual bool SupportsSignatureAlgorithm(short signatureAlgorithm) + { + return SupportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.DigitalSignature); + } + + /// + public virtual bool SupportsSignatureAlgorithmCA(short signatureAlgorithm) + { + return SupportsSignatureAlgorithm(signatureAlgorithm, KeyUsage.KeyCertSign); + } + + /// + 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); + } + + /// + 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); + } + + /// + 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; + } + } + + /// + public virtual void ValidateKeyUsage(int keyUsageBits) + { + if (!SupportsKeyUsage(keyUsageBits)) + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + /// + protected virtual void ValidateRsa_Pkcs1() + { + if (!SupportsRsa_Pkcs1()) + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + /// + protected virtual void ValidateRsa_Pss_Pss(short signatureAlgorithm) + { + if (!SupportsRsa_Pss_Pss(signatureAlgorithm)) + throw new TlsFatalAlert(AlertDescription.certificate_unknown); + } + + /// + 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. + *

+ * This class provides default implementations for everything. If you need to customise it, extend the class + * and override the appropriate methods. + *

+ */ + 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 +{ + /// Support class for ephemeral Diffie-Hellman using the BC light-weight library. + 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; + } + + /// + public virtual byte[] GenerateEphemeral() + { + this.m_localKeyPair = m_domain.GenerateKeyPair(); + + return m_domain.EncodePublicKey((DHPublicKeyParameters)m_localKeyPair.Public); + } + + /// + public virtual void ReceivePeerValue(byte[] peerValue) + { + this.m_peerPublicKey = m_domain.DecodePublicKey(peerValue); + } + + /// + 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 +{ + /// BC light-weight support class for Diffie-Hellman key pair generation and key agreement over a + /// specified Diffie-Hellman configuration. + 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); + } + + /// + public virtual BigInteger DecodeParameter(byte[] encoding) + { + if (dhConfig.IsPadded && GetValueLength(dhParameters) != encoding.Length) + throw new TlsFatalAlert(AlertDescription.illegal_parameter); + + return new BigInteger(1, encoding); + } + + /// + 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); + } + } + + /// + public virtual byte[] EncodeParameter(BigInteger x) + { + return EncodeValue(dhParameters, dhConfig.IsPadded, x); + } + + /// + 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 +{ + /// Implementation class for generation of the raw DSA signature type using the BC light-weight API. + /// + 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 +{ + /// Implementation class for the verification of the raw DSA signature type using the BC light-weight API. + /// + 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 +{ + /// BC light-weight base class for the signers implementing the two DSA style algorithms from FIPS PUB + /// 186-4: DSA and ECDSA. + 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 +{ + /// BC light-weight base class for the verifiers supporting the two DSA style algorithms from FIPS PUB + /// 186-4: DSA and ECDSA. + 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 +{ + /// Support class for ephemeral Elliptic Curve Diffie-Hellman using the BC light-weight library. + 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 +{ + /// Implementation class for generation of ECDSA signatures in TLS 1.3+ using the BC light-weight API. + /// + 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 +{ + /// Implementation class for verification of ECDSA signatures in TLS 1.3+ using the BC light-weight API. + /// + 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 +{ + /// Implementation class for generation of the raw ECDSA signature type using the BC light-weight API. + /// + 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 +{ + /// Implementation class for the verification of the raw ECDSA signature type using the BC light-weight + /// API. + 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 +{ + /// Operator supporting the generation of RSASSA-PSS signatures using the BC light-weight API. + 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 +{ + /// Operator supporting the verification of RSASSA-PSS signatures using the BC light-weight API. + 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 +{ + /// Operator supporting the generation of RSASSA-PKCS1-v1_5 signatures using the BC light-weight API. + /// + 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 +{ + /// Operator supporting the verification of RSASSA-PKCS1-v1_5 signatures using the BC light-weight API. + /// + 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 +{ + /// BC light-weight support class for handling TLS secrets and deriving key material and other secrets + /// from them. + 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 +{ + /// Support class for X25519 using the BC light-weight library. + 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 +{ + /// Support class for X448 using the BC light-weight library. + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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; + + /// We generate the CA's certificate. + 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); + } + + /// We generate an intermediate certificate signed by our CA. + 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); + } + + /// We generate a certificate signed by our CA's intermediate certificate. + public static X509Certificate CreateEndEntityCert(string endEntityDN, AsymmetricKeyParameter pubKey, + AsymmetricKeyParameter caPrivKey, X509Certificate caCert) + { + X509V3CertificateGenerator gen = CreateBaseEndEntityGenerator(endEntityDN, pubKey, caCert); + + return SignV3(gen, caPrivKey); + } + + /// We generate a certificate signed by our CA's intermediate certificate with ExtendedKeyUsage + /// extension. + 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 +{ + /// A simple test designed to conduct a TLS handshake with an external TLS server. + /// + /// 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.
+ [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[] { "", "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 +{ + /// A simple test designed to conduct a TLS handshake with an external TLS client. + /// + /// 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. + /// + [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 +{ + /// A simple test designed to conduct a TLS handshake with an external TLS server. + /// + /// 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. + /// + [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[] { "", "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 +{ + /// A simple test designed to conduct a TLS handshake with an external TLS client. + /// + /// 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. + /// + [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; + + /// Client does not authenticate, ignores any certificate request. + public const int CLIENT_AUTH_NONE = 0; + + /// Client will authenticate if it receives a certificate request. + public const int CLIENT_AUTH_VALID = 1; + + /// Client will authenticate if it receives a certificate request, with an invalid certificate. + /// + public const int CLIENT_AUTH_INVALID_CERT = 2; + + /// Client will authenticate if it receives a certificate request, with an invalid CertificateVerify + /// signature. + public const int CLIENT_AUTH_INVALID_VERIFY = 3; + + public const int CRYPTO_BC = 0; + + /// Server will not request a client certificate. + public const int SERVER_CERT_REQ_NONE = 0; + + /// Server will request a client certificate but receiving one is optional. + public const int SERVER_CERT_REQ_OPTIONAL = 1; + + /// Server will request a client certificate and receiving one is mandatory. + public const int SERVER_CERT_REQ_MANDATORY = 2; + + /// Configures the client authentication behaviour of the test client. Use CLIENT_AUTH_* constants. + /// + public int clientAuth = CLIENT_AUTH_VALID; + + /// 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). + public SignatureAndHashAlgorithm clientAuthSigAlg = null; + + /// 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. + public SignatureAndHashAlgorithm clientAuthSigAlgClaimed = null; + + /// Control whether the client will call + /// to check the server + /// certificate chain. + public bool clientCheckSigAlgOfServerCerts = true; + + public int clientCrypto = CRYPTO_BC; + + /// Configures whether the client will send an empty key_share extension in initial ClientHello. + /// + public bool clientEmptyKeyShare = false; + + /// Configures whether the client will indicate version fallback via TLS_FALLBACK_SCSV. + public bool clientFallback = false; + + /// Configures whether a (TLS 1.2+) client may send the signature_algorithms extension in ClientHello. + /// + public bool clientSendSignatureAlgorithms = true; + + /// Configures whether a (TLS 1.2+) client may send the signature_algorithms_cert extension in + /// ClientHello. + public bool clientSendSignatureAlgorithmsCert = true; + + /// Configures the supported protocol versions for the client. If null, uses the library's default. + /// + public ProtocolVersion[] clientSupportedVersions = null; + + /// 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). + public SignatureAndHashAlgorithm serverAuthSigAlg = null; + + /// Configures whether the test server will send a certificate request. + public int serverCertReq = SERVER_CERT_REQ_OPTIONAL; + + /// 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. + public IList serverCertReqSigAlgs = null; + + /// Control whether the server will call + /// to check the client + /// certificate chain. + public bool serverCheckSigAlgOfClientCerts = true; + + public int serverCrypto = CRYPTO_BC; + + /// Configures a protocol version the server will unconditionally negotiate. + /// + /// Ignored if null. + /// + public ProtocolVersion serverNegotiateVersion = null; + + /// Configures the supported protocol versions for the server. + /// + /// If null, uses the library's default. + /// + public ProtocolVersion[] serverSupportedVersions = null; + + /// Configures the connection end at which a fatal alert is expected to be raised. + /// + /// Use constants. + /// + public int expectFatalAlertConnectionEnd = -1; + + /// Configures the type of fatal alert expected to be raised. + /// + /// Use constants. + /// + 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; + } + } +} -- cgit 1.4.1