Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java/org/spongycastle/crypto/tls')
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsAgreementCredentials.java7
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsCipherFactory.java13
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsClient.java235
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsContext.java133
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsCredentials.java6
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsEncryptionCredentials.java7
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsKeyExchange.java166
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsPeer.java42
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsServer.java335
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsSigner.java38
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsSignerCredentials.java11
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AlertDescription.java216
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AlertLevel.java10
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/AlwaysValidVerifyer.java23
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/BulkCipherAlgorithm.java23
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ByteQueue.java153
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CertChainType.java15
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/Certificate.java148
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CertificateRequest.java158
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CertificateStatus.java105
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CertificateStatusRequest.java98
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CertificateStatusType.java9
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CertificateURL.java133
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CertificateVerifyer.java16
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/Chacha20Poly1305.java156
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ChangeCipherSpec.java6
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CipherSuite.java351
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CipherType.java18
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ClientAuthenticationType.java11
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ClientCertificateType.java22
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CombinedHash.java135
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/CompressionMethod.java24
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ConnectionEnd.java13
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ContentType.java13
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSClientProtocol.java831
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSEpoch.java52
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSHandshakeRetransmit.java9
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSProtocol.java78
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSReassembler.java133
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSRecordLayer.java516
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSReliableHandshake.java453
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSReplayWindow.java90
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSServerProtocol.java667
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DTLSTransport.java80
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DatagramTransport.java21
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsAgreementCredentials.java78
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsCipherFactory.java237
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsClient.java453
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsEncryptionCredentials.java62
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsServer.java550
-rwxr-xr-xcore/src/main/java/org/spongycastle/crypto/tls/DefaultTlsSignerCredentials.java104
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DeferredHash.java207
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DigestAlgorithm.java23
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DigestInputBuffer.java13
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/DigitallySigned.java72
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ECBasisType.java15
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ECCurveType.java28
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ECPointFormat.java15
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/EncryptionAlgorithm.java67
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ExporterLabel.java31
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ExtensionType.java60
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/HandshakeType.java39
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/HashAlgorithm.java15
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/HeartbeatExtension.java56
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMessage.java111
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMessageType.java15
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMode.java15
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/KeyExchangeAlgorithm.java52
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/LegacyTlsAuthentication.java28
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/LegacyTlsClient.java33
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/MACAlgorithm.java23
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/MaxFragmentLength.java17
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/NameType.java9
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/NamedCurve.java71
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/NewSessionTicket.java55
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/OCSPStatusRequest.java131
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/PRFAlgorithm.java22
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/PSKTlsClient.java256
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ProtocolVersion.java158
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/RecordStream.java365
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SRPTlsClient.java121
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SRTPProtectionProfile.java12
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SSL3Mac.java114
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SecurityParameters.java91
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ServerDHParams.java63
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ServerName.java112
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ServerNameList.java86
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/ServerOnlyTlsAuthentication.java10
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SessionParameters.java142
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SignatureAlgorithm.java12
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SignatureAndHashAlgorithm.java96
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SignerInputBuffer.java13
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SupplementalDataEntry.java23
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/SupplementalDataType.java12
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsAEADCipher.java193
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsAgreementCredentials.java12
-rwxr-xr-xcore/src/main/java/org/spongycastle/crypto/tls/TlsAuthentication.java26
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsBlockCipher.java386
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsCipher.java14
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsCipherFactory.java12
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsClient.java79
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsClientContext.java6
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsClientContextImpl.java18
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsClientProtocol.java917
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsCompression.java10
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsContext.java45
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsCredentials.java6
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsDHEKeyExchange.java115
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsDHKeyExchange.java208
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsDHUtils.java105
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsDSASigner.java92
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsDSSSigner.java26
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsECCUtils.java690
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsECDHEKeyExchange.java221
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsECDHKeyExchange.java219
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsECDSASigner.java26
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsEncryptionCredentials.java9
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsExtensionsUtils.java267
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsFatalAlert.java21
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsHandshakeHash.java21
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsInputStream.java47
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsKeyExchange.java54
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsMac.java172
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsNullCipher.java123
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsNullCompression.java17
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsOutputStream.java44
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsPSKIdentity.java12
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsPSKKeyExchange.java285
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsPeer.java46
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java1184
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsProtocolHandler.java22
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsRSAKeyExchange.java191
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsRSASigner.java112
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsRSAUtils.java140
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsSRPKeyExchange.java205
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsSRPUtils.java48
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsSRTPUtils.java74
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsServer.java90
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsServerContext.java6
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsServerContextImpl.java18
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsServerProtocol.java778
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsSession.java12
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsSessionImpl.java48
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsSigner.java34
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsSignerCredentials.java12
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsStreamCipher.java178
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/TlsUtils.java1780
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/UDPTransport.java75
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/URLAndHash.java104
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/UseSRTPData.java52
-rw-r--r--core/src/main/java/org/spongycastle/crypto/tls/UserMappingType.java12
151 files changed, 19891 insertions, 0 deletions
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsAgreementCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsAgreementCredentials.java
new file mode 100644
index 00000000..188dfafe
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsAgreementCredentials.java
@@ -0,0 +1,7 @@
+package org.spongycastle.crypto.tls;
+
+public abstract class AbstractTlsAgreementCredentials
+ extends AbstractTlsCredentials
+ implements TlsAgreementCredentials
+{
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsCipherFactory.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsCipherFactory.java
new file mode 100644
index 00000000..755c7b7e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsCipherFactory.java
@@ -0,0 +1,13 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public class AbstractTlsCipherFactory
+ implements TlsCipherFactory
+{
+ public TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm)
+ throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsClient.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsClient.java
new file mode 100644
index 00000000..81c399ff
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsClient.java
@@ -0,0 +1,235 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+public abstract class AbstractTlsClient
+ extends AbstractTlsPeer
+ implements TlsClient
+{
+ protected TlsCipherFactory cipherFactory;
+
+ protected TlsClientContext context;
+
+ protected Vector supportedSignatureAlgorithms;
+ protected int[] namedCurves;
+ protected short[] clientECPointFormats, serverECPointFormats;
+
+ protected int selectedCipherSuite;
+ protected short selectedCompressionMethod;
+
+ public AbstractTlsClient()
+ {
+ this(new DefaultTlsCipherFactory());
+ }
+
+ public AbstractTlsClient(TlsCipherFactory cipherFactory)
+ {
+ this.cipherFactory = cipherFactory;
+ }
+
+ public void init(TlsClientContext context)
+ {
+ this.context = context;
+ }
+
+ public TlsSession getSessionToResume()
+ {
+ return null;
+ }
+
+ /**
+ * RFC 5246 E.1. "TLS clients that wish to negotiate with older servers MAY send any value
+ * {03,XX} as the record layer version number. Typical values would be {03,00}, the lowest
+ * version number supported by the client, and the value of ClientHello.client_version. No
+ * single value will guarantee interoperability with all old servers, but this is a complex
+ * topic beyond the scope of this document."
+ */
+ public ProtocolVersion getClientHelloRecordLayerVersion()
+ {
+ // "{03,00}"
+ // return ProtocolVersion.SSLv3;
+
+ // "the lowest version number supported by the client"
+ // return getMinimumVersion();
+
+ // "the value of ClientHello.client_version"
+ return getClientVersion();
+ }
+
+ public ProtocolVersion getClientVersion()
+ {
+ return ProtocolVersion.TLSv12;
+ }
+
+ public Hashtable getClientExtensions()
+ throws IOException
+ {
+ Hashtable clientExtensions = null;
+
+ ProtocolVersion clientVersion = context.getClientVersion();
+
+ /*
+ * 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 (TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion))
+ {
+ // TODO Provide a way for the user to specify the acceptable hash/signature algorithms.
+
+ short[] hashAlgorithms = new short[]{ HashAlgorithm.sha512, HashAlgorithm.sha384, HashAlgorithm.sha256,
+ HashAlgorithm.sha224, HashAlgorithm.sha1 };
+
+ // TODO Sort out ECDSA signatures and add them as the preferred option here
+ short[] signatureAlgorithms = new short[]{ SignatureAlgorithm.rsa };
+
+ this.supportedSignatureAlgorithms = new Vector();
+ for (int i = 0; i < hashAlgorithms.length; ++i)
+ {
+ for (int j = 0; j < signatureAlgorithms.length; ++j)
+ {
+ this.supportedSignatureAlgorithms.addElement(new SignatureAndHashAlgorithm(hashAlgorithms[i],
+ signatureAlgorithms[j]));
+ }
+ }
+
+ /*
+ * RFC 5264 7.4.3. Currently, DSA [DSS] may only be used with SHA-1.
+ */
+ this.supportedSignatureAlgorithms.addElement(new SignatureAndHashAlgorithm(HashAlgorithm.sha1,
+ SignatureAlgorithm.dsa));
+
+ clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(clientExtensions);
+
+ TlsUtils.addSignatureAlgorithmsExtension(clientExtensions, supportedSignatureAlgorithms);
+ }
+
+ if (TlsECCUtils.containsECCCipherSuites(getCipherSuites()))
+ {
+ /*
+ * RFC 4492 5.1. A client that proposes ECC cipher suites in its ClientHello message
+ * appends these extensions (along with any others), enumerating the curves it supports
+ * and the point formats it can parse. Clients SHOULD send both the Supported Elliptic
+ * Curves Extension and the Supported Point Formats Extension.
+ */
+ /*
+ * TODO Could just add all the curves since we support them all, but users may not want
+ * to use unnecessarily large fields. Need configuration options.
+ */
+ this.namedCurves = new int[]{ NamedCurve.secp256r1, NamedCurve.secp384r1 };
+ this.clientECPointFormats = new short[]{ ECPointFormat.uncompressed,
+ ECPointFormat.ansiX962_compressed_prime, ECPointFormat.ansiX962_compressed_char2, };
+
+ clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(clientExtensions);
+
+ TlsECCUtils.addSupportedEllipticCurvesExtension(clientExtensions, namedCurves);
+ TlsECCUtils.addSupportedPointFormatsExtension(clientExtensions, clientECPointFormats);
+ }
+
+ return clientExtensions;
+ }
+
+ public ProtocolVersion getMinimumVersion()
+ {
+ return ProtocolVersion.TLSv10;
+ }
+
+ public void notifyServerVersion(ProtocolVersion serverVersion)
+ throws IOException
+ {
+ if (!getMinimumVersion().isEqualOrEarlierVersionOf(serverVersion))
+ {
+ throw new TlsFatalAlert(AlertDescription.protocol_version);
+ }
+ }
+
+ public short[] getCompressionMethods()
+ {
+ return new short[]{CompressionMethod._null};
+ }
+
+ public void notifySessionID(byte[] sessionID)
+ {
+ // Currently ignored
+ }
+
+ public void notifySelectedCipherSuite(int selectedCipherSuite)
+ {
+ this.selectedCipherSuite = selectedCipherSuite;
+ }
+
+ public void notifySelectedCompressionMethod(short selectedCompressionMethod)
+ {
+ this.selectedCompressionMethod = selectedCompressionMethod;
+ }
+
+ public void processServerExtensions(Hashtable serverExtensions)
+ throws IOException
+ {
+ /*
+ * TlsProtocol implementation validates that any server extensions received correspond to
+ * client extensions sent. By default, we don't send any, and this method is not called.
+ */
+ if (serverExtensions != null)
+ {
+ /*
+ * RFC 5246 7.4.1.4.1. Servers MUST NOT send this extension.
+ */
+ if (serverExtensions.containsKey(TlsUtils.EXT_signature_algorithms))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ int[] namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(serverExtensions);
+ if (namedCurves != null)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ this.serverECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(serverExtensions);
+ if (this.serverECPointFormats != null && !TlsECCUtils.isECCCipherSuite(this.selectedCipherSuite))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+ }
+
+ public void processServerSupplementalData(Vector serverSupplementalData)
+ throws IOException
+ {
+ if (serverSupplementalData != null)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ public Vector getClientSupplementalData()
+ throws IOException
+ {
+ return null;
+ }
+
+ public TlsCompression getCompression()
+ throws IOException
+ {
+ switch (selectedCompressionMethod)
+ {
+ case CompressionMethod._null:
+ return new TlsNullCompression();
+
+ default:
+ /*
+ * Note: internal error here; the TlsProtocol implementation verifies that the
+ * server-selected compression method was in the list of client-offered compression
+ * methods, so if we now can't produce an implementation, we shouldn't have offered it!
+ */
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public void notifyNewSessionTicket(NewSessionTicket newSessionTicket)
+ throws IOException
+ {
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsContext.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsContext.java
new file mode 100644
index 00000000..a9504bef
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsContext.java
@@ -0,0 +1,133 @@
+package org.spongycastle.crypto.tls;
+
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.prng.DigestRandomGenerator;
+import org.spongycastle.crypto.prng.RandomGenerator;
+import org.spongycastle.util.Times;
+
+abstract class AbstractTlsContext
+ implements TlsContext
+{
+ private static long counter = Times.nanoTime();
+
+ private synchronized static long nextCounterValue()
+ {
+ return ++counter;
+ }
+
+ private RandomGenerator nonceRandom;
+ private SecureRandom secureRandom;
+ private SecurityParameters securityParameters;
+
+ private ProtocolVersion clientVersion = null;
+ private ProtocolVersion serverVersion = null;
+ private TlsSession session = null;
+ private Object userObject = null;
+
+ AbstractTlsContext(SecureRandom secureRandom, SecurityParameters securityParameters)
+ {
+ secureRandom.setSeed(nextCounterValue());
+ secureRandom.setSeed(Times.nanoTime());
+
+ this.nonceRandom = new DigestRandomGenerator(TlsUtils.createHash(HashAlgorithm.sha256));
+ this.nonceRandom.addSeedMaterial(secureRandom.generateSeed(32));
+
+ this.secureRandom = secureRandom;
+ this.securityParameters = securityParameters;
+ }
+
+ public RandomGenerator getNonceRandomGenerator()
+ {
+ return nonceRandom;
+ }
+
+ public SecureRandom getSecureRandom()
+ {
+ return secureRandom;
+ }
+
+ public SecurityParameters getSecurityParameters()
+ {
+ return securityParameters;
+ }
+
+ public ProtocolVersion getClientVersion()
+ {
+ return clientVersion;
+ }
+
+ void setClientVersion(ProtocolVersion clientVersion)
+ {
+ this.clientVersion = clientVersion;
+ }
+
+ public ProtocolVersion getServerVersion()
+ {
+ return serverVersion;
+ }
+
+ void setServerVersion(ProtocolVersion serverVersion)
+ {
+ this.serverVersion = serverVersion;
+ }
+
+ public TlsSession getResumableSession()
+ {
+ return session;
+ }
+
+ void setResumableSession(TlsSession session)
+ {
+ this.session = session;
+ }
+
+ public Object getUserObject()
+ {
+ return userObject;
+ }
+
+ public void setUserObject(Object userObject)
+ {
+ this.userObject = userObject;
+ }
+
+ public byte[] exportKeyingMaterial(String asciiLabel, byte[] context_value, int length)
+ {
+ if (context_value != null && !TlsUtils.isValidUint16(context_value.length))
+ {
+ throw new IllegalArgumentException("'context_value' must have length less than 2^16 (or be null)");
+ }
+
+ SecurityParameters sp = getSecurityParameters();
+ byte[] cr = sp.getClientRandom(), sr = sp.getServerRandom();
+
+ int seedLength = cr.length + sr.length;
+ if (context_value != null)
+ {
+ seedLength += (2 + context_value.length);
+ }
+
+ byte[] seed = new byte[seedLength];
+ int seedPos = 0;
+
+ System.arraycopy(cr, 0, seed, seedPos, cr.length);
+ seedPos += cr.length;
+ System.arraycopy(sr, 0, seed, seedPos, sr.length);
+ seedPos += sr.length;
+ if (context_value != null)
+ {
+ TlsUtils.writeUint16(context_value.length, seed, seedPos);
+ seedPos += 2;
+ System.arraycopy(context_value, 0, seed, seedPos, context_value.length);
+ seedPos += context_value.length;
+ }
+
+ if (seedPos != seedLength)
+ {
+ throw new IllegalStateException("error in calculation of seed for export");
+ }
+
+ return TlsUtils.PRF(this, sp.getMasterSecret(), asciiLabel, seed, length);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsCredentials.java
new file mode 100644
index 00000000..ab6cda7e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsCredentials.java
@@ -0,0 +1,6 @@
+package org.spongycastle.crypto.tls;
+
+public abstract class AbstractTlsCredentials
+ implements TlsCredentials
+{
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsEncryptionCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsEncryptionCredentials.java
new file mode 100644
index 00000000..8eb0147e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsEncryptionCredentials.java
@@ -0,0 +1,7 @@
+package org.spongycastle.crypto.tls;
+
+public abstract class AbstractTlsEncryptionCredentials
+ extends AbstractTlsCredentials
+ implements TlsEncryptionCredentials
+{
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsKeyExchange.java
new file mode 100644
index 00000000..4920007d
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsKeyExchange.java
@@ -0,0 +1,166 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+public abstract class AbstractTlsKeyExchange
+ implements TlsKeyExchange
+{
+ protected int keyExchange;
+ protected Vector supportedSignatureAlgorithms;
+
+ protected TlsContext context;
+
+ protected AbstractTlsKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms)
+ {
+ this.keyExchange = keyExchange;
+ this.supportedSignatureAlgorithms = supportedSignatureAlgorithms;
+ }
+
+ public void init(TlsContext context)
+ {
+ this.context = context;
+
+ ProtocolVersion clientVersion = context.getClientVersion();
+
+ if (TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion))
+ {
+ /*
+ * RFC 5264 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}.
+ */
+ if (this.supportedSignatureAlgorithms == null)
+ {
+ switch (keyExchange)
+ {
+ case KeyExchangeAlgorithm.DH_DSS:
+ case KeyExchangeAlgorithm.DHE_DSS:
+ case KeyExchangeAlgorithm.SRP_DSS:
+ {
+ this.supportedSignatureAlgorithms = TlsUtils.getDefaultDSSSignatureAlgorithms();
+ break;
+ }
+
+ case KeyExchangeAlgorithm.ECDH_ECDSA:
+ case KeyExchangeAlgorithm.ECDHE_ECDSA:
+ {
+ this.supportedSignatureAlgorithms = TlsUtils.getDefaultECDSASignatureAlgorithms();
+ break;
+ }
+
+ 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:
+ {
+ this.supportedSignatureAlgorithms = TlsUtils.getDefaultRSASignatureAlgorithms();
+ break;
+ }
+
+ case KeyExchangeAlgorithm.DHE_PSK:
+ case KeyExchangeAlgorithm.ECDHE_PSK:
+ case KeyExchangeAlgorithm.PSK:
+ case KeyExchangeAlgorithm.SRP:
+ break;
+
+ default:
+ throw new IllegalStateException("unsupported key exchange algorithm");
+ }
+ }
+
+ }
+ else if (this.supportedSignatureAlgorithms != null)
+ {
+ throw new IllegalStateException("supported_signature_algorithms not allowed for " + clientVersion);
+ }
+ }
+
+ public void processServerCertificate(Certificate serverCertificate)
+ throws IOException
+ {
+ if (supportedSignatureAlgorithms == null)
+ {
+ /*
+ * TODO RFC 2264 7.4.2. Unless otherwise specified, the signing algorithm for the
+ * certificate must be the same as the algorithm for the certificate key.
+ */
+ }
+ else
+ {
+ /*
+ * TODO RFC 5264 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.
+ */
+ }
+ }
+
+ public void processServerCredentials(TlsCredentials serverCredentials)
+ throws IOException
+ {
+ processServerCertificate(serverCredentials.getCertificate());
+ }
+
+ public boolean requiresServerKeyExchange()
+ {
+ return false;
+ }
+
+ public byte[] generateServerKeyExchange()
+ throws IOException
+ {
+ if (requiresServerKeyExchange())
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ return null;
+ }
+
+ public void skipServerKeyExchange()
+ throws IOException
+ {
+ if (requiresServerKeyExchange())
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ public void processServerKeyExchange(InputStream input)
+ throws IOException
+ {
+ if (!requiresServerKeyExchange())
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ public void skipClientCredentials()
+ throws IOException
+ {
+ }
+
+ public void processClientCertificate(Certificate clientCertificate)
+ throws IOException
+ {
+ }
+
+ public void processClientKeyExchange(InputStream input)
+ throws IOException
+ {
+ // Key exchange implementation MUST support client key exchange
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsPeer.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsPeer.java
new file mode 100644
index 00000000..cf90455f
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsPeer.java
@@ -0,0 +1,42 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public abstract class AbstractTlsPeer
+ implements TlsPeer
+{
+ public boolean 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 void notifySecureRenegotiation(boolean secureRenegotiation) throws IOException
+ {
+ if (!secureRenegotiation)
+ {
+ /*
+ * RFC 5746 3.4/3.6. In this case, some clients/servers may want to terminate the handshake instead
+ * of continuing; see Section 4.1/4.3 for discussion.
+ */
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ }
+
+ public void notifyAlertRaised(short alertLevel, short alertDescription, String message, Exception cause)
+ {
+ }
+
+ public void notifyAlertReceived(short alertLevel, short alertDescription)
+ {
+ }
+
+ public void notifyHandshakeComplete() throws IOException
+ {
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsServer.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsServer.java
new file mode 100644
index 00000000..f4ffdfdd
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsServer.java
@@ -0,0 +1,335 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.spongycastle.util.Arrays;
+
+public abstract class AbstractTlsServer
+ extends AbstractTlsPeer
+ implements TlsServer
+{
+ protected TlsCipherFactory cipherFactory;
+
+ protected TlsServerContext context;
+
+ protected ProtocolVersion clientVersion;
+ protected int[] offeredCipherSuites;
+ protected short[] offeredCompressionMethods;
+ protected Hashtable clientExtensions;
+
+ protected boolean encryptThenMACOffered;
+ protected short maxFragmentLengthOffered;
+ protected boolean truncatedHMacOffered;
+ protected Vector supportedSignatureAlgorithms;
+ protected boolean eccCipherSuitesOffered;
+ protected int[] namedCurves;
+ protected short[] clientECPointFormats, serverECPointFormats;
+
+ protected ProtocolVersion serverVersion;
+ protected int selectedCipherSuite;
+ protected short selectedCompressionMethod;
+ protected Hashtable serverExtensions;
+
+ public AbstractTlsServer()
+ {
+ this(new DefaultTlsCipherFactory());
+ }
+
+ public AbstractTlsServer(TlsCipherFactory cipherFactory)
+ {
+ this.cipherFactory = cipherFactory;
+ }
+
+ protected boolean allowEncryptThenMAC()
+ {
+ return true;
+ }
+
+ protected boolean allowTruncatedHMac()
+ {
+ return false;
+ }
+
+ protected Hashtable checkServerExtensions()
+ {
+ return this.serverExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(this.serverExtensions);
+ }
+
+ protected abstract int[] getCipherSuites();
+
+ protected short[] getCompressionMethods()
+ {
+ return new short[]{CompressionMethod._null};
+ }
+
+ protected ProtocolVersion getMaximumVersion()
+ {
+ return ProtocolVersion.TLSv11;
+ }
+
+ protected ProtocolVersion getMinimumVersion()
+ {
+ return ProtocolVersion.TLSv10;
+ }
+
+ protected boolean supportsClientECCCapabilities(int[] namedCurves, short[] ecPointFormats)
+ {
+ // NOTE: BC supports all the current set of point formats so we don't check them here
+
+ if (namedCurves == 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 TlsECCUtils.hasAnySupportedNamedCurves();
+ }
+
+ for (int i = 0; i < namedCurves.length; ++i)
+ {
+ int namedCurve = namedCurves[i];
+ if (NamedCurve.isValid(namedCurve)
+ && (!NamedCurve.refersToASpecificNamedCurve(namedCurve) || TlsECCUtils.isSupportedNamedCurve(namedCurve)))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void init(TlsServerContext context)
+ {
+ this.context = context;
+ }
+
+ public void notifyClientVersion(ProtocolVersion clientVersion)
+ throws IOException
+ {
+ this.clientVersion = clientVersion;
+ }
+
+ public void notifyOfferedCipherSuites(int[] offeredCipherSuites)
+ throws IOException
+ {
+ this.offeredCipherSuites = offeredCipherSuites;
+ this.eccCipherSuitesOffered = TlsECCUtils.containsECCCipherSuites(this.offeredCipherSuites);
+ }
+
+ public void notifyOfferedCompressionMethods(short[] offeredCompressionMethods)
+ throws IOException
+ {
+ this.offeredCompressionMethods = offeredCompressionMethods;
+ }
+
+ public void processClientExtensions(Hashtable clientExtensions)
+ throws IOException
+ {
+ this.clientExtensions = clientExtensions;
+
+ if (clientExtensions != null)
+ {
+ this.encryptThenMACOffered = TlsExtensionsUtils.hasEncryptThenMACExtension(clientExtensions);
+ this.maxFragmentLengthOffered = TlsExtensionsUtils.getMaxFragmentLengthExtension(clientExtensions);
+ this.truncatedHMacOffered = TlsExtensionsUtils.hasTruncatedHMacExtension(clientExtensions);
+
+ this.supportedSignatureAlgorithms = TlsUtils.getSignatureAlgorithmsExtension(clientExtensions);
+ if (this.supportedSignatureAlgorithms != null)
+ {
+ /*
+ * 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 (!TlsUtils.isSignatureAlgorithmsExtensionAllowed(clientVersion))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ this.namedCurves = TlsECCUtils.getSupportedEllipticCurvesExtension(clientExtensions);
+ this.clientECPointFormats = TlsECCUtils.getSupportedPointFormatsExtension(clientExtensions);
+ }
+
+ /*
+ * RFC 4429 4. The client MUST NOT include these extensions in the ClientHello message if it
+ * does not propose any ECC cipher suites.
+ */
+ if (!this.eccCipherSuitesOffered && (this.namedCurves != null || this.clientECPointFormats != null))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ public ProtocolVersion getServerVersion()
+ throws IOException
+ {
+ if (getMinimumVersion().isEqualOrEarlierVersionOf(clientVersion))
+ {
+ ProtocolVersion maximumVersion = getMaximumVersion();
+ if (clientVersion.isEqualOrEarlierVersionOf(maximumVersion))
+ {
+ return serverVersion = clientVersion;
+ }
+ if (clientVersion.isLaterVersionOf(maximumVersion))
+ {
+ return serverVersion = maximumVersion;
+ }
+ }
+ throw new TlsFatalAlert(AlertDescription.protocol_version);
+ }
+
+ public int getSelectedCipherSuite()
+ throws IOException
+ {
+ /*
+ * TODO 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.
+ */
+
+ /*
+ * 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 [...].
+ */
+ boolean eccCipherSuitesEnabled = supportsClientECCCapabilities(this.namedCurves, this.clientECPointFormats);
+
+ int[] cipherSuites = getCipherSuites();
+ for (int i = 0; i < cipherSuites.length; ++i)
+ {
+ int cipherSuite = cipherSuites[i];
+
+ if (Arrays.contains(this.offeredCipherSuites, cipherSuite)
+ && (eccCipherSuitesEnabled || !TlsECCUtils.isECCCipherSuite(cipherSuite))
+ && TlsUtils.isValidCipherSuiteForVersion(cipherSuite, serverVersion))
+ {
+ return this.selectedCipherSuite = cipherSuite;
+ }
+ }
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+
+ public short getSelectedCompressionMethod()
+ throws IOException
+ {
+ short[] compressionMethods = getCompressionMethods();
+ for (int i = 0; i < compressionMethods.length; ++i)
+ {
+ if (Arrays.contains(offeredCompressionMethods, compressionMethods[i]))
+ {
+ return this.selectedCompressionMethod = compressionMethods[i];
+ }
+ }
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+
+ // Hashtable is (Integer -> byte[])
+ public Hashtable getServerExtensions()
+ throws IOException
+ {
+ if (this.encryptThenMACOffered && allowEncryptThenMAC())
+ {
+ /*
+ * draft-ietf-tls-encrypt-then-mac-03 3. If a server receives an encrypt-then-MAC
+ * request extension from a client and then selects a stream or AEAD cipher suite, it
+ * MUST NOT send an encrypt-then-MAC response extension back to the client.
+ */
+ if (TlsUtils.isBlockCipherSuite(this.selectedCipherSuite))
+ {
+ TlsExtensionsUtils.addEncryptThenMACExtension(checkServerExtensions());
+ }
+ }
+
+ if (this.maxFragmentLengthOffered >= 0)
+ {
+ TlsExtensionsUtils.addMaxFragmentLengthExtension(checkServerExtensions(), this.maxFragmentLengthOffered);
+ }
+
+ if (this.truncatedHMacOffered && allowTruncatedHMac())
+ {
+ TlsExtensionsUtils.addTruncatedHMacExtension(checkServerExtensions());
+ }
+
+ if (this.clientECPointFormats != null && TlsECCUtils.isECCCipherSuite(this.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.
+ */
+ this.serverECPointFormats = new short[]{ ECPointFormat.uncompressed,
+ ECPointFormat.ansiX962_compressed_prime, ECPointFormat.ansiX962_compressed_char2, };
+
+ TlsECCUtils.addSupportedPointFormatsExtension(checkServerExtensions(), serverECPointFormats);
+ }
+
+ return serverExtensions;
+ }
+
+ public Vector getServerSupplementalData()
+ throws IOException
+ {
+ return null;
+ }
+
+ public CertificateStatus getCertificateStatus()
+ throws IOException
+ {
+ return null;
+ }
+
+ public CertificateRequest getCertificateRequest()
+ throws IOException
+ {
+ return null;
+ }
+
+ public void processClientSupplementalData(Vector clientSupplementalData)
+ throws IOException
+ {
+ if (clientSupplementalData != null)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ public void notifyClientCertificate(Certificate clientCertificate)
+ throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ public TlsCompression getCompression()
+ throws IOException
+ {
+ switch (selectedCompressionMethod)
+ {
+ case CompressionMethod._null:
+ return new TlsNullCompression();
+
+ default:
+ /*
+ * Note: internal error here; we selected the compression method, so if we now can't
+ * produce an implementation, we shouldn't have chosen it!
+ */
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public NewSessionTicket getNewSessionTicket()
+ throws IOException
+ {
+ /*
+ * 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, TlsUtils.EMPTY_BYTES);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsSigner.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsSigner.java
new file mode 100644
index 00000000..cc88f5a2
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsSigner.java
@@ -0,0 +1,38 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+
+public abstract class AbstractTlsSigner
+ implements TlsSigner
+{
+ protected TlsContext context;
+
+ public void init(TlsContext context)
+ {
+ this.context = context;
+ }
+
+ public byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1)
+ throws CryptoException
+ {
+ return generateRawSignature(null, privateKey, md5AndSha1);
+ }
+
+ public boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1)
+ throws CryptoException
+ {
+ return verifyRawSignature(null, sigBytes, publicKey, md5AndSha1);
+ }
+
+ public Signer createSigner(AsymmetricKeyParameter privateKey)
+ {
+ return createSigner(null, privateKey);
+ }
+
+ public Signer createVerifyer(AsymmetricKeyParameter publicKey)
+ {
+ return createVerifyer(null, publicKey);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsSignerCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsSignerCredentials.java
new file mode 100644
index 00000000..d10e6499
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AbstractTlsSignerCredentials.java
@@ -0,0 +1,11 @@
+package org.spongycastle.crypto.tls;
+
+public abstract class AbstractTlsSignerCredentials
+ extends AbstractTlsCredentials
+ implements TlsSignerCredentials
+{
+ public SignatureAndHashAlgorithm getSignatureAndHashAlgorithm()
+ {
+ throw new IllegalStateException("TlsSignerCredentials implementation does not support (D)TLS 1.2+");
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AlertDescription.java b/core/src/main/java/org/spongycastle/crypto/tls/AlertDescription.java
new file mode 100644
index 00000000..275cb16e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AlertDescription.java
@@ -0,0 +1,216 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 5246 7.2.
+ */
+public 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final short no_certificate = 41;
+
+ /**
+ * A certificate was corrupt, contained signatures that did not verify correctly, etc.
+ */
+ public static final short bad_certificate = 42;
+
+ /**
+ * A certificate was of an unsupported type.
+ */
+ public static final short unsupported_certificate = 43;
+
+ /**
+ * A certificate was revoked by its signer.
+ */
+ public static final short certificate_revoked = 44;
+
+ /**
+ * A certificate has expired or is not currently valid.
+ */
+ public static final short certificate_expired = 45;
+
+ /**
+ * Some other (unspecified) issue arose in processing the certificate, rendering it
+ * unacceptable.
+ */
+ public static final short certificate_unknown = 46;
+
+ /**
+ * A field in the handshake was out of range or inconsistent with other fields. This message is
+ * always fatal.
+ */
+ public static final 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 static final 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 static final 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 static final 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 static final short decrypt_error = 51;
+
+ /**
+ * This alert was used in some earlier versions of TLS. It MUST NOT be sent by compliant
+ * implementations.
+ */
+ public static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final 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 static final short unknown_psk_identity = 115;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AlertLevel.java b/core/src/main/java/org/spongycastle/crypto/tls/AlertLevel.java
new file mode 100644
index 00000000..2ba4542f
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AlertLevel.java
@@ -0,0 +1,10 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 5246 7.2
+ */
+public class AlertLevel
+{
+ public static final short warning = 1;
+ public static final short fatal = 2;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/AlwaysValidVerifyer.java b/core/src/main/java/org/spongycastle/crypto/tls/AlwaysValidVerifyer.java
new file mode 100644
index 00000000..990a98f9
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/AlwaysValidVerifyer.java
@@ -0,0 +1,23 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * A certificate verifyer, that will always return true.
+ * <pre>
+ * DO NOT USE THIS FILE UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING.
+ * </pre>
+ *
+ * @deprecated Perform certificate verification in TlsAuthentication implementation
+ */
+public class AlwaysValidVerifyer
+ implements CertificateVerifyer
+{
+ /**
+ * Return true.
+ *
+ * @see org.spongycastle.crypto.tls.CertificateVerifyer#isValid(org.spongycastle.asn1.x509.Certificate[])
+ */
+ public boolean isValid(org.spongycastle.asn1.x509.Certificate[] certs)
+ {
+ return true;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/BulkCipherAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/BulkCipherAlgorithm.java
new file mode 100644
index 00000000..e56cd713
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/BulkCipherAlgorithm.java
@@ -0,0 +1,23 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class BulkCipherAlgorithm
+{
+ public static final int _null = 0;
+ public static final int rc4 = 1;
+ public static final int rc2 = 2;
+ public static final int des = 3;
+ public static final int _3des = 4;
+ public static final int des40 = 5;
+
+ /*
+ * RFC 4346
+ */
+ public static final int aes = 6;
+ public static final int idea = 7;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ByteQueue.java b/core/src/main/java/org/spongycastle/crypto/tls/ByteQueue.java
new file mode 100644
index 00000000..ce21ddaf
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ByteQueue.java
@@ -0,0 +1,153 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * A queue for bytes. This file could be more optimized.
+ */
+public class ByteQueue
+{
+ /**
+ * @return 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 initial size for our buffer.
+ */
+ private static final int DEFAULT_CAPACITY = 1024;
+
+ /**
+ * The buffer where we store our data.
+ */
+ private byte[] databuf;
+
+ /**
+ * How many bytes at the beginning of the buffer are skipped.
+ */
+ private int skipped = 0;
+
+ /**
+ * How many bytes in the buffer are valid data.
+ */
+ private int available = 0;
+
+ public ByteQueue()
+ {
+ this(DEFAULT_CAPACITY);
+ }
+
+ public ByteQueue(int capacity)
+ {
+ databuf = new byte[capacity];
+ }
+
+ /**
+ * Read data from the buffer.
+ *
+ * @param buf The buffer where the read data will be copied to.
+ * @param offset How many bytes to skip at the beginning of buf.
+ * @param len How many bytes to read at all.
+ * @param skip 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 IllegalArgumentException("Buffer size of " + buf.length
+ + " is too small for a read of " + len + " bytes");
+ }
+ if ((available - skip) < len)
+ {
+ throw new IllegalStateException("Not enough data to read");
+ }
+ System.arraycopy(databuf, skipped + skip, buf, offset, len);
+ }
+
+ /**
+ * Add some data to our buffer.
+ *
+ * @param buf A byte-array to read data from.
+ * @param off How many bytes to skip at the beginning of the array.
+ * @param len How many bytes to read from the array.
+ */
+ public void addData(byte[] buf, int off, int len)
+ {
+ if ((skipped + available + len) > databuf.length)
+ {
+ int desiredSize = ByteQueue.nextTwoPow(available + len);
+ if (desiredSize > databuf.length)
+ {
+ byte[] tmp = new byte[desiredSize];
+ System.arraycopy(databuf, skipped, tmp, 0, available);
+ databuf = tmp;
+ }
+ else
+ {
+ System.arraycopy(databuf, skipped, databuf, 0, available);
+ }
+ skipped = 0;
+ }
+
+ System.arraycopy(buf, off, databuf, skipped + available, len);
+ available += len;
+ }
+
+ /**
+ * Remove some bytes from our data from the beginning.
+ *
+ * @param i How many bytes to remove.
+ */
+ public void removeData(int i)
+ {
+ if (i > available)
+ {
+ throw new IllegalStateException("Cannot remove " + i + " bytes, only got " + available);
+ }
+
+ /*
+ * Skip the data.
+ */
+ available -= i;
+ skipped += i;
+ }
+
+ /**
+ * Remove data from the buffer.
+ *
+ * @param buf The buffer where the removed data will be copied to.
+ * @param off How many bytes to skip at the beginning of buf.
+ * @param len How many bytes to read at all.
+ * @param skip 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;
+ }
+
+ /**
+ * @return The number of bytes which are available in this buffer.
+ */
+ public int size()
+ {
+ return available;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CertChainType.java b/core/src/main/java/org/spongycastle/crypto/tls/CertChainType.java
new file mode 100644
index 00000000..85749615
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CertChainType.java
@@ -0,0 +1,15 @@
+package org.spongycastle.crypto.tls;
+
+/*
+ * RFC 3546 3.3.
+ */
+public class CertChainType
+{
+ public static final short individual_certs = 0;
+ public static final short pkipath = 1;
+
+ public static boolean isValid(short certChainType)
+ {
+ return certChainType >= individual_certs && certChainType <= pkipath;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/Certificate.java b/core/src/main/java/org/spongycastle/crypto/tls/Certificate.java
new file mode 100644
index 00000000..df2a2033
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/Certificate.java
@@ -0,0 +1,148 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ASN1Primitive;
+
+/**
+ * Parsing and encoding of a <i>Certificate</i> struct from RFC 4346.
+ * <pre>
+ * opaque ASN.1Cert&lt;2^24-1&gt;;
+ *
+ * struct {
+ * ASN.1Cert certificate_list&lt;0..2^24-1&gt;;
+ * } Certificate;
+ * </pre>
+ *
+ * @see org.spongycastle.asn1.x509.Certificate
+ */
+public class Certificate
+{
+ public static final Certificate EMPTY_CHAIN = new Certificate(
+ new org.spongycastle.asn1.x509.Certificate[0]);
+
+ protected org.spongycastle.asn1.x509.Certificate[] certificateList;
+
+ public Certificate(org.spongycastle.asn1.x509.Certificate[] certificateList)
+ {
+ if (certificateList == null)
+ {
+ throw new IllegalArgumentException("'certificateList' cannot be null");
+ }
+
+ this.certificateList = certificateList;
+ }
+
+ /**
+ * @deprecated use {@link #getCertificateList()} instead
+ */
+ public org.spongycastle.asn1.x509.Certificate[] getCerts()
+ {
+ return getCertificateList();
+ }
+
+ /**
+ * @return an array of {@link org.spongycastle.asn1.x509.Certificate} representing a certificate
+ * chain.
+ */
+ public org.spongycastle.asn1.x509.Certificate[] getCertificateList()
+ {
+ return cloneCertificateList();
+ }
+
+ public org.spongycastle.asn1.x509.Certificate getCertificateAt(int index)
+ {
+ return certificateList[index];
+ }
+
+ public int getLength()
+ {
+ return certificateList.length;
+ }
+
+ /**
+ * @return <code>true</code> if this certificate chain contains no certificates, or
+ * <code>false</code> otherwise.
+ */
+ public boolean isEmpty()
+ {
+ return certificateList.length == 0;
+ }
+
+ /**
+ * Encode this {@link Certificate} to an {@link OutputStream}.
+ *
+ * @param output the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output)
+ throws IOException
+ {
+ Vector derEncodings = new Vector(this.certificateList.length);
+
+ int totalLength = 0;
+ for (int i = 0; i < this.certificateList.length; ++i)
+ {
+ byte[] derEncoding = certificateList[i].getEncoded(ASN1Encoding.DER);
+ derEncodings.addElement(derEncoding);
+ totalLength += derEncoding.length + 3;
+ }
+
+ TlsUtils.checkUint24(totalLength);
+ TlsUtils.writeUint24(totalLength, output);
+
+ for (int i = 0; i < derEncodings.size(); ++i)
+ {
+ byte[] derEncoding = (byte[])derEncodings.elementAt(i);
+ TlsUtils.writeOpaque24(derEncoding, output);
+ }
+ }
+
+ /**
+ * Parse a {@link Certificate} from an {@link InputStream}.
+ *
+ * @param input the {@link InputStream} to parse from.
+ * @return a {@link Certificate} object.
+ * @throws IOException
+ */
+ public static Certificate parse(InputStream input)
+ throws IOException
+ {
+ int totalLength = TlsUtils.readUint24(input);
+ if (totalLength == 0)
+ {
+ return EMPTY_CHAIN;
+ }
+
+ byte[] certListData = TlsUtils.readFully(totalLength, input);
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(certListData);
+
+ Vector certificate_list = new Vector();
+ while (buf.available() > 0)
+ {
+ byte[] derEncoding = TlsUtils.readOpaque24(buf);
+ ASN1Primitive asn1Cert = TlsUtils.readDERObject(derEncoding);
+ certificate_list.addElement(org.spongycastle.asn1.x509.Certificate.getInstance(asn1Cert));
+ }
+
+ org.spongycastle.asn1.x509.Certificate[] certificateList = new org.spongycastle.asn1.x509.Certificate[certificate_list.size()];
+ for (int i = 0; i < certificate_list.size(); i++)
+ {
+ certificateList[i] = (org.spongycastle.asn1.x509.Certificate)certificate_list.elementAt(i);
+ }
+ return new Certificate(certificateList);
+ }
+
+ protected org.spongycastle.asn1.x509.Certificate[] cloneCertificateList()
+ {
+ org.spongycastle.asn1.x509.Certificate[] result = new org.spongycastle.asn1.x509.Certificate[certificateList.length];
+ System.arraycopy(certificateList, 0, result, 0, result.length);
+ return result;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CertificateRequest.java b/core/src/main/java/org/spongycastle/crypto/tls/CertificateRequest.java
new file mode 100644
index 00000000..ae40206b
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CertificateRequest.java
@@ -0,0 +1,158 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ASN1Primitive;
+import org.spongycastle.asn1.x500.X500Name;
+
+/**
+ * Parsing and encoding of a <i>CertificateRequest</i> struct from RFC 4346.
+ * <pre>
+ * struct {
+ * ClientCertificateType certificate_types&lt;1..2^8-1&gt;;
+ * DistinguishedName certificate_authorities&lt;3..2^16-1&gt;;
+ * } CertificateRequest;
+ * </pre>
+ *
+ * @see ClientCertificateType
+ * @see X500Name
+ */
+public class CertificateRequest
+{
+ protected short[] certificateTypes;
+ protected Vector supportedSignatureAlgorithms;
+ protected Vector certificateAuthorities;
+
+ /**
+ * @param certificateTypes see {@link ClientCertificateType} for valid constants.
+ * @param certificateAuthorities a {@link Vector} of {@link X500Name}.
+ */
+ public CertificateRequest(short[] certificateTypes, Vector supportedSignatureAlgorithms, Vector certificateAuthorities)
+ {
+ this.certificateTypes = certificateTypes;
+ this.supportedSignatureAlgorithms = supportedSignatureAlgorithms;
+ this.certificateAuthorities = certificateAuthorities;
+ }
+
+ /**
+ * @return an array of certificate types
+ * @see ClientCertificateType
+ */
+ public short[] getCertificateTypes()
+ {
+ return certificateTypes;
+ }
+
+ /**
+ * @return a {@link Vector} of {@link SignatureAndHashAlgorithm} (or null before TLS 1.2).
+ */
+ public Vector getSupportedSignatureAlgorithms()
+ {
+ return supportedSignatureAlgorithms;
+ }
+
+ /**
+ * @return a {@link Vector} of {@link X500Name}
+ */
+ public Vector getCertificateAuthorities()
+ {
+ return certificateAuthorities;
+ }
+
+ /**
+ * Encode this {@link CertificateRequest} to an {@link OutputStream}.
+ *
+ * @param output the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output)
+ throws IOException
+ {
+ if (certificateTypes == null || certificateTypes.length == 0)
+ {
+ TlsUtils.writeUint8(0, output);
+ }
+ else
+ {
+ TlsUtils.writeUint8ArrayWithUint8Length(certificateTypes, output);
+ }
+
+ if (supportedSignatureAlgorithms != null)
+ {
+ // TODO Check whether SignatureAlgorithm.anonymous is allowed here
+ TlsUtils.encodeSupportedSignatureAlgorithms(supportedSignatureAlgorithms, false, output);
+ }
+
+ if (certificateAuthorities == null || certificateAuthorities.isEmpty())
+ {
+ TlsUtils.writeUint16(0, output);
+ }
+ else
+ {
+ Vector derEncodings = new Vector(certificateAuthorities.size());
+
+ int totalLength = 0;
+ for (int i = 0; i < certificateAuthorities.size(); ++i)
+ {
+ X500Name certificateAuthority = (X500Name)certificateAuthorities.elementAt(i);
+ byte[] derEncoding = certificateAuthority.getEncoded(ASN1Encoding.DER);
+ derEncodings.addElement(derEncoding);
+ totalLength += derEncoding.length + 2;
+ }
+
+ TlsUtils.checkUint16(totalLength);
+ TlsUtils.writeUint16(totalLength, output);
+
+ for (int i = 0; i < derEncodings.size(); ++i)
+ {
+ byte[] derEncoding = (byte[])derEncodings.elementAt(i);
+ TlsUtils.writeOpaque16(derEncoding, output);
+ }
+ }
+ }
+
+ /**
+ * Parse a {@link CertificateRequest} from an {@link InputStream}.
+ *
+ * @param context
+ * the {@link TlsContext} of the current connection.
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link CertificateRequest} object.
+ * @throws IOException
+ */
+ public static CertificateRequest parse(TlsContext context, InputStream input)
+ throws IOException
+ {
+ int numTypes = TlsUtils.readUint8(input);
+ short[] certificateTypes = new short[numTypes];
+ for (int i = 0; i < numTypes; ++i)
+ {
+ certificateTypes[i] = TlsUtils.readUint8(input);
+ }
+
+ Vector supportedSignatureAlgorithms = null;
+ if (TlsUtils.isTLSv12(context))
+ {
+ // TODO Check whether SignatureAlgorithm.anonymous is allowed here
+ supportedSignatureAlgorithms = TlsUtils.parseSupportedSignatureAlgorithms(false, input);
+ }
+
+ Vector certificateAuthorities = new Vector();
+ byte[] certAuthData = TlsUtils.readOpaque16(input);
+ ByteArrayInputStream bis = new ByteArrayInputStream(certAuthData);
+ while (bis.available() > 0)
+ {
+ byte[] derEncoding = TlsUtils.readOpaque16(bis);
+ ASN1Primitive asn1 = TlsUtils.readDERObject(derEncoding);
+ certificateAuthorities.addElement(X500Name.getInstance(asn1));
+ }
+
+ return new CertificateRequest(certificateTypes, supportedSignatureAlgorithms, certificateAuthorities);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatus.java b/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatus.java
new file mode 100644
index 00000000..14b0a0d9
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatus.java
@@ -0,0 +1,105 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ocsp.OCSPResponse;
+
+public class CertificateStatus
+{
+ protected short statusType;
+ protected Object response;
+
+ public CertificateStatus(short statusType, Object response)
+ {
+ if (!isCorrectType(statusType, response))
+ {
+ throw new IllegalArgumentException("'response' is not an instance of the correct type");
+ }
+
+ this.statusType = statusType;
+ this.response = response;
+ }
+
+ public short getStatusType()
+ {
+ return statusType;
+ }
+
+ public Object getResponse()
+ {
+ return response;
+ }
+
+ public OCSPResponse getOCSPResponse()
+ {
+ if (!isCorrectType(CertificateStatusType.ocsp, response))
+ {
+ throw new IllegalStateException("'response' is not an OCSPResponse");
+ }
+ return (OCSPResponse)response;
+ }
+
+ /**
+ * Encode this {@link CertificateStatus} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output) throws IOException
+ {
+ TlsUtils.writeUint8(statusType, output);
+
+ switch (statusType)
+ {
+ case CertificateStatusType.ocsp:
+ byte[] derEncoding = ((OCSPResponse) response).getEncoded(ASN1Encoding.DER);
+ TlsUtils.writeOpaque24(derEncoding, output);
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ /**
+ * Parse a {@link CertificateStatus} from an {@link InputStream}.
+ *
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link CertificateStatus} object.
+ * @throws IOException
+ */
+ public static CertificateStatus parse(InputStream input) throws IOException
+ {
+ short status_type = TlsUtils.readUint8(input);
+ Object response;
+
+ switch (status_type)
+ {
+ case CertificateStatusType.ocsp:
+ {
+ byte[] derEncoding = TlsUtils.readOpaque24(input);
+ response = OCSPResponse.getInstance(TlsUtils.readDERObject(derEncoding));
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ return new CertificateStatus(status_type, response);
+ }
+
+ protected static boolean isCorrectType(short statusType, Object response)
+ {
+ switch (statusType)
+ {
+ case CertificateStatusType.ocsp:
+ return response instanceof OCSPResponse;
+ default:
+ throw new IllegalArgumentException("'statusType' is an unsupported value");
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatusRequest.java b/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatusRequest.java
new file mode 100644
index 00000000..16318057
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatusRequest.java
@@ -0,0 +1,98 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class CertificateStatusRequest
+{
+ protected short statusType;
+ protected Object request;
+
+ public CertificateStatusRequest(short statusType, Object request)
+ {
+ if (!isCorrectType(statusType, request))
+ {
+ throw new IllegalArgumentException("'request' is not an instance of the correct type");
+ }
+
+ this.statusType = statusType;
+ this.request = request;
+ }
+
+ public short getStatusType()
+ {
+ return statusType;
+ }
+
+ public Object getRequest()
+ {
+ return request;
+ }
+
+ public OCSPStatusRequest getOCSPStatusRequest()
+ {
+ if (!isCorrectType(CertificateStatusType.ocsp, request))
+ {
+ throw new IllegalStateException("'request' is not an OCSPStatusRequest");
+ }
+ return (OCSPStatusRequest)request;
+ }
+
+ /**
+ * Encode this {@link CertificateStatusRequest} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output) throws IOException
+ {
+ TlsUtils.writeUint8(statusType, output);
+
+ switch (statusType)
+ {
+ case CertificateStatusType.ocsp:
+ ((OCSPStatusRequest) request).encode(output);
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ /**
+ * Parse a {@link CertificateStatusRequest} from an {@link InputStream}.
+ *
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link CertificateStatusRequest} object.
+ * @throws IOException
+ */
+ public static CertificateStatusRequest parse(InputStream input) throws IOException
+ {
+ short status_type = TlsUtils.readUint8(input);
+ Object result;
+
+ switch (status_type)
+ {
+ case CertificateStatusType.ocsp:
+ result = OCSPStatusRequest.parse(input);
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ return new CertificateStatusRequest(status_type, result);
+ }
+
+ protected static boolean isCorrectType(short statusType, Object request)
+ {
+ switch (statusType)
+ {
+ case CertificateStatusType.ocsp:
+ return request instanceof OCSPStatusRequest;
+ default:
+ throw new IllegalArgumentException("'statusType' is an unsupported value");
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatusType.java b/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatusType.java
new file mode 100644
index 00000000..75090262
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CertificateStatusType.java
@@ -0,0 +1,9 @@
+package org.spongycastle.crypto.tls;
+
+public class CertificateStatusType
+{
+ /*
+ * RFC 3546 3.6
+ */
+ public static final short ocsp = 1;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CertificateURL.java b/core/src/main/java/org/spongycastle/crypto/tls/CertificateURL.java
new file mode 100644
index 00000000..f6b7df53
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CertificateURL.java
@@ -0,0 +1,133 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+/*
+ * RFC 3546 3.3
+ */
+public class CertificateURL
+{
+ protected short type;
+ protected Vector urlAndHashList;
+
+ /**
+ * @param type
+ * see {@link CertChainType} for valid constants.
+ * @param urlAndHashList
+ * a {@link Vector} of {@link URLAndHash}.
+ */
+ public CertificateURL(short type, Vector urlAndHashList)
+ {
+ if (!CertChainType.isValid(type))
+ {
+ throw new IllegalArgumentException("'type' is not a valid CertChainType value");
+ }
+ if (urlAndHashList == null || urlAndHashList.isEmpty())
+ {
+ throw new IllegalArgumentException("'urlAndHashList' must have length > 0");
+ }
+
+ this.type = type;
+ this.urlAndHashList = urlAndHashList;
+ }
+
+ /**
+ * @return {@link CertChainType}
+ */
+ public short getType()
+ {
+ return type;
+ }
+
+ /**
+ * @return a {@link Vector} of {@link URLAndHash}
+ */
+ public Vector getURLAndHashList()
+ {
+ return urlAndHashList;
+ }
+
+ /**
+ * Encode this {@link CertificateURL} to an {@link OutputStream}.
+ *
+ * @param output the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output)
+ throws IOException
+ {
+ TlsUtils.writeUint8(this.type, output);
+
+ ListBuffer16 buf = new ListBuffer16();
+ for (int i = 0; i < this.urlAndHashList.size(); ++i)
+ {
+ URLAndHash urlAndHash = (URLAndHash)this.urlAndHashList.elementAt(i);
+ urlAndHash.encode(buf);
+ }
+ buf.encodeTo(output);
+ }
+
+ /**
+ * Parse a {@link CertificateURL} from an {@link InputStream}.
+ *
+ * @param context
+ * the {@link TlsContext} of the current connection.
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link CertificateURL} object.
+ * @throws IOException
+ */
+ public static CertificateURL parse(TlsContext context, InputStream input)
+ throws IOException
+ {
+ short type = TlsUtils.readUint8(input);
+ if (!CertChainType.isValid(type))
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ int totalLength = TlsUtils.readUint16(input);
+ if (totalLength < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ byte[] urlAndHashListData = TlsUtils.readFully(totalLength, input);
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(urlAndHashListData);
+
+ Vector url_and_hash_list = new Vector();
+ while (buf.available() > 0)
+ {
+ URLAndHash url_and_hash = URLAndHash.parse(context, buf);
+ url_and_hash_list.addElement(url_and_hash);
+ }
+
+ return new CertificateURL(type, url_and_hash_list);
+ }
+
+ // TODO Could be more generally useful
+ class ListBuffer16 extends ByteArrayOutputStream
+ {
+ ListBuffer16() throws IOException
+ {
+ // Reserve space for length
+ TlsUtils.writeUint16(0, this);
+ }
+
+ void encodeTo(OutputStream output) throws IOException
+ {
+ // Patch actual length back in
+ int length = count - 2;
+ TlsUtils.checkUint16(length);
+ TlsUtils.writeUint16(length, buf, 0);
+ output.write(buf, 0, count);
+ buf = null;
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CertificateVerifyer.java b/core/src/main/java/org/spongycastle/crypto/tls/CertificateVerifyer.java
new file mode 100644
index 00000000..65c764a4
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CertificateVerifyer.java
@@ -0,0 +1,16 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * This should be implemented by any class which can find out, if a given certificate
+ * chain is being accepted by an client.
+ *
+ * @deprecated Perform certificate verification in TlsAuthentication implementation
+ */
+public interface CertificateVerifyer
+{
+ /**
+ * @param certs The certs, which are part of the chain.
+ * @return True, if the chain is accepted, false otherwise.
+ */
+ public boolean isValid(org.spongycastle.asn1.x509.Certificate[] certs);
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/Chacha20Poly1305.java b/core/src/main/java/org/spongycastle/crypto/tls/Chacha20Poly1305.java
new file mode 100644
index 00000000..aa2c8960
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/Chacha20Poly1305.java
@@ -0,0 +1,156 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.Mac;
+import org.spongycastle.crypto.engines.ChaChaEngine;
+import org.spongycastle.crypto.generators.Poly1305KeyGenerator;
+import org.spongycastle.crypto.macs.Poly1305;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.crypto.params.ParametersWithIV;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.Pack;
+
+public class Chacha20Poly1305 implements TlsCipher
+{
+ protected TlsContext context;
+
+ protected ChaChaEngine encryptCipher;
+ protected ChaChaEngine decryptCipher;
+
+ public Chacha20Poly1305(TlsContext context) throws IOException
+ {
+ if (!TlsUtils.isTLSv12(context))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ this.context = context;
+
+ byte[] key_block = TlsUtils.calculateKeyBlock(context, 64);
+
+ KeyParameter client_write_key = new KeyParameter(key_block, 0, 32);
+ KeyParameter server_write_key = new KeyParameter(key_block, 32, 32);
+
+ this.encryptCipher = new ChaChaEngine(20);
+ this.decryptCipher = new ChaChaEngine(20);
+
+ KeyParameter encryptKey, decryptKey;
+ if (context.isServer())
+ {
+ encryptKey = server_write_key;
+ decryptKey = client_write_key;
+ }
+ else
+ {
+ encryptKey = client_write_key;
+ decryptKey = server_write_key;
+ }
+
+ byte[] dummyNonce = new byte[8];
+
+ this.encryptCipher.init(true, new ParametersWithIV(encryptKey, dummyNonce));
+ this.decryptCipher.init(false, new ParametersWithIV(decryptKey, dummyNonce));
+ }
+
+ public int getPlaintextLimit(int ciphertextLimit)
+ {
+ return ciphertextLimit - 16;
+ }
+
+ public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len) throws IOException
+ {
+ int ciphertextLength = len + 16;
+
+ KeyParameter macKey = initRecordMAC(encryptCipher, true, seqNo);
+
+ byte[] output = new byte[ciphertextLength];
+ encryptCipher.processBytes(plaintext, offset, len, output, 0);
+
+ byte[] additionalData = getAdditionalData(seqNo, type, len);
+ byte[] mac = calculateRecordMAC(macKey, additionalData, output, 0, len);
+ System.arraycopy(mac, 0, output, len, mac.length);
+
+ return output;
+ }
+
+ public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len) throws IOException
+ {
+ if (getPlaintextLimit(len) < 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ int plaintextLength = len - 16;
+
+ byte[] receivedMAC = Arrays.copyOfRange(ciphertext, offset + plaintextLength, offset + len);
+
+ KeyParameter macKey = initRecordMAC(decryptCipher, false, seqNo);
+
+ byte[] additionalData = getAdditionalData(seqNo, type, plaintextLength);
+ byte[] calculatedMAC = calculateRecordMAC(macKey, additionalData, ciphertext, offset, plaintextLength);
+
+ if (!Arrays.constantTimeAreEqual(calculatedMAC, receivedMAC))
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+ }
+
+ byte[] output = new byte[plaintextLength];
+ decryptCipher.processBytes(ciphertext, offset, plaintextLength, output, 0);
+
+ return output;
+ }
+
+ protected KeyParameter initRecordMAC(ChaChaEngine cipher, boolean forEncryption, long seqNo)
+ {
+ byte[] nonce = new byte[8];
+ TlsUtils.writeUint64(seqNo, nonce, 0);
+
+ cipher.init(forEncryption, new ParametersWithIV(null, nonce));
+
+ byte[] firstBlock = new byte[64];
+ cipher.processBytes(firstBlock, 0, firstBlock.length, firstBlock, 0);
+
+ // NOTE: The BC implementation puts 'r' after 'k'
+ System.arraycopy(firstBlock, 0, firstBlock, 32, 16);
+ KeyParameter macKey = new KeyParameter(firstBlock, 16, 32);
+ Poly1305KeyGenerator.clamp(macKey.getKey());
+ return macKey;
+ }
+
+ protected byte[] calculateRecordMAC(KeyParameter macKey, byte[] additionalData, byte[] buf, int off, int len)
+ {
+ Mac mac = new Poly1305();
+ mac.init(macKey);
+
+ updateRecordMAC(mac, additionalData, 0, additionalData.length);
+ updateRecordMAC(mac, buf, off, len);
+
+ byte[] output = new byte[mac.getMacSize()];
+ mac.doFinal(output, 0);
+ return output;
+ }
+
+ protected void updateRecordMAC(Mac mac, byte[] buf, int off, int len)
+ {
+ mac.update(buf, off, len);
+
+ byte[] longLen = Pack.longToLittleEndian(len & 0xFFFFFFFFL);
+ mac.update(longLen, 0, longLen.length);
+ }
+
+ protected byte[] getAdditionalData(long seqNo, short type, int len) throws IOException
+ {
+ /*
+ * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version +
+ * TLSCompressed.length
+ */
+ byte[] additional_data = new byte[13];
+ TlsUtils.writeUint64(seqNo, additional_data, 0);
+ TlsUtils.writeUint8(type, additional_data, 8);
+ TlsUtils.writeVersion(context.getServerVersion(), additional_data, 9);
+ TlsUtils.writeUint16(len, additional_data, 11);
+
+ return additional_data;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ChangeCipherSpec.java b/core/src/main/java/org/spongycastle/crypto/tls/ChangeCipherSpec.java
new file mode 100644
index 00000000..b3a23c39
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ChangeCipherSpec.java
@@ -0,0 +1,6 @@
+package org.spongycastle.crypto.tls;
+
+public class ChangeCipherSpec
+{
+ public static final short change_cipher_spec = 1;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CipherSuite.java b/core/src/main/java/org/spongycastle/crypto/tls/CipherSuite.java
new file mode 100644
index 00000000..420f0d29
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CipherSuite.java
@@ -0,0 +1,351 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246 A.5
+ */
+public class CipherSuite
+{
+ public static final int TLS_NULL_WITH_NULL_NULL = 0x0000;
+ public static final int TLS_RSA_WITH_NULL_MD5 = 0x0001;
+ public static final int TLS_RSA_WITH_NULL_SHA = 0x0002;
+ public static final int TLS_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003;
+ public static final int TLS_RSA_WITH_RC4_128_MD5 = 0x0004;
+ public static final int TLS_RSA_WITH_RC4_128_SHA = 0x0005;
+ public static final int TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006;
+ public static final int TLS_RSA_WITH_IDEA_CBC_SHA = 0x0007;
+ public static final int TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008;
+ public static final int TLS_RSA_WITH_DES_CBC_SHA = 0x0009;
+ public static final int TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A;
+ public static final int TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B;
+ public static final int TLS_DH_DSS_WITH_DES_CBC_SHA = 0x000C;
+ public static final int TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D;
+ public static final int TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E;
+ public static final int TLS_DH_RSA_WITH_DES_CBC_SHA = 0x000F;
+ public static final int TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010;
+ public static final int TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011;
+ public static final int TLS_DHE_DSS_WITH_DES_CBC_SHA = 0x0012;
+ public static final int TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013;
+ public static final int TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014;
+ public static final int TLS_DHE_RSA_WITH_DES_CBC_SHA = 0x0015;
+ public static final int TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016;
+ public static final int TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017;
+ public static final int TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018;
+ public static final int TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x0019;
+ public static final int TLS_DH_anon_WITH_DES_CBC_SHA = 0x001A;
+ public static final 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 static final int TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F;
+ public static final int TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x0030;
+ public static final int TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031;
+ public static final int TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032;
+ public static final int TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033;
+ public static final int TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034;
+ public static final int TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035;
+ public static final int TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x0036;
+ public static final int TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037;
+ public static final int TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038;
+ public static final int TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039;
+ public static final int TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A;
+
+ /*
+ * RFC 5932
+ */
+ public static final int TLS_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0041;
+ public static final int TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0042;
+ public static final int TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0043;
+ public static final int TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA = 0x0044;
+ public static final int TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA = 0x0045;
+ public static final int TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA = 0x0046;
+
+ public static final int TLS_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0084;
+ public static final int TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0085;
+ public static final int TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0086;
+ public static final int TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA = 0x0087;
+ public static final int TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA = 0x0088;
+ public static final int TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA = 0x0089;
+
+ public static final int TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BA;
+ public static final int TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BB;
+ public static final int TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BC;
+ public static final int TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BD;
+ public static final int TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BE;
+ public static final int TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 = 0x00BF;
+
+ public static final int TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C0;
+ public static final int TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C1;
+ public static final int TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C2;
+ public static final int TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C3;
+ public static final int TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C4;
+ public static final int TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 = 0x00C5;
+
+ /*
+ * RFC 4162
+ */
+ public static final int TLS_RSA_WITH_SEED_CBC_SHA = 0x0096;
+ public static final int TLS_DH_DSS_WITH_SEED_CBC_SHA = 0x0097;
+ public static final int TLS_DH_RSA_WITH_SEED_CBC_SHA = 0x0098;
+ public static final int TLS_DHE_DSS_WITH_SEED_CBC_SHA = 0x0099;
+ public static final int TLS_DHE_RSA_WITH_SEED_CBC_SHA = 0x009A;
+ public static final int TLS_DH_anon_WITH_SEED_CBC_SHA = 0x009B;
+
+ /*
+ * RFC 4279
+ */
+ public static final int TLS_PSK_WITH_RC4_128_SHA = 0x008A;
+ public static final int TLS_PSK_WITH_3DES_EDE_CBC_SHA = 0x008B;
+ public static final int TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C;
+ public static final int TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D;
+ public static final int TLS_DHE_PSK_WITH_RC4_128_SHA = 0x008E;
+ public static final int TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA = 0x008F;
+ public static final int TLS_DHE_PSK_WITH_AES_128_CBC_SHA = 0x0090;
+ public static final int TLS_DHE_PSK_WITH_AES_256_CBC_SHA = 0x0091;
+ public static final int TLS_RSA_PSK_WITH_RC4_128_SHA = 0x0092;
+ public static final int TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA = 0x0093;
+ public static final int TLS_RSA_PSK_WITH_AES_128_CBC_SHA = 0x0094;
+ public static final int TLS_RSA_PSK_WITH_AES_256_CBC_SHA = 0x0095;
+
+ /*
+ * RFC 4492
+ */
+ public static final int TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001;
+ public static final int TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002;
+ public static final int TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003;
+ public static final int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004;
+ public static final int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005;
+ public static final int TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006;
+ public static final int TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007;
+ public static final int TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008;
+ public static final int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009;
+ public static final int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A;
+ public static final int TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B;
+ public static final int TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C;
+ public static final int TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D;
+ public static final int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E;
+ public static final int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F;
+ public static final int TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010;
+ public static final int TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011;
+ public static final int TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012;
+ public static final int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013;
+ public static final int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014;
+ public static final int TLS_ECDH_anon_WITH_NULL_SHA = 0xC015;
+ public static final int TLS_ECDH_anon_WITH_RC4_128_SHA = 0xC016;
+ public static final int TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017;
+ public static final int TLS_ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018;
+ public static final int TLS_ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019;
+
+ /*
+ * RFC 4785
+ */
+ public static final int TLS_PSK_WITH_NULL_SHA = 0x002C;
+ public static final int TLS_DHE_PSK_WITH_NULL_SHA = 0x002D;
+ public static final int TLS_RSA_PSK_WITH_NULL_SHA = 0x002E;
+
+ /*
+ * RFC 5054
+ */
+ public static final int TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA = 0xC01A;
+ public static final int TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA = 0xC01B;
+ public static final int TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA = 0xC01C;
+ public static final int TLS_SRP_SHA_WITH_AES_128_CBC_SHA = 0xC01D;
+ public static final int TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA = 0xC01E;
+ public static final int TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA = 0xC01F;
+ public static final int TLS_SRP_SHA_WITH_AES_256_CBC_SHA = 0xC020;
+ public static final int TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA = 0xC021;
+ public static final int TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = 0xC022;
+
+ /*
+ * RFC 5246
+ */
+ public static final int TLS_RSA_WITH_NULL_SHA256 = 0x003B;
+ public static final int TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C;
+ public static final int TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D;
+ public static final int TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x003E;
+ public static final int TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003F;
+ public static final int TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040;
+ public static final int TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067;
+ public static final int TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x0068;
+ public static final int TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069;
+ public static final int TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A;
+ public static final int TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B;
+ public static final int TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C;
+ public static final int TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D;
+
+ /*
+ * RFC 5288
+ */
+ public static final int TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C;
+ public static final int TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D;
+ public static final int TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E;
+ public static final int TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F;
+ public static final int TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00A0;
+ public static final int TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00A1;
+ public static final int TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2;
+ public static final int TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3;
+ public static final int TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = 0x00A4;
+ public static final int TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = 0x00A5;
+ public static final int TLS_DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6;
+ public static final int TLS_DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7;
+
+ /*
+ * RFC 5289
+ */
+ public static final int TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023;
+ public static final int TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024;
+ public static final int TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025;
+ public static final int TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026;
+ public static final int TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027;
+ public static final int TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028;
+ public static final int TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029;
+ public static final int TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A;
+ public static final int TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B;
+ public static final int TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C;
+ public static final int TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D;
+ public static final int TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E;
+ public static final int TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F;
+ public static final int TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030;
+ public static final int TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031;
+ public static final int TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032;
+
+ /*
+ * RFC 5487
+ */
+ public static final int TLS_PSK_WITH_AES_128_GCM_SHA256 = 0x00A8;
+ public static final int TLS_PSK_WITH_AES_256_GCM_SHA384 = 0x00A9;
+ public static final int TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = 0x00AA;
+ public static final int TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = 0x00AB;
+ public static final int TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = 0x00AC;
+ public static final int TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = 0x00AD;
+ public static final int TLS_PSK_WITH_AES_128_CBC_SHA256 = 0x00AE;
+ public static final int TLS_PSK_WITH_AES_256_CBC_SHA384 = 0x00AF;
+ public static final int TLS_PSK_WITH_NULL_SHA256 = 0x00B0;
+ public static final int TLS_PSK_WITH_NULL_SHA384 = 0x00B1;
+ public static final int TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 = 0x00B2;
+ public static final int TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 = 0x00B3;
+ public static final int TLS_DHE_PSK_WITH_NULL_SHA256 = 0x00B4;
+ public static final int TLS_DHE_PSK_WITH_NULL_SHA384 = 0x00B5;
+ public static final int TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 = 0x00B6;
+ public static final int TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 = 0x00B7;
+ public static final int TLS_RSA_PSK_WITH_NULL_SHA256 = 0x00B8;
+ public static final int TLS_RSA_PSK_WITH_NULL_SHA384 = 0x00B9;
+
+ /*
+ * RFC 5489
+ */
+ public static final int TLS_ECDHE_PSK_WITH_RC4_128_SHA = 0xC033;
+ public static final int TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA = 0xC034;
+ public static final int TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA = 0xC035;
+ public static final int TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA = 0xC036;
+ public static final int TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 = 0xC037;
+ public static final int TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 = 0xC038;
+ public static final int TLS_ECDHE_PSK_WITH_NULL_SHA = 0xC039;
+ public static final int TLS_ECDHE_PSK_WITH_NULL_SHA256 = 0xC03A;
+ public static final int TLS_ECDHE_PSK_WITH_NULL_SHA384 = 0xC03B;
+
+ /*
+ * RFC 5746
+ */
+ public static final int TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF;
+
+ /*
+ * RFC 6367
+ */
+ public static final int TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC072;
+ public static final int TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC073;
+ public static final int TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC074;
+ public static final int TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC075;
+ public static final int TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC076;
+ public static final int TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC077;
+ public static final int TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 = 0xC078;
+ public static final int TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 = 0xC079;
+
+ public static final int TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07A;
+ public static final int TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07B;
+ public static final int TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07C;
+ public static final int TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07D;
+ public static final int TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC07E;
+ public static final int TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC07F;
+ public static final int TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 = 0xC080;
+ public static final int TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 = 0xC081;
+ public static final int TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 = 0xC082;
+ public static final int TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 = 0xC083;
+ public static final int TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 = 0xC084;
+ public static final int TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 = 0xC085;
+ public static final int TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC086;
+ public static final int TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC087;
+ public static final int TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC088;
+ public static final int TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC089;
+ public static final int TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08A;
+ public static final int TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08B;
+ public static final int TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08C;
+ public static final int TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08D;
+
+ public static final int TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC08E;
+ public static final int TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC08F;
+ public static final int TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC090;
+ public static final int TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC091;
+ public static final int TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 = 0xC092;
+ public static final int TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 = 0xC093;
+ public static final int TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC094;
+ public static final int TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC095;
+ public static final int TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC096;
+ public static final int TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC097;
+ public static final int TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC098;
+ public static final int TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC099;
+ public static final int TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 = 0xC09A;
+ public static final int TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 = 0xC09B;
+
+ /*
+ * RFC 6655
+ */
+ public static final int TLS_RSA_WITH_AES_128_CCM = 0xC09C;
+ public static final int TLS_RSA_WITH_AES_256_CCM = 0xC09D;
+ public static final int TLS_DHE_RSA_WITH_AES_128_CCM = 0xC09E;
+ public static final int TLS_DHE_RSA_WITH_AES_256_CCM = 0xC09F;
+ public static final int TLS_RSA_WITH_AES_128_CCM_8 = 0xC0A0;
+ public static final int TLS_RSA_WITH_AES_256_CCM_8 = 0xC0A1;
+ public static final int TLS_DHE_RSA_WITH_AES_128_CCM_8 = 0xC0A2;
+ public static final int TLS_DHE_RSA_WITH_AES_256_CCM_8 = 0xC0A3;
+ public static final int TLS_PSK_WITH_AES_128_CCM = 0xC0A4;
+ public static final int TLS_PSK_WITH_AES_256_CCM = 0xC0A5;
+ public static final int TLS_DHE_PSK_WITH_AES_128_CCM = 0xC0A6;
+ public static final int TLS_DHE_PSK_WITH_AES_256_CCM = 0xC0A7;
+ public static final int TLS_PSK_WITH_AES_128_CCM_8 = 0xC0A8;
+ public static final int TLS_PSK_WITH_AES_256_CCM_8 = 0xC0A9;
+ public static final int TLS_PSK_DHE_WITH_AES_128_CCM_8 = 0xC0AA;
+ public static final int TLS_PSK_DHE_WITH_AES_256_CCM_8 = 0xC0AB;
+
+ /*
+ * draft-agl-tls-chacha20poly1305-04
+ */
+ public static final int TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCC13;
+ public static final int TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCC14;
+ public static final int TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = 0xCC15;
+
+ /*
+ * draft-josefsson-salsa20-tls-04
+ */
+ public static final int TLS_RSA_WITH_ESTREAM_SALSA20_SHA1 = 0xE410;
+ public static final int TLS_RSA_WITH_SALSA20_SHA1 = 0xE411;
+ public static final int TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1 = 0xE412;
+ public static final int TLS_ECDHE_RSA_WITH_SALSA20_SHA1 = 0xE413;
+ public static final int TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1 = 0xE414;
+ public static final int TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1 = 0xE415;
+ public static final int TLS_PSK_WITH_ESTREAM_SALSA20_SHA1 = 0xE416;
+ public static final int TLS_PSK_WITH_SALSA20_SHA1 = 0xE417;
+ public static final int TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1 = 0xE418;
+ public static final int TLS_ECDHE_PSK_WITH_SALSA20_SHA1 = 0xE419;
+ public static final int TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1 = 0xE41A;
+ public static final int TLS_RSA_PSK_WITH_SALSA20_SHA1 = 0xE41B;
+ public static final int TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1 = 0xE41C;
+ public static final int TLS_DHE_PSK_WITH_SALSA20_SHA1 = 0xE41D;
+ public static final int TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1 = 0xE41E;
+ public static final int TLS_DHE_RSA_WITH_SALSA20_SHA1 = 0xE41F;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CipherType.java b/core/src/main/java/org/spongycastle/crypto/tls/CipherType.java
new file mode 100644
index 00000000..253da6f6
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CipherType.java
@@ -0,0 +1,18 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class CipherType
+{
+ public static final int stream = 0;
+ public static final int block = 1;
+
+ /*
+ * RFC 5246
+ */
+ public static final int aead = 2;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ClientAuthenticationType.java b/core/src/main/java/org/spongycastle/crypto/tls/ClientAuthenticationType.java
new file mode 100644
index 00000000..90eb48f2
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ClientAuthenticationType.java
@@ -0,0 +1,11 @@
+package org.spongycastle.crypto.tls;
+
+public class ClientAuthenticationType
+{
+ /*
+ * RFC 5077 4
+ */
+ public static final short anonymous = 0;
+ public static final short certificate_based = 1;
+ public static final short psk = 2;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ClientCertificateType.java b/core/src/main/java/org/spongycastle/crypto/tls/ClientCertificateType.java
new file mode 100644
index 00000000..35752b07
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ClientCertificateType.java
@@ -0,0 +1,22 @@
+package org.spongycastle.crypto.tls;
+
+public class ClientCertificateType
+{
+ /*
+ * RFC 4346 7.4.4
+ */
+ public static final short rsa_sign = 1;
+ public static final short dss_sign = 2;
+ public static final short rsa_fixed_dh = 3;
+ public static final short dss_fixed_dh = 4;
+ public static final short rsa_ephemeral_dh_RESERVED = 5;
+ public static final short dss_ephemeral_dh_RESERVED = 6;
+ public static final short fortezza_dms_RESERVED = 20;
+
+ /*
+ * RFC 4492 5.5
+ */
+ public static final short ecdsa_sign = 64;
+ public static final short rsa_fixed_ecdh = 65;
+ public static final short ecdsa_fixed_ecdh = 66;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CombinedHash.java b/core/src/main/java/org/spongycastle/crypto/tls/CombinedHash.java
new file mode 100644
index 00000000..b32ad91e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CombinedHash.java
@@ -0,0 +1,135 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.Digest;
+
+/**
+ * A combined hash, which implements md5(m) || sha1(m).
+ */
+class CombinedHash
+ implements TlsHandshakeHash
+{
+ protected TlsContext context;
+ protected Digest md5;
+ protected Digest sha1;
+
+ CombinedHash()
+ {
+ this.md5 = TlsUtils.createHash(HashAlgorithm.md5);
+ this.sha1 = TlsUtils.createHash(HashAlgorithm.sha1);
+ }
+
+ CombinedHash(CombinedHash t)
+ {
+ this.context = t.context;
+ this.md5 = TlsUtils.cloneHash(HashAlgorithm.md5, t.md5);
+ this.sha1 = TlsUtils.cloneHash(HashAlgorithm.sha1, t.sha1);
+ }
+
+ public void init(TlsContext context)
+ {
+ this.context = context;
+ }
+
+ public TlsHandshakeHash notifyPRFDetermined()
+ {
+ return this;
+ }
+
+ public void trackHashAlgorithm(short hashAlgorithm)
+ {
+ throw new IllegalStateException("CombinedHash only supports calculating the legacy PRF for handshake hash");
+ }
+
+ public void sealHashAlgorithms()
+ {
+ }
+
+ public TlsHandshakeHash stopTracking()
+ {
+ return new CombinedHash(this);
+ }
+
+ public Digest forkPRFHash()
+ {
+ return new CombinedHash(this);
+ }
+
+ public byte[] getFinalHash(short hashAlgorithm)
+ {
+ throw new IllegalStateException("CombinedHash doesn't support multiple hashes");
+ }
+
+ /**
+ * @see org.spongycastle.crypto.Digest#getAlgorithmName()
+ */
+ public String getAlgorithmName()
+ {
+ return md5.getAlgorithmName() + " and " + sha1.getAlgorithmName();
+ }
+
+ /**
+ * @see org.spongycastle.crypto.Digest#getDigestSize()
+ */
+ public int getDigestSize()
+ {
+ return md5.getDigestSize() + sha1.getDigestSize();
+ }
+
+ /**
+ * @see org.spongycastle.crypto.Digest#update(byte)
+ */
+ public void update(byte in)
+ {
+ md5.update(in);
+ sha1.update(in);
+ }
+
+ /**
+ * @see org.spongycastle.crypto.Digest#update(byte[], int, int)
+ */
+ public void update(byte[] in, int inOff, int len)
+ {
+ md5.update(in, inOff, len);
+ sha1.update(in, inOff, len);
+ }
+
+ /**
+ * @see org.spongycastle.crypto.Digest#doFinal(byte[], int)
+ */
+ public int doFinal(byte[] out, int outOff)
+ {
+ if (context != null && TlsUtils.isSSL(context))
+ {
+ ssl3Complete(md5, SSL3Mac.IPAD, SSL3Mac.OPAD, 48);
+ ssl3Complete(sha1, SSL3Mac.IPAD, SSL3Mac.OPAD, 40);
+ }
+
+ int i1 = md5.doFinal(out, outOff);
+ int i2 = sha1.doFinal(out, outOff + i1);
+ return i1 + i2;
+ }
+
+ /**
+ * @see org.spongycastle.crypto.Digest#reset()
+ */
+ public void reset()
+ {
+ md5.reset();
+ sha1.reset();
+ }
+
+ protected void ssl3Complete(Digest d, byte[] ipad, byte[] opad, int padLength)
+ {
+ byte[] master_secret = context.getSecurityParameters().masterSecret;
+
+ d.update(master_secret, 0, master_secret.length);
+ d.update(ipad, 0, padLength);
+
+ byte[] tmp = new byte[d.getDigestSize()];
+ d.doFinal(tmp, 0);
+
+ d.update(master_secret, 0, master_secret.length);
+ d.update(opad, 0, padLength);
+ d.update(tmp, 0, tmp.length);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/CompressionMethod.java b/core/src/main/java/org/spongycastle/crypto/tls/CompressionMethod.java
new file mode 100644
index 00000000..2a19b7ff
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/CompressionMethod.java
@@ -0,0 +1,24 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246 6.1
+ */
+public class CompressionMethod
+{
+ public static final short _null = 0;
+
+ /**
+ * @deprecated use '_null' instead
+ */
+ public static final short NULL = _null;
+
+ /*
+ * RFC 3749 2
+ */
+ public static final short DEFLATE = 1;
+
+ /*
+ * Values from 224 decimal (0xE0) through 255 decimal (0xFF)
+ * inclusive are reserved for private use.
+ */
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ConnectionEnd.java b/core/src/main/java/org/spongycastle/crypto/tls/ConnectionEnd.java
new file mode 100644
index 00000000..4964eff7
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ConnectionEnd.java
@@ -0,0 +1,13 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class ConnectionEnd
+{
+ public static final int server = 0;
+ public static final int client = 1;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ContentType.java b/core/src/main/java/org/spongycastle/crypto/tls/ContentType.java
new file mode 100644
index 00000000..8704dd80
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ContentType.java
@@ -0,0 +1,13 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246 6.2.1
+ */
+public class ContentType
+{
+ public static final short change_cipher_spec = 20;
+ public static final short alert = 21;
+ public static final short handshake = 22;
+ public static final short application_data = 23;
+ public static final short heartbeat = 24;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSClientProtocol.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSClientProtocol.java
new file mode 100644
index 00000000..f5a4f80c
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSClientProtocol.java
@@ -0,0 +1,831 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.spongycastle.util.Arrays;
+
+public class DTLSClientProtocol
+ extends DTLSProtocol
+{
+ public DTLSClientProtocol(SecureRandom secureRandom)
+ {
+ super(secureRandom);
+ }
+
+ public DTLSTransport connect(TlsClient client, DatagramTransport transport)
+ throws IOException
+ {
+ if (client == null)
+ {
+ throw new IllegalArgumentException("'client' cannot be null");
+ }
+ if (transport == null)
+ {
+ throw new IllegalArgumentException("'transport' cannot be null");
+ }
+
+ SecurityParameters securityParameters = new SecurityParameters();
+ securityParameters.entity = ConnectionEnd.client;
+
+ ClientHandshakeState state = new ClientHandshakeState();
+ state.client = client;
+ state.clientContext = new TlsClientContextImpl(secureRandom, securityParameters);
+
+ securityParameters.clientRandom = TlsProtocol.createRandomBlock(client.shouldUseGMTUnixTime(),
+ state.clientContext.getNonceRandomGenerator());
+
+ client.init(state.clientContext);
+
+ DTLSRecordLayer recordLayer = new DTLSRecordLayer(transport, state.clientContext, client, ContentType.handshake);
+
+ TlsSession sessionToResume = state.client.getSessionToResume();
+ if (sessionToResume != null)
+ {
+ SessionParameters sessionParameters = sessionToResume.exportSessionParameters();
+ if (sessionParameters != null)
+ {
+ state.tlsSession = sessionToResume;
+ state.sessionParameters = sessionParameters;
+ }
+ }
+
+ try
+ {
+ return clientHandshake(state, recordLayer);
+ }
+ catch (TlsFatalAlert fatalAlert)
+ {
+ recordLayer.fail(fatalAlert.getAlertDescription());
+ throw fatalAlert;
+ }
+ catch (IOException e)
+ {
+ recordLayer.fail(AlertDescription.internal_error);
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ recordLayer.fail(AlertDescription.internal_error);
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ protected DTLSTransport clientHandshake(ClientHandshakeState state, DTLSRecordLayer recordLayer)
+ throws IOException
+ {
+ SecurityParameters securityParameters = state.clientContext.getSecurityParameters();
+ DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.clientContext, recordLayer);
+
+ byte[] clientHelloBody = generateClientHello(state, state.client);
+ handshake.sendMessage(HandshakeType.client_hello, clientHelloBody);
+
+ DTLSReliableHandshake.Message serverMessage = handshake.receiveMessage();
+
+ while (serverMessage.getType() == HandshakeType.hello_verify_request)
+ {
+ ProtocolVersion recordLayerVersion = recordLayer.resetDiscoveredPeerVersion();
+ ProtocolVersion client_version = state.clientContext.getClientVersion();
+
+ /*
+ * 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 (!recordLayerVersion.isEqualOrEarlierVersionOf(client_version))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ byte[] cookie = processHelloVerifyRequest(state, serverMessage.getBody());
+ byte[] patched = patchClientHelloWithCookie(clientHelloBody, cookie);
+
+ handshake.resetHandshakeMessagesDigest();
+ handshake.sendMessage(HandshakeType.client_hello, patched);
+
+ serverMessage = handshake.receiveMessage();
+ }
+
+ if (serverMessage.getType() == HandshakeType.server_hello)
+ {
+ reportServerVersion(state, recordLayer.getDiscoveredPeerVersion());
+
+ processServerHello(state, serverMessage.getBody());
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ if (state.maxFragmentLength >= 0)
+ {
+ int plainTextLimit = 1 << (8 + state.maxFragmentLength);
+ recordLayer.setPlaintextLimit(plainTextLimit);
+ }
+
+ securityParameters.cipherSuite = state.selectedCipherSuite;
+ securityParameters.compressionAlgorithm = state.selectedCompressionMethod;
+ securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.clientContext, state.selectedCipherSuite);
+
+ /*
+ * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has
+ * a verify_data_length equal to 12. This includes all existing cipher suites.
+ */
+ securityParameters.verifyDataLength = 12;
+
+ handshake.notifyHelloComplete();
+
+ boolean resumedSession = state.selectedSessionID.length > 0 && state.tlsSession != null
+ && Arrays.areEqual(state.selectedSessionID, state.tlsSession.getSessionID());
+
+ if (resumedSession)
+ {
+ if (securityParameters.getCipherSuite() != state.sessionParameters.getCipherSuite()
+ || securityParameters.getCompressionAlgorithm() != state.sessionParameters.getCompressionAlgorithm())
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ securityParameters.masterSecret = Arrays.clone(state.sessionParameters.getMasterSecret());
+ recordLayer.initPendingEpoch(state.client.getCipher());
+
+ // NOTE: Calculated exclusive of the actual Finished message from the server
+ byte[] expectedServerVerifyData = TlsUtils.calculateVerifyData(state.clientContext, ExporterLabel.server_finished,
+ TlsProtocol.getCurrentPRFHash(state.clientContext, handshake.getHandshakeHash(), null));
+ processFinished(handshake.receiveMessageBody(HandshakeType.finished), expectedServerVerifyData);
+
+ // NOTE: Calculated exclusive of the Finished message itself
+ byte[] clientVerifyData = TlsUtils.calculateVerifyData(state.clientContext, ExporterLabel.client_finished,
+ TlsProtocol.getCurrentPRFHash(state.clientContext, handshake.getHandshakeHash(), null));
+ handshake.sendMessage(HandshakeType.finished, clientVerifyData);
+
+ handshake.finish();
+
+ state.clientContext.setResumableSession(state.tlsSession);
+
+ state.client.notifyHandshakeComplete();
+
+ return new DTLSTransport(recordLayer);
+ }
+
+ invalidateSession(state);
+
+ if (state.selectedSessionID.length > 0)
+ {
+ state.tlsSession = new TlsSessionImpl(state.selectedSessionID, null);
+ }
+
+ serverMessage = handshake.receiveMessage();
+
+ if (serverMessage.getType() == HandshakeType.supplemental_data)
+ {
+ processServerSupplementalData(state, serverMessage.getBody());
+ serverMessage = handshake.receiveMessage();
+ }
+ else
+ {
+ state.client.processServerSupplementalData(null);
+ }
+
+ state.keyExchange = state.client.getKeyExchange();
+ state.keyExchange.init(state.clientContext);
+
+ Certificate serverCertificate = null;
+
+ if (serverMessage.getType() == HandshakeType.certificate)
+ {
+ serverCertificate = processServerCertificate(state, serverMessage.getBody());
+ serverMessage = handshake.receiveMessage();
+ }
+ else
+ {
+ // Okay, Certificate is optional
+ state.keyExchange.skipServerCredentials();
+ }
+
+ // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+ if (serverCertificate == null || serverCertificate.isEmpty())
+ {
+ state.allowCertificateStatus = false;
+ }
+
+ if (serverMessage.getType() == HandshakeType.certificate_status)
+ {
+ processCertificateStatus(state, serverMessage.getBody());
+ serverMessage = handshake.receiveMessage();
+ }
+ else
+ {
+ // Okay, CertificateStatus is optional
+ }
+
+ if (serverMessage.getType() == HandshakeType.server_key_exchange)
+ {
+ processServerKeyExchange(state, serverMessage.getBody());
+ serverMessage = handshake.receiveMessage();
+ }
+ else
+ {
+ // Okay, ServerKeyExchange is optional
+ state.keyExchange.skipServerKeyExchange();
+ }
+
+ if (serverMessage.getType() == HandshakeType.certificate_request)
+ {
+ processCertificateRequest(state, serverMessage.getBody());
+
+ /*
+ * TODO Give the client a chance to immediately select the CertificateVerify hash
+ * algorithm here to avoid tracking the other hash algorithms unnecessarily?
+ */
+ TlsUtils.trackHashAlgorithms(handshake.getHandshakeHash(),
+ state.certificateRequest.getSupportedSignatureAlgorithms());
+
+ serverMessage = handshake.receiveMessage();
+ }
+ else
+ {
+ // Okay, CertificateRequest is optional
+ }
+
+ if (serverMessage.getType() == HandshakeType.server_hello_done)
+ {
+ if (serverMessage.getBody().length != 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ handshake.getHandshakeHash().sealHashAlgorithms();
+
+ Vector clientSupplementalData = state.client.getClientSupplementalData();
+ if (clientSupplementalData != null)
+ {
+ byte[] supplementalDataBody = generateSupplementalData(clientSupplementalData);
+ handshake.sendMessage(HandshakeType.supplemental_data, supplementalDataBody);
+ }
+
+ if (state.certificateRequest != null)
+ {
+ state.clientCredentials = state.authentication.getClientCredentials(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 (state.clientCredentials != null)
+ {
+ clientCertificate = state.clientCredentials.getCertificate();
+ }
+ if (clientCertificate == null)
+ {
+ clientCertificate = Certificate.EMPTY_CHAIN;
+ }
+
+ byte[] certificateBody = generateCertificate(clientCertificate);
+ handshake.sendMessage(HandshakeType.certificate, certificateBody);
+ }
+
+ if (state.clientCredentials != null)
+ {
+ state.keyExchange.processClientCredentials(state.clientCredentials);
+ }
+ else
+ {
+ state.keyExchange.skipClientCredentials();
+ }
+
+ byte[] clientKeyExchangeBody = generateClientKeyExchange(state);
+ handshake.sendMessage(HandshakeType.client_key_exchange, clientKeyExchangeBody);
+
+ TlsProtocol.establishMasterSecret(state.clientContext, state.keyExchange);
+ recordLayer.initPendingEpoch(state.client.getCipher());
+
+ TlsHandshakeHash prepareFinishHash = handshake.prepareToFinish();
+
+ if (state.clientCredentials != null && state.clientCredentials instanceof TlsSignerCredentials)
+ {
+ TlsSignerCredentials signerCredentials = (TlsSignerCredentials)state.clientCredentials;
+
+ /*
+ * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
+ */
+ SignatureAndHashAlgorithm signatureAndHashAlgorithm;
+ byte[] hash;
+
+ if (TlsUtils.isTLSv12(state.clientContext))
+ {
+ signatureAndHashAlgorithm = signerCredentials.getSignatureAndHashAlgorithm();
+ if (signatureAndHashAlgorithm == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ hash = prepareFinishHash.getFinalHash(signatureAndHashAlgorithm.getHash());
+ }
+ else
+ {
+ signatureAndHashAlgorithm = null;
+ hash = TlsProtocol.getCurrentPRFHash(state.clientContext, prepareFinishHash, null);
+ }
+
+ byte[] signature = signerCredentials.generateCertificateSignature(hash);
+ DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature);
+ byte[] certificateVerifyBody = generateCertificateVerify(state, certificateVerify);
+ handshake.sendMessage(HandshakeType.certificate_verify, certificateVerifyBody);
+ }
+
+ // NOTE: Calculated exclusive of the Finished message itself
+ byte[] clientVerifyData = TlsUtils.calculateVerifyData(state.clientContext, ExporterLabel.client_finished,
+ TlsProtocol.getCurrentPRFHash(state.clientContext, handshake.getHandshakeHash(), null));
+ handshake.sendMessage(HandshakeType.finished, clientVerifyData);
+
+ if (state.expectSessionTicket)
+ {
+ serverMessage = handshake.receiveMessage();
+ if (serverMessage.getType() == HandshakeType.session_ticket)
+ {
+ processNewSessionTicket(state, serverMessage.getBody());
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ // NOTE: Calculated exclusive of the actual Finished message from the server
+ byte[] expectedServerVerifyData = TlsUtils.calculateVerifyData(state.clientContext, ExporterLabel.server_finished,
+ TlsProtocol.getCurrentPRFHash(state.clientContext, handshake.getHandshakeHash(), null));
+ processFinished(handshake.receiveMessageBody(HandshakeType.finished), expectedServerVerifyData);
+
+ handshake.finish();
+
+ if (state.tlsSession != null)
+ {
+ state.sessionParameters = new SessionParameters.Builder()
+ .setCipherSuite(securityParameters.cipherSuite)
+ .setCompressionAlgorithm(securityParameters.compressionAlgorithm)
+ .setMasterSecret(securityParameters.masterSecret)
+ .setPeerCertificate(serverCertificate)
+ .build();
+
+ state.tlsSession = TlsUtils.importSession(state.tlsSession.getSessionID(), state.sessionParameters);
+
+ state.clientContext.setResumableSession(state.tlsSession);
+ }
+
+ state.client.notifyHandshakeComplete();
+
+ return new DTLSTransport(recordLayer);
+ }
+
+ protected byte[] generateCertificateVerify(ClientHandshakeState state, DigitallySigned certificateVerify)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ certificateVerify.encode(buf);
+ return buf.toByteArray();
+ }
+
+ protected byte[] generateClientHello(ClientHandshakeState state, TlsClient client)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ ProtocolVersion client_version = client.getClientVersion();
+ if (!client_version.isDTLS())
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ state.clientContext.setClientVersion(client_version);
+ TlsUtils.writeVersion(client_version, buf);
+
+ buf.write(state.clientContext.getSecurityParameters().getClientRandom());
+
+ // Session ID
+ byte[] session_id = TlsUtils.EMPTY_BYTES;
+ if (state.tlsSession != null)
+ {
+ session_id = state.tlsSession.getSessionID();
+ if (session_id == null || session_id.length > 32)
+ {
+ session_id = TlsUtils.EMPTY_BYTES;
+ }
+ }
+ TlsUtils.writeOpaque8(session_id, buf);
+
+ // Cookie
+ TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf);
+
+ /*
+ * Cipher suites
+ */
+ state.offeredCipherSuites = client.getCipherSuites();
+
+ // Integer -> byte[]
+ state.clientExtensions = client.getClientExtensions();
+
+ // Cipher Suites (and SCSV)
+ {
+ /*
+ * RFC 5746 3.4. The client MUST include either an empty "renegotiation_info" extension,
+ * or the TLS_EMPTY_RENEGOTIATION_INFO_SCSV signaling cipher suite value in the
+ * ClientHello. Including both is NOT RECOMMENDED.
+ */
+ byte[] renegExtData = TlsUtils.getExtensionData(state.clientExtensions, TlsProtocol.EXT_RenegotiationInfo);
+ boolean noRenegExt = (null == renegExtData);
+
+ boolean noSCSV = !Arrays.contains(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+
+ if (noRenegExt && noSCSV)
+ {
+ // TODO Consider whether to default to a client extension instead
+ state.offeredCipherSuites = Arrays.append(state.offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+ }
+
+ TlsUtils.writeUint16ArrayWithUint16Length(state.offeredCipherSuites, buf);
+ }
+
+ // TODO Add support for compression
+ // Compression methods
+ // state.offeredCompressionMethods = client.getCompressionMethods();
+ state.offeredCompressionMethods = new short[]{ CompressionMethod._null };
+
+ TlsUtils.writeUint8ArrayWithUint8Length(state.offeredCompressionMethods, buf);
+
+ // Extensions
+ if (state.clientExtensions != null)
+ {
+ TlsProtocol.writeExtensions(buf, state.clientExtensions);
+ }
+
+ return buf.toByteArray();
+ }
+
+ protected byte[] generateClientKeyExchange(ClientHandshakeState state)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ state.keyExchange.generateClientKeyExchange(buf);
+ return buf.toByteArray();
+ }
+
+ protected void invalidateSession(ClientHandshakeState state)
+ {
+ if (state.sessionParameters != null)
+ {
+ state.sessionParameters.clear();
+ state.sessionParameters = null;
+ }
+
+ if (state.tlsSession != null)
+ {
+ state.tlsSession.invalidate();
+ state.tlsSession = null;
+ }
+ }
+
+ protected void processCertificateRequest(ClientHandshakeState state, byte[] body)
+ throws IOException
+ {
+ if (state.authentication == null)
+ {
+ /*
+ * 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);
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ state.certificateRequest = CertificateRequest.parse(state.clientContext, buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ state.keyExchange.validateCertificateRequest(state.certificateRequest);
+ }
+
+ protected void processCertificateStatus(ClientHandshakeState state, byte[] body)
+ throws IOException
+ {
+ if (!state.allowCertificateStatus)
+ {
+ /*
+ * RFC 3546 3.6. 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..
+ */
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ state.certificateStatus = CertificateStatus.parse(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ // TODO[RFC 3546] Figure out how to provide this to the client/authentication.
+ }
+
+ protected byte[] processHelloVerifyRequest(ClientHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ ProtocolVersion server_version = TlsUtils.readVersion(buf);
+ byte[] cookie = TlsUtils.readOpaque8(buf);
+
+ 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.getClientVersion()))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ /*
+ * 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.
+ */
+ if (!ProtocolVersion.DTLSv12.isEqualOrEarlierVersionOf(server_version) && cookie.length > 32)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return cookie;
+ }
+
+ protected void processNewSessionTicket(ClientHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ NewSessionTicket newSessionTicket = NewSessionTicket.parse(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ state.client.notifyNewSessionTicket(newSessionTicket);
+ }
+
+ protected Certificate processServerCertificate(ClientHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ Certificate serverCertificate = Certificate.parse(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ state.keyExchange.processServerCertificate(serverCertificate);
+ state.authentication = state.client.getAuthentication();
+ state.authentication.notifyServerCertificate(serverCertificate);
+
+ return serverCertificate;
+ }
+
+ protected void processServerHello(ClientHandshakeState state, byte[] body)
+ throws IOException
+ {
+ SecurityParameters securityParameters = state.clientContext.getSecurityParameters();
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ ProtocolVersion server_version = TlsUtils.readVersion(buf);
+ reportServerVersion(state, server_version);
+
+ securityParameters.serverRandom = TlsUtils.readFully(32, buf);
+
+ state.selectedSessionID = TlsUtils.readOpaque8(buf);
+ if (state.selectedSessionID.length > 32)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ state.client.notifySessionID(state.selectedSessionID);
+
+ state.selectedCipherSuite = TlsUtils.readUint16(buf);
+ if (!Arrays.contains(state.offeredCipherSuites, state.selectedCipherSuite)
+ || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+ || state.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+ || !TlsUtils.isValidCipherSuiteForVersion(state.selectedCipherSuite, server_version))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ validateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.illegal_parameter);
+
+ state.client.notifySelectedCipherSuite(state.selectedCipherSuite);
+
+ state.selectedCompressionMethod = TlsUtils.readUint8(buf);
+ if (!Arrays.contains(state.offeredCompressionMethods, state.selectedCompressionMethod))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ state.client.notifySelectedCompressionMethod(state.selectedCompressionMethod);
+
+ /*
+ * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server
+ * hello message when the client has requested extended functionality via the extended
+ * client hello message specified in Section 2.1. ... Note that the extended server hello
+ * message is only sent in response to an extended client hello message. This prevents the
+ * possibility that the extended server hello message could "break" existing TLS 1.0
+ * clients.
+ */
+
+ /*
+ * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+ * extensions appearing in the client hello, and send a server hello containing no
+ * extensions.
+ */
+
+ // Integer -> byte[]
+ Hashtable serverExtensions = TlsProtocol.readExtensions(buf);
+
+ /*
+ * 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 (serverExtensions != null)
+ {
+ Enumeration e = serverExtensions.keys();
+ while (e.hasMoreElements())
+ {
+ Integer extType = (Integer)e.nextElement();
+
+ /*
+ * RFC 5746 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. TLS implementations
+ * MUST continue to comply with Section 7.4.1.4 for all other extensions.
+ */
+ if (!extType.equals(TlsProtocol.EXT_RenegotiationInfo)
+ && null == TlsUtils.getExtensionData(state.clientExtensions, extType))
+ {
+ /*
+ * RFC 3546 2.3 Note that for all extension types (including those defined in
+ * future), the extension type MUST NOT appear in the extended server hello
+ * unless the same extension type appeared in the corresponding client hello.
+ * Thus clients MUST abort the handshake if they receive an extension type in
+ * the extended server hello that they did not request in the associated
+ * (extended) client hello.
+ */
+ throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+ }
+ }
+
+ /*
+ * 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 = (byte[])serverExtensions.get(TlsProtocol.EXT_RenegotiationInfo);
+ 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).
+ */
+ state.secure_renegotiation = true;
+
+ if (!Arrays.constantTimeAreEqual(renegExtData,
+ TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES)))
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ }
+ }
+
+ /*
+ * draft-ietf-tls-encrypt-then-mac-03 3. If a server receives an encrypt-then-MAC
+ * request extension from a client and then selects a stream or AEAD cipher suite, it
+ * MUST NOT send an encrypt-then-MAC response extension back to the client.
+ */
+ boolean serverSentEncryptThenMAC = TlsExtensionsUtils.hasEncryptThenMACExtension(serverExtensions);
+ if (serverSentEncryptThenMAC && !TlsUtils.isBlockCipherSuite(state.selectedCipherSuite))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ securityParameters.encryptThenMAC = serverSentEncryptThenMAC;
+
+ state.maxFragmentLength = evaluateMaxFragmentLengthExtension(state.clientExtensions, serverExtensions,
+ AlertDescription.illegal_parameter);
+
+ securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(serverExtensions);
+
+ state.allowCertificateStatus = TlsUtils.hasExpectedEmptyExtensionData(serverExtensions,
+ TlsExtensionsUtils.EXT_status_request, AlertDescription.illegal_parameter);
+
+ state.expectSessionTicket = TlsUtils.hasExpectedEmptyExtensionData(serverExtensions,
+ TlsProtocol.EXT_SessionTicket, AlertDescription.illegal_parameter);
+ }
+
+ state.client.notifySecureRenegotiation(state.secure_renegotiation);
+
+ if (state.clientExtensions != null)
+ {
+ state.client.processServerExtensions(serverExtensions);
+ }
+ }
+
+ protected void processServerKeyExchange(ClientHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ state.keyExchange.processServerKeyExchange(buf);
+
+ TlsProtocol.assertEmpty(buf);
+ }
+
+ protected void processServerSupplementalData(ClientHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+ Vector serverSupplementalData = TlsProtocol.readSupplementalDataMessage(buf);
+ state.client.processServerSupplementalData(serverSupplementalData);
+ }
+
+ protected void reportServerVersion(ClientHandshakeState state, ProtocolVersion server_version)
+ throws IOException
+ {
+ TlsClientContextImpl clientContext = state.clientContext;
+ ProtocolVersion currentServerVersion = clientContext.getServerVersion();
+ if (null == currentServerVersion)
+ {
+ clientContext.setServerVersion(server_version);
+ state.client.notifyServerVersion(server_version);
+ }
+ else if (!currentServerVersion.equals(server_version))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ protected static byte[] patchClientHelloWithCookie(byte[] clientHelloBody, byte[] cookie)
+ throws IOException
+ {
+ int sessionIDPos = 34;
+ int sessionIDLength = TlsUtils.readUint8(clientHelloBody, sessionIDPos);
+
+ int cookieLengthPos = sessionIDPos + 1 + sessionIDLength;
+ int cookiePos = cookieLengthPos + 1;
+
+ byte[] patched = new byte[clientHelloBody.length + cookie.length];
+ System.arraycopy(clientHelloBody, 0, patched, 0, cookieLengthPos);
+ TlsUtils.checkUint8(cookie.length);
+ TlsUtils.writeUint8(cookie.length, patched, cookieLengthPos);
+ System.arraycopy(cookie, 0, patched, cookiePos, cookie.length);
+ System.arraycopy(clientHelloBody, cookiePos, patched, cookiePos + cookie.length, clientHelloBody.length
+ - cookiePos);
+
+ return patched;
+ }
+
+ protected static class ClientHandshakeState
+ {
+ TlsClient client = null;
+ TlsClientContextImpl clientContext = null;
+ TlsSession tlsSession = null;
+ SessionParameters sessionParameters = null;
+ SessionParameters.Builder sessionParametersBuilder = null;
+ int[] offeredCipherSuites = null;
+ short[] offeredCompressionMethods = null;
+ Hashtable clientExtensions = null;
+ byte[] selectedSessionID = null;
+ int selectedCipherSuite = -1;
+ short selectedCompressionMethod = -1;
+ boolean secure_renegotiation = false;
+ short maxFragmentLength = -1;
+ boolean allowCertificateStatus = false;
+ boolean expectSessionTicket = false;
+ TlsKeyExchange keyExchange = null;
+ TlsAuthentication authentication = null;
+ CertificateStatus certificateStatus = null;
+ CertificateRequest certificateRequest = null;
+ TlsCredentials clientCredentials = null;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSEpoch.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSEpoch.java
new file mode 100644
index 00000000..33dda1c7
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSEpoch.java
@@ -0,0 +1,52 @@
+package org.spongycastle.crypto.tls;
+
+class DTLSEpoch
+{
+ private final DTLSReplayWindow replayWindow = new DTLSReplayWindow();
+
+ private final int epoch;
+ private final TlsCipher cipher;
+
+ private long sequence_number = 0;
+
+ DTLSEpoch(int epoch, TlsCipher cipher)
+ {
+ if (epoch < 0)
+ {
+ throw new IllegalArgumentException("'epoch' must be >= 0");
+ }
+ if (cipher == null)
+ {
+ throw new IllegalArgumentException("'cipher' cannot be null");
+ }
+
+ this.epoch = epoch;
+ this.cipher = cipher;
+ }
+
+ long allocateSequenceNumber()
+ {
+ // TODO Check for overflow
+ return sequence_number++;
+ }
+
+ TlsCipher getCipher()
+ {
+ return cipher;
+ }
+
+ int getEpoch()
+ {
+ return epoch;
+ }
+
+ DTLSReplayWindow getReplayWindow()
+ {
+ return replayWindow;
+ }
+
+ long getSequence_number()
+ {
+ return sequence_number;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSHandshakeRetransmit.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSHandshakeRetransmit.java
new file mode 100644
index 00000000..f0b0086c
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSHandshakeRetransmit.java
@@ -0,0 +1,9 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+interface DTLSHandshakeRetransmit
+{
+ void receivedHandshakeRecord(int epoch, byte[] buf, int off, int len)
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSProtocol.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSProtocol.java
new file mode 100644
index 00000000..13d5db00
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSProtocol.java
@@ -0,0 +1,78 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.spongycastle.util.Arrays;
+
+public abstract class DTLSProtocol
+{
+ protected final SecureRandom secureRandom;
+
+ protected DTLSProtocol(SecureRandom secureRandom)
+ {
+ if (secureRandom == null)
+ {
+ throw new IllegalArgumentException("'secureRandom' cannot be null");
+ }
+
+ this.secureRandom = secureRandom;
+ }
+
+ protected void processFinished(byte[] body, byte[] expected_verify_data)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data))
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ }
+
+ protected static short evaluateMaxFragmentLengthExtension(Hashtable clientExtensions, Hashtable serverExtensions,
+ short alertDescription) throws IOException
+ {
+ short maxFragmentLength = TlsExtensionsUtils.getMaxFragmentLengthExtension(serverExtensions);
+ if (maxFragmentLength >= 0 && maxFragmentLength != TlsExtensionsUtils.getMaxFragmentLengthExtension(clientExtensions))
+ {
+ throw new TlsFatalAlert(alertDescription);
+ }
+ return maxFragmentLength;
+ }
+
+ protected static byte[] generateCertificate(Certificate certificate)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ certificate.encode(buf);
+ return buf.toByteArray();
+ }
+
+ protected static byte[] generateSupplementalData(Vector supplementalData)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ TlsProtocol.writeSupplementalData(buf, supplementalData);
+ return buf.toByteArray();
+ }
+
+ protected static void validateSelectedCipherSuite(int selectedCipherSuite, short alertDescription)
+ throws IOException
+ {
+ switch (TlsUtils.getEncryptionAlgorithm(selectedCipherSuite))
+ {
+ case EncryptionAlgorithm.RC4_40:
+ case EncryptionAlgorithm.RC4_128:
+ throw new TlsFatalAlert(alertDescription);
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSReassembler.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSReassembler.java
new file mode 100644
index 00000000..810d6979
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSReassembler.java
@@ -0,0 +1,133 @@
+package org.spongycastle.crypto.tls;
+
+import java.util.Vector;
+
+class DTLSReassembler
+{
+ private final short msg_type;
+ private final byte[] body;
+
+ private Vector missing = new Vector();
+
+ DTLSReassembler(short msg_type, int length)
+ {
+ this.msg_type = msg_type;
+ this.body = new byte[length];
+ this.missing.addElement(new Range(0, length));
+ }
+
+ short getType()
+ {
+ return msg_type;
+ }
+
+ byte[] getBodyIfComplete()
+ {
+ return missing.isEmpty() ? body : null;
+ }
+
+ 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 (this.msg_type != msg_type || this.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 && !missing.isEmpty())
+ {
+ Range firstRange = (Range)missing.firstElement();
+ if (firstRange.getEnd() == 0)
+ {
+ missing.removeElementAt(0);
+ }
+ }
+ return;
+ }
+
+ for (int i = 0; i < missing.size(); ++i)
+ {
+ Range range = (Range)missing.elementAt(i);
+ if (range.getStart() >= fragment_end)
+ {
+ break;
+ }
+ if (range.getEnd() > fragment_offset)
+ {
+
+ int copyStart = Math.max(range.getStart(), fragment_offset);
+ int copyEnd = Math.min(range.getEnd(), fragment_end);
+ int copyLength = copyEnd - copyStart;
+
+ System.arraycopy(buf, off + copyStart - fragment_offset, body, copyStart,
+ copyLength);
+
+ if (copyStart == range.getStart())
+ {
+ if (copyEnd == range.getEnd())
+ {
+ missing.removeElementAt(i--);
+ }
+ else
+ {
+ range.setStart(copyEnd);
+ }
+ }
+ else
+ {
+ if (copyEnd == range.getEnd())
+ {
+ range.setEnd(copyStart);
+ }
+ else
+ {
+ missing.insertElementAt(new Range(copyEnd, range.getEnd()), ++i);
+ range.setEnd(copyStart);
+ }
+ }
+ }
+ }
+ }
+
+ void reset()
+ {
+ this.missing.removeAllElements();
+ this.missing.addElement(new Range(0, body.length));
+ }
+
+ private static class Range
+ {
+ private int start, end;
+
+ Range(int start, int end)
+ {
+ this.start = start;
+ this.end = end;
+ }
+
+ public int getStart()
+ {
+ return start;
+ }
+
+ public void setStart(int start)
+ {
+ this.start = start;
+ }
+
+ public int getEnd()
+ {
+ return end;
+ }
+
+ public void setEnd(int end)
+ {
+ this.end = end;
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSRecordLayer.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSRecordLayer.java
new file mode 100644
index 00000000..1f798d5a
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSRecordLayer.java
@@ -0,0 +1,516 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+class DTLSRecordLayer
+ implements DatagramTransport
+{
+ private static final int RECORD_HEADER_LENGTH = 13;
+ private static final int MAX_FRAGMENT_LENGTH = 1 << 14;
+ private static final long TCP_MSL = 1000L * 60 * 2;
+ private static final long RETRANSMIT_TIMEOUT = TCP_MSL * 2;
+
+ private final DatagramTransport transport;
+ private final TlsContext context;
+ private final TlsPeer peer;
+
+ private final ByteQueue recordQueue = new ByteQueue();
+
+ private volatile boolean closed = false;
+ private volatile boolean failed = false;
+ private volatile ProtocolVersion discoveredPeerVersion = null;
+ private volatile boolean inHandshake;
+ private volatile int plaintextLimit;
+ private DTLSEpoch currentEpoch, pendingEpoch;
+ private DTLSEpoch readEpoch, writeEpoch;
+
+ private DTLSHandshakeRetransmit retransmit = null;
+ private DTLSEpoch retransmitEpoch = null;
+ private long retransmitExpiry = 0;
+
+ DTLSRecordLayer(DatagramTransport transport, TlsContext context, TlsPeer peer, short contentType)
+ {
+ this.transport = transport;
+ this.context = context;
+ this.peer = peer;
+
+ this.inHandshake = true;
+
+ this.currentEpoch = new DTLSEpoch(0, new TlsNullCipher(context));
+ this.pendingEpoch = null;
+ this.readEpoch = currentEpoch;
+ this.writeEpoch = currentEpoch;
+
+ setPlaintextLimit(MAX_FRAGMENT_LENGTH);
+ }
+
+ void setPlaintextLimit(int plaintextLimit)
+ {
+ this.plaintextLimit = plaintextLimit;
+ }
+
+ ProtocolVersion getDiscoveredPeerVersion()
+ {
+ return discoveredPeerVersion;
+ }
+
+ ProtocolVersion resetDiscoveredPeerVersion()
+ {
+ ProtocolVersion result = discoveredPeerVersion;
+ discoveredPeerVersion = null;
+ return result;
+ }
+
+ void initPendingEpoch(TlsCipher pendingCipher)
+ {
+ if (pendingEpoch != null)
+ {
+ throw new IllegalStateException();
+ }
+
+ /*
+ * 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.pendingEpoch = new DTLSEpoch(writeEpoch.getEpoch() + 1, pendingCipher);
+ }
+
+ void handshakeSuccessful(DTLSHandshakeRetransmit retransmit)
+ {
+ if (readEpoch == currentEpoch || writeEpoch == currentEpoch)
+ {
+ // TODO
+ throw new IllegalStateException();
+ }
+
+ if (retransmit != null)
+ {
+ this.retransmit = retransmit;
+ this.retransmitEpoch = currentEpoch;
+ this.retransmitExpiry = System.currentTimeMillis() + RETRANSMIT_TIMEOUT;
+ }
+
+ this.inHandshake = false;
+ this.currentEpoch = pendingEpoch;
+ this.pendingEpoch = null;
+ }
+
+ void resetWriteEpoch()
+ {
+ if (retransmitEpoch != null)
+ {
+ this.writeEpoch = retransmitEpoch;
+ }
+ else
+ {
+ this.writeEpoch = currentEpoch;
+ }
+ }
+
+ public int getReceiveLimit()
+ throws IOException
+ {
+ return Math.min(this.plaintextLimit,
+ readEpoch.getCipher().getPlaintextLimit(transport.getReceiveLimit() - RECORD_HEADER_LENGTH));
+ }
+
+ public int getSendLimit()
+ throws IOException
+ {
+ return Math.min(this.plaintextLimit,
+ writeEpoch.getCipher().getPlaintextLimit(transport.getSendLimit() - RECORD_HEADER_LENGTH));
+ }
+
+ public int receive(byte[] buf, int off, int len, int waitMillis)
+ throws IOException
+ {
+ byte[] record = null;
+
+ for (;;)
+ {
+ int receiveLimit = Math.min(len, getReceiveLimit()) + RECORD_HEADER_LENGTH;
+ if (record == null || record.length < receiveLimit)
+ {
+ record = new byte[receiveLimit];
+ }
+
+ try
+ {
+ if (retransmit != null && System.currentTimeMillis() > retransmitExpiry)
+ {
+ retransmit = null;
+ retransmitEpoch = null;
+ }
+
+ int received = receiveRecord(record, 0, receiveLimit, waitMillis);
+ if (received < 0)
+ {
+ return received;
+ }
+ if (received < RECORD_HEADER_LENGTH)
+ {
+ continue;
+ }
+ int length = TlsUtils.readUint16(record, 11);
+ if (received != (length + RECORD_HEADER_LENGTH))
+ {
+ continue;
+ }
+
+ short type = TlsUtils.readUint8(record, 0);
+
+ // TODO Support user-specified custom protocols?
+ switch (type)
+ {
+ case ContentType.alert:
+ case ContentType.application_data:
+ case ContentType.change_cipher_spec:
+ case ContentType.handshake:
+ case ContentType.heartbeat:
+ break;
+ default:
+ // TODO Exception?
+ continue;
+ }
+
+ int epoch = TlsUtils.readUint16(record, 3);
+
+ DTLSEpoch recordEpoch = null;
+ if (epoch == readEpoch.getEpoch())
+ {
+ recordEpoch = readEpoch;
+ }
+ else if (type == ContentType.handshake && retransmitEpoch != null
+ && epoch == retransmitEpoch.getEpoch())
+ {
+ recordEpoch = retransmitEpoch;
+ }
+
+ if (recordEpoch == null)
+ {
+ continue;
+ }
+
+ long seq = TlsUtils.readUint48(record, 5);
+ if (recordEpoch.getReplayWindow().shouldDiscard(seq))
+ {
+ continue;
+ }
+
+ ProtocolVersion version = TlsUtils.readVersion(record, 1);
+ if (discoveredPeerVersion != null && !discoveredPeerVersion.equals(version))
+ {
+ continue;
+ }
+
+ byte[] plaintext = recordEpoch.getCipher().decodeCiphertext(
+ getMacSequenceNumber(recordEpoch.getEpoch(), seq), type, record, RECORD_HEADER_LENGTH,
+ received - RECORD_HEADER_LENGTH);
+
+ recordEpoch.getReplayWindow().reportAuthenticated(seq);
+
+ if (plaintext.length > this.plaintextLimit)
+ {
+ continue;
+ }
+
+ if (discoveredPeerVersion == null)
+ {
+ discoveredPeerVersion = version;
+ }
+
+ switch (type)
+ {
+ case ContentType.alert:
+ {
+ if (plaintext.length == 2)
+ {
+ short alertLevel = plaintext[0];
+ short alertDescription = plaintext[1];
+
+ peer.notifyAlertReceived(alertLevel, alertDescription);
+
+ if (alertLevel == AlertLevel.fatal)
+ {
+ fail(alertDescription);
+ throw new TlsFatalAlert(alertDescription);
+ }
+
+ // TODO Can close_notify be a fatal alert?
+ if (alertDescription == AlertDescription.close_notify)
+ {
+ closeTransport();
+ }
+ }
+
+ continue;
+ }
+ case ContentType.application_data:
+ {
+ if (inHandshake)
+ {
+ // TODO Consider buffering application data for new epoch that arrives
+ // out-of-order with the Finished message
+ continue;
+ }
+ break;
+ }
+ case ContentType.change_cipher_spec:
+ {
+ // Implicitly receive change_cipher_spec and change to pending cipher state
+
+ for (int i = 0; i < plaintext.length; ++i)
+ {
+ short message = TlsUtils.readUint8(plaintext, i);
+ if (message != ChangeCipherSpec.change_cipher_spec)
+ {
+ continue;
+ }
+
+ if (pendingEpoch != null)
+ {
+ readEpoch = pendingEpoch;
+ }
+ }
+
+ continue;
+ }
+ case ContentType.handshake:
+ {
+ if (!inHandshake)
+ {
+ if (retransmit != null)
+ {
+ retransmit.receivedHandshakeRecord(epoch, plaintext, 0, plaintext.length);
+ }
+
+ // TODO Consider support for HelloRequest
+ continue;
+ }
+ break;
+ }
+ case ContentType.heartbeat:
+ {
+ // TODO[RFC 6520]
+ continue;
+ }
+ }
+
+ /*
+ * NOTE: If we receive any non-handshake data in the new epoch implies the peer has
+ * received our final flight.
+ */
+ if (!inHandshake && retransmit != null)
+ {
+ this.retransmit = null;
+ this.retransmitEpoch = null;
+ }
+
+ System.arraycopy(plaintext, 0, buf, off, plaintext.length);
+ return plaintext.length;
+ }
+ catch (IOException e)
+ {
+ // NOTE: Assume this is a timeout for the moment
+ throw e;
+ }
+ }
+ }
+
+ public void send(byte[] buf, int off, int len)
+ throws IOException
+ {
+ short contentType = ContentType.application_data;
+
+ if (this.inHandshake || this.writeEpoch == this.retransmitEpoch)
+ {
+ contentType = ContentType.handshake;
+
+ short handshakeType = TlsUtils.readUint8(buf, off);
+ if (handshakeType == HandshakeType.finished)
+ {
+ DTLSEpoch nextEpoch = null;
+ if (this.inHandshake)
+ {
+ nextEpoch = pendingEpoch;
+ }
+ else if (this.writeEpoch == this.retransmitEpoch)
+ {
+ nextEpoch = currentEpoch;
+ }
+
+ if (nextEpoch == null)
+ {
+ // TODO
+ throw new IllegalStateException();
+ }
+
+ // 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);
+
+ writeEpoch = nextEpoch;
+ }
+ }
+
+ sendRecord(contentType, buf, off, len);
+ }
+
+ public void close()
+ throws IOException
+ {
+ if (!closed)
+ {
+ if (inHandshake)
+ {
+ warn(AlertDescription.user_canceled, "User canceled handshake");
+ }
+ closeTransport();
+ }
+ }
+
+ void fail(short alertDescription)
+ {
+ if (!closed)
+ {
+ try
+ {
+ raiseAlert(AlertLevel.fatal, alertDescription, null, null);
+ }
+ catch (Exception e)
+ {
+ // Ignore
+ }
+
+ failed = true;
+
+ closeTransport();
+ }
+ }
+
+ void warn(short alertDescription, String message)
+ throws IOException
+ {
+ raiseAlert(AlertLevel.warning, alertDescription, message, null);
+ }
+
+ private void closeTransport()
+ {
+ if (!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 (!failed)
+ {
+ warn(AlertDescription.close_notify, null);
+ }
+ transport.close();
+ }
+ catch (Exception e)
+ {
+ // Ignore
+ }
+
+ closed = true;
+ }
+ }
+
+ private void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause)
+ throws IOException
+ {
+ 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 receiveRecord(byte[] buf, int off, int len, int waitMillis)
+ throws IOException
+ {
+ if (recordQueue.size() > 0)
+ {
+ int length = 0;
+ if (recordQueue.size() >= RECORD_HEADER_LENGTH)
+ {
+ byte[] lengthBytes = new byte[2];
+ recordQueue.read(lengthBytes, 0, 2, 11);
+ length = TlsUtils.readUint16(lengthBytes, 0);
+ }
+
+ int received = Math.min(recordQueue.size(), RECORD_HEADER_LENGTH + length);
+ recordQueue.removeData(buf, off, received, 0);
+ return received;
+ }
+
+ int received = transport.receive(buf, off, len, waitMillis);
+ if (received >= RECORD_HEADER_LENGTH)
+ {
+ int fragmentLength = TlsUtils.readUint16(buf, off + 11);
+ int recordLength = RECORD_HEADER_LENGTH + fragmentLength;
+ if (received > recordLength)
+ {
+ recordQueue.addData(buf, off + recordLength, received - recordLength);
+ received = recordLength;
+ }
+ }
+
+ return received;
+ }
+
+ private void sendRecord(short contentType, byte[] buf, int off, int len)
+ throws IOException
+ {
+ if (len > this.plaintextLimit)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ /*
+ * RFC 5264 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);
+ }
+
+ int recordEpoch = writeEpoch.getEpoch();
+ long recordSequenceNumber = writeEpoch.allocateSequenceNumber();
+
+ byte[] ciphertext = writeEpoch.getCipher().encodePlaintext(
+ getMacSequenceNumber(recordEpoch, recordSequenceNumber), contentType, buf, off, len);
+
+ // TODO Check the ciphertext length?
+
+ byte[] record = new byte[ciphertext.length + RECORD_HEADER_LENGTH];
+ TlsUtils.writeUint8(contentType, record, 0);
+ ProtocolVersion version = discoveredPeerVersion != null ? discoveredPeerVersion : context.getClientVersion();
+ TlsUtils.writeVersion(version, record, 1);
+ TlsUtils.writeUint16(recordEpoch, record, 3);
+ TlsUtils.writeUint48(recordSequenceNumber, record, 5);
+ TlsUtils.writeUint16(ciphertext.length, record, 11);
+ System.arraycopy(ciphertext, 0, record, RECORD_HEADER_LENGTH, ciphertext.length);
+
+ transport.send(record, 0, record.length);
+ }
+
+ private static long getMacSequenceNumber(int epoch, long sequence_number)
+ {
+ return ((epoch & 0xFFFFFFFFL) << 48) | sequence_number;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSReliableHandshake.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSReliableHandshake.java
new file mode 100644
index 00000000..d89dabca
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSReliableHandshake.java
@@ -0,0 +1,453 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.spongycastle.util.Integers;
+
+class DTLSReliableHandshake
+{
+ private final static int MAX_RECEIVE_AHEAD = 10;
+
+ private final DTLSRecordLayer recordLayer;
+
+ private TlsHandshakeHash handshakeHash;
+
+ private Hashtable currentInboundFlight = new Hashtable();
+ private Hashtable previousInboundFlight = null;
+ private Vector outboundFlight = new Vector();
+ private boolean sending = true;
+
+ private int message_seq = 0, next_receive_seq = 0;
+
+ DTLSReliableHandshake(TlsContext context, DTLSRecordLayer transport)
+ {
+ this.recordLayer = transport;
+ this.handshakeHash = new DeferredHash();
+ this.handshakeHash.init(context);
+ }
+
+ void notifyHelloComplete()
+ {
+ this.handshakeHash = handshakeHash.notifyPRFDetermined();
+ }
+
+ TlsHandshakeHash getHandshakeHash()
+ {
+ return handshakeHash;
+ }
+
+ TlsHandshakeHash prepareToFinish()
+ {
+ TlsHandshakeHash result = handshakeHash;
+ this.handshakeHash = handshakeHash.stopTracking();
+ return result;
+ }
+
+ void sendMessage(short msg_type, byte[] body)
+ throws IOException
+ {
+ TlsUtils.checkUint24(body.length);
+
+ if (!sending)
+ {
+ checkInboundFlight();
+ sending = true;
+ outboundFlight.removeAllElements();
+ }
+
+ Message message = new Message(message_seq++, msg_type, body);
+
+ outboundFlight.addElement(message);
+
+ writeMessage(message);
+ updateHandshakeMessagesDigest(message);
+ }
+
+ byte[] receiveMessageBody(short msg_type)
+ throws IOException
+ {
+ Message message = receiveMessage();
+ if (message.getType() != msg_type)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ return message.getBody();
+ }
+
+ Message receiveMessage()
+ throws IOException
+ {
+ if (sending)
+ {
+ sending = false;
+ prepareInboundFlight();
+ }
+
+ // Check if we already have the next message waiting
+ {
+ DTLSReassembler next = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(next_receive_seq));
+ if (next != null)
+ {
+ byte[] body = next.getBodyIfComplete();
+ if (body != null)
+ {
+ previousInboundFlight = null;
+ return updateHandshakeMessagesDigest(new Message(next_receive_seq++, next.getType(), body));
+ }
+ }
+ }
+
+ byte[] buf = null;
+
+ // TODO Check the conditions under which we should reset this
+ int readTimeoutMillis = 1000;
+
+ for (;;)
+ {
+ int receiveLimit = recordLayer.getReceiveLimit();
+ if (buf == null || buf.length < receiveLimit)
+ {
+ buf = new byte[receiveLimit];
+ }
+
+ // TODO Handle records containing multiple handshake messages
+
+ try
+ {
+ for (; ; )
+ {
+ int received = recordLayer.receive(buf, 0, receiveLimit, readTimeoutMillis);
+ if (received < 0)
+ {
+ break;
+ }
+ if (received < 12)
+ {
+ continue;
+ }
+ int fragment_length = TlsUtils.readUint24(buf, 9);
+ if (received != (fragment_length + 12))
+ {
+ continue;
+ }
+ int seq = TlsUtils.readUint16(buf, 4);
+ if (seq > (next_receive_seq + MAX_RECEIVE_AHEAD))
+ {
+ continue;
+ }
+ short msg_type = TlsUtils.readUint8(buf, 0);
+ int length = TlsUtils.readUint24(buf, 1);
+ int fragment_offset = TlsUtils.readUint24(buf, 6);
+ if (fragment_offset + fragment_length > length)
+ {
+ continue;
+ }
+
+ if (seq < next_receive_seq)
+ {
+ /*
+ * NOTE: If we receive the previous flight of incoming messages in full
+ * again, retransmit our last flight
+ */
+ if (previousInboundFlight != null)
+ {
+ DTLSReassembler reassembler = (DTLSReassembler)previousInboundFlight.get(Integers
+ .valueOf(seq));
+ if (reassembler != null)
+ {
+ reassembler.contributeFragment(msg_type, length, buf, 12, fragment_offset,
+ fragment_length);
+
+ if (checkAll(previousInboundFlight))
+ {
+ resendOutboundFlight();
+
+ /*
+ * TODO[DTLS] implementations SHOULD back off handshake packet
+ * size during the retransmit backoff.
+ */
+ readTimeoutMillis = Math.min(readTimeoutMillis * 2, 60000);
+
+ resetAll(previousInboundFlight);
+ }
+ }
+ }
+ }
+ else
+ {
+ DTLSReassembler reassembler = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(seq));
+ if (reassembler == null)
+ {
+ reassembler = new DTLSReassembler(msg_type, length);
+ currentInboundFlight.put(Integers.valueOf(seq), reassembler);
+ }
+
+ reassembler.contributeFragment(msg_type, length, buf, 12, fragment_offset, fragment_length);
+
+ if (seq == next_receive_seq)
+ {
+ byte[] body = reassembler.getBodyIfComplete();
+ if (body != null)
+ {
+ previousInboundFlight = null;
+ return updateHandshakeMessagesDigest(new Message(next_receive_seq++,
+ reassembler.getType(), body));
+ }
+ }
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ // NOTE: Assume this is a timeout for the moment
+ }
+
+ resendOutboundFlight();
+
+ /*
+ * TODO[DTLS] implementations SHOULD back off handshake packet size during the
+ * retransmit backoff.
+ */
+ readTimeoutMillis = Math.min(readTimeoutMillis * 2, 60000);
+ }
+ }
+
+ void finish()
+ {
+ DTLSHandshakeRetransmit retransmit = null;
+ if (!sending)
+ {
+ checkInboundFlight();
+ }
+ else if (currentInboundFlight != 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 DTLSHandshakeRetransmit()
+ {
+ public void receivedHandshakeRecord(int epoch, byte[] buf, int off, int len)
+ throws IOException
+ {
+ /*
+ * TODO Need to handle the case where the previous inbound flight contains
+ * messages from two epochs.
+ */
+ if (len < 12)
+ {
+ return;
+ }
+ int fragment_length = TlsUtils.readUint24(buf, off + 9);
+ if (len != (fragment_length + 12))
+ {
+ return;
+ }
+ int seq = TlsUtils.readUint16(buf, off + 4);
+ if (seq >= next_receive_seq)
+ {
+ return;
+ }
+
+ short msg_type = TlsUtils.readUint8(buf, off);
+
+ // TODO This is a hack that only works until we try to support renegotiation
+ int expectedEpoch = msg_type == HandshakeType.finished ? 1 : 0;
+ if (epoch != expectedEpoch)
+ {
+ return;
+ }
+
+ int length = TlsUtils.readUint24(buf, off + 1);
+ int fragment_offset = TlsUtils.readUint24(buf, off + 6);
+ if (fragment_offset + fragment_length > length)
+ {
+ return;
+ }
+
+ DTLSReassembler reassembler = (DTLSReassembler)currentInboundFlight.get(Integers.valueOf(seq));
+ if (reassembler != null)
+ {
+ reassembler.contributeFragment(msg_type, length, buf, off + 12, fragment_offset,
+ fragment_length);
+ if (checkAll(currentInboundFlight))
+ {
+ resendOutboundFlight();
+ resetAll(currentInboundFlight);
+ }
+ }
+ }
+ };
+ }
+
+ recordLayer.handshakeSuccessful(retransmit);
+ }
+
+ void resetHandshakeMessagesDigest()
+ {
+ handshakeHash.reset();
+ }
+
+ /**
+ * Check that there are no "extra" messages left in the current inbound flight
+ */
+ private void checkInboundFlight()
+ {
+ Enumeration e = currentInboundFlight.keys();
+ while (e.hasMoreElements())
+ {
+ Integer key = (Integer)e.nextElement();
+ if (key.intValue() >= next_receive_seq)
+ {
+ // TODO Should this be considered an error?
+ }
+ }
+ }
+
+ private void prepareInboundFlight()
+ {
+ resetAll(currentInboundFlight);
+ previousInboundFlight = currentInboundFlight;
+ currentInboundFlight = new Hashtable();
+ }
+
+ private void resendOutboundFlight()
+ throws IOException
+ {
+ recordLayer.resetWriteEpoch();
+ for (int i = 0; i < outboundFlight.size(); ++i)
+ {
+ writeMessage((Message)outboundFlight.elementAt(i));
+ }
+ }
+
+ private Message updateHandshakeMessagesDigest(Message message)
+ throws IOException
+ {
+ if (message.getType() != HandshakeType.hello_request)
+ {
+ byte[] body = message.getBody();
+ byte[] buf = new byte[12];
+ TlsUtils.writeUint8(message.getType(), buf, 0);
+ TlsUtils.writeUint24(body.length, buf, 1);
+ TlsUtils.writeUint16(message.getSeq(), buf, 4);
+ TlsUtils.writeUint24(0, buf, 6);
+ TlsUtils.writeUint24(body.length, buf, 9);
+ handshakeHash.update(buf, 0, buf.length);
+ handshakeHash.update(body, 0, body.length);
+ }
+ return message;
+ }
+
+ private void writeMessage(Message message)
+ throws IOException
+ {
+ int sendLimit = recordLayer.getSendLimit();
+ int fragmentLimit = sendLimit - 12;
+
+ // 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.getBody().length;
+
+ // NOTE: Must still send a fragment if body is empty
+ int fragment_offset = 0;
+ do
+ {
+ int fragment_length = 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)
+ throws IOException
+ {
+ RecordLayerBuffer fragment = new RecordLayerBuffer(12 + fragment_length);
+ TlsUtils.writeUint8(message.getType(), fragment);
+ TlsUtils.writeUint24(message.getBody().length, fragment);
+ TlsUtils.writeUint16(message.getSeq(), fragment);
+ TlsUtils.writeUint24(fragment_offset, fragment);
+ TlsUtils.writeUint24(fragment_length, fragment);
+ fragment.write(message.getBody(), fragment_offset, fragment_length);
+
+ fragment.sendToRecordLayer(recordLayer);
+ }
+
+ private static boolean checkAll(Hashtable inboundFlight)
+ {
+ Enumeration e = inboundFlight.elements();
+ while (e.hasMoreElements())
+ {
+ if (((DTLSReassembler)e.nextElement()).getBodyIfComplete() == null)
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static void resetAll(Hashtable inboundFlight)
+ {
+ Enumeration e = inboundFlight.elements();
+ while (e.hasMoreElements())
+ {
+ ((DTLSReassembler)e.nextElement()).reset();
+ }
+ }
+
+ static class Message
+ {
+ private final int message_seq;
+ private final short msg_type;
+ private final byte[] body;
+
+ private Message(int message_seq, short msg_type, byte[] body)
+ {
+ this.message_seq = message_seq;
+ this.msg_type = msg_type;
+ this.body = body;
+ }
+
+ public int getSeq()
+ {
+ return message_seq;
+ }
+
+ public short getType()
+ {
+ return msg_type;
+ }
+
+ public byte[] getBody()
+ {
+ return body;
+ }
+ }
+
+ static class RecordLayerBuffer extends ByteArrayOutputStream
+ {
+ RecordLayerBuffer(int size)
+ {
+ super(size);
+ }
+
+ void sendToRecordLayer(DTLSRecordLayer recordLayer) throws IOException
+ {
+ recordLayer.send(buf, 0, count);
+ buf = null;
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSReplayWindow.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSReplayWindow.java
new file mode 100644
index 00000000..d7db51c3
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSReplayWindow.java
@@ -0,0 +1,90 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 4347 4.1.2.5 Anti-replay
+ * <p/>
+ * Support fast rejection of duplicate records by maintaining a sliding receive window
+ */
+class DTLSReplayWindow
+{
+ private static final long VALID_SEQ_MASK = 0x0000FFFFFFFFFFFFL;
+
+ private static final long WINDOW_SIZE = 64L;
+
+ private long latestConfirmedSeq = -1;
+ private long bitmap = 0;
+
+ /**
+ * Check whether a received record with the given sequence number should be rejected as a duplicate.
+ *
+ * @param seq the 48-bit DTLSPlainText.sequence_number field of a received record.
+ * @return true if the record should be discarded without further processing.
+ */
+ boolean shouldDiscard(long seq)
+ {
+ if ((seq & VALID_SEQ_MASK) != seq)
+ {
+ return true;
+ }
+
+ if (seq <= latestConfirmedSeq)
+ {
+ long diff = latestConfirmedSeq - seq;
+ if (diff >= WINDOW_SIZE)
+ {
+ return true;
+ }
+ if ((bitmap & (1L << diff)) != 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Report that a received record with the given sequence number passed authentication checks.
+ *
+ * @param seq the 48-bit DTLSPlainText.sequence_number field of an authenticated record.
+ */
+ void reportAuthenticated(long seq)
+ {
+ if ((seq & VALID_SEQ_MASK) != seq)
+ {
+ throw new IllegalArgumentException("'seq' out of range");
+ }
+
+ if (seq <= latestConfirmedSeq)
+ {
+ long diff = latestConfirmedSeq - seq;
+ if (diff < WINDOW_SIZE)
+ {
+ bitmap |= (1L << diff);
+ }
+ }
+ else
+ {
+ long diff = seq - latestConfirmedSeq;
+ if (diff >= WINDOW_SIZE)
+ {
+ bitmap = 1;
+ }
+ else
+ {
+ bitmap <<= (int)diff; // for earlier JDKs
+ bitmap |= 1;
+ }
+ latestConfirmedSeq = seq;
+ }
+ }
+
+ /**
+ * When a new epoch begins, sequence numbers begin again at 0
+ */
+ void reset()
+ {
+ latestConfirmedSeq = -1;
+ bitmap = 0;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSServerProtocol.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSServerProtocol.java
new file mode 100644
index 00000000..6adb61e5
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSServerProtocol.java
@@ -0,0 +1,667 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.util.PublicKeyFactory;
+import org.spongycastle.util.Arrays;
+
+public class DTLSServerProtocol
+ extends DTLSProtocol
+{
+ protected boolean verifyRequests = true;
+
+ public DTLSServerProtocol(SecureRandom secureRandom)
+ {
+ super(secureRandom);
+ }
+
+ public boolean getVerifyRequests()
+ {
+ return verifyRequests;
+ }
+
+ public void setVerifyRequests(boolean verifyRequests)
+ {
+ this.verifyRequests = verifyRequests;
+ }
+
+ public DTLSTransport accept(TlsServer server, DatagramTransport transport)
+ throws IOException
+ {
+ if (server == null)
+ {
+ throw new IllegalArgumentException("'server' cannot be null");
+ }
+ if (transport == null)
+ {
+ throw new IllegalArgumentException("'transport' cannot be null");
+ }
+
+ SecurityParameters securityParameters = new SecurityParameters();
+ securityParameters.entity = ConnectionEnd.server;
+
+ ServerHandshakeState state = new ServerHandshakeState();
+ state.server = server;
+ state.serverContext = new TlsServerContextImpl(secureRandom, securityParameters);
+
+ securityParameters.serverRandom = TlsProtocol.createRandomBlock(server.shouldUseGMTUnixTime(),
+ state.serverContext.getNonceRandomGenerator());
+
+ server.init(state.serverContext);
+
+ DTLSRecordLayer recordLayer = new DTLSRecordLayer(transport, state.serverContext, server, ContentType.handshake);
+
+ // TODO Need to handle sending of HelloVerifyRequest without entering a full connection
+
+ try
+ {
+ return serverHandshake(state, recordLayer);
+ }
+ catch (TlsFatalAlert fatalAlert)
+ {
+ recordLayer.fail(fatalAlert.getAlertDescription());
+ throw fatalAlert;
+ }
+ catch (IOException e)
+ {
+ recordLayer.fail(AlertDescription.internal_error);
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ recordLayer.fail(AlertDescription.internal_error);
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ protected DTLSTransport serverHandshake(ServerHandshakeState state, DTLSRecordLayer recordLayer)
+ throws IOException
+ {
+ SecurityParameters securityParameters = state.serverContext.getSecurityParameters();
+ DTLSReliableHandshake handshake = new DTLSReliableHandshake(state.serverContext, recordLayer);
+
+ DTLSReliableHandshake.Message clientMessage = handshake.receiveMessage();
+
+ {
+ // NOTE: After receiving a record from the client, we discover the record layer version
+ ProtocolVersion client_version = recordLayer.getDiscoveredPeerVersion();
+ // TODO Read RFCs for guidance on the expected record layer version number
+ state.serverContext.setClientVersion(client_version);
+ }
+
+ if (clientMessage.getType() == HandshakeType.client_hello)
+ {
+ processClientHello(state, clientMessage.getBody());
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ {
+ byte[] serverHelloBody = generateServerHello(state);
+
+ if (state.maxFragmentLength >= 0)
+ {
+ int plainTextLimit = 1 << (8 + state.maxFragmentLength);
+ recordLayer.setPlaintextLimit(plainTextLimit);
+ }
+
+ securityParameters.cipherSuite = state.selectedCipherSuite;
+ securityParameters.compressionAlgorithm = state.selectedCompressionMethod;
+ securityParameters.prfAlgorithm = TlsProtocol.getPRFAlgorithm(state.serverContext,
+ state.selectedCipherSuite);
+
+ /*
+ * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length
+ * has a verify_data_length equal to 12. This includes all existing cipher suites.
+ */
+ securityParameters.verifyDataLength = 12;
+
+ handshake.sendMessage(HandshakeType.server_hello, serverHelloBody);
+ }
+
+ handshake.notifyHelloComplete();
+
+ Vector serverSupplementalData = state.server.getServerSupplementalData();
+ if (serverSupplementalData != null)
+ {
+ byte[] supplementalDataBody = generateSupplementalData(serverSupplementalData);
+ handshake.sendMessage(HandshakeType.supplemental_data, supplementalDataBody);
+ }
+
+ state.keyExchange = state.server.getKeyExchange();
+ state.keyExchange.init(state.serverContext);
+
+ state.serverCredentials = state.server.getCredentials();
+
+ Certificate serverCertificate = null;
+
+ if (state.serverCredentials == null)
+ {
+ state.keyExchange.skipServerCredentials();
+ }
+ else
+ {
+ state.keyExchange.processServerCredentials(state.serverCredentials);
+
+ serverCertificate = state.serverCredentials.getCertificate();
+ byte[] certificateBody = generateCertificate(serverCertificate);
+ handshake.sendMessage(HandshakeType.certificate, certificateBody);
+ }
+
+ // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+ if (serverCertificate == null || serverCertificate.isEmpty())
+ {
+ state.allowCertificateStatus = false;
+ }
+
+ if (state.allowCertificateStatus)
+ {
+ 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 (state.certificateRequest != null)
+ {
+ state.keyExchange.validateCertificateRequest(state.certificateRequest);
+
+ byte[] certificateRequestBody = generateCertificateRequest(state, state.certificateRequest);
+ handshake.sendMessage(HandshakeType.certificate_request, certificateRequestBody);
+
+ TlsUtils.trackHashAlgorithms(handshake.getHandshakeHash(),
+ state.certificateRequest.getSupportedSignatureAlgorithms());
+ }
+ }
+
+ handshake.sendMessage(HandshakeType.server_hello_done, TlsUtils.EMPTY_BYTES);
+
+ handshake.getHandshakeHash().sealHashAlgorithms();
+
+ clientMessage = handshake.receiveMessage();
+
+ if (clientMessage.getType() == HandshakeType.supplemental_data)
+ {
+ processClientSupplementalData(state, clientMessage.getBody());
+ clientMessage = handshake.receiveMessage();
+ }
+ else
+ {
+ state.server.processClientSupplementalData(null);
+ }
+
+ if (state.certificateRequest == null)
+ {
+ state.keyExchange.skipClientCredentials();
+ }
+ else
+ {
+ if (clientMessage.getType() == HandshakeType.certificate)
+ {
+ processClientCertificate(state, clientMessage.getBody());
+ clientMessage = handshake.receiveMessage();
+ }
+ else
+ {
+ if (TlsUtils.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.EMPTY_CHAIN);
+ }
+ }
+
+ if (clientMessage.getType() == HandshakeType.client_key_exchange)
+ {
+ processClientKeyExchange(state, clientMessage.getBody());
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ TlsProtocol.establishMasterSecret(state.serverContext, state.keyExchange);
+ recordLayer.initPendingEpoch(state.server.getCipher());
+
+ TlsHandshakeHash prepareFinishHash = handshake.prepareToFinish();
+
+ /*
+ * 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(state))
+ {
+ byte[] certificateVerifyBody = handshake.receiveMessageBody(HandshakeType.certificate_verify);
+ processCertificateVerify(state, certificateVerifyBody, prepareFinishHash);
+ }
+
+ // NOTE: Calculated exclusive of the actual Finished message from the client
+ byte[] expectedClientVerifyData = TlsUtils.calculateVerifyData(state.serverContext, ExporterLabel.client_finished,
+ TlsProtocol.getCurrentPRFHash(state.serverContext, handshake.getHandshakeHash(), null));
+ processFinished(handshake.receiveMessageBody(HandshakeType.finished), expectedClientVerifyData);
+
+ if (state.expectSessionTicket)
+ {
+ NewSessionTicket newSessionTicket = state.server.getNewSessionTicket();
+ byte[] newSessionTicketBody = generateNewSessionTicket(state, newSessionTicket);
+ handshake.sendMessage(HandshakeType.session_ticket, newSessionTicketBody);
+ }
+
+ // NOTE: Calculated exclusive of the Finished message itself
+ byte[] serverVerifyData = TlsUtils.calculateVerifyData(state.serverContext, ExporterLabel.server_finished,
+ TlsProtocol.getCurrentPRFHash(state.serverContext, handshake.getHandshakeHash(), null));
+ handshake.sendMessage(HandshakeType.finished, serverVerifyData);
+
+ handshake.finish();
+
+ state.server.notifyHandshakeComplete();
+
+ return new DTLSTransport(recordLayer);
+ }
+
+ protected byte[] generateCertificateRequest(ServerHandshakeState state, CertificateRequest certificateRequest)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ certificateRequest.encode(buf);
+ return buf.toByteArray();
+ }
+
+ protected byte[] generateCertificateStatus(ServerHandshakeState state, CertificateStatus certificateStatus)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ certificateStatus.encode(buf);
+ return buf.toByteArray();
+ }
+
+ protected byte[] generateNewSessionTicket(ServerHandshakeState state, NewSessionTicket newSessionTicket)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ newSessionTicket.encode(buf);
+ return buf.toByteArray();
+ }
+
+ protected byte[] generateServerHello(ServerHandshakeState state)
+ throws IOException
+ {
+ SecurityParameters securityParameters = state.serverContext.getSecurityParameters();
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ ProtocolVersion server_version = state.server.getServerVersion();
+ if (!server_version.isEqualOrEarlierVersionOf(state.serverContext.getClientVersion()))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ // TODO Read RFCs for guidance on the expected record layer version number
+ // recordStream.setReadVersion(server_version);
+ // recordStream.setWriteVersion(server_version);
+ // recordStream.setRestrictReadVersion(true);
+ state.serverContext.setServerVersion(server_version);
+
+ TlsUtils.writeVersion(state.serverContext.getServerVersion(), buf);
+
+ buf.write(securityParameters.getServerRandom());
+
+ /*
+ * The server may return an empty session_id to indicate that the session will not be cached
+ * and therefore cannot be resumed.
+ */
+ TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, buf);
+
+ state.selectedCipherSuite = state.server.getSelectedCipherSuite();
+ if (!Arrays.contains(state.offeredCipherSuites, state.selectedCipherSuite)
+ || state.selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+ || state.selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+ || !TlsUtils.isValidCipherSuiteForVersion(state.selectedCipherSuite, server_version))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ validateSelectedCipherSuite(state.selectedCipherSuite, AlertDescription.internal_error);
+
+ state.selectedCompressionMethod = state.server.getSelectedCompressionMethod();
+ if (!Arrays.contains(state.offeredCompressionMethods, state.selectedCompressionMethod))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ TlsUtils.writeUint16(state.selectedCipherSuite, buf);
+ TlsUtils.writeUint8(state.selectedCompressionMethod, buf);
+
+ state.serverExtensions = state.server.getServerExtensions();
+
+ /*
+ * RFC 5746 3.6. Server Behavior: Initial Handshake
+ */
+ if (state.secure_renegotiation)
+ {
+ byte[] renegExtData = TlsUtils.getExtensionData(state.serverExtensions, TlsProtocol.EXT_RenegotiationInfo);
+ boolean 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 = TlsExtensionsUtils.ensureExtensionsInitialised(state.serverExtensions);
+ state.serverExtensions.put(TlsProtocol.EXT_RenegotiationInfo,
+ TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES));
+ }
+ }
+
+ if (state.serverExtensions != null)
+ {
+ securityParameters.encryptThenMAC = TlsExtensionsUtils.hasEncryptThenMACExtension(state.serverExtensions);
+
+ state.maxFragmentLength = evaluateMaxFragmentLengthExtension(state.clientExtensions, state.serverExtensions,
+ AlertDescription.internal_error);
+
+ securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(state.serverExtensions);
+
+ state.allowCertificateStatus = TlsUtils.hasExpectedEmptyExtensionData(state.serverExtensions,
+ TlsExtensionsUtils.EXT_status_request, AlertDescription.internal_error);
+
+ state.expectSessionTicket = TlsUtils.hasExpectedEmptyExtensionData(state.serverExtensions,
+ TlsProtocol.EXT_SessionTicket, AlertDescription.internal_error);
+
+ TlsProtocol.writeExtensions(buf, state.serverExtensions);
+ }
+
+ return buf.toByteArray();
+ }
+
+ protected void notifyClientCertificate(ServerHandshakeState state, Certificate clientCertificate)
+ throws IOException
+ {
+ if (state.certificateRequest == null)
+ {
+ throw new IllegalStateException();
+ }
+
+ if (state.clientCertificate != null)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ state.clientCertificate = clientCertificate;
+
+ if (clientCertificate.isEmpty())
+ {
+ state.keyExchange.skipClientCredentials();
+ }
+ else
+ {
+
+ /*
+ * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request
+ * message was non-empty, one of the certificates in the certificate chain SHOULD be
+ * issued by one of the listed CAs.
+ */
+
+ state.clientCertificateType = TlsUtils.getClientCertificateType(clientCertificate,
+ state.serverCredentials.getCertificate());
+
+ state.keyExchange.processClientCertificate(clientCertificate);
+ }
+
+ /*
+ * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its
+ * discretion either continue the handshake without client authentication, or respond with a
+ * fatal handshake_failure alert. Also, if some aspect of the certificate chain was
+ * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its
+ * discretion either continue the handshake (considering the client unauthenticated) or send
+ * a fatal alert.
+ */
+ state.server.notifyClientCertificate(clientCertificate);
+ }
+
+ protected void processClientCertificate(ServerHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ Certificate clientCertificate = Certificate.parse(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ notifyClientCertificate(state, clientCertificate);
+ }
+
+ protected void processCertificateVerify(ServerHandshakeState state, byte[] body, TlsHandshakeHash prepareFinishHash)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ DigitallySigned clientCertificateVerify = DigitallySigned.parse(state.serverContext, buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ // Verify the CertificateVerify message contains a correct signature.
+ boolean verified = false;
+ try
+ {
+ byte[] certificateVerifyHash;
+ if (TlsUtils.isTLSv12(state.serverContext))
+ {
+ certificateVerifyHash = prepareFinishHash.getFinalHash(clientCertificateVerify.getAlgorithm().getHash());
+ }
+ else
+ {
+ certificateVerifyHash = TlsProtocol.getCurrentPRFHash(state.serverContext, prepareFinishHash, null);
+ }
+
+ org.spongycastle.asn1.x509.Certificate x509Cert = state.clientCertificate.getCertificateAt(0);
+ SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+ AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo);
+
+ TlsSigner tlsSigner = TlsUtils.createTlsSigner(state.clientCertificateType);
+ tlsSigner.init(state.serverContext);
+ verified = tlsSigner.verifyRawSignature(clientCertificateVerify.getAlgorithm(),
+ clientCertificateVerify.getSignature(), publicKey, certificateVerifyHash);
+ }
+ catch (Exception e)
+ {
+ }
+
+ if (!verified)
+ {
+ throw new TlsFatalAlert(AlertDescription.decrypt_error);
+ }
+ }
+
+ protected void processClientHello(ServerHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ // TODO Read RFCs for guidance on the expected record layer version number
+ ProtocolVersion client_version = TlsUtils.readVersion(buf);
+ if (!client_version.isDTLS())
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ /*
+ * Read the client random
+ */
+ byte[] client_random = TlsUtils.readFully(32, buf);
+
+ byte[] sessionID = TlsUtils.readOpaque8(buf);
+ if (sessionID.length > 32)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ // TODO RFC 4347 has the cookie length restricted to 32, but not in RFC 6347
+ byte[] cookie = TlsUtils.readOpaque8(buf);
+
+ int cipher_suites_length = TlsUtils.readUint16(buf);
+ if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0)
+ {
+ 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."
+ */
+ state.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf);
+
+ int compression_methods_length = TlsUtils.readUint8(buf);
+ if (compression_methods_length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ state.offeredCompressionMethods = TlsUtils.readUint8Array(compression_methods_length, buf);
+
+ /*
+ * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+ * extensions appearing in the client hello, and send a server hello containing no
+ * extensions.
+ */
+ state.clientExtensions = TlsProtocol.readExtensions(buf);
+
+ state.serverContext.setClientVersion(client_version);
+
+ state.server.notifyClientVersion(client_version);
+
+ state.serverContext.getSecurityParameters().clientRandom = client_random;
+
+ state.server.notifyOfferedCipherSuites(state.offeredCipherSuites);
+ state.server.notifyOfferedCompressionMethods(state.offeredCompressionMethods);
+
+ /*
+ * 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))
+ {
+ state.secure_renegotiation = true;
+ }
+
+ /*
+ * The server MUST check if the "renegotiation_info" extension is included in the
+ * ClientHello.
+ */
+ byte[] renegExtData = TlsUtils.getExtensionData(state.clientExtensions, TlsProtocol.EXT_RenegotiationInfo);
+ 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.
+ */
+ state.secure_renegotiation = true;
+
+ if (!Arrays.constantTimeAreEqual(renegExtData, TlsProtocol.createRenegotiationInfo(TlsUtils.EMPTY_BYTES)))
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ }
+ }
+
+ state.server.notifySecureRenegotiation(state.secure_renegotiation);
+
+ if (state.clientExtensions != null)
+ {
+ state.server.processClientExtensions(state.clientExtensions);
+ }
+ }
+
+ protected void processClientKeyExchange(ServerHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+
+ state.keyExchange.processClientKeyExchange(buf);
+
+ TlsProtocol.assertEmpty(buf);
+ }
+
+ protected void processClientSupplementalData(ServerHandshakeState state, byte[] body)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(body);
+ Vector clientSupplementalData = TlsProtocol.readSupplementalDataMessage(buf);
+ state.server.processClientSupplementalData(clientSupplementalData);
+ }
+
+ protected boolean expectCertificateVerifyMessage(ServerHandshakeState state)
+ {
+ return state.clientCertificateType >= 0 && TlsUtils.hasSigningCapability(state.clientCertificateType);
+ }
+
+ protected static class ServerHandshakeState
+ {
+ TlsServer server = null;
+ TlsServerContextImpl serverContext = null;
+ int[] offeredCipherSuites;
+ short[] offeredCompressionMethods;
+ Hashtable clientExtensions;
+ int selectedCipherSuite = -1;
+ short selectedCompressionMethod = -1;
+ boolean secure_renegotiation = false;
+ short maxFragmentLength = -1;
+ boolean allowCertificateStatus = false;
+ boolean expectSessionTicket = false;
+ Hashtable serverExtensions = null;
+ TlsKeyExchange keyExchange = null;
+ TlsCredentials serverCredentials = null;
+ CertificateRequest certificateRequest = null;
+ short clientCertificateType = -1;
+ Certificate clientCertificate = null;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DTLSTransport.java b/core/src/main/java/org/spongycastle/crypto/tls/DTLSTransport.java
new file mode 100644
index 00000000..ba6377b1
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DTLSTransport.java
@@ -0,0 +1,80 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public class DTLSTransport
+ implements DatagramTransport
+{
+ private final DTLSRecordLayer recordLayer;
+
+ DTLSTransport(DTLSRecordLayer recordLayer)
+ {
+ this.recordLayer = recordLayer;
+ }
+
+ public int getReceiveLimit()
+ throws IOException
+ {
+ return recordLayer.getReceiveLimit();
+ }
+
+ public int getSendLimit()
+ throws IOException
+ {
+ return recordLayer.getSendLimit();
+ }
+
+ public int receive(byte[] buf, int off, int len, int waitMillis)
+ throws IOException
+ {
+ try
+ {
+ return recordLayer.receive(buf, off, len, waitMillis);
+ }
+ catch (TlsFatalAlert fatalAlert)
+ {
+ recordLayer.fail(fatalAlert.getAlertDescription());
+ throw fatalAlert;
+ }
+ catch (IOException e)
+ {
+ recordLayer.fail(AlertDescription.internal_error);
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ recordLayer.fail(AlertDescription.internal_error);
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public void send(byte[] buf, int off, int len)
+ throws IOException
+ {
+ try
+ {
+ recordLayer.send(buf, off, len);
+ }
+ catch (TlsFatalAlert fatalAlert)
+ {
+ recordLayer.fail(fatalAlert.getAlertDescription());
+ throw fatalAlert;
+ }
+ catch (IOException e)
+ {
+ recordLayer.fail(AlertDescription.internal_error);
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ recordLayer.fail(AlertDescription.internal_error);
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public void close()
+ throws IOException
+ {
+ recordLayer.close();
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DatagramTransport.java b/core/src/main/java/org/spongycastle/crypto/tls/DatagramTransport.java
new file mode 100644
index 00000000..9ceac9ef
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DatagramTransport.java
@@ -0,0 +1,21 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface DatagramTransport
+{
+ int getReceiveLimit()
+ throws IOException;
+
+ int getSendLimit()
+ throws IOException;
+
+ int receive(byte[] buf, int off, int len, int waitMillis)
+ throws IOException;
+
+ void send(byte[] buf, int off, int len)
+ throws IOException;
+
+ void close()
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsAgreementCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsAgreementCredentials.java
new file mode 100644
index 00000000..be981d8a
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsAgreementCredentials.java
@@ -0,0 +1,78 @@
+package org.spongycastle.crypto.tls;
+
+import java.math.BigInteger;
+
+import org.spongycastle.crypto.BasicAgreement;
+import org.spongycastle.crypto.agreement.DHBasicAgreement;
+import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.DHPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.util.BigIntegers;
+
+public class DefaultTlsAgreementCredentials
+ extends AbstractTlsAgreementCredentials
+{
+ protected Certificate certificate;
+ protected AsymmetricKeyParameter privateKey;
+
+ protected BasicAgreement basicAgreement;
+ protected boolean truncateAgreement;
+
+ public DefaultTlsAgreementCredentials(Certificate certificate, AsymmetricKeyParameter privateKey)
+ {
+ if (certificate == null)
+ {
+ throw new IllegalArgumentException("'certificate' cannot be null");
+ }
+ if (certificate.isEmpty())
+ {
+ throw new IllegalArgumentException("'certificate' cannot be empty");
+ }
+ if (privateKey == null)
+ {
+ throw new IllegalArgumentException("'privateKey' cannot be null");
+ }
+ if (!privateKey.isPrivate())
+ {
+ throw new IllegalArgumentException("'privateKey' must be private");
+ }
+
+ if (privateKey instanceof DHPrivateKeyParameters)
+ {
+ basicAgreement = new DHBasicAgreement();
+ truncateAgreement = true;
+ }
+ else if (privateKey instanceof ECPrivateKeyParameters)
+ {
+ basicAgreement = new ECDHBasicAgreement();
+ truncateAgreement = false;
+ }
+ else
+ {
+ throw new IllegalArgumentException("'privateKey' type not supported: "
+ + privateKey.getClass().getName());
+ }
+
+ this.certificate = certificate;
+ this.privateKey = privateKey;
+ }
+
+ public Certificate getCertificate()
+ {
+ return certificate;
+ }
+
+ public byte[] generateAgreement(AsymmetricKeyParameter peerPublicKey)
+ {
+ basicAgreement.init(privateKey);
+ BigInteger agreementValue = basicAgreement.calculateAgreement(peerPublicKey);
+
+ if (truncateAgreement)
+ {
+ return BigIntegers.asUnsignedByteArray(agreementValue);
+ }
+
+ return BigIntegers.asUnsignedByteArray(basicAgreement.getFieldSize(), agreementValue);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsCipherFactory.java b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsCipherFactory.java
new file mode 100644
index 00000000..9c1c68a4
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsCipherFactory.java
@@ -0,0 +1,237 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.StreamCipher;
+import org.spongycastle.crypto.digests.MD5Digest;
+import org.spongycastle.crypto.digests.SHA1Digest;
+import org.spongycastle.crypto.digests.SHA256Digest;
+import org.spongycastle.crypto.digests.SHA384Digest;
+import org.spongycastle.crypto.digests.SHA512Digest;
+import org.spongycastle.crypto.engines.AESEngine;
+import org.spongycastle.crypto.engines.CamelliaEngine;
+import org.spongycastle.crypto.engines.DESedeEngine;
+import org.spongycastle.crypto.engines.RC4Engine;
+import org.spongycastle.crypto.engines.SEEDEngine;
+import org.spongycastle.crypto.engines.Salsa20Engine;
+import org.spongycastle.crypto.modes.AEADBlockCipher;
+import org.spongycastle.crypto.modes.CBCBlockCipher;
+import org.spongycastle.crypto.modes.CCMBlockCipher;
+import org.spongycastle.crypto.modes.GCMBlockCipher;
+
+public class DefaultTlsCipherFactory
+ extends AbstractTlsCipherFactory
+{
+ public TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm)
+ throws IOException
+ {
+ switch (encryptionAlgorithm)
+ {
+ case EncryptionAlgorithm._3DES_EDE_CBC:
+ return createDESedeCipher(context, macAlgorithm);
+ case EncryptionAlgorithm.AEAD_CHACHA20_POLY1305:
+ // NOTE: Ignores macAlgorithm
+ return createChaCha20Poly1305(context);
+ case EncryptionAlgorithm.AES_128_CBC:
+ return createAESCipher(context, 16, macAlgorithm);
+ case EncryptionAlgorithm.AES_128_CCM:
+ // NOTE: Ignores macAlgorithm
+ return createCipher_AES_CCM(context, 16, 16);
+ case EncryptionAlgorithm.AES_128_CCM_8:
+ // NOTE: Ignores macAlgorithm
+ return createCipher_AES_CCM(context, 16, 8);
+ case EncryptionAlgorithm.AES_256_CCM:
+ // NOTE: Ignores macAlgorithm
+ return createCipher_AES_CCM(context, 32, 16);
+ case EncryptionAlgorithm.AES_256_CCM_8:
+ // NOTE: Ignores macAlgorithm
+ return createCipher_AES_CCM(context, 32, 8);
+ case EncryptionAlgorithm.AES_128_GCM:
+ // NOTE: Ignores macAlgorithm
+ return createCipher_AES_GCM(context, 16, 16);
+ case EncryptionAlgorithm.AES_256_CBC:
+ return createAESCipher(context, 32, macAlgorithm);
+ case EncryptionAlgorithm.AES_256_GCM:
+ // NOTE: Ignores macAlgorithm
+ return createCipher_AES_GCM(context, 32, 16);
+ case EncryptionAlgorithm.CAMELLIA_128_CBC:
+ return createCamelliaCipher(context, 16, macAlgorithm);
+ case EncryptionAlgorithm.CAMELLIA_128_GCM:
+ // NOTE: Ignores macAlgorithm
+ return createCipher_Camellia_GCM(context, 16, 16);
+ case EncryptionAlgorithm.CAMELLIA_256_CBC:
+ return createCamelliaCipher(context, 32, macAlgorithm);
+ case EncryptionAlgorithm.CAMELLIA_256_GCM:
+ // NOTE: Ignores macAlgorithm
+ return createCipher_Camellia_GCM(context, 32, 16);
+ case EncryptionAlgorithm.ESTREAM_SALSA20:
+ return createSalsa20Cipher(context, 12, 32, macAlgorithm);
+ case EncryptionAlgorithm.NULL:
+ return createNullCipher(context, macAlgorithm);
+ case EncryptionAlgorithm.RC4_128:
+ return createRC4Cipher(context, 16, macAlgorithm);
+ case EncryptionAlgorithm.SALSA20:
+ return createSalsa20Cipher(context, 20, 32, macAlgorithm);
+ case EncryptionAlgorithm.SEED_CBC:
+ return createSEEDCipher(context, macAlgorithm);
+ default:
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ protected TlsBlockCipher createAESCipher(TlsContext context, int cipherKeySize, int macAlgorithm)
+ throws IOException
+ {
+ return new TlsBlockCipher(context, createAESBlockCipher(), createAESBlockCipher(),
+ createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize);
+ }
+
+ protected TlsBlockCipher createCamelliaCipher(TlsContext context, int cipherKeySize, int macAlgorithm)
+ throws IOException
+ {
+ return new TlsBlockCipher(context, createCamelliaBlockCipher(),
+ createCamelliaBlockCipher(), createHMACDigest(macAlgorithm),
+ createHMACDigest(macAlgorithm), cipherKeySize);
+ }
+
+ protected TlsCipher createChaCha20Poly1305(TlsContext context) throws IOException
+ {
+ return new Chacha20Poly1305(context);
+ }
+
+ protected TlsAEADCipher createCipher_AES_CCM(TlsContext context, int cipherKeySize, int macSize)
+ throws IOException
+ {
+ return new TlsAEADCipher(context, createAEADBlockCipher_AES_CCM(),
+ createAEADBlockCipher_AES_CCM(), cipherKeySize, macSize);
+ }
+
+ protected TlsAEADCipher createCipher_AES_GCM(TlsContext context, int cipherKeySize, int macSize)
+ throws IOException
+ {
+ return new TlsAEADCipher(context, createAEADBlockCipher_AES_GCM(),
+ createAEADBlockCipher_AES_GCM(), cipherKeySize, macSize);
+ }
+
+ protected TlsAEADCipher createCipher_Camellia_GCM(TlsContext context, int cipherKeySize, int macSize)
+ throws IOException
+ {
+ return new TlsAEADCipher(context, createAEADBlockCipher_Camellia_GCM(),
+ createAEADBlockCipher_Camellia_GCM(), cipherKeySize, macSize);
+ }
+
+ protected TlsBlockCipher createDESedeCipher(TlsContext context, int macAlgorithm)
+ throws IOException
+ {
+ return new TlsBlockCipher(context, createDESedeBlockCipher(), createDESedeBlockCipher(),
+ createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 24);
+ }
+
+ protected TlsNullCipher createNullCipher(TlsContext context, int macAlgorithm)
+ throws IOException
+ {
+ return new TlsNullCipher(context, createHMACDigest(macAlgorithm),
+ createHMACDigest(macAlgorithm));
+ }
+
+ protected TlsStreamCipher createRC4Cipher(TlsContext context, int cipherKeySize, int macAlgorithm)
+ throws IOException
+ {
+ return new TlsStreamCipher(context, createRC4StreamCipher(), createRC4StreamCipher(),
+ createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize, false);
+ }
+
+ protected TlsStreamCipher createSalsa20Cipher(TlsContext context, int rounds, int cipherKeySize, int macAlgorithm)
+ throws IOException
+ {
+ return new TlsStreamCipher(context, createSalsa20StreamCipher(rounds), createSalsa20StreamCipher(rounds),
+ createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), cipherKeySize, true);
+ }
+
+ protected TlsBlockCipher createSEEDCipher(TlsContext context, int macAlgorithm)
+ throws IOException
+ {
+ return new TlsBlockCipher(context, createSEEDBlockCipher(), createSEEDBlockCipher(),
+ createHMACDigest(macAlgorithm), createHMACDigest(macAlgorithm), 16);
+ }
+
+ protected BlockCipher createAESEngine()
+ {
+ return new AESEngine();
+ }
+
+ protected BlockCipher createCamelliaEngine()
+ {
+ return new CamelliaEngine();
+ }
+
+ protected BlockCipher createAESBlockCipher()
+ {
+ return new CBCBlockCipher(createAESEngine());
+ }
+
+ protected AEADBlockCipher createAEADBlockCipher_AES_CCM()
+ {
+ return new CCMBlockCipher(createAESEngine());
+ }
+
+ protected AEADBlockCipher createAEADBlockCipher_AES_GCM()
+ {
+ // TODO Consider allowing custom configuration of multiplier
+ return new GCMBlockCipher(createAESEngine());
+ }
+
+ protected AEADBlockCipher createAEADBlockCipher_Camellia_GCM()
+ {
+ // TODO Consider allowing custom configuration of multiplier
+ return new GCMBlockCipher(createCamelliaEngine());
+ }
+
+ protected BlockCipher createCamelliaBlockCipher()
+ {
+ return new CBCBlockCipher(createCamelliaEngine());
+ }
+
+ protected BlockCipher createDESedeBlockCipher()
+ {
+ return new CBCBlockCipher(new DESedeEngine());
+ }
+
+ protected StreamCipher createRC4StreamCipher()
+ {
+ return new RC4Engine();
+ }
+
+ protected StreamCipher createSalsa20StreamCipher(int rounds)
+ {
+ return new Salsa20Engine(rounds);
+ }
+
+ protected BlockCipher createSEEDBlockCipher()
+ {
+ return new CBCBlockCipher(new SEEDEngine());
+ }
+
+ protected Digest createHMACDigest(int macAlgorithm) throws IOException
+ {
+ switch (macAlgorithm)
+ {
+ case MACAlgorithm._null:
+ return null;
+ case MACAlgorithm.hmac_md5:
+ return TlsUtils.createHash(HashAlgorithm.md5);
+ case MACAlgorithm.hmac_sha1:
+ return TlsUtils.createHash(HashAlgorithm.sha1);
+ case MACAlgorithm.hmac_sha256:
+ return TlsUtils.createHash(HashAlgorithm.sha256);
+ case MACAlgorithm.hmac_sha384:
+ return TlsUtils.createHash(HashAlgorithm.sha384);
+ case MACAlgorithm.hmac_sha512:
+ return TlsUtils.createHash(HashAlgorithm.sha512);
+ default:
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsClient.java b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsClient.java
new file mode 100644
index 00000000..0c17b032
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsClient.java
@@ -0,0 +1,453 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public abstract class DefaultTlsClient
+ extends AbstractTlsClient
+{
+ public DefaultTlsClient()
+ {
+ super();
+ }
+
+ public DefaultTlsClient(TlsCipherFactory cipherFactory)
+ {
+ super(cipherFactory);
+ }
+
+ public int[] getCipherSuites()
+ {
+ return new int[]
+ {
+ 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_RSA_WITH_AES_128_GCM_SHA256,
+ CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
+ CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
+ };
+ }
+
+ public TlsKeyExchange getKeyExchange()
+ throws IOException
+ {
+ switch (selectedCipherSuite)
+ {
+ 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_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 createDHKeyExchange(KeyExchangeAlgorithm.DH_DSS);
+
+ case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_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_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 createDHKeyExchange(KeyExchangeAlgorithm.DH_RSA);
+
+ case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_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_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 createDHEKeyExchange(KeyExchangeAlgorithm.DHE_DSS);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+ return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_RSA);
+
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_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_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:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return createECDHKeyExchange(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_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:
+ case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return createECDHKeyExchange(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_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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1:
+ return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA);
+
+ 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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+ return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA);
+
+ 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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+ return createRSAKeyExchange();
+
+ 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);
+ }
+ }
+
+ public TlsCipher getCipher()
+ throws IOException
+ {
+ switch (selectedCipherSuite)
+ {
+ case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AEAD_CHACHA20_POLY1305, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM_8, MACAlgorithm._null);
+
+ 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_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_RSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM_8, MACAlgorithm._null);
+
+ 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_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_RSA_WITH_AES_256_GCM_SHA384:
+ case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+ 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_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_DHE_RSA_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_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_DHE_RSA_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_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_GCM, MACAlgorithm._null);
+
+ 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_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha384);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_md5);
+
+ 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_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_md5);
+
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.hmac_sha1);
+
+ 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 cipherFactory.createCipher(context, EncryptionAlgorithm.SEED_CBC, MACAlgorithm.hmac_sha1);
+
+ 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);
+ }
+ }
+
+ protected TlsKeyExchange createDHKeyExchange(int keyExchange)
+ {
+ return new TlsDHKeyExchange(keyExchange, supportedSignatureAlgorithms, null);
+ }
+
+ protected TlsKeyExchange createDHEKeyExchange(int keyExchange)
+ {
+ return new TlsDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, null);
+ }
+
+ protected TlsKeyExchange createECDHKeyExchange(int keyExchange)
+ {
+ return new TlsECDHKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats,
+ serverECPointFormats);
+ }
+
+ protected TlsKeyExchange createECDHEKeyExchange(int keyExchange)
+ {
+ return new TlsECDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats,
+ serverECPointFormats);
+ }
+
+ protected TlsKeyExchange createRSAKeyExchange()
+ {
+ return new TlsRSAKeyExchange(supportedSignatureAlgorithms);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsEncryptionCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsEncryptionCredentials.java
new file mode 100644
index 00000000..53f9315e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsEncryptionCredentials.java
@@ -0,0 +1,62 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.crypto.encodings.PKCS1Encoding;
+import org.spongycastle.crypto.engines.RSABlindedEngine;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+
+public class DefaultTlsEncryptionCredentials extends AbstractTlsEncryptionCredentials
+{
+ protected TlsContext context;
+ protected Certificate certificate;
+ protected AsymmetricKeyParameter privateKey;
+
+ public DefaultTlsEncryptionCredentials(TlsContext context, Certificate certificate,
+ AsymmetricKeyParameter privateKey)
+ {
+ if (certificate == null)
+ {
+ throw new IllegalArgumentException("'certificate' cannot be null");
+ }
+ if (certificate.isEmpty())
+ {
+ throw new IllegalArgumentException("'certificate' cannot be empty");
+ }
+ if (privateKey == null)
+ {
+ throw new IllegalArgumentException("'privateKey' cannot be null");
+ }
+ if (!privateKey.isPrivate())
+ {
+ throw new IllegalArgumentException("'privateKey' must be private");
+ }
+
+ if (privateKey instanceof RSAKeyParameters)
+ {
+ }
+ else
+ {
+ throw new IllegalArgumentException("'privateKey' type not supported: "
+ + privateKey.getClass().getName());
+ }
+
+ this.context = context;
+ this.certificate = certificate;
+ this.privateKey = privateKey;
+ }
+
+ public Certificate getCertificate()
+ {
+ return certificate;
+ }
+
+ public byte[] decryptPreMasterSecret(byte[] encryptedPreMasterSecret)
+ throws IOException
+ {
+ return TlsRSAUtils.safeDecryptPreMasterSecret(context, (RSAKeyParameters)privateKey, encryptedPreMasterSecret);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsServer.java b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsServer.java
new file mode 100644
index 00000000..3a2ac3b2
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsServer.java
@@ -0,0 +1,550 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.agreement.DHStandardGroups;
+import org.spongycastle.crypto.params.DHParameters;
+
+public abstract class DefaultTlsServer
+ extends AbstractTlsServer
+{
+ public DefaultTlsServer()
+ {
+ super();
+ }
+
+ public DefaultTlsServer(TlsCipherFactory cipherFactory)
+ {
+ super(cipherFactory);
+ }
+
+ protected TlsEncryptionCredentials getRSAEncryptionCredentials()
+ throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ protected TlsSignerCredentials getRSASignerCredentials()
+ throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ protected DHParameters getDHParameters()
+ {
+ return DHStandardGroups.rfc5114_1024_160;
+ }
+
+ protected int[] getCipherSuites()
+ {
+ return new int[]
+ {
+ 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_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 TlsCredentials getCredentials()
+ throws IOException
+ {
+ switch (selectedCipherSuite)
+ {
+ case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_AES_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_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_MD5:
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+ return getRSAEncryptionCredentials();
+
+ 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_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:
+ 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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+ return getRSASignerCredentials();
+
+ default:
+ /*
+ * Note: internal error here; selected a key exchange we don't implement!
+ */
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public TlsKeyExchange getKeyExchange()
+ throws IOException
+ {
+ switch (selectedCipherSuite)
+ {
+ 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_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 createDHKeyExchange(KeyExchangeAlgorithm.DH_DSS);
+
+ case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_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_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 createDHKeyExchange(KeyExchangeAlgorithm.DH_RSA);
+
+ case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_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_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 createDHEKeyExchange(KeyExchangeAlgorithm.DHE_DSS);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_DHE_RSA_WITH_SEED_CBC_SHA:
+ return createDHEKeyExchange(KeyExchangeAlgorithm.DHE_RSA);
+
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_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_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:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ return createECDHKeyExchange(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_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:
+ case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ return createECDHKeyExchange(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_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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1:
+ return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_ECDSA);
+
+ 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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+ return createECDHEKeyExchange(KeyExchangeAlgorithm.ECDHE_RSA);
+
+ 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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_SEED_CBC_SHA:
+ return createRSAKeyExchange();
+
+ default:
+ /*
+ * Note: internal error here; selected a key exchange we don't implement!
+ */
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public TlsCipher getCipher()
+ throws IOException
+ {
+ switch (selectedCipherSuite)
+ {
+ case CipherSuite.TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AEAD_CHACHA20_POLY1305, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CCM_8:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM_8, MACAlgorithm._null);
+
+ 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_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_RSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CCM:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CCM_8:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CCM_8:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM_8, MACAlgorithm._null);
+
+ 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_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_RSA_WITH_AES_256_GCM_SHA384:
+ case CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+ 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_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_DHE_RSA_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_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_DHE_RSA_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_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_GCM, MACAlgorithm._null);
+
+ 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_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha384);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_md5);
+
+ 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_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_RSA_WITH_NULL_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_md5);
+
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.hmac_sha1);
+
+ 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 cipherFactory.createCipher(context, EncryptionAlgorithm.SEED_CBC, MACAlgorithm.hmac_sha1);
+
+ default:
+ /*
+ * Note: internal error here; selected a cipher suite we don't implement!
+ */
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ protected TlsKeyExchange createDHKeyExchange(int keyExchange)
+ {
+ return new TlsDHKeyExchange(keyExchange, supportedSignatureAlgorithms, getDHParameters());
+ }
+
+ protected TlsKeyExchange createDHEKeyExchange(int keyExchange)
+ {
+ return new TlsDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, getDHParameters());
+ }
+
+ protected TlsKeyExchange createECDHKeyExchange(int keyExchange)
+ {
+ return new TlsECDHKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats,
+ serverECPointFormats);
+ }
+
+ protected TlsKeyExchange createECDHEKeyExchange(int keyExchange)
+ {
+ return new TlsECDHEKeyExchange(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats,
+ serverECPointFormats);
+ }
+
+ protected TlsKeyExchange createRSAKeyExchange()
+ {
+ return new TlsRSAKeyExchange(supportedSignatureAlgorithms);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsSignerCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsSignerCredentials.java
new file mode 100755
index 00000000..51ac6e88
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DefaultTlsSignerCredentials.java
@@ -0,0 +1,104 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.DSAPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+
+public class DefaultTlsSignerCredentials
+ extends AbstractTlsSignerCredentials
+{
+ protected TlsContext context;
+ protected Certificate certificate;
+ protected AsymmetricKeyParameter privateKey;
+ protected SignatureAndHashAlgorithm signatureAndHashAlgorithm;
+
+ protected TlsSigner signer;
+
+ public DefaultTlsSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey)
+ {
+ this(context, certificate, privateKey, null);
+ }
+
+ public DefaultTlsSignerCredentials(TlsContext context, Certificate certificate, AsymmetricKeyParameter privateKey,
+ SignatureAndHashAlgorithm signatureAndHashAlgorithm)
+ {
+ if (certificate == null)
+ {
+ throw new IllegalArgumentException("'certificate' cannot be null");
+ }
+ if (certificate.isEmpty())
+ {
+ throw new IllegalArgumentException("'certificate' cannot be empty");
+ }
+ if (privateKey == null)
+ {
+ throw new IllegalArgumentException("'privateKey' cannot be null");
+ }
+ if (!privateKey.isPrivate())
+ {
+ throw new IllegalArgumentException("'privateKey' must be private");
+ }
+ if (TlsUtils.isTLSv12(context) && signatureAndHashAlgorithm == null)
+ {
+ throw new IllegalArgumentException("'signatureAndHashAlgorithm' cannot be null for (D)TLS 1.2+");
+ }
+
+ if (privateKey instanceof RSAKeyParameters)
+ {
+ this.signer = new TlsRSASigner();
+ }
+ else if (privateKey instanceof DSAPrivateKeyParameters)
+ {
+ this.signer = new TlsDSSSigner();
+ }
+ else if (privateKey instanceof ECPrivateKeyParameters)
+ {
+ this.signer = new TlsECDSASigner();
+ }
+ else
+ {
+ throw new IllegalArgumentException("'privateKey' type not supported: " + privateKey.getClass().getName());
+ }
+
+ this.signer.init(context);
+
+ this.context = context;
+ this.certificate = certificate;
+ this.privateKey = privateKey;
+ this.signatureAndHashAlgorithm = signatureAndHashAlgorithm;
+ }
+
+ public Certificate getCertificate()
+ {
+ return certificate;
+ }
+
+ public byte[] generateCertificateSignature(byte[] hash)
+ throws IOException
+ {
+ try
+ {
+ if (TlsUtils.isTLSv12(context))
+ {
+ return signer.generateRawSignature(signatureAndHashAlgorithm, privateKey, hash);
+ }
+ else
+ {
+ return signer.generateRawSignature(privateKey, hash);
+ }
+ }
+ catch (CryptoException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public SignatureAndHashAlgorithm getSignatureAndHashAlgorithm()
+ {
+ return signatureAndHashAlgorithm;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DeferredHash.java b/core/src/main/java/org/spongycastle/crypto/tls/DeferredHash.java
new file mode 100644
index 00000000..9ac7d346
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DeferredHash.java
@@ -0,0 +1,207 @@
+package org.spongycastle.crypto.tls;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.util.Shorts;
+
+/**
+ * Buffers input until the hash algorithm is determined.
+ */
+class DeferredHash
+ implements TlsHandshakeHash
+{
+ protected static final int BUFFERING_HASH_LIMIT = 4;
+
+ protected TlsContext context;
+
+ private DigestInputBuffer buf;
+ private Hashtable hashes;
+ private Short prfHashAlgorithm;
+
+ DeferredHash()
+ {
+ this.buf = new DigestInputBuffer();
+ this.hashes = new Hashtable();
+ this.prfHashAlgorithm = null;
+ }
+
+ private DeferredHash(Short prfHashAlgorithm, Digest prfHash)
+ {
+ this.buf = null;
+ this.hashes = new Hashtable();
+ this.prfHashAlgorithm = prfHashAlgorithm;
+ hashes.put(prfHashAlgorithm, prfHash);
+ }
+
+ public void init(TlsContext context)
+ {
+ this.context = context;
+ }
+
+ public TlsHandshakeHash notifyPRFDetermined()
+ {
+ int prfAlgorithm = context.getSecurityParameters().getPrfAlgorithm();
+ if (prfAlgorithm == PRFAlgorithm.tls_prf_legacy)
+ {
+ CombinedHash legacyHash = new CombinedHash();
+ legacyHash.init(context);
+ buf.updateDigest(legacyHash);
+ return legacyHash.notifyPRFDetermined();
+ }
+
+ this.prfHashAlgorithm = Shorts.valueOf(TlsUtils.getHashAlgorithmForPRFAlgorithm(prfAlgorithm));
+
+ checkTrackingHash(prfHashAlgorithm);
+
+ return this;
+ }
+
+ public void trackHashAlgorithm(short hashAlgorithm)
+ {
+ if (buf == null)
+ {
+ throw new IllegalStateException("Too late to track more hash algorithms");
+ }
+
+ checkTrackingHash(Shorts.valueOf(hashAlgorithm));
+ }
+
+ public void sealHashAlgorithms()
+ {
+ checkStopBuffering();
+ }
+
+ public TlsHandshakeHash stopTracking()
+ {
+ Digest prfHash = TlsUtils.cloneHash(prfHashAlgorithm.shortValue(), (Digest)hashes.get(prfHashAlgorithm));
+ if (buf != null)
+ {
+ buf.updateDigest(prfHash);
+ }
+ DeferredHash result = new DeferredHash(prfHashAlgorithm, prfHash);
+ result.init(context);
+ return result;
+ }
+
+ public Digest forkPRFHash()
+ {
+ checkStopBuffering();
+
+ if (buf != null)
+ {
+ Digest prfHash = TlsUtils.createHash(prfHashAlgorithm.shortValue());
+ buf.updateDigest(prfHash);
+ return prfHash;
+ }
+
+ return TlsUtils.cloneHash(prfHashAlgorithm.shortValue(), (Digest)hashes.get(prfHashAlgorithm));
+ }
+
+ public byte[] getFinalHash(short hashAlgorithm)
+ {
+ Digest d = (Digest)hashes.get(Shorts.valueOf(hashAlgorithm));
+ if (d == null)
+ {
+ throw new IllegalStateException("HashAlgorithm " + hashAlgorithm + " is not being tracked");
+ }
+
+ d = TlsUtils.cloneHash(hashAlgorithm, d);
+ if (buf != null)
+ {
+ buf.updateDigest(d);
+ }
+
+ byte[] bs = new byte[d.getDigestSize()];
+ d.doFinal(bs, 0);
+ return bs;
+ }
+
+ public String getAlgorithmName()
+ {
+ throw new IllegalStateException("Use fork() to get a definite Digest");
+ }
+
+ public int getDigestSize()
+ {
+ throw new IllegalStateException("Use fork() to get a definite Digest");
+ }
+
+ public void update(byte input)
+ {
+ if (buf != null)
+ {
+ buf.write(input);
+ return;
+ }
+
+ Enumeration e = hashes.elements();
+ while (e.hasMoreElements())
+ {
+ Digest hash = (Digest)e.nextElement();
+ hash.update(input);
+ }
+ }
+
+ public void update(byte[] input, int inOff, int len)
+ {
+ if (buf != null)
+ {
+ buf.write(input, inOff, len);
+ return;
+ }
+
+ Enumeration e = hashes.elements();
+ while (e.hasMoreElements())
+ {
+ Digest hash = (Digest)e.nextElement();
+ hash.update(input, inOff, len);
+ }
+ }
+
+ public int doFinal(byte[] output, int outOff)
+ {
+ throw new IllegalStateException("Use fork() to get a definite Digest");
+ }
+
+ public void reset()
+ {
+ if (buf != null)
+ {
+ buf.reset();
+ return;
+ }
+
+ Enumeration e = hashes.elements();
+ while (e.hasMoreElements())
+ {
+ Digest hash = (Digest)e.nextElement();
+ hash.reset();
+ }
+ }
+
+ protected void checkStopBuffering()
+ {
+ if (buf != null && hashes.size() <= BUFFERING_HASH_LIMIT)
+ {
+ Enumeration e = hashes.elements();
+ while (e.hasMoreElements())
+ {
+ Digest hash = (Digest)e.nextElement();
+ buf.updateDigest(hash);
+ }
+
+ this.buf = null;
+ }
+ }
+
+ protected void checkTrackingHash(Short hashAlgorithm)
+ {
+ if (!hashes.containsKey(hashAlgorithm))
+ {
+ Digest hash = TlsUtils.createHash(hashAlgorithm.shortValue());
+ hashes.put(hashAlgorithm, hash);
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DigestAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/DigestAlgorithm.java
new file mode 100644
index 00000000..a1459553
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DigestAlgorithm.java
@@ -0,0 +1,23 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ *
+ * @deprecated use MACAlgorithm constants instead
+ */
+public class DigestAlgorithm
+{
+ public static final int NULL = 0;
+ public static final int MD5 = 1;
+ public static final int SHA = 2;
+
+ /*
+ * RFC 5246
+ */
+ public static final int SHA256 = 3;
+ public static final int SHA384 = 4;
+ public static final int SHA512 = 5;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DigestInputBuffer.java b/core/src/main/java/org/spongycastle/crypto/tls/DigestInputBuffer.java
new file mode 100644
index 00000000..a8f6594e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DigestInputBuffer.java
@@ -0,0 +1,13 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+
+import org.spongycastle.crypto.Digest;
+
+class DigestInputBuffer extends ByteArrayOutputStream
+{
+ void updateDigest(Digest d)
+ {
+ d.update(this.buf, 0, count);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/DigitallySigned.java b/core/src/main/java/org/spongycastle/crypto/tls/DigitallySigned.java
new file mode 100644
index 00000000..8ab8a8d4
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/DigitallySigned.java
@@ -0,0 +1,72 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class DigitallySigned
+{
+ protected SignatureAndHashAlgorithm algorithm;
+ protected byte[] signature;
+
+ public DigitallySigned(SignatureAndHashAlgorithm algorithm, byte[] signature)
+ {
+ if (signature == null)
+ {
+ throw new IllegalArgumentException("'signature' cannot be null");
+ }
+
+ this.algorithm = algorithm;
+ this.signature = signature;
+ }
+
+ /**
+ * @return a {@link SignatureAndHashAlgorithm} (or null before TLS 1.2).
+ */
+ public SignatureAndHashAlgorithm getAlgorithm()
+ {
+ return algorithm;
+ }
+
+ public byte[] getSignature()
+ {
+ return signature;
+ }
+
+ /**
+ * Encode this {@link DigitallySigned} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output) throws IOException
+ {
+ if (algorithm != null)
+ {
+ algorithm.encode(output);
+ }
+ TlsUtils.writeOpaque16(signature, output);
+ }
+
+ /**
+ * Parse a {@link DigitallySigned} from an {@link InputStream}.
+ *
+ * @param context
+ * the {@link TlsContext} of the current connection.
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link DigitallySigned} object.
+ * @throws IOException
+ */
+ public static DigitallySigned parse(TlsContext context, InputStream input) throws IOException
+ {
+ SignatureAndHashAlgorithm algorithm = null;
+ if (TlsUtils.isTLSv12(context))
+ {
+ algorithm = SignatureAndHashAlgorithm.parse(input);
+ }
+ byte[] signature = TlsUtils.readOpaque16(input);
+ return new DigitallySigned(algorithm, signature);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ECBasisType.java b/core/src/main/java/org/spongycastle/crypto/tls/ECBasisType.java
new file mode 100644
index 00000000..3a8d6a57
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ECBasisType.java
@@ -0,0 +1,15 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 4492 5.4. (Errata ID: 2389)
+ */
+public class ECBasisType
+{
+ public static final short ec_basis_trinomial = 1;
+ public static final short ec_basis_pentanomial = 2;
+
+ public static boolean isValid(short ecBasisType)
+ {
+ return ecBasisType >= ec_basis_trinomial && ecBasisType <= ec_basis_pentanomial;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ECCurveType.java b/core/src/main/java/org/spongycastle/crypto/tls/ECCurveType.java
new file mode 100644
index 00000000..01266b08
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ECCurveType.java
@@ -0,0 +1,28 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 4492 5.4
+ */
+public class ECCurveType
+{
+ /**
+ * Indicates the elliptic curve domain parameters are conveyed verbosely, and the
+ * underlying finite field is a prime field.
+ */
+ public static final short explicit_prime = 1;
+
+ /**
+ * Indicates the elliptic curve domain parameters are conveyed verbosely, and the
+ * underlying finite field is a characteristic-2 field.
+ */
+ public static final short explicit_char2 = 2;
+
+ /**
+ * Indicates that a named curve is used. This option SHOULD be used when applicable.
+ */
+ public static final short named_curve = 3;
+
+ /*
+ * Values 248 through 255 are reserved for private use.
+ */
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ECPointFormat.java b/core/src/main/java/org/spongycastle/crypto/tls/ECPointFormat.java
new file mode 100644
index 00000000..794bc757
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ECPointFormat.java
@@ -0,0 +1,15 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 4492 5.1.2
+ */
+public class ECPointFormat
+{
+ public static final short uncompressed = 0;
+ public static final short ansiX962_compressed_prime = 1;
+ public static final short ansiX962_compressed_char2 = 2;
+
+ /*
+ * reserved (248..255)
+ */
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/EncryptionAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/EncryptionAlgorithm.java
new file mode 100644
index 00000000..9fcc7299
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/EncryptionAlgorithm.java
@@ -0,0 +1,67 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class EncryptionAlgorithm
+{
+ public static final int NULL = 0;
+ public static final int RC4_40 = 1;
+ public static final int RC4_128 = 2;
+ public static final int RC2_CBC_40 = 3;
+ public static final int IDEA_CBC = 4;
+ public static final int DES40_CBC = 5;
+ public static final int DES_CBC = 6;
+ public static final int _3DES_EDE_CBC = 7;
+
+ /*
+ * RFC 3268
+ */
+ public static final int AES_128_CBC = 8;
+ public static final int AES_256_CBC = 9;
+
+ /*
+ * RFC 5289
+ */
+ public static final int AES_128_GCM = 10;
+ public static final int AES_256_GCM = 11;
+
+ /*
+ * RFC 5932
+ */
+ public static final int CAMELLIA_128_CBC = 12;
+ public static final int CAMELLIA_256_CBC = 13;
+
+ /*
+ * RFC 4162
+ */
+ public static final int SEED_CBC = 14;
+
+ /*
+ * RFC 6655
+ */
+ public static final int AES_128_CCM = 15;
+ public static final int AES_128_CCM_8 = 16;
+ public static final int AES_256_CCM = 17;
+ public static final int AES_256_CCM_8 = 18;
+
+ /*
+ * RFC 6367
+ */
+ public static final int CAMELLIA_128_GCM = 19;
+ public static final int CAMELLIA_256_GCM = 20;
+
+ /*
+ * draft-josefsson-salsa20-tls-04
+ */
+ public static final int ESTREAM_SALSA20 = 100;
+ public static final int SALSA20 = 101;
+
+ /*
+ * draft-agl-tls-chacha20poly1305-04
+ */
+ public static final int AEAD_CHACHA20_POLY1305 = 102;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ExporterLabel.java b/core/src/main/java/org/spongycastle/crypto/tls/ExporterLabel.java
new file mode 100644
index 00000000..419680a8
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ExporterLabel.java
@@ -0,0 +1,31 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 5705
+ */
+public class ExporterLabel
+{
+ /*
+ * RFC 5246
+ */
+ public static final String client_finished = "client finished";
+ public static final String server_finished = "server finished";
+ public static final String master_secret = "master secret";
+ public static final String key_expansion = "key expansion";
+
+ /*
+ * RFC 5216
+ */
+ public static final String client_EAP_encryption = "client EAP encryption";
+
+ /*
+ * RFC 5281
+ */
+ public static final String ttls_keying_material = "ttls keying material";
+ public static final String ttls_challenge = "ttls challenge";
+
+ /*
+ * RFC 5764
+ */
+ public static final String dtls_srtp = "EXTRACTOR-dtls_srtp";
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ExtensionType.java b/core/src/main/java/org/spongycastle/crypto/tls/ExtensionType.java
new file mode 100644
index 00000000..9047d1db
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ExtensionType.java
@@ -0,0 +1,60 @@
+package org.spongycastle.crypto.tls;
+
+public class ExtensionType
+{
+ /*
+ * RFC 2546 2.3.
+ */
+ public static final int server_name = 0;
+ public static final int max_fragment_length = 1;
+ public static final int client_certificate_url = 2;
+ public static final int trusted_ca_keys = 3;
+ public static final int truncated_hmac = 4;
+ public static final int status_request = 5;
+
+ /*
+ * RFC 4681
+ */
+ public static final int user_mapping = 6;
+
+ /*
+ * RFC 4492 5.1.
+ */
+ public static final int elliptic_curves = 10;
+ public static final int ec_point_formats = 11;
+
+ /*
+ * RFC 5054 2.8.1.
+ */
+ public static final int srp = 12;
+
+ /*
+ * RFC 5246 7.4.1.4.
+ */
+ public static final int signature_algorithms = 13;
+
+ /*
+ * RFC 5764 9.
+ */
+ public static final int use_srtp = 14;
+
+ /*
+ * RFC 6520 6.
+ */
+ public static final int heartbeat = 15;
+
+ /*
+ * RFC 5077 7.
+ */
+ public static final int session_ticket = 35;
+
+ /*
+ * draft-ietf-tls-encrypt-then-mac-03
+ */
+ public static final int encrypt_then_mac = 22;
+
+ /*
+ * RFC 5746 3.2.
+ */
+ public static final int renegotiation_info = 0xff01;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/HandshakeType.java b/core/src/main/java/org/spongycastle/crypto/tls/HandshakeType.java
new file mode 100644
index 00000000..f03135f1
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/HandshakeType.java
@@ -0,0 +1,39 @@
+package org.spongycastle.crypto.tls;
+
+public class HandshakeType
+{
+ /*
+ * RFC 2246 7.4
+ */
+ public static final short hello_request = 0;
+ public static final short client_hello = 1;
+ public static final short server_hello = 2;
+ public static final short certificate = 11;
+ public static final short server_key_exchange = 12;
+ public static final short certificate_request = 13;
+ public static final short server_hello_done = 14;
+ public static final short certificate_verify = 15;
+ public static final short client_key_exchange = 16;
+ public static final short finished = 20;
+
+ /*
+ * RFC 3546 2.4
+ */
+ public static final short certificate_url = 21;
+ public static final short certificate_status = 22;
+
+ /*
+ * (DTLS) RFC 4347 4.3.2
+ */
+ public static final short hello_verify_request = 3;
+
+ /*
+ * RFC 4680
+ */
+ public static final short supplemental_data = 23;
+
+ /*
+ * RFC 5077
+ */
+ public static final short session_ticket = 4;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/HashAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/HashAlgorithm.java
new file mode 100644
index 00000000..0e32be9c
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/HashAlgorithm.java
@@ -0,0 +1,15 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 5246 7.4.1.4.1
+ */
+public class HashAlgorithm
+{
+ public static final short none = 0;
+ public static final short md5 = 1;
+ public static final short sha1 = 2;
+ public static final short sha224 = 3;
+ public static final short sha256 = 4;
+ public static final short sha384 = 5;
+ public static final short sha512 = 6;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatExtension.java b/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatExtension.java
new file mode 100644
index 00000000..4c325014
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatExtension.java
@@ -0,0 +1,56 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class HeartbeatExtension
+{
+ protected short mode;
+
+ public HeartbeatExtension(short mode)
+ {
+ if (!HeartbeatMode.isValid(mode))
+ {
+ throw new IllegalArgumentException("'mode' is not a valid HeartbeatMode value");
+ }
+
+ this.mode = mode;
+ }
+
+ public short getMode()
+ {
+ return mode;
+ }
+
+ /**
+ * Encode this {@link HeartbeatExtension} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output) throws IOException
+ {
+ TlsUtils.writeUint8(mode, output);
+ }
+
+ /**
+ * Parse a {@link HeartbeatExtension} from an {@link InputStream}.
+ *
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link HeartbeatExtension} object.
+ * @throws IOException
+ */
+ public static HeartbeatExtension parse(InputStream input) throws IOException
+ {
+ short mode = TlsUtils.readUint8(input);
+ if (!HeartbeatMode.isValid(mode))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return new HeartbeatExtension(mode);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMessage.java b/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMessage.java
new file mode 100644
index 00000000..e09005b0
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMessage.java
@@ -0,0 +1,111 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.io.Streams;
+
+public class HeartbeatMessage
+{
+ protected short type;
+ protected byte[] payload;
+ protected int paddingLength;
+
+ public HeartbeatMessage(short type, byte[] payload, int paddingLength)
+ {
+ if (!HeartbeatMessageType.isValid(type))
+ {
+ throw new IllegalArgumentException("'type' is not a valid HeartbeatMessageType value");
+ }
+ if (payload == null || payload.length >= (1 << 16))
+ {
+ throw new IllegalArgumentException("'payload' must have length < 2^16");
+ }
+ if (paddingLength < 16)
+ {
+ throw new IllegalArgumentException("'paddingLength' must be at least 16");
+ }
+
+ this.type = type;
+ this.payload = payload;
+ this.paddingLength = paddingLength;
+ }
+
+ /**
+ * Encode this {@link HeartbeatMessage} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(TlsContext context, OutputStream output) throws IOException
+ {
+ TlsUtils.writeUint8(type, output);
+
+ TlsUtils.checkUint16(payload.length);
+ TlsUtils.writeUint16(payload.length, output);
+ output.write(payload);
+
+ byte[] padding = new byte[paddingLength];
+ context.getNonceRandomGenerator().nextBytes(padding);
+ output.write(padding);
+ }
+
+ /**
+ * Parse a {@link HeartbeatMessage} from an {@link InputStream}.
+ *
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link HeartbeatMessage} object.
+ * @throws IOException
+ */
+ public static HeartbeatMessage parse(InputStream input) throws IOException
+ {
+ short type = TlsUtils.readUint8(input);
+ if (!HeartbeatMessageType.isValid(type))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ int payload_length = TlsUtils.readUint16(input);
+
+ PayloadBuffer buf = new PayloadBuffer();
+ Streams.pipeAll(input, buf);
+
+ byte[] payload = buf.toTruncatedByteArray(payload_length);
+ if (payload == null)
+ {
+ /*
+ * RFC 6520 4. If the payload_length of a received HeartbeatMessage is too large, the
+ * received HeartbeatMessage MUST be discarded silently.
+ */
+ return null;
+ }
+
+ int padding_length = buf.size() - payload.length;
+
+ /*
+ * RFC 6520 4. The padding of a received HeartbeatMessage message MUST be ignored
+ */
+ return new HeartbeatMessage(type, payload, padding_length);
+ }
+
+ static class PayloadBuffer extends ByteArrayOutputStream
+ {
+ byte[] toTruncatedByteArray(int payloadLength)
+ {
+ /*
+ * RFC 6520 4. The padding_length MUST be at least 16.
+ */
+ int minimumCount = payloadLength + 16;
+ if (count < minimumCount)
+ {
+ return null;
+ }
+ return Arrays.copyOf(buf, payloadLength);
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMessageType.java b/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMessageType.java
new file mode 100644
index 00000000..2d21a1e8
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMessageType.java
@@ -0,0 +1,15 @@
+package org.spongycastle.crypto.tls;
+
+/*
+ * RFC 6520 3.
+ */
+public class HeartbeatMessageType
+{
+ public static final short heartbeat_request = 1;
+ public static final short heartbeat_response = 2;
+
+ public static boolean isValid(short heartbeatMessageType)
+ {
+ return heartbeatMessageType >= heartbeat_request && heartbeatMessageType <= heartbeat_response;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMode.java b/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMode.java
new file mode 100644
index 00000000..1c9472b2
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/HeartbeatMode.java
@@ -0,0 +1,15 @@
+package org.spongycastle.crypto.tls;
+
+/*
+ * RFC 6520
+ */
+public class HeartbeatMode
+{
+ public static final short peer_allowed_to_send = 1;
+ public static final short peer_not_allowed_to_send = 2;
+
+ public static boolean isValid(short heartbeatMode)
+ {
+ return heartbeatMode >= peer_allowed_to_send && heartbeatMode <= peer_not_allowed_to_send;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/KeyExchangeAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/KeyExchangeAlgorithm.java
new file mode 100644
index 00000000..86834461
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/KeyExchangeAlgorithm.java
@@ -0,0 +1,52 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class KeyExchangeAlgorithm
+{
+ public static final int NULL = 0;
+ public static final int RSA = 1;
+ public static final int RSA_EXPORT = 2;
+ public static final int DHE_DSS = 3;
+ public static final int DHE_DSS_EXPORT = 4;
+ public static final int DHE_RSA = 5;
+ public static final int DHE_RSA_EXPORT = 6;
+ public static final int DH_DSS = 7;
+ public static final int DH_DSS_EXPORT = 8;
+ public static final int DH_RSA = 9;
+ public static final int DH_RSA_EXPORT = 10;
+ public static final int DH_anon = 11;
+ public static final int DH_anon_EXPORT = 12;
+
+ /*
+ * RFC 4279
+ */
+ public static final int PSK = 13;
+ public static final int DHE_PSK = 14;
+ public static final int RSA_PSK = 15;
+
+ /*
+ * RFC 4429
+ */
+ public static final int ECDH_ECDSA = 16;
+ public static final int ECDHE_ECDSA = 17;
+ public static final int ECDH_RSA = 18;
+ public static final int ECDHE_RSA = 19;
+ public static final int ECDH_anon = 20;
+
+ /*
+ * RFC 5054
+ */
+ public static final int SRP = 21;
+ public static final int SRP_DSS = 22;
+ public static final int SRP_RSA = 23;
+
+ /*
+ * RFC 5489
+ */
+ public static final int ECDHE_PSK = 24;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/LegacyTlsAuthentication.java b/core/src/main/java/org/spongycastle/crypto/tls/LegacyTlsAuthentication.java
new file mode 100644
index 00000000..5e8a67ee
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/LegacyTlsAuthentication.java
@@ -0,0 +1,28 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+/**
+ * A temporary class to wrap old CertificateVerifyer stuff for new TlsAuthentication
+ *
+ * @deprecated
+ */
+public class LegacyTlsAuthentication
+ extends ServerOnlyTlsAuthentication
+{
+ protected CertificateVerifyer verifyer;
+
+ public LegacyTlsAuthentication(CertificateVerifyer verifyer)
+ {
+ this.verifyer = verifyer;
+ }
+
+ public void notifyServerCertificate(Certificate serverCertificate)
+ throws IOException
+ {
+ if (!this.verifyer.isValid(serverCertificate.getCertificateList()))
+ {
+ throw new TlsFatalAlert(AlertDescription.user_canceled);
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/LegacyTlsClient.java b/core/src/main/java/org/spongycastle/crypto/tls/LegacyTlsClient.java
new file mode 100644
index 00000000..32384ff9
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/LegacyTlsClient.java
@@ -0,0 +1,33 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+/**
+ * A temporary class to use LegacyTlsAuthentication
+ *
+ * @deprecated
+ */
+public class LegacyTlsClient
+ extends DefaultTlsClient
+{
+ /**
+ * @deprecated
+ */
+ protected CertificateVerifyer verifyer;
+
+ /**
+ * @deprecated
+ */
+ public LegacyTlsClient(CertificateVerifyer verifyer)
+ {
+ super();
+
+ this.verifyer = verifyer;
+ }
+
+ public TlsAuthentication getAuthentication()
+ throws IOException
+ {
+ return new LegacyTlsAuthentication(verifyer);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/MACAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/MACAlgorithm.java
new file mode 100644
index 00000000..5b522b6c
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/MACAlgorithm.java
@@ -0,0 +1,23 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 2246
+ * <p>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class MACAlgorithm
+{
+ public static final int _null = 0;
+ public static final int md5 = 1;
+ public static final int sha = 2;
+
+ /*
+ * RFC 5246
+ */
+ public static final int hmac_md5 = md5;
+ public static final int hmac_sha1 = sha;
+ public static final int hmac_sha256 = 3;
+ public static final int hmac_sha384 = 4;
+ public static final int hmac_sha512 = 5;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/MaxFragmentLength.java b/core/src/main/java/org/spongycastle/crypto/tls/MaxFragmentLength.java
new file mode 100644
index 00000000..6d864114
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/MaxFragmentLength.java
@@ -0,0 +1,17 @@
+package org.spongycastle.crypto.tls;
+
+public class MaxFragmentLength
+{
+ /*
+ * RFC 3546 3.2.
+ */
+ public static final short pow2_9 = 1;
+ public static final short pow2_10 = 2;
+ public static final short pow2_11 = 3;
+ public static final short pow2_12 = 4;
+
+ public static boolean isValid(short maxFragmentLength)
+ {
+ return maxFragmentLength >= pow2_9 && maxFragmentLength <= pow2_12;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/NameType.java b/core/src/main/java/org/spongycastle/crypto/tls/NameType.java
new file mode 100644
index 00000000..86ca0ef0
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/NameType.java
@@ -0,0 +1,9 @@
+package org.spongycastle.crypto.tls;
+
+public class NameType
+{
+ /*
+ * RFC 3546 3.1.
+ */
+ public static final short host_name = 0;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/NamedCurve.java b/core/src/main/java/org/spongycastle/crypto/tls/NamedCurve.java
new file mode 100644
index 00000000..8f733587
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/NamedCurve.java
@@ -0,0 +1,71 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 4492 5.1.1
+ * <p>
+ * The named curves defined here are those specified in SEC 2 [13]. Note that many of these curves
+ * are also recommended in ANSI X9.62 [7] and FIPS 186-2 [11]. Values 0xFE00 through 0xFEFF are
+ * reserved for private use. Values 0xFF01 and 0xFF02 indicate that the client supports arbitrary
+ * prime and characteristic-2 curves, respectively (the curve parameters must be encoded explicitly
+ * in ECParameters).
+ */
+public class NamedCurve
+{
+ public static final int sect163k1 = 1;
+ public static final int sect163r1 = 2;
+ public static final int sect163r2 = 3;
+ public static final int sect193r1 = 4;
+ public static final int sect193r2 = 5;
+ public static final int sect233k1 = 6;
+ public static final int sect233r1 = 7;
+ public static final int sect239k1 = 8;
+ public static final int sect283k1 = 9;
+ public static final int sect283r1 = 10;
+ public static final int sect409k1 = 11;
+ public static final int sect409r1 = 12;
+ public static final int sect571k1 = 13;
+ public static final int sect571r1 = 14;
+ public static final int secp160k1 = 15;
+ public static final int secp160r1 = 16;
+ public static final int secp160r2 = 17;
+ public static final int secp192k1 = 18;
+ public static final int secp192r1 = 19;
+ public static final int secp224k1 = 20;
+ public static final int secp224r1 = 21;
+ public static final int secp256k1 = 22;
+ public static final int secp256r1 = 23;
+ public static final int secp384r1 = 24;
+ public static final int secp521r1 = 25;
+
+ /*
+ * RFC 7027
+ */
+ public static final int brainpoolP256r1 = 26;
+ public static final int brainpoolP384r1 = 27;
+ public static final int brainpoolP512r1 = 28;
+
+ /*
+ * reserved (0xFE00..0xFEFF)
+ */
+
+ public static final int arbitrary_explicit_prime_curves = 0xFF01;
+ public static final int arbitrary_explicit_char2_curves = 0xFF02;
+
+ public static boolean isValid(int namedCurve)
+ {
+ return (namedCurve >= sect163k1 && namedCurve <= brainpoolP512r1)
+ || (namedCurve >= arbitrary_explicit_prime_curves && namedCurve <= arbitrary_explicit_char2_curves);
+ }
+
+ public static boolean refersToASpecificNamedCurve(int namedCurve)
+ {
+ switch (namedCurve)
+ {
+ case arbitrary_explicit_prime_curves:
+ case arbitrary_explicit_char2_curves:
+ return false;
+ default:
+ return true;
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/NewSessionTicket.java b/core/src/main/java/org/spongycastle/crypto/tls/NewSessionTicket.java
new file mode 100644
index 00000000..d9d67c28
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/NewSessionTicket.java
@@ -0,0 +1,55 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class NewSessionTicket
+{
+ protected long ticketLifetimeHint;
+ protected byte[] ticket;
+
+ public NewSessionTicket(long ticketLifetimeHint, byte[] ticket)
+ {
+ this.ticketLifetimeHint = ticketLifetimeHint;
+ this.ticket = ticket;
+ }
+
+ public long getTicketLifetimeHint()
+ {
+ return ticketLifetimeHint;
+ }
+
+ public byte[] getTicket()
+ {
+ return ticket;
+ }
+
+ /**
+ * Encode this {@link NewSessionTicket} to an {@link OutputStream}.
+ *
+ * @param output the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output)
+ throws IOException
+ {
+ TlsUtils.writeUint32(ticketLifetimeHint, output);
+ TlsUtils.writeOpaque16(ticket, output);
+ }
+
+ /**
+ * Parse a {@link NewSessionTicket} from an {@link InputStream}.
+ *
+ * @param input the {@link InputStream} to parse from.
+ * @return a {@link NewSessionTicket} object.
+ * @throws IOException
+ */
+ public static NewSessionTicket parse(InputStream input)
+ throws IOException
+ {
+ long ticketLifetimeHint = TlsUtils.readUint32(input);
+ byte[] ticket = TlsUtils.readOpaque16(input);
+ return new NewSessionTicket(ticketLifetimeHint, ticket);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/OCSPStatusRequest.java b/core/src/main/java/org/spongycastle/crypto/tls/OCSPStatusRequest.java
new file mode 100644
index 00000000..25c45deb
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/OCSPStatusRequest.java
@@ -0,0 +1,131 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ocsp.ResponderID;
+import org.spongycastle.asn1.x509.Extensions;
+
+/**
+ * RFC 3546 3.6
+ */
+public class OCSPStatusRequest
+{
+ protected Vector responderIDList;
+ protected Extensions requestExtensions;
+
+ /**
+ * @param responderIDList
+ * a {@link Vector} of {@link ResponderID}, specifying the list of trusted OCSP
+ * responders. An empty list has the special meaning that the responders are
+ * implicitly known to the server - e.g., by prior arrangement.
+ * @param requestExtensions
+ * OCSP request extensions. A null value means that there are no extensions.
+ */
+ public OCSPStatusRequest(Vector responderIDList, Extensions requestExtensions)
+ {
+ this.responderIDList = responderIDList;
+ this.requestExtensions = requestExtensions;
+ }
+
+ /**
+ * @return a {@link Vector} of {@link ResponderID}
+ */
+ public Vector getResponderIDList()
+ {
+ return responderIDList;
+ }
+
+ /**
+ * @return OCSP request extensions
+ */
+ public Extensions getRequestExtensions()
+ {
+ return requestExtensions;
+ }
+
+ /**
+ * Encode this {@link OCSPStatusRequest} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output) throws IOException
+ {
+ if (responderIDList == null || responderIDList.isEmpty())
+ {
+ TlsUtils.writeUint16(0, output);
+ }
+ else
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ for (int i = 0; i < responderIDList.size(); ++i)
+ {
+ ResponderID responderID = (ResponderID) responderIDList.elementAt(i);
+ byte[] derEncoding = responderID.getEncoded(ASN1Encoding.DER);
+ TlsUtils.writeOpaque16(derEncoding, buf);
+ }
+ TlsUtils.checkUint16(buf.size());
+ TlsUtils.writeUint16(buf.size(), output);
+ buf.writeTo(output);
+ }
+
+ if (requestExtensions == null)
+ {
+ TlsUtils.writeUint16(0, output);
+ }
+ else
+ {
+ byte[] derEncoding = requestExtensions.getEncoded(ASN1Encoding.DER);
+ TlsUtils.checkUint16(derEncoding.length);
+ TlsUtils.writeUint16(derEncoding.length, output);
+ output.write(derEncoding);
+ }
+ }
+
+ /**
+ * Parse an {@link OCSPStatusRequest} from an {@link InputStream}.
+ *
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return an {@link OCSPStatusRequest} object.
+ * @throws IOException
+ */
+ public static OCSPStatusRequest parse(InputStream input) throws IOException
+ {
+ Vector responderIDList = new Vector();
+ {
+ int length = TlsUtils.readUint16(input);
+ if (length > 0)
+ {
+ byte[] data = TlsUtils.readFully(length, input);
+ ByteArrayInputStream buf = new ByteArrayInputStream(data);
+ do
+ {
+ byte[] derEncoding = TlsUtils.readOpaque16(buf);
+ ResponderID responderID = ResponderID.getInstance(TlsUtils.readDERObject(derEncoding));
+ responderIDList.addElement(responderID);
+ }
+ while (buf.available() > 0);
+ }
+ }
+
+ Extensions requestExtensions = null;
+ {
+ int length = TlsUtils.readUint16(input);
+ if (length > 0)
+ {
+ byte[] derEncoding = TlsUtils.readFully(length, input);
+ requestExtensions = Extensions.getInstance(TlsUtils.readDERObject(derEncoding));
+ }
+ }
+
+ return new OCSPStatusRequest(responderIDList, requestExtensions);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/PRFAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/PRFAlgorithm.java
new file mode 100644
index 00000000..63da881e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/PRFAlgorithm.java
@@ -0,0 +1,22 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 5246
+ * <p>
+ * Note that the values here are implementation-specific and arbitrary. It is recommended not to
+ * depend on the particular values (e.g. serialization).
+ */
+public class PRFAlgorithm
+{
+ /*
+ * Placeholder to refer to the legacy TLS algorithm
+ */
+ public static final int tls_prf_legacy = 0;
+
+ public static final int tls_prf_sha256 = 1;
+
+ /*
+ * Implied by RFC 5288
+ */
+ public static final int tls_prf_sha384 = 2;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/PSKTlsClient.java b/core/src/main/java/org/spongycastle/crypto/tls/PSKTlsClient.java
new file mode 100644
index 00000000..318535e1
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/PSKTlsClient.java
@@ -0,0 +1,256 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public abstract class PSKTlsClient
+ extends AbstractTlsClient
+{
+ protected TlsPSKIdentity pskIdentity;
+
+ public PSKTlsClient(TlsPSKIdentity pskIdentity)
+ {
+ super();
+ this.pskIdentity = pskIdentity;
+ }
+
+ public PSKTlsClient(TlsCipherFactory cipherFactory, TlsPSKIdentity pskIdentity)
+ {
+ super(cipherFactory);
+ this.pskIdentity = pskIdentity;
+ }
+
+ public int[] getCipherSuites()
+ {
+ return new int[] { CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
+ CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA, CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
+ CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA };
+ }
+
+ public TlsKeyExchange getKeyExchange() throws IOException
+ {
+ switch (selectedCipherSuite)
+ {
+ 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_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_ESTREAM_SALSA20_SHA1:
+ 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_DHE_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+ case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+ return createPSKKeyExchange(KeyExchangeAlgorithm.DHE_PSK);
+
+ 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_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1:
+ return createPSKKeyExchange(KeyExchangeAlgorithm.ECDHE_PSK);
+
+ 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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+ case CipherSuite.TLS_PSK_WITH_NULL_SHA256:
+ case CipherSuite.TLS_PSK_WITH_NULL_SHA384:
+ case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1:
+ return createPSKKeyExchange(KeyExchangeAlgorithm.PSK);
+
+ case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_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_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_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA256:
+ case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA384:
+ case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1:
+ return createPSKKeyExchange(KeyExchangeAlgorithm.RSA_PSK);
+
+ 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);
+ }
+ }
+
+ public TlsCipher getCipher() throws IOException
+ {
+ switch (selectedCipherSuite)
+ {
+ case CipherSuite.TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_PSK_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+ case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_PSK_DHE_WITH_AES_128_CCM_8:
+ case CipherSuite.TLS_PSK_WITH_AES_128_CCM_8:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CCM_8, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_128_GCM_SHA256:
+ case CipherSuite.TLS_PSK_WITH_AES_128_GCM_SHA256:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_128_GCM_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha384);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CCM:
+ case CipherSuite.TLS_PSK_WITH_AES_256_CCM:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_PSK_DHE_WITH_AES_256_CCM_8:
+ case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CCM_8, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_256_GCM_SHA384:
+ case CipherSuite.TLS_PSK_WITH_AES_256_GCM_SHA384:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_256_GCM_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_CBC, MACAlgorithm.hmac_sha256);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_128_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+ case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+ case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_CBC, MACAlgorithm.hmac_sha384);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.CAMELLIA_256_GCM, MACAlgorithm._null);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.ESTREAM_SALSA20, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+ case CipherSuite.TLS_PSK_WITH_NULL_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_NULL_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha1);
+
+ 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:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha256);
+
+ 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 cipherFactory.createCipher(context, EncryptionAlgorithm.NULL, MACAlgorithm.hmac_sha384);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.RC4_128, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.SALSA20, MACAlgorithm.hmac_sha1);
+
+ 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);
+ }
+ }
+
+ protected TlsKeyExchange createPSKKeyExchange(int keyExchange)
+ {
+ return new TlsPSKKeyExchange(keyExchange, supportedSignatureAlgorithms, pskIdentity, null, namedCurves,
+ clientECPointFormats, serverECPointFormats);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ProtocolVersion.java b/core/src/main/java/org/spongycastle/crypto/tls/ProtocolVersion.java
new file mode 100644
index 00000000..2bd9a0a8
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ProtocolVersion.java
@@ -0,0 +1,158 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.util.Strings;
+
+public final class ProtocolVersion
+{
+ public static final ProtocolVersion SSLv3 = new ProtocolVersion(0x0300, "SSL 3.0");
+ public static final ProtocolVersion TLSv10 = new ProtocolVersion(0x0301, "TLS 1.0");
+ public static final ProtocolVersion TLSv11 = new ProtocolVersion(0x0302, "TLS 1.1");
+ public static final ProtocolVersion TLSv12 = new ProtocolVersion(0x0303, "TLS 1.2");
+ public static final ProtocolVersion DTLSv10 = new ProtocolVersion(0xFEFF, "DTLS 1.0");
+ public static final ProtocolVersion DTLSv12 = new ProtocolVersion(0xFEFD, "DTLS 1.2");
+
+ private int version;
+ private String name;
+
+ private ProtocolVersion(int v, String name)
+ {
+ this.version = v & 0xffff;
+ this.name = name;
+ }
+
+ public int getFullVersion()
+ {
+ return version;
+ }
+
+ public int getMajorVersion()
+ {
+ return version >> 8;
+ }
+
+ public int getMinorVersion()
+ {
+ return version & 0xff;
+ }
+
+ public boolean isDTLS()
+ {
+ return getMajorVersion() == 0xFE;
+ }
+
+ public boolean isSSL()
+ {
+ return this == SSLv3;
+ }
+
+ public boolean isTLS()
+ {
+ return getMajorVersion() == 0x03;
+ }
+
+ public ProtocolVersion getEquivalentTLSVersion()
+ {
+ if (!isDTLS())
+ {
+ return this;
+ }
+ if (this == DTLSv10)
+ {
+ return TLSv11;
+ }
+ return TLSv12;
+ }
+
+ public boolean isEqualOrEarlierVersionOf(ProtocolVersion version)
+ {
+ if (getMajorVersion() != version.getMajorVersion())
+ {
+ return false;
+ }
+ int diffMinorVersion = version.getMinorVersion() - getMinorVersion();
+ return isDTLS() ? diffMinorVersion <= 0 : diffMinorVersion >= 0;
+ }
+
+ public boolean isLaterVersionOf(ProtocolVersion version)
+ {
+ if (getMajorVersion() != version.getMajorVersion())
+ {
+ return false;
+ }
+ int diffMinorVersion = version.getMinorVersion() - getMinorVersion();
+ return isDTLS() ? diffMinorVersion > 0 : diffMinorVersion < 0;
+ }
+
+ public boolean equals(Object other)
+ {
+ return this == other || (other instanceof ProtocolVersion && equals((ProtocolVersion)other));
+ }
+
+ public boolean equals(ProtocolVersion other)
+ {
+ return other != null && this.version == other.version;
+ }
+
+ public int hashCode()
+ {
+ return version;
+ }
+
+ public static ProtocolVersion get(int major, int minor)
+ throws IOException
+ {
+ switch (major)
+ {
+ case 0x03:
+ {
+ switch (minor)
+ {
+ case 0x00:
+ return SSLv3;
+ case 0x01:
+ return TLSv10;
+ case 0x02:
+ return TLSv11;
+ case 0x03:
+ return TLSv12;
+ }
+ return getUnknownVersion(major, minor, "TLS");
+ }
+ case 0xFE:
+ {
+ switch (minor)
+ {
+ case 0xFF:
+ return DTLSv10;
+ case 0xFE:
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ case 0xFD:
+ return DTLSv12;
+ }
+ return getUnknownVersion(major, minor, "DTLS");
+ }
+ default:
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+ }
+
+ public String toString()
+ {
+ return name;
+ }
+
+ private static ProtocolVersion getUnknownVersion(int major, int minor, String prefix)
+ throws IOException
+ {
+ TlsUtils.checkUint8(major);
+ TlsUtils.checkUint8(minor);
+
+ int v = (major << 8) | minor;
+ String hex = Strings.toUpperCase(Integer.toHexString(0x10000 | v).substring(1));
+ return new ProtocolVersion(v, prefix + " 0x" + hex);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/RecordStream.java b/core/src/main/java/org/spongycastle/crypto/tls/RecordStream.java
new file mode 100644
index 00000000..3e1b3873
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/RecordStream.java
@@ -0,0 +1,365 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * An implementation of the TLS 1.0/1.1/1.2 record layer, allowing downgrade to SSLv3.
+ */
+class RecordStream
+{
+ private static int DEFAULT_PLAINTEXT_LIMIT = (1 << 14);
+
+ private TlsProtocol handler;
+ private InputStream input;
+ private OutputStream output;
+ private TlsCompression pendingCompression = null, readCompression = null, writeCompression = null;
+ private TlsCipher pendingCipher = null, readCipher = null, writeCipher = null;
+ private long readSeqNo = 0, writeSeqNo = 0;
+ private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+
+ private TlsContext context = null;
+ private TlsHandshakeHash handshakeHash = null;
+
+ private ProtocolVersion readVersion = null, writeVersion = null;
+ private boolean restrictReadVersion = true;
+
+ private int plaintextLimit, compressedLimit, ciphertextLimit;
+
+ RecordStream(TlsProtocol handler, InputStream input, OutputStream output)
+ {
+ this.handler = handler;
+ this.input = input;
+ this.output = output;
+ this.readCompression = new TlsNullCompression();
+ this.writeCompression = this.readCompression;
+ this.readCipher = new TlsNullCipher(context);
+ this.writeCipher = this.readCipher;
+
+ setPlaintextLimit(DEFAULT_PLAINTEXT_LIMIT);
+ }
+
+ void init(TlsContext context)
+ {
+ this.context = context;
+ this.handshakeHash = new DeferredHash();
+ this.handshakeHash.init(context);
+ }
+
+ int getPlaintextLimit()
+ {
+ return plaintextLimit;
+ }
+
+ void setPlaintextLimit(int plaintextLimit)
+ {
+ this.plaintextLimit = plaintextLimit;
+ this.compressedLimit = this.plaintextLimit + 1024;
+ this.ciphertextLimit = this.compressedLimit + 1024;
+ }
+
+ ProtocolVersion getReadVersion()
+ {
+ return readVersion;
+ }
+
+ void setReadVersion(ProtocolVersion readVersion)
+ {
+ this.readVersion = readVersion;
+ }
+
+ void setWriteVersion(ProtocolVersion writeVersion)
+ {
+ this.writeVersion = writeVersion;
+ }
+
+ /**
+ * RFC 5246 E.1. "Earlier versions of the TLS specification were not fully clear on what the
+ * record layer version number (TLSPlaintext.version) should contain when sending ClientHello
+ * (i.e., before it is known which version of the protocol will be employed). Thus, TLS servers
+ * compliant with this specification MUST accept any value {03,XX} as the record layer version
+ * number for ClientHello."
+ */
+ void setRestrictReadVersion(boolean enabled)
+ {
+ this.restrictReadVersion = enabled;
+ }
+
+ void setPendingConnectionState(TlsCompression tlsCompression, TlsCipher tlsCipher)
+ {
+ this.pendingCompression = tlsCompression;
+ this.pendingCipher = tlsCipher;
+ }
+
+ void sentWriteCipherSpec()
+ throws IOException
+ {
+ if (pendingCompression == null || pendingCipher == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ this.writeCompression = this.pendingCompression;
+ this.writeCipher = this.pendingCipher;
+ this.writeSeqNo = 0;
+ }
+
+ void receivedReadCipherSpec()
+ throws IOException
+ {
+ if (pendingCompression == null || pendingCipher == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ this.readCompression = this.pendingCompression;
+ this.readCipher = this.pendingCipher;
+ this.readSeqNo = 0;
+ }
+
+ void finaliseHandshake()
+ throws IOException
+ {
+ if (readCompression != pendingCompression || writeCompression != pendingCompression
+ || readCipher != pendingCipher || writeCipher != pendingCipher)
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ pendingCompression = null;
+ pendingCipher = null;
+ }
+
+ public boolean readRecord()
+ throws IOException
+ {
+ byte[] recordHeader = TlsUtils.readAllOrNothing(5, input);
+ if (recordHeader == null)
+ {
+ return false;
+ }
+
+ short type = TlsUtils.readUint8(recordHeader, 0);
+
+ /*
+ * RFC 5246 6. If a TLS implementation receives an unexpected record type, it MUST send an
+ * unexpected_message alert.
+ */
+ checkType(type, AlertDescription.unexpected_message);
+
+ if (!restrictReadVersion)
+ {
+ int version = TlsUtils.readVersionRaw(recordHeader, 1);
+ if ((version & 0xffffff00) != 0x0300)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+ else
+ {
+ ProtocolVersion version = TlsUtils.readVersion(recordHeader, 1);
+ if (readVersion == null)
+ {
+ readVersion = version;
+ }
+ else if (!version.equals(readVersion))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ int length = TlsUtils.readUint16(recordHeader, 3);
+ byte[] plaintext = decodeAndVerify(type, input, length);
+ handler.processRecord(type, plaintext, 0, plaintext.length);
+ return true;
+ }
+
+ protected byte[] decodeAndVerify(short type, InputStream input, int len)
+ throws IOException
+ {
+ checkLength(len, ciphertextLimit, AlertDescription.record_overflow);
+
+ byte[] buf = TlsUtils.readFully(len, input);
+ byte[] decoded = readCipher.decodeCiphertext(readSeqNo++, type, buf, 0, buf.length);
+
+ checkLength(decoded.length, compressedLimit, AlertDescription.record_overflow);
+
+ /*
+ * TODO RFC5264 6.2.2. Implementation note: Decompression functions are responsible for
+ * ensuring that messages cannot cause internal buffer overflows.
+ */
+ OutputStream cOut = readCompression.decompress(buffer);
+ if (cOut != buffer)
+ {
+ cOut.write(decoded, 0, decoded.length);
+ cOut.flush();
+ decoded = getBufferContents();
+ }
+
+ /*
+ * RFC 5264 6.2.2. If the decompression function encounters a TLSCompressed.fragment that
+ * would decompress to a length in excess of 2^14 bytes, it should report a fatal
+ * decompression failure error.
+ */
+ checkLength(decoded.length, plaintextLimit, AlertDescription.decompression_failure);
+
+ /*
+ * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+ * or ChangeCipherSpec content types.
+ */
+ if (decoded.length < 1 && type != ContentType.application_data)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return decoded;
+ }
+
+ protected void writeRecord(short type, byte[] plaintext, int plaintextOffset, int plaintextLength)
+ throws IOException
+ {
+ // Never send anything until a valid ClientHello has been received
+ if (writeVersion == null)
+ {
+ return;
+ }
+
+ /*
+ * RFC 5264 6. Implementations MUST NOT send record types not defined in this document
+ * unless negotiated by some extension.
+ */
+ checkType(type, AlertDescription.internal_error);
+
+ /*
+ * RFC 5264 6.2.1 The length should not exceed 2^14.
+ */
+ checkLength(plaintextLength, plaintextLimit, AlertDescription.internal_error);
+
+ /*
+ * RFC 5264 6.2.1 Implementations MUST NOT send zero-length fragments of Handshake, Alert,
+ * or ChangeCipherSpec content types.
+ */
+ if (plaintextLength < 1 && type != ContentType.application_data)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ if (type == ContentType.handshake)
+ {
+ updateHandshakeData(plaintext, plaintextOffset, plaintextLength);
+ }
+
+ OutputStream cOut = writeCompression.compress(buffer);
+
+ byte[] ciphertext;
+ if (cOut == buffer)
+ {
+ ciphertext = writeCipher.encodePlaintext(writeSeqNo++, type, plaintext, plaintextOffset, plaintextLength);
+ }
+ else
+ {
+ cOut.write(plaintext, plaintextOffset, plaintextLength);
+ cOut.flush();
+ byte[] compressed = getBufferContents();
+
+ /*
+ * RFC5264 6.2.2. Compression must be lossless and may not increase the content length
+ * by more than 1024 bytes.
+ */
+ checkLength(compressed.length, plaintextLength + 1024, AlertDescription.internal_error);
+
+ ciphertext = writeCipher.encodePlaintext(writeSeqNo++, type, compressed, 0, compressed.length);
+ }
+
+ /*
+ * RFC 5264 6.2.3. The length may not exceed 2^14 + 2048.
+ */
+ checkLength(ciphertext.length, ciphertextLimit, AlertDescription.internal_error);
+
+ byte[] record = new byte[ciphertext.length + 5];
+ TlsUtils.writeUint8(type, record, 0);
+ TlsUtils.writeVersion(writeVersion, record, 1);
+ TlsUtils.writeUint16(ciphertext.length, record, 3);
+ System.arraycopy(ciphertext, 0, record, 5, ciphertext.length);
+ output.write(record);
+ output.flush();
+ }
+
+ void notifyHelloComplete()
+ {
+ this.handshakeHash = handshakeHash.notifyPRFDetermined();
+ }
+
+ TlsHandshakeHash getHandshakeHash()
+ {
+ return handshakeHash;
+ }
+
+ TlsHandshakeHash prepareToFinish()
+ {
+ TlsHandshakeHash result = handshakeHash;
+ this.handshakeHash = handshakeHash.stopTracking();
+ return result;
+ }
+
+ void updateHandshakeData(byte[] message, int offset, int len)
+ {
+ handshakeHash.update(message, offset, len);
+ }
+
+ protected void safeClose()
+ {
+ try
+ {
+ input.close();
+ }
+ catch (IOException e)
+ {
+ }
+
+ try
+ {
+ output.close();
+ }
+ catch (IOException e)
+ {
+ }
+ }
+
+ protected void flush()
+ throws IOException
+ {
+ output.flush();
+ }
+
+ private byte[] getBufferContents()
+ {
+ byte[] contents = buffer.toByteArray();
+ buffer.reset();
+ return contents;
+ }
+
+ private static void checkType(short type, short alertDescription)
+ throws IOException
+ {
+ switch (type)
+ {
+ case ContentType.application_data:
+ case ContentType.alert:
+ case ContentType.change_cipher_spec:
+ case ContentType.handshake:
+ case ContentType.heartbeat:
+ break;
+ default:
+ throw new TlsFatalAlert(alertDescription);
+ }
+ }
+
+ private static void checkLength(int length, int limit, short alertDescription)
+ throws IOException
+ {
+ if (length > limit)
+ {
+ throw new TlsFatalAlert(alertDescription);
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SRPTlsClient.java b/core/src/main/java/org/spongycastle/crypto/tls/SRPTlsClient.java
new file mode 100644
index 00000000..65aa6cee
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SRPTlsClient.java
@@ -0,0 +1,121 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.spongycastle.util.Arrays;
+
+public abstract class SRPTlsClient
+ extends AbstractTlsClient
+{
+ /**
+ * @deprecated use TlsSRPUtils.EXT_SRP instead
+ */
+ public static final Integer EXT_SRP = TlsSRPUtils.EXT_SRP;
+
+ protected byte[] identity;
+ protected byte[] password;
+
+ public SRPTlsClient(byte[] identity, byte[] password)
+ {
+ super();
+ this.identity = Arrays.clone(identity);
+ this.password = Arrays.clone(password);
+ }
+
+ public SRPTlsClient(TlsCipherFactory cipherFactory, byte[] identity, byte[] password)
+ {
+ super(cipherFactory);
+ this.identity = Arrays.clone(identity);
+ this.password = Arrays.clone(password);
+ }
+
+ public int[] getCipherSuites()
+ {
+ return new int[] { CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA };
+ }
+
+ public Hashtable getClientExtensions()
+ throws IOException
+ {
+ Hashtable clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(super.getClientExtensions());
+ TlsSRPUtils.addSRPExtension(clientExtensions, this.identity);
+ return clientExtensions;
+ }
+
+ public void processServerExtensions(Hashtable serverExtensions)
+ throws IOException
+ {
+ if (!TlsUtils.hasExpectedEmptyExtensionData(serverExtensions, TlsSRPUtils.EXT_SRP, AlertDescription.illegal_parameter))
+ {
+ // No explicit guidance in RFC 5054 here; we allow an optional empty extension from server
+ }
+ }
+
+ public TlsKeyExchange getKeyExchange()
+ throws IOException
+ {
+
+ switch (selectedCipherSuite)
+ {
+ case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA:
+ return createSRPKeyExchange(KeyExchangeAlgorithm.SRP);
+
+ case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA:
+ return createSRPKeyExchange(KeyExchangeAlgorithm.SRP_RSA);
+
+ case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA:
+ return createSRPKeyExchange(KeyExchangeAlgorithm.SRP_DSS);
+
+ default:
+ /*
+ * Note: internal error here; the 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);
+ }
+ }
+
+ public TlsCipher getCipher()
+ throws IOException
+ {
+
+ switch (selectedCipherSuite)
+ {
+ case CipherSuite.TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm._3DES_EDE_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_SRP_SHA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_128_CBC, MACAlgorithm.hmac_sha1);
+
+ case CipherSuite.TLS_SRP_SHA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA:
+ return cipherFactory.createCipher(context, EncryptionAlgorithm.AES_256_CBC, MACAlgorithm.hmac_sha1);
+
+ 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);
+ }
+ }
+
+ protected TlsKeyExchange createSRPKeyExchange(int keyExchange)
+ {
+ return new TlsSRPKeyExchange(keyExchange, supportedSignatureAlgorithms, identity, password);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SRTPProtectionProfile.java b/core/src/main/java/org/spongycastle/crypto/tls/SRTPProtectionProfile.java
new file mode 100644
index 00000000..6581b54a
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SRTPProtectionProfile.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+public class SRTPProtectionProfile
+{
+ /*
+ * RFC 5764 4.1.2.
+ */
+ public static final int SRTP_AES128_CM_HMAC_SHA1_80 = 0x0001;
+ public static final int SRTP_AES128_CM_HMAC_SHA1_32 = 0x0002;
+ public static final int SRTP_NULL_HMAC_SHA1_80 = 0x0005;
+ public static final int SRTP_NULL_HMAC_SHA1_32 = 0x0006;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SSL3Mac.java b/core/src/main/java/org/spongycastle/crypto/tls/SSL3Mac.java
new file mode 100644
index 00000000..6b3f2faf
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SSL3Mac.java
@@ -0,0 +1,114 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Mac;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.util.Arrays;
+
+/**
+ * HMAC implementation based on original internet draft for HMAC (RFC 2104)
+ * <p>
+ * The difference is that padding is concatenated versus XORed with the key
+ * <p>
+ * H(K + opad, H(K + ipad, text))
+ */
+public class SSL3Mac
+ implements Mac
+{
+ private final static byte IPAD_BYTE = (byte)0x36;
+ private final static byte OPAD_BYTE = (byte)0x5C;
+
+ static final byte[] IPAD = genPad(IPAD_BYTE, 48);
+ static final byte[] OPAD = genPad(OPAD_BYTE, 48);
+
+ private Digest digest;
+
+ private byte[] secret;
+ private int padLength;
+
+ /**
+ * Base constructor for one of the standard digest algorithms that the byteLength of
+ * the algorithm is know for. Behaviour is undefined for digests other than MD5 or SHA1.
+ *
+ * @param digest the digest.
+ */
+ public SSL3Mac(Digest digest)
+ {
+ this.digest = digest;
+
+ if (digest.getDigestSize() == 20)
+ {
+ this.padLength = 40;
+ }
+ else
+ {
+ this.padLength = 48;
+ }
+ }
+
+ public String getAlgorithmName()
+ {
+ return digest.getAlgorithmName() + "/SSL3MAC";
+ }
+
+ public Digest getUnderlyingDigest()
+ {
+ return digest;
+ }
+
+ public void init(CipherParameters params)
+ {
+ secret = Arrays.clone(((KeyParameter)params).getKey());
+
+ reset();
+ }
+
+ public int getMacSize()
+ {
+ return digest.getDigestSize();
+ }
+
+ public void update(byte in)
+ {
+ digest.update(in);
+ }
+
+ public void update(byte[] in, int inOff, int len)
+ {
+ digest.update(in, inOff, len);
+ }
+
+ public int doFinal(byte[] out, int outOff)
+ {
+ byte[] tmp = new byte[digest.getDigestSize()];
+ digest.doFinal(tmp, 0);
+
+ digest.update(secret, 0, secret.length);
+ digest.update(OPAD, 0, padLength);
+ digest.update(tmp, 0, tmp.length);
+
+ int len = digest.doFinal(out, outOff);
+
+ reset();
+
+ return len;
+ }
+
+ /**
+ * Reset the mac generator.
+ */
+ public void reset()
+ {
+ digest.reset();
+ digest.update(secret, 0, secret.length);
+ digest.update(IPAD, 0, padLength);
+ }
+
+ private static byte[] genPad(byte b, int count)
+ {
+ byte[] padding = new byte[count];
+ Arrays.fill(padding, b);
+ return padding;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SecurityParameters.java b/core/src/main/java/org/spongycastle/crypto/tls/SecurityParameters.java
new file mode 100644
index 00000000..36648311
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SecurityParameters.java
@@ -0,0 +1,91 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.util.Arrays;
+
+public class SecurityParameters
+{
+ int entity = -1;
+ int cipherSuite = -1;
+ short compressionAlgorithm = -1;
+ int prfAlgorithm = -1;
+ int verifyDataLength = -1;
+ byte[] masterSecret = null;
+ byte[] clientRandom = null;
+ byte[] serverRandom = null;
+
+ // TODO Keep these internal, since it's maybe not the ideal place for them
+ short maxFragmentLength = -1;
+ boolean truncatedHMac = false;
+ boolean encryptThenMAC = false;
+
+ void copySessionParametersFrom(SecurityParameters other)
+ {
+ this.entity = other.entity;
+ this.cipherSuite = other.cipherSuite;
+ this.compressionAlgorithm = other.compressionAlgorithm;
+ this.prfAlgorithm = other.prfAlgorithm;
+ this.verifyDataLength = other.verifyDataLength;
+ this.masterSecret = Arrays.clone(other.masterSecret);
+ }
+
+ void clear()
+ {
+ if (this.masterSecret != null)
+ {
+ Arrays.fill(this.masterSecret, (byte)0);
+ this.masterSecret = null;
+ }
+ }
+
+ /**
+ * @return {@link ConnectionEnd}
+ */
+ public int getEntity()
+ {
+ return entity;
+ }
+
+ /**
+ * @return {@link CipherSuite}
+ */
+ public int getCipherSuite()
+ {
+ return cipherSuite;
+ }
+
+ /**
+ * @return {@link CompressionMethod}
+ */
+ public short getCompressionAlgorithm()
+ {
+ return compressionAlgorithm;
+ }
+
+ /**
+ * @return {@link PRFAlgorithm}
+ */
+ public int getPrfAlgorithm()
+ {
+ return prfAlgorithm;
+ }
+
+ public int getVerifyDataLength()
+ {
+ return verifyDataLength;
+ }
+
+ public byte[] getMasterSecret()
+ {
+ return masterSecret;
+ }
+
+ public byte[] getClientRandom()
+ {
+ return clientRandom;
+ }
+
+ public byte[] getServerRandom()
+ {
+ return serverRandom;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ServerDHParams.java b/core/src/main/java/org/spongycastle/crypto/tls/ServerDHParams.java
new file mode 100644
index 00000000..fa22e629
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ServerDHParams.java
@@ -0,0 +1,63 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+
+import org.spongycastle.crypto.params.DHParameters;
+import org.spongycastle.crypto.params.DHPublicKeyParameters;
+
+public class ServerDHParams
+{
+ protected DHPublicKeyParameters publicKey;
+
+ public ServerDHParams(DHPublicKeyParameters publicKey)
+ {
+ if (publicKey == null)
+ {
+ throw new IllegalArgumentException("'publicKey' cannot be null");
+ }
+
+ this.publicKey = publicKey;
+ }
+
+ public DHPublicKeyParameters getPublicKey()
+ {
+ return publicKey;
+ }
+
+ /**
+ * Encode this {@link ServerDHParams} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output) throws IOException
+ {
+ DHParameters dhParameters = publicKey.getParameters();
+ BigInteger Ys = publicKey.getY();
+
+ TlsDHUtils.writeDHParameter(dhParameters.getP(), output);
+ TlsDHUtils.writeDHParameter(dhParameters.getG(), output);
+ TlsDHUtils.writeDHParameter(Ys, output);
+ }
+
+ /**
+ * Parse a {@link ServerDHParams} from an {@link InputStream}.
+ *
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link ServerDHParams} object.
+ * @throws IOException
+ */
+ public static ServerDHParams parse(InputStream input) throws IOException
+ {
+ BigInteger p = TlsDHUtils.readDHParameter(input);
+ BigInteger g = TlsDHUtils.readDHParameter(input);
+ BigInteger Ys = TlsDHUtils.readDHParameter(input);
+
+ return new ServerDHParams(new DHPublicKeyParameters(Ys, new DHParameters(p, g)));
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ServerName.java b/core/src/main/java/org/spongycastle/crypto/tls/ServerName.java
new file mode 100644
index 00000000..8e260291
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ServerName.java
@@ -0,0 +1,112 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.spongycastle.util.Strings;
+
+public class ServerName
+{
+ protected short nameType;
+ protected Object name;
+
+ public ServerName(short nameType, Object name)
+ {
+ if (!isCorrectType(nameType, name))
+ {
+ throw new IllegalArgumentException("'name' is not an instance of the correct type");
+ }
+
+ this.nameType = nameType;
+ this.name = name;
+ }
+
+ public short getNameType()
+ {
+ return nameType;
+ }
+
+ public Object getName()
+ {
+ return name;
+ }
+
+ public String getHostName()
+ {
+ if (!isCorrectType(NameType.host_name, name))
+ {
+ throw new IllegalStateException("'name' is not a HostName string");
+ }
+ return (String)name;
+ }
+
+ /**
+ * Encode this {@link ServerName} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output) throws IOException
+ {
+ TlsUtils.writeUint8(nameType, output);
+
+ switch (nameType)
+ {
+ case NameType.host_name:
+ byte[] utf8Encoding = Strings.toUTF8ByteArray((String)name);
+ if (utf8Encoding.length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ TlsUtils.writeOpaque16(utf8Encoding, output);
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ /**
+ * Parse a {@link ServerName} from an {@link InputStream}.
+ *
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link ServerName} object.
+ * @throws IOException
+ */
+ public static ServerName parse(InputStream input) throws IOException
+ {
+ short name_type = TlsUtils.readUint8(input);
+ Object name;
+
+ switch (name_type)
+ {
+ case NameType.host_name:
+ {
+ byte[] utf8Encoding = TlsUtils.readOpaque16(input);
+ if (utf8Encoding.length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+ name = Strings.fromUTF8ByteArray(utf8Encoding);
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ return new ServerName(name_type, name);
+ }
+
+ protected static boolean isCorrectType(short nameType, Object name)
+ {
+ switch (nameType)
+ {
+ case NameType.host_name:
+ return name instanceof String;
+ default:
+ throw new IllegalArgumentException("'name' is an unsupported value");
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ServerNameList.java b/core/src/main/java/org/spongycastle/crypto/tls/ServerNameList.java
new file mode 100644
index 00000000..67c56e53
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ServerNameList.java
@@ -0,0 +1,86 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+public class ServerNameList
+{
+ protected Vector serverNameList;
+
+ /**
+ * @param serverNameList a {@link Vector} of {@link ServerName}.
+ */
+ public ServerNameList(Vector serverNameList)
+ {
+ if (serverNameList == null || serverNameList.isEmpty())
+ {
+ throw new IllegalArgumentException("'serverNameList' must not be null or empty");
+ }
+
+ this.serverNameList = serverNameList;
+ }
+
+ /**
+ * @return a {@link Vector} of {@link ServerName}.
+ */
+ public Vector getServerNameList()
+ {
+ return serverNameList;
+ }
+
+ /**
+ * Encode this {@link ServerNameList} to an {@link OutputStream}.
+ *
+ * @param output
+ * the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output) throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ for (int i = 0; i < serverNameList.size(); ++i)
+ {
+ ServerName entry = (ServerName)serverNameList.elementAt(i);
+ entry.encode(buf);
+ }
+
+ TlsUtils.checkUint16(buf.size());
+ TlsUtils.writeUint16(buf.size(), output);
+ buf.writeTo(output);
+ }
+
+ /**
+ * Parse a {@link ServerNameList} from an {@link InputStream}.
+ *
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link ServerNameList} object.
+ * @throws IOException
+ */
+ public static ServerNameList parse(InputStream input) throws IOException
+ {
+ int length = TlsUtils.readUint16(input);
+ if (length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ byte[] data = TlsUtils.readFully(length, input);
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(data);
+
+ Vector server_name_list = new Vector();
+ while (buf.available() > 0)
+ {
+ ServerName entry = ServerName.parse(buf);
+ server_name_list.addElement(entry);
+ }
+
+ return new ServerNameList(server_name_list);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/ServerOnlyTlsAuthentication.java b/core/src/main/java/org/spongycastle/crypto/tls/ServerOnlyTlsAuthentication.java
new file mode 100644
index 00000000..325209ba
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/ServerOnlyTlsAuthentication.java
@@ -0,0 +1,10 @@
+package org.spongycastle.crypto.tls;
+
+public abstract class ServerOnlyTlsAuthentication
+ implements TlsAuthentication
+{
+ public final TlsCredentials getClientCredentials(CertificateRequest certificateRequest)
+ {
+ return null;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SessionParameters.java b/core/src/main/java/org/spongycastle/crypto/tls/SessionParameters.java
new file mode 100644
index 00000000..cf8ec194
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SessionParameters.java
@@ -0,0 +1,142 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.spongycastle.util.Arrays;
+
+public final class SessionParameters
+{
+ public static final class Builder
+ {
+ private int cipherSuite = -1;
+ private short compressionAlgorithm = -1;
+ private byte[] masterSecret = null;
+ private Certificate peerCertificate = null;
+ private byte[] encodedServerExtensions = null;
+
+ public Builder()
+ {
+ }
+
+ public SessionParameters build()
+ {
+ validate(this.cipherSuite >= 0, "cipherSuite");
+ validate(this.compressionAlgorithm >= 0, "compressionAlgorithm");
+ validate(this.masterSecret != null, "masterSecret");
+ return new SessionParameters(cipherSuite, compressionAlgorithm, masterSecret, peerCertificate,
+ encodedServerExtensions);
+ }
+
+ public Builder setCipherSuite(int cipherSuite)
+ {
+ this.cipherSuite = cipherSuite;
+ return this;
+ }
+
+ public Builder setCompressionAlgorithm(short compressionAlgorithm)
+ {
+ this.compressionAlgorithm = compressionAlgorithm;
+ return this;
+ }
+
+ public Builder setMasterSecret(byte[] masterSecret)
+ {
+ this.masterSecret = masterSecret;
+ return this;
+ }
+
+ public Builder setPeerCertificate(Certificate peerCertificate)
+ {
+ this.peerCertificate = peerCertificate;
+ return this;
+ }
+
+ public Builder setServerExtensions(Hashtable serverExtensions)
+ throws IOException
+ {
+ if (serverExtensions == null)
+ {
+ encodedServerExtensions = null;
+ }
+ else
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ TlsProtocol.writeExtensions(buf, serverExtensions);
+ encodedServerExtensions = buf.toByteArray();
+ }
+ return this;
+ }
+
+ private void validate(boolean condition, String parameter)
+ {
+ if (!condition)
+ {
+ throw new IllegalStateException("Required session parameter '" + parameter + "' not configured");
+ }
+ }
+ }
+
+ private int cipherSuite;
+ private short compressionAlgorithm;
+ private byte[] masterSecret;
+ private Certificate peerCertificate;
+ private byte[] encodedServerExtensions;
+
+ private SessionParameters(int cipherSuite, short compressionAlgorithm, byte[] masterSecret,
+ Certificate peerCertificate, byte[] encodedServerExtensions)
+ {
+ this.cipherSuite = cipherSuite;
+ this.compressionAlgorithm = compressionAlgorithm;
+ this.masterSecret = Arrays.clone(masterSecret);
+ this.peerCertificate = peerCertificate;
+ this.encodedServerExtensions = encodedServerExtensions;
+ }
+
+ public void clear()
+ {
+ if (this.masterSecret != null)
+ {
+ Arrays.fill(this.masterSecret, (byte)0);
+ }
+ }
+
+ public SessionParameters copy()
+ {
+ return new SessionParameters(cipherSuite, compressionAlgorithm, masterSecret, peerCertificate,
+ encodedServerExtensions);
+ }
+
+ public int getCipherSuite()
+ {
+ return cipherSuite;
+ }
+
+ public short getCompressionAlgorithm()
+ {
+ return compressionAlgorithm;
+ }
+
+ public byte[] getMasterSecret()
+ {
+ return masterSecret;
+ }
+
+ public Certificate getPeerCertificate()
+ {
+ return peerCertificate;
+ }
+
+ public Hashtable readServerExtensions() throws IOException
+ {
+ if (encodedServerExtensions == null)
+ {
+ return null;
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(encodedServerExtensions);
+ return TlsProtocol.readExtensions(buf);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SignatureAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/SignatureAlgorithm.java
new file mode 100644
index 00000000..5da3b27b
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SignatureAlgorithm.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 5246 7.4.1.4.1 (in RFC 2246, there were no specific values assigned)
+ */
+public class SignatureAlgorithm
+{
+ public static final short anonymous = 0;
+ public static final short rsa = 1;
+ public static final short dsa = 2;
+ public static final short ecdsa = 3;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SignatureAndHashAlgorithm.java b/core/src/main/java/org/spongycastle/crypto/tls/SignatureAndHashAlgorithm.java
new file mode 100644
index 00000000..211c96c3
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SignatureAndHashAlgorithm.java
@@ -0,0 +1,96 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * RFC 5246 7.4.1.4.1
+ */
+public class SignatureAndHashAlgorithm
+{
+ protected short hash;
+ protected short signature;
+
+ /**
+ * @param hash {@link HashAlgorithm}
+ * @param signature {@link SignatureAlgorithm}
+ */
+ public SignatureAndHashAlgorithm(short hash, short signature)
+ {
+ if (!TlsUtils.isValidUint8(hash))
+ {
+ throw new IllegalArgumentException("'hash' should be a uint8");
+ }
+ if (!TlsUtils.isValidUint8(signature))
+ {
+ throw new IllegalArgumentException("'signature' should be a uint8");
+ }
+ if (signature == SignatureAlgorithm.anonymous)
+ {
+ throw new IllegalArgumentException("'signature' MUST NOT be \"anonymous\"");
+ }
+
+ this.hash = hash;
+ this.signature = signature;
+ }
+
+ /**
+ * @return {@link HashAlgorithm}
+ */
+ public short getHash()
+ {
+ return hash;
+ }
+
+ /**
+ * @return {@link SignatureAlgorithm}
+ */
+ public short getSignature()
+ {
+ return signature;
+ }
+
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof SignatureAndHashAlgorithm))
+ {
+ return false;
+ }
+ SignatureAndHashAlgorithm other = (SignatureAndHashAlgorithm)obj;
+ return other.getHash() == getHash() && other.getSignature() == getSignature();
+ }
+
+ public int hashCode()
+ {
+ return (getHash() << 16) | getSignature();
+ }
+
+ /**
+ * Encode this {@link SignatureAndHashAlgorithm} to an {@link OutputStream}.
+ *
+ * @param output the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output)
+ throws IOException
+ {
+ TlsUtils.writeUint8(getHash(), output);
+ TlsUtils.writeUint8(getSignature(), output);
+ }
+
+ /**
+ * Parse a {@link SignatureAndHashAlgorithm} from an {@link InputStream}.
+ *
+ * @param input the {@link InputStream} to parse from.
+ * @return a {@link SignatureAndHashAlgorithm} object.
+ * @throws IOException
+ */
+ public static SignatureAndHashAlgorithm parse(InputStream input)
+ throws IOException
+ {
+ short hash = TlsUtils.readUint8(input);
+ short signature = TlsUtils.readUint8(input);
+ return new SignatureAndHashAlgorithm(hash, signature);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SignerInputBuffer.java b/core/src/main/java/org/spongycastle/crypto/tls/SignerInputBuffer.java
new file mode 100644
index 00000000..39a68e6c
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SignerInputBuffer.java
@@ -0,0 +1,13 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+
+import org.spongycastle.crypto.Signer;
+
+class SignerInputBuffer extends ByteArrayOutputStream
+{
+ void updateSigner(Signer s)
+ {
+ s.update(this.buf, 0, count);
+ }
+} \ No newline at end of file
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SupplementalDataEntry.java b/core/src/main/java/org/spongycastle/crypto/tls/SupplementalDataEntry.java
new file mode 100644
index 00000000..2633623d
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SupplementalDataEntry.java
@@ -0,0 +1,23 @@
+package org.spongycastle.crypto.tls;
+
+public class SupplementalDataEntry
+{
+ protected int dataType;
+ protected byte[] data;
+
+ public SupplementalDataEntry(int dataType, byte[] data)
+ {
+ this.dataType = dataType;
+ this.data = data;
+ }
+
+ public int getDataType()
+ {
+ return dataType;
+ }
+
+ public byte[] getData()
+ {
+ return data;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/SupplementalDataType.java b/core/src/main/java/org/spongycastle/crypto/tls/SupplementalDataType.java
new file mode 100644
index 00000000..5cabe8c3
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/SupplementalDataType.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 4680
+ */
+public class SupplementalDataType
+{
+ /*
+ * RFC 4681
+ */
+ public static final int user_mapping_data = 0;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsAEADCipher.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsAEADCipher.java
new file mode 100644
index 00000000..0ddc2fe5
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsAEADCipher.java
@@ -0,0 +1,193 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.modes.AEADBlockCipher;
+import org.spongycastle.crypto.params.AEADParameters;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.util.Arrays;
+
+public class TlsAEADCipher
+ implements TlsCipher
+{
+ protected TlsContext context;
+ protected int macSize;
+ protected int nonce_explicit_length;
+
+ protected AEADBlockCipher encryptCipher;
+ protected AEADBlockCipher decryptCipher;
+
+ protected byte[] encryptImplicitNonce, decryptImplicitNonce;
+
+ public TlsAEADCipher(TlsContext context, AEADBlockCipher clientWriteCipher, AEADBlockCipher serverWriteCipher,
+ int cipherKeySize, int macSize) throws IOException
+ {
+ if (!TlsUtils.isTLSv12(context))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ this.context = context;
+ this.macSize = macSize;
+
+ // NOTE: Valid for RFC 5288/6655 ciphers but may need review for other AEAD ciphers
+ this.nonce_explicit_length = 8;
+
+ // TODO SecurityParameters.fixed_iv_length
+ int fixed_iv_length = 4;
+
+ int key_block_size = (2 * cipherKeySize) + (2 * fixed_iv_length);
+
+ byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size);
+
+ int offset = 0;
+
+ KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+ offset += cipherKeySize;
+ KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+ offset += cipherKeySize;
+ byte[] client_write_IV = Arrays.copyOfRange(key_block, offset, offset + fixed_iv_length);
+ offset += fixed_iv_length;
+ byte[] server_write_IV = Arrays.copyOfRange(key_block, offset, offset + fixed_iv_length);
+ offset += fixed_iv_length;
+
+ if (offset != key_block_size)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ KeyParameter encryptKey, decryptKey;
+ if (context.isServer())
+ {
+ this.encryptCipher = serverWriteCipher;
+ this.decryptCipher = clientWriteCipher;
+ this.encryptImplicitNonce = server_write_IV;
+ this.decryptImplicitNonce = client_write_IV;
+ encryptKey = server_write_key;
+ decryptKey = client_write_key;
+ }
+ else
+ {
+ this.encryptCipher = clientWriteCipher;
+ this.decryptCipher = serverWriteCipher;
+ this.encryptImplicitNonce = client_write_IV;
+ this.decryptImplicitNonce = server_write_IV;
+ encryptKey = client_write_key;
+ decryptKey = server_write_key;
+ }
+
+ byte[] dummyNonce = new byte[fixed_iv_length + nonce_explicit_length];
+
+ this.encryptCipher.init(true, new AEADParameters(encryptKey, 8 * macSize, dummyNonce));
+ this.decryptCipher.init(false, new AEADParameters(decryptKey, 8 * macSize, dummyNonce));
+ }
+
+ public int getPlaintextLimit(int ciphertextLimit)
+ {
+ // TODO We ought to be able to ask the decryptCipher (independently of it's current state!)
+ return ciphertextLimit - macSize - nonce_explicit_length;
+ }
+
+ public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+ throws IOException
+ {
+ byte[] nonce = new byte[this.encryptImplicitNonce.length + nonce_explicit_length];
+ System.arraycopy(encryptImplicitNonce, 0, nonce, 0, encryptImplicitNonce.length);
+
+ /*
+ * RFC 5288/6655 The nonce_explicit MAY be the 64-bit sequence number.
+ *
+ * (May need review for other AEAD ciphers).
+ */
+ TlsUtils.writeUint64(seqNo, nonce, encryptImplicitNonce.length);
+
+ int plaintextOffset = offset;
+ int plaintextLength = len;
+ int ciphertextLength = encryptCipher.getOutputSize(plaintextLength);
+
+ byte[] output = new byte[nonce_explicit_length + ciphertextLength];
+ System.arraycopy(nonce, encryptImplicitNonce.length, output, 0, nonce_explicit_length);
+ int outputPos = nonce_explicit_length;
+
+ byte[] additionalData = getAdditionalData(seqNo, type, plaintextLength);
+ AEADParameters parameters = new AEADParameters(null, 8 * macSize, nonce, additionalData);
+
+ try
+ {
+ encryptCipher.init(true, parameters);
+ outputPos += encryptCipher.processBytes(plaintext, plaintextOffset, plaintextLength, output, outputPos);
+ outputPos += encryptCipher.doFinal(output, outputPos);
+ }
+ catch (Exception e)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ if (outputPos != output.length)
+ {
+ // NOTE: Existing AEAD cipher implementations all give exact output lengths
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ return output;
+ }
+
+ public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+ throws IOException
+ {
+ if (getPlaintextLimit(len) < 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ byte[] nonce = new byte[this.decryptImplicitNonce.length + nonce_explicit_length];
+ System.arraycopy(decryptImplicitNonce, 0, nonce, 0, decryptImplicitNonce.length);
+ System.arraycopy(ciphertext, offset, nonce, decryptImplicitNonce.length, nonce_explicit_length);
+
+ int ciphertextOffset = offset + nonce_explicit_length;
+ int ciphertextLength = len - nonce_explicit_length;
+ int plaintextLength = decryptCipher.getOutputSize(ciphertextLength);
+
+ byte[] output = new byte[plaintextLength];
+ int outputPos = 0;
+
+ byte[] additionalData = getAdditionalData(seqNo, type, plaintextLength);
+ AEADParameters parameters = new AEADParameters(null, 8 * macSize, nonce, additionalData);
+
+ try
+ {
+ decryptCipher.init(false, parameters);
+ outputPos += decryptCipher.processBytes(ciphertext, ciphertextOffset, ciphertextLength, output, outputPos);
+ outputPos += decryptCipher.doFinal(output, outputPos);
+ }
+ catch (Exception e)
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+ }
+
+ if (outputPos != output.length)
+ {
+ // NOTE: Existing AEAD cipher implementations all give exact output lengths
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ return output;
+ }
+
+ protected byte[] getAdditionalData(long seqNo, short type, int len)
+ throws IOException
+ {
+ /*
+ * additional_data = seq_num + TLSCompressed.type + TLSCompressed.version +
+ * TLSCompressed.length
+ */
+
+ byte[] additional_data = new byte[13];
+ TlsUtils.writeUint64(seqNo, additional_data, 0);
+ TlsUtils.writeUint8(type, additional_data, 8);
+ TlsUtils.writeVersion(context.getServerVersion(), additional_data, 9);
+ TlsUtils.writeUint16(len, additional_data, 11);
+
+ return additional_data;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsAgreementCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsAgreementCredentials.java
new file mode 100644
index 00000000..8926d6fb
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsAgreementCredentials.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+
+public interface TlsAgreementCredentials
+ extends TlsCredentials
+{
+ byte[] generateAgreement(AsymmetricKeyParameter peerPublicKey)
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsAuthentication.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsAuthentication.java
new file mode 100755
index 00000000..8783dd75
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsAuthentication.java
@@ -0,0 +1,26 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsAuthentication
+{
+ /**
+ * Called by the protocol handler to report the server certificate
+ * Note: this method is responsible for certificate verification and validation
+ *
+ * @param serverCertificate the server certificate received
+ * @throws IOException
+ */
+ void notifyServerCertificate(Certificate serverCertificate)
+ throws IOException;
+
+ /**
+ * Return client credentials in response to server's certificate request
+ *
+ * @param certificateRequest details of the certificate request
+ * @return a TlsCredentials object or null for no client authentication
+ * @throws IOException
+ */
+ TlsCredentials getClientCredentials(CertificateRequest certificateRequest)
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsBlockCipher.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsBlockCipher.java
new file mode 100644
index 00000000..4dd67595
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsBlockCipher.java
@@ -0,0 +1,386 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.BlockCipher;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.crypto.params.ParametersWithIV;
+import org.spongycastle.util.Arrays;
+
+/**
+ * A generic TLS 1.0-1.2 / SSLv3 block cipher. This can be used for AES or 3DES for example.
+ */
+public class TlsBlockCipher
+ implements TlsCipher
+{
+ protected TlsContext context;
+ protected byte[] randomData;
+ protected boolean useExplicitIV;
+ private boolean encryptThenMAC;
+
+ protected BlockCipher encryptCipher;
+ protected BlockCipher decryptCipher;
+
+ protected TlsMac writeMac;
+ protected TlsMac readMac;
+
+ public TlsMac getWriteMac()
+ {
+ return writeMac;
+ }
+
+ public TlsMac getReadMac()
+ {
+ return readMac;
+ }
+
+ public TlsBlockCipher(TlsContext context, BlockCipher clientWriteCipher, BlockCipher serverWriteCipher,
+ Digest clientWriteDigest, Digest serverWriteDigest, int cipherKeySize) throws IOException
+ {
+ this.context = context;
+
+ this.randomData = new byte[256];
+ context.getNonceRandomGenerator().nextBytes(randomData);
+
+ this.useExplicitIV = TlsUtils.isTLSv11(context);
+ this.encryptThenMAC = context.getSecurityParameters().encryptThenMAC;
+
+ int key_block_size = (2 * cipherKeySize) + clientWriteDigest.getDigestSize()
+ + serverWriteDigest.getDigestSize();
+
+ // From TLS 1.1 onwards, block ciphers don't need client_write_IV
+ if (!useExplicitIV)
+ {
+ key_block_size += clientWriteCipher.getBlockSize() + serverWriteCipher.getBlockSize();
+ }
+
+ byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size);
+
+ int offset = 0;
+
+ TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+ clientWriteDigest.getDigestSize());
+ offset += clientWriteDigest.getDigestSize();
+ TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+ serverWriteDigest.getDigestSize());
+ offset += serverWriteDigest.getDigestSize();
+
+ KeyParameter client_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+ offset += cipherKeySize;
+ KeyParameter server_write_key = new KeyParameter(key_block, offset, cipherKeySize);
+ offset += cipherKeySize;
+
+ byte[] client_write_IV, server_write_IV;
+ if (useExplicitIV)
+ {
+ client_write_IV = new byte[clientWriteCipher.getBlockSize()];
+ server_write_IV = new byte[serverWriteCipher.getBlockSize()];
+ }
+ else
+ {
+ client_write_IV = Arrays.copyOfRange(key_block, offset, offset + clientWriteCipher.getBlockSize());
+ offset += clientWriteCipher.getBlockSize();
+ server_write_IV = Arrays.copyOfRange(key_block, offset, offset + serverWriteCipher.getBlockSize());
+ offset += serverWriteCipher.getBlockSize();
+ }
+
+ if (offset != key_block_size)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ CipherParameters encryptParams, decryptParams;
+ if (context.isServer())
+ {
+ this.writeMac = serverWriteMac;
+ this.readMac = clientWriteMac;
+ this.encryptCipher = serverWriteCipher;
+ this.decryptCipher = clientWriteCipher;
+ encryptParams = new ParametersWithIV(server_write_key, server_write_IV);
+ decryptParams = new ParametersWithIV(client_write_key, client_write_IV);
+ }
+ else
+ {
+ this.writeMac = clientWriteMac;
+ this.readMac = serverWriteMac;
+ this.encryptCipher = clientWriteCipher;
+ this.decryptCipher = serverWriteCipher;
+ encryptParams = new ParametersWithIV(client_write_key, client_write_IV);
+ decryptParams = new ParametersWithIV(server_write_key, server_write_IV);
+ }
+
+ this.encryptCipher.init(true, encryptParams);
+ this.decryptCipher.init(false, decryptParams);
+ }
+
+ public int getPlaintextLimit(int ciphertextLimit)
+ {
+ int blockSize = encryptCipher.getBlockSize();
+ int macSize = writeMac.getSize();
+
+ int plaintextLimit = ciphertextLimit;
+
+ // An explicit IV consumes 1 block
+ if (useExplicitIV)
+ {
+ plaintextLimit -= blockSize;
+ }
+
+ // Leave room for the MAC, and require block-alignment
+ if (encryptThenMAC)
+ {
+ plaintextLimit -= macSize;
+ plaintextLimit -= plaintextLimit % blockSize;
+ }
+ else
+ {
+ plaintextLimit -= plaintextLimit % blockSize;
+ plaintextLimit -= macSize;
+ }
+
+ // Minimum 1 byte of padding
+ --plaintextLimit;
+
+ return plaintextLimit;
+ }
+
+ public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+ {
+ int blockSize = encryptCipher.getBlockSize();
+ int macSize = writeMac.getSize();
+
+ ProtocolVersion version = context.getServerVersion();
+
+ int enc_input_length = len;
+ if (!encryptThenMAC)
+ {
+ enc_input_length += macSize;
+ }
+
+ int padding_length = blockSize - 1 - (enc_input_length % blockSize);
+
+ // TODO[DTLS] Consider supporting in DTLS (without exceeding send limit though)
+ if (!version.isDTLS() && !version.isSSL())
+ {
+ // Add a random number of extra blocks worth of padding
+ int maxExtraPadBlocks = (255 - padding_length) / blockSize;
+ int actualExtraPadBlocks = chooseExtraPadBlocks(context.getSecureRandom(), maxExtraPadBlocks);
+ padding_length += actualExtraPadBlocks * blockSize;
+ }
+
+ int totalSize = len + macSize + padding_length + 1;
+ if (useExplicitIV)
+ {
+ totalSize += blockSize;
+ }
+
+ byte[] outBuf = new byte[totalSize];
+ int outOff = 0;
+
+ if (useExplicitIV)
+ {
+ byte[] explicitIV = new byte[blockSize];
+ context.getNonceRandomGenerator().nextBytes(explicitIV);
+
+ encryptCipher.init(true, new ParametersWithIV(null, explicitIV));
+
+ System.arraycopy(explicitIV, 0, outBuf, outOff, blockSize);
+ outOff += blockSize;
+ }
+
+ int blocks_start = outOff;
+
+ System.arraycopy(plaintext, offset, outBuf, outOff, len);
+ outOff += len;
+
+ if (!encryptThenMAC)
+ {
+ byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len);
+ System.arraycopy(mac, 0, outBuf, outOff, mac.length);
+ outOff += mac.length;
+ }
+
+ for (int i = 0; i <= padding_length; i++)
+ {
+ outBuf[outOff++] = (byte)padding_length;
+ }
+
+ for (int i = blocks_start; i < outOff; i += blockSize)
+ {
+ encryptCipher.processBlock(outBuf, i, outBuf, i);
+ }
+
+ if (encryptThenMAC)
+ {
+ byte[] mac = writeMac.calculateMac(seqNo, type, outBuf, 0, outOff);
+ System.arraycopy(mac, 0, outBuf, outOff, mac.length);
+ outOff += mac.length;
+ }
+
+// assert outBuf.length == outOff;
+
+ return outBuf;
+ }
+
+ public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+ throws IOException
+ {
+ int blockSize = decryptCipher.getBlockSize();
+ int macSize = readMac.getSize();
+
+ int minLen = blockSize;
+ if (encryptThenMAC)
+ {
+ minLen += macSize;
+ }
+ else
+ {
+ minLen = Math.max(minLen, macSize + 1);
+ }
+
+ if (useExplicitIV)
+ {
+ minLen += blockSize;
+ }
+
+ if (len < minLen)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ int blocks_length = len;
+ if (encryptThenMAC)
+ {
+ blocks_length -= macSize;
+ }
+
+ if (blocks_length % blockSize != 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decryption_failed);
+ }
+
+ if (encryptThenMAC)
+ {
+ int end = offset + len;
+ byte[] receivedMac = Arrays.copyOfRange(ciphertext, end - macSize, end);
+ byte[] calculatedMac = readMac.calculateMac(seqNo, type, ciphertext, offset, len - macSize);
+
+ boolean badMac = !Arrays.constantTimeAreEqual(calculatedMac, receivedMac);
+
+ if (badMac)
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+ }
+ }
+
+ if (useExplicitIV)
+ {
+ decryptCipher.init(false, new ParametersWithIV(null, ciphertext, offset, blockSize));
+
+ offset += blockSize;
+ blocks_length -= blockSize;
+ }
+
+ for (int i = 0; i < blocks_length; i += blockSize)
+ {
+ decryptCipher.processBlock(ciphertext, offset + i, ciphertext, offset + i);
+ }
+
+ // If there's anything wrong with the padding, this will return zero
+ int totalPad = checkPaddingConstantTime(ciphertext, offset, blocks_length, blockSize, encryptThenMAC ? 0 : macSize);
+
+ int dec_output_length = blocks_length - totalPad;
+
+ if (!encryptThenMAC)
+ {
+ dec_output_length -= macSize;
+ int macInputLen = dec_output_length;
+ int macOff = offset + macInputLen;
+ byte[] receivedMac = Arrays.copyOfRange(ciphertext, macOff, macOff + macSize);
+ byte[] calculatedMac = readMac.calculateMacConstantTime(seqNo, type, ciphertext, offset, macInputLen,
+ blocks_length - macSize, randomData);
+
+ boolean badMac = !Arrays.constantTimeAreEqual(calculatedMac, receivedMac);
+
+ if (badMac || totalPad == 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+ }
+ }
+
+ return Arrays.copyOfRange(ciphertext, offset, offset + dec_output_length);
+ }
+
+ protected 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;
+
+ if ((TlsUtils.isSSL(context) && totalPad > blockSize) || (macSize + totalPad > len))
+ {
+ totalPad = 0;
+ }
+ else
+ {
+ int padPos = end - totalPad;
+ do
+ {
+ padDiff |= (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 = randomData;
+ while (dummyIndex < 256)
+ {
+ padDiff |= (dummyPad[dummyIndex++] ^ lastByte);
+ }
+ // Ensure the above loop is not eliminated
+ dummyPad[0] ^= padDiff;
+ }
+
+ return totalPad;
+ }
+
+ protected int chooseExtraPadBlocks(SecureRandom r, int max)
+ {
+ // return r.nextInt(max + 1);
+
+ int x = r.nextInt();
+ int n = lowestBitSet(x);
+ return Math.min(n, max);
+ }
+
+ protected int lowestBitSet(int x)
+ {
+ if (x == 0)
+ {
+ return 32;
+ }
+
+ int n = 0;
+ while ((x & 1) == 0)
+ {
+ ++n;
+ x >>= 1;
+ }
+ return n;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsCipher.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsCipher.java
new file mode 100644
index 00000000..6a8f6bc0
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsCipher.java
@@ -0,0 +1,14 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsCipher
+{
+ int getPlaintextLimit(int ciphertextLimit);
+
+ byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+ throws IOException;
+
+ byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsCipherFactory.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsCipherFactory.java
new file mode 100644
index 00000000..31b2bacf
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsCipherFactory.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsCipherFactory
+{
+ /**
+ * See enumeration classes EncryptionAlgorithm, MACAlgorithm for appropriate argument values
+ */
+ TlsCipher createCipher(TlsContext context, int encryptionAlgorithm, int macAlgorithm)
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsClient.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsClient.java
new file mode 100644
index 00000000..bce22061
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsClient.java
@@ -0,0 +1,79 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+public interface TlsClient
+ extends 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.
+ *
+ * @return A {@link TlsSession} representing the resumable session to be used for this
+ * connection, or null to use a new session.
+ * @see SessionParameters#getPeerCertificate()
+ */
+ TlsSession getSessionToResume();
+
+ ProtocolVersion getClientHelloRecordLayerVersion();
+
+ ProtocolVersion getClientVersion();
+
+ int[] getCipherSuites();
+
+ short[] getCompressionMethods();
+
+ // Hashtable is (Integer -> byte[])
+ Hashtable getClientExtensions()
+ throws IOException;
+
+ void notifyServerVersion(ProtocolVersion selectedVersion)
+ throws IOException;
+
+ /**
+ * Notifies the client of the session_id sent in the ServerHello.
+ *
+ * @param sessionID
+ * @see TlsContext#getResumableSession()
+ */
+ void notifySessionID(byte[] sessionID);
+
+ void notifySelectedCipherSuite(int selectedCipherSuite);
+
+ void notifySelectedCompressionMethod(short selectedCompressionMethod);
+
+ // Hashtable is (Integer -> byte[])
+ void processServerExtensions(Hashtable serverExtensions)
+ throws IOException;
+
+ // Vector is (SupplementalDataEntry)
+ void processServerSupplementalData(Vector serverSupplementalData)
+ throws IOException;
+
+ TlsKeyExchange getKeyExchange()
+ throws IOException;
+
+ TlsAuthentication getAuthentication()
+ throws IOException;
+
+ // Vector is (SupplementalDataEntry)
+ Vector getClientSupplementalData()
+ throws IOException;
+
+ /**
+ * RFC 5077 3.3. NewSessionTicket Handshake Message
+ * <p>
+ * 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. <i>RFC 5077 4. Recommended Ticket Construction</i>.
+ *
+ * @param newSessionTicket The ticket.
+ * @throws IOException
+ */
+ void notifyNewSessionTicket(NewSessionTicket newSessionTicket)
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsClientContext.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsClientContext.java
new file mode 100644
index 00000000..245245b2
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsClientContext.java
@@ -0,0 +1,6 @@
+package org.spongycastle.crypto.tls;
+
+public interface TlsClientContext
+ extends TlsContext
+{
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsClientContextImpl.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsClientContextImpl.java
new file mode 100644
index 00000000..ca392944
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsClientContextImpl.java
@@ -0,0 +1,18 @@
+package org.spongycastle.crypto.tls;
+
+import java.security.SecureRandom;
+
+class TlsClientContextImpl
+ extends AbstractTlsContext
+ implements TlsClientContext
+{
+ TlsClientContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters)
+ {
+ super(secureRandom, securityParameters);
+ }
+
+ public boolean isServer()
+ {
+ return false;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsClientProtocol.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsClientProtocol.java
new file mode 100644
index 00000000..b50bd7d8
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsClientProtocol.java
@@ -0,0 +1,917 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.spongycastle.crypto.prng.ThreadedSeedGenerator;
+import org.spongycastle.util.Arrays;
+
+public class TlsClientProtocol
+ extends TlsProtocol
+{
+ protected TlsClient tlsClient = null;
+ protected TlsClientContextImpl tlsClientContext = null;
+
+ protected byte[] selectedSessionID = null;
+
+ protected TlsKeyExchange keyExchange = null;
+ protected TlsAuthentication authentication = null;
+
+ protected CertificateStatus certificateStatus = null;
+ protected CertificateRequest certificateRequest = null;
+
+ private static SecureRandom createSecureRandom()
+ {
+ /*
+ * We use our threaded seed generator to generate a good random seed. If the user has a
+ * better random seed, he should use the constructor with a SecureRandom.
+ */
+ ThreadedSeedGenerator tsg = new ThreadedSeedGenerator();
+ SecureRandom random = new SecureRandom();
+
+ /*
+ * Hopefully, 20 bytes in fast mode are good enough.
+ */
+ random.setSeed(tsg.generateSeed(20, true));
+
+ return random;
+ }
+
+ /**
+ * @deprecated use alternate constructor taking an explicit {@link SecureRandom}
+ */
+ public TlsClientProtocol(InputStream input, OutputStream output)
+ {
+ this(input, output, createSecureRandom());
+ }
+
+ public TlsClientProtocol(InputStream input, OutputStream output, SecureRandom secureRandom)
+ {
+ super(input, output, secureRandom);
+ }
+
+ /**
+ * Initiates a TLS handshake in the role of client
+ *
+ * @param tlsClient The {@link TlsClient} to use for the handshake.
+ * @throws IOException If handshake was not successful.
+ */
+ public void connect(TlsClient tlsClient) throws IOException
+ {
+ if (tlsClient == null)
+ {
+ throw new IllegalArgumentException("'tlsClient' cannot be null");
+ }
+ if (this.tlsClient != null)
+ {
+ throw new IllegalStateException("'connect' can only be called once");
+ }
+
+ this.tlsClient = tlsClient;
+
+ this.securityParameters = new SecurityParameters();
+ this.securityParameters.entity = ConnectionEnd.client;
+
+ this.tlsClientContext = new TlsClientContextImpl(secureRandom, securityParameters);
+
+ this.securityParameters.clientRandom = createRandomBlock(tlsClient.shouldUseGMTUnixTime(),
+ tlsClientContext.getNonceRandomGenerator());
+
+ this.tlsClient.init(tlsClientContext);
+ this.recordStream.init(tlsClientContext);
+
+ TlsSession sessionToResume = tlsClient.getSessionToResume();
+ if (sessionToResume != null)
+ {
+ SessionParameters sessionParameters = sessionToResume.exportSessionParameters();
+ if (sessionParameters != null)
+ {
+ this.tlsSession = sessionToResume;
+ this.sessionParameters = sessionParameters;
+ }
+ }
+
+ sendClientHelloMessage();
+ this.connection_state = CS_CLIENT_HELLO;
+
+ completeHandshake();
+ }
+
+ protected void cleanupHandshake()
+ {
+ super.cleanupHandshake();
+
+ this.selectedSessionID = null;
+ this.keyExchange = null;
+ this.authentication = null;
+ this.certificateStatus = null;
+ this.certificateRequest = null;
+ }
+
+ protected AbstractTlsContext getContext()
+ {
+ return tlsClientContext;
+ }
+
+ protected TlsPeer getPeer()
+ {
+ return tlsClient;
+ }
+
+ protected void handleHandshakeMessage(short type, byte[] data)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(data);
+
+ if (this.resumedSession)
+ {
+ if (type != HandshakeType.finished || this.connection_state != CS_SERVER_HELLO)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ processFinishedMessage(buf);
+ this.connection_state = CS_SERVER_FINISHED;
+
+ sendFinishedMessage();
+ this.connection_state = CS_CLIENT_FINISHED;
+ this.connection_state = CS_END;
+
+ return;
+ }
+
+ switch (type)
+ {
+ case HandshakeType.certificate:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_HELLO:
+ {
+ handleSupplementalData(null);
+ // NB: Fall through to next case label
+ }
+ case CS_SERVER_SUPPLEMENTAL_DATA:
+ {
+ // Parse the Certificate message and send to cipher suite
+
+ this.peerCertificate = Certificate.parse(buf);
+
+ assertEmpty(buf);
+
+ // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+ if (this.peerCertificate == null || this.peerCertificate.isEmpty())
+ {
+ this.allowCertificateStatus = false;
+ }
+
+ this.keyExchange.processServerCertificate(this.peerCertificate);
+
+ this.authentication = tlsClient.getAuthentication();
+ this.authentication.notifyServerCertificate(this.peerCertificate);
+
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ this.connection_state = CS_SERVER_CERTIFICATE;
+ break;
+ }
+ case HandshakeType.certificate_status:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_CERTIFICATE:
+ {
+ if (!this.allowCertificateStatus)
+ {
+ /*
+ * RFC 3546 3.6. 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..
+ */
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ this.certificateStatus = CertificateStatus.parse(buf);
+
+ assertEmpty(buf);
+
+ // TODO[RFC 3546] Figure out how to provide this to the client/authentication.
+
+ this.connection_state = CS_CERTIFICATE_STATUS;
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.finished:
+ {
+ switch (this.connection_state)
+ {
+ case CS_CLIENT_FINISHED:
+ {
+ if (this.expectSessionTicket)
+ {
+ /*
+ * RFC 5077 3.3. This message MUST be sent if the server included a
+ * SessionTicket extension in the ServerHello.
+ */
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ // NB: Fall through to next case label
+ }
+ case CS_SERVER_SESSION_TICKET:
+ {
+ processFinishedMessage(buf);
+ this.connection_state = CS_SERVER_FINISHED;
+ this.connection_state = CS_END;
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.server_hello:
+ {
+ switch (this.connection_state)
+ {
+ case CS_CLIENT_HELLO:
+ {
+ receiveServerHelloMessage(buf);
+ this.connection_state = CS_SERVER_HELLO;
+
+ if (this.securityParameters.maxFragmentLength >= 0)
+ {
+ int plainTextLimit = 1 << (8 + this.securityParameters.maxFragmentLength);
+ recordStream.setPlaintextLimit(plainTextLimit);
+ }
+
+ this.securityParameters.prfAlgorithm = getPRFAlgorithm(getContext(),
+ this.securityParameters.getCipherSuite());
+
+ /*
+ * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify
+ * verify_data_length has a verify_data_length equal to 12. This includes all
+ * existing cipher suites.
+ */
+ this.securityParameters.verifyDataLength = 12;
+
+ this.recordStream.notifyHelloComplete();
+
+ if (this.resumedSession)
+ {
+ this.securityParameters.masterSecret = Arrays.clone(this.sessionParameters.getMasterSecret());
+ this.recordStream.setPendingConnectionState(getPeer().getCompression(), getPeer().getCipher());
+
+ sendChangeCipherSpecMessage();
+ }
+ else
+ {
+ invalidateSession();
+
+ if (this.selectedSessionID.length > 0)
+ {
+ this.tlsSession = new TlsSessionImpl(this.selectedSessionID, null);
+ }
+ }
+
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.supplemental_data:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_HELLO:
+ {
+ handleSupplementalData(readSupplementalDataMessage(buf));
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.server_hello_done:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_HELLO:
+ {
+ handleSupplementalData(null);
+ // NB: Fall through to next case label
+ }
+ case CS_SERVER_SUPPLEMENTAL_DATA:
+ {
+ // There was no server certificate message; check it's OK
+ this.keyExchange.skipServerCredentials();
+ this.authentication = null;
+
+ // NB: Fall through to next case label
+ }
+ case CS_SERVER_CERTIFICATE:
+ case CS_CERTIFICATE_STATUS:
+ {
+ // There was no server key exchange message; check it's OK
+ this.keyExchange.skipServerKeyExchange();
+
+ // NB: Fall through to next case label
+ }
+ case CS_SERVER_KEY_EXCHANGE:
+ case CS_CERTIFICATE_REQUEST:
+ {
+ assertEmpty(buf);
+
+ this.connection_state = CS_SERVER_HELLO_DONE;
+
+ this.recordStream.getHandshakeHash().sealHashAlgorithms();
+
+ Vector clientSupplementalData = tlsClient.getClientSupplementalData();
+ if (clientSupplementalData != null)
+ {
+ sendSupplementalDataMessage(clientSupplementalData);
+ }
+ this.connection_state = CS_CLIENT_SUPPLEMENTAL_DATA;
+
+ TlsCredentials clientCreds = null;
+ if (certificateRequest == null)
+ {
+ this.keyExchange.skipClientCredentials();
+ }
+ else
+ {
+ clientCreds = this.authentication.getClientCredentials(certificateRequest);
+
+ if (clientCreds == null)
+ {
+ this.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.
+ */
+ sendCertificateMessage(Certificate.EMPTY_CHAIN);
+ }
+ else
+ {
+ this.keyExchange.processClientCredentials(clientCreds);
+
+ sendCertificateMessage(clientCreds.getCertificate());
+ }
+ }
+
+ this.connection_state = CS_CLIENT_CERTIFICATE;
+
+ /*
+ * Send the client key exchange message, depending on the key exchange we are using
+ * in our CipherSuite.
+ */
+ sendClientKeyExchangeMessage();
+ this.connection_state = CS_CLIENT_KEY_EXCHANGE;
+
+ establishMasterSecret(getContext(), keyExchange);
+ recordStream.setPendingConnectionState(getPeer().getCompression(), getPeer().getCipher());
+
+ TlsHandshakeHash prepareFinishHash = recordStream.prepareToFinish();
+
+ if (clientCreds != null && clientCreds instanceof TlsSignerCredentials)
+ {
+ TlsSignerCredentials signerCredentials = (TlsSignerCredentials)clientCreds;
+
+ /*
+ * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
+ */
+ SignatureAndHashAlgorithm signatureAndHashAlgorithm;
+ byte[] hash;
+
+ if (TlsUtils.isTLSv12(getContext()))
+ {
+ signatureAndHashAlgorithm = signerCredentials.getSignatureAndHashAlgorithm();
+ if (signatureAndHashAlgorithm == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ hash = prepareFinishHash.getFinalHash(signatureAndHashAlgorithm.getHash());
+ }
+ else
+ {
+ signatureAndHashAlgorithm = null;
+ hash = getCurrentPRFHash(getContext(), prepareFinishHash, null);
+ }
+
+ byte[] signature = signerCredentials.generateCertificateSignature(hash);
+ DigitallySigned certificateVerify = new DigitallySigned(signatureAndHashAlgorithm, signature);
+ sendCertificateVerifyMessage(certificateVerify);
+
+ this.connection_state = CS_CERTIFICATE_VERIFY;
+ }
+
+ sendChangeCipherSpecMessage();
+ sendFinishedMessage();
+ this.connection_state = CS_CLIENT_FINISHED;
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ break;
+ }
+ case HandshakeType.server_key_exchange:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_HELLO:
+ {
+ handleSupplementalData(null);
+ // NB: Fall through to next case label
+ }
+ case CS_SERVER_SUPPLEMENTAL_DATA:
+ {
+ // There was no server certificate message; check it's OK
+ this.keyExchange.skipServerCredentials();
+ this.authentication = null;
+
+ // NB: Fall through to next case label
+ }
+ case CS_SERVER_CERTIFICATE:
+ case CS_CERTIFICATE_STATUS:
+ {
+ this.keyExchange.processServerKeyExchange(buf);
+
+ assertEmpty(buf);
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ this.connection_state = CS_SERVER_KEY_EXCHANGE;
+ break;
+ }
+ case HandshakeType.certificate_request:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_CERTIFICATE:
+ case CS_CERTIFICATE_STATUS:
+ {
+ // There was no server key exchange message; check it's OK
+ this.keyExchange.skipServerKeyExchange();
+
+ // NB: Fall through to next case label
+ }
+ case CS_SERVER_KEY_EXCHANGE:
+ {
+ if (this.authentication == null)
+ {
+ /*
+ * 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);
+ }
+
+ this.certificateRequest = CertificateRequest.parse(getContext(), buf);
+
+ assertEmpty(buf);
+
+ this.keyExchange.validateCertificateRequest(this.certificateRequest);
+
+ /*
+ * TODO Give the client a chance to immediately select the CertificateVerify hash
+ * algorithm here to avoid tracking the other hash algorithms unnecessarily?
+ */
+ TlsUtils.trackHashAlgorithms(this.recordStream.getHandshakeHash(),
+ this.certificateRequest.getSupportedSignatureAlgorithms());
+
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ this.connection_state = CS_CERTIFICATE_REQUEST;
+ break;
+ }
+ case HandshakeType.session_ticket:
+ {
+ switch (this.connection_state)
+ {
+ case CS_CLIENT_FINISHED:
+ {
+ if (!this.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();
+
+ receiveNewSessionTicketMessage(buf);
+ this.connection_state = CS_SERVER_SESSION_TICKET;
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+ 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 (this.connection_state == CS_END)
+ {
+ /*
+ * RFC 5746 4.5 SSLv3 clients that refuse renegotiation SHOULD use a fatal
+ * handshake_failure alert.
+ */
+ if (TlsUtils.isSSL(getContext()))
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+
+ String message = "Renegotiation not supported";
+ raiseWarning(AlertDescription.no_renegotiation, message);
+ }
+ break;
+ }
+ case HandshakeType.client_hello:
+ case HandshakeType.client_key_exchange:
+ case HandshakeType.certificate_verify:
+ case HandshakeType.hello_verify_request:
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ protected void handleSupplementalData(Vector serverSupplementalData)
+ throws IOException
+ {
+ this.tlsClient.processServerSupplementalData(serverSupplementalData);
+ this.connection_state = CS_SERVER_SUPPLEMENTAL_DATA;
+
+ this.keyExchange = tlsClient.getKeyExchange();
+ this.keyExchange.init(getContext());
+ }
+
+ protected void receiveNewSessionTicketMessage(ByteArrayInputStream buf)
+ throws IOException
+ {
+ NewSessionTicket newSessionTicket = NewSessionTicket.parse(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ tlsClient.notifyNewSessionTicket(newSessionTicket);
+ }
+
+ protected void receiveServerHelloMessage(ByteArrayInputStream buf)
+ throws IOException
+ {
+ ProtocolVersion server_version = TlsUtils.readVersion(buf);
+ if (server_version.isDTLS())
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ // Check that this matches what the server is sending in the record layer
+ if (!server_version.equals(this.recordStream.getReadVersion()))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ ProtocolVersion client_version = getContext().getClientVersion();
+ if (!server_version.isEqualOrEarlierVersionOf(client_version))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ this.recordStream.setWriteVersion(server_version);
+ getContext().setServerVersion(server_version);
+ this.tlsClient.notifyServerVersion(server_version);
+
+ /*
+ * Read the server random
+ */
+ this.securityParameters.serverRandom = TlsUtils.readFully(32, buf);
+
+ this.selectedSessionID = TlsUtils.readOpaque8(buf);
+ if (this.selectedSessionID.length > 32)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ this.tlsClient.notifySessionID(this.selectedSessionID);
+
+ this.resumedSession = this.selectedSessionID.length > 0 && this.tlsSession != null
+ && Arrays.areEqual(this.selectedSessionID, this.tlsSession.getSessionID());
+
+ /*
+ * 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 selectedCipherSuite = TlsUtils.readUint16(buf);
+ if (!Arrays.contains(this.offeredCipherSuites, selectedCipherSuite)
+ || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+ || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+ || !TlsUtils.isValidCipherSuiteForVersion(selectedCipherSuite, server_version))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ this.tlsClient.notifySelectedCipherSuite(selectedCipherSuite);
+
+ /*
+ * Find out which CompressionMethod the server has chosen and check that it was one of the
+ * offered ones.
+ */
+ short selectedCompressionMethod = TlsUtils.readUint8(buf);
+ if (!Arrays.contains(this.offeredCompressionMethods, selectedCompressionMethod))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ this.tlsClient.notifySelectedCompressionMethod(selectedCompressionMethod);
+
+ /*
+ * RFC3546 2.2 The extended server hello message format MAY be sent in place of the server
+ * hello message when the client has requested extended functionality via the extended
+ * client hello message specified in Section 2.1. ... Note that the extended server hello
+ * message is only sent in response to an extended client hello message. This prevents the
+ * possibility that the extended server hello message could "break" existing TLS 1.0
+ * clients.
+ */
+ this.serverExtensions = readExtensions(buf);
+
+ /*
+ * 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 (this.serverExtensions != null)
+ {
+ Enumeration e = this.serverExtensions.keys();
+ while (e.hasMoreElements())
+ {
+ Integer extType = (Integer)e.nextElement();
+
+ /*
+ * 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.equals(EXT_RenegotiationInfo))
+ {
+ continue;
+ }
+
+ /*
+ * RFC 3546 2.3. If [...] the older session is resumed, then the server MUST ignore
+ * extensions appearing in the client hello, and send a server hello containing no
+ * extensions[.]
+ */
+ if (this.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 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 == TlsUtils.getExtensionData(this.clientExtensions, extType))
+ {
+ throw new TlsFatalAlert(AlertDescription.unsupported_extension);
+ }
+ }
+ }
+
+ /*
+ * 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 = TlsUtils.getExtensionData(this.serverExtensions, EXT_RenegotiationInfo);
+ 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).
+ */
+ this.secure_renegotiation = true;
+
+ if (!Arrays.constantTimeAreEqual(renegExtData, createRenegotiationInfo(TlsUtils.EMPTY_BYTES)))
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ }
+ }
+
+ // TODO[compat-gnutls] GnuTLS test server fails to send renegotiation_info extension when resuming
+ this.tlsClient.notifySecureRenegotiation(this.secure_renegotiation);
+
+ Hashtable sessionClientExtensions = clientExtensions, sessionServerExtensions = serverExtensions;
+ if (this.resumedSession)
+ {
+ if (selectedCipherSuite != this.sessionParameters.getCipherSuite()
+ || selectedCompressionMethod != this.sessionParameters.getCompressionAlgorithm())
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ sessionClientExtensions = null;
+ sessionServerExtensions = this.sessionParameters.readServerExtensions();
+ }
+
+ this.securityParameters.cipherSuite = selectedCipherSuite;
+ this.securityParameters.compressionAlgorithm = selectedCompressionMethod;
+
+ if (sessionServerExtensions != null)
+ {
+ /*
+ * draft-ietf-tls-encrypt-then-mac-03 3. If a server receives an encrypt-then-MAC
+ * request extension from a client and then selects a stream or AEAD cipher suite, it
+ * MUST NOT send an encrypt-then-MAC response extension back to the client.
+ */
+ boolean serverSentEncryptThenMAC = TlsExtensionsUtils.hasEncryptThenMACExtension(sessionServerExtensions);
+ if (serverSentEncryptThenMAC && !TlsUtils.isBlockCipherSuite(selectedCipherSuite))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ this.securityParameters.encryptThenMAC = serverSentEncryptThenMAC;
+
+ this.securityParameters.maxFragmentLength = processMaxFragmentLengthExtension(sessionClientExtensions,
+ sessionServerExtensions, AlertDescription.illegal_parameter);
+
+ this.securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(sessionServerExtensions);
+
+ /*
+ * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in
+ * a session resumption handshake.
+ */
+ this.allowCertificateStatus = !this.resumedSession
+ && TlsUtils.hasExpectedEmptyExtensionData(sessionServerExtensions,
+ TlsExtensionsUtils.EXT_status_request, AlertDescription.illegal_parameter);
+
+ this.expectSessionTicket = !this.resumedSession
+ && TlsUtils.hasExpectedEmptyExtensionData(sessionServerExtensions, TlsProtocol.EXT_SessionTicket,
+ AlertDescription.illegal_parameter);
+ }
+
+ if (sessionClientExtensions != null)
+ {
+ this.tlsClient.processServerExtensions(sessionServerExtensions);
+ }
+ }
+
+ protected void sendCertificateVerifyMessage(DigitallySigned certificateVerify)
+ throws IOException
+ {
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_verify);
+
+ certificateVerify.encode(message);
+
+ message.writeToRecordStream();
+ }
+
+ protected void sendClientHelloMessage()
+ throws IOException
+ {
+ this.recordStream.setWriteVersion(this.tlsClient.getClientHelloRecordLayerVersion());
+
+ ProtocolVersion client_version = this.tlsClient.getClientVersion();
+ if (client_version.isDTLS())
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ getContext().setClientVersion(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[] session_id = TlsUtils.EMPTY_BYTES;
+ if (this.tlsSession != null)
+ {
+ session_id = this.tlsSession.getSessionID();
+ if (session_id == null || session_id.length > 32)
+ {
+ session_id = TlsUtils.EMPTY_BYTES;
+ }
+ }
+
+ this.offeredCipherSuites = this.tlsClient.getCipherSuites();
+
+ this.offeredCompressionMethods = this.tlsClient.getCompressionMethods();
+
+ if (session_id.length > 0 && this.sessionParameters != null)
+ {
+ if (!Arrays.contains(this.offeredCipherSuites, sessionParameters.getCipherSuite())
+ || !Arrays.contains(this.offeredCompressionMethods, sessionParameters.getCompressionAlgorithm()))
+ {
+ session_id = TlsUtils.EMPTY_BYTES;
+ }
+ }
+
+ this.clientExtensions = this.tlsClient.getClientExtensions();
+
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.client_hello);
+
+ TlsUtils.writeVersion(client_version, message);
+
+ message.write(this.securityParameters.getClientRandom());
+
+ TlsUtils.writeOpaque8(session_id, message);
+
+ // 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.
+ */
+ byte[] renegExtData = TlsUtils.getExtensionData(clientExtensions, EXT_RenegotiationInfo);
+ boolean noRenegExt = (null == renegExtData);
+
+ boolean noSCSV = !Arrays.contains(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+
+ if (noRenegExt && noSCSV)
+ {
+ // TODO Consider whether to default to a client extension instead
+// this.clientExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(this.clientExtensions);
+// this.clientExtensions.put(EXT_RenegotiationInfo, createRenegotiationInfo(TlsUtils.EMPTY_BYTES));
+ this.offeredCipherSuites = Arrays.append(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+ }
+
+ TlsUtils.writeUint16ArrayWithUint16Length(offeredCipherSuites, message);
+ }
+
+ TlsUtils.writeUint8ArrayWithUint8Length(offeredCompressionMethods, message);
+
+ if (clientExtensions != null)
+ {
+ writeExtensions(message, clientExtensions);
+ }
+
+ message.writeToRecordStream();
+ }
+
+ protected void sendClientKeyExchangeMessage()
+ throws IOException
+ {
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.client_key_exchange);
+
+ this.keyExchange.generateClientKeyExchange(message);
+
+ message.writeToRecordStream();
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsCompression.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsCompression.java
new file mode 100644
index 00000000..e6d475ce
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsCompression.java
@@ -0,0 +1,10 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.OutputStream;
+
+public interface TlsCompression
+{
+ OutputStream compress(OutputStream output);
+
+ OutputStream decompress(OutputStream output);
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsContext.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsContext.java
new file mode 100644
index 00000000..a47d1d6e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsContext.java
@@ -0,0 +1,45 @@
+package org.spongycastle.crypto.tls;
+
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.prng.RandomGenerator;
+
+public interface TlsContext
+{
+ RandomGenerator getNonceRandomGenerator();
+
+ SecureRandom getSecureRandom();
+
+ SecurityParameters getSecurityParameters();
+
+ boolean isServer();
+
+ ProtocolVersion getClientVersion();
+
+ ProtocolVersion getServerVersion();
+
+ /**
+ * Used to get the resumable session, if any, used by this connection. Only available after the
+ * handshake has successfully completed.
+ *
+ * @return A {@link TlsSession} representing the resumable session used by this connection, or
+ * null if no resumable session available.
+ * @see TlsPeer#notifyHandshakeComplete()
+ */
+ TlsSession getResumableSession();
+
+ Object getUserObject();
+
+ void setUserObject(Object userObject);
+
+ /**
+ * Export keying material according to RFC 5705: "Keying Material Exporters for TLS".
+ *
+ * @param asciiLabel indicates which application will use the exported keys.
+ * @param context_value allows the application using the exporter to mix its own data with the TLS PRF for
+ * the exporter output.
+ * @param length the number of bytes to generate
+ * @return a pseudorandom bit string of 'length' bytes generated from the master_secret.
+ */
+ byte[] exportKeyingMaterial(String asciiLabel, byte[] context_value, int length);
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsCredentials.java
new file mode 100644
index 00000000..26923394
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsCredentials.java
@@ -0,0 +1,6 @@
+package org.spongycastle.crypto.tls;
+
+public interface TlsCredentials
+{
+ Certificate getCertificate();
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsDHEKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsDHEKeyExchange.java
new file mode 100644
index 00000000..fa4bd98f
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsDHEKeyExchange.java
@@ -0,0 +1,115 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.params.DHParameters;
+import org.spongycastle.util.io.TeeInputStream;
+
+public class TlsDHEKeyExchange
+ extends TlsDHKeyExchange
+{
+ protected TlsSignerCredentials serverCredentials = null;
+
+ public TlsDHEKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, DHParameters dhParameters)
+ {
+ super(keyExchange, supportedSignatureAlgorithms, dhParameters);
+ }
+
+ public void processServerCredentials(TlsCredentials serverCredentials)
+ throws IOException
+ {
+ if (!(serverCredentials instanceof TlsSignerCredentials))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ processServerCertificate(serverCredentials.getCertificate());
+
+ this.serverCredentials = (TlsSignerCredentials)serverCredentials;
+ }
+
+ public byte[] generateServerKeyExchange()
+ throws IOException
+ {
+ if (this.dhParameters == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ DigestInputBuffer buf = new DigestInputBuffer();
+
+ this.dhAgreeServerPrivateKey = TlsDHUtils.generateEphemeralServerKeyExchange(context.getSecureRandom(),
+ this.dhParameters, buf);
+
+ /*
+ * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
+ */
+ SignatureAndHashAlgorithm signatureAndHashAlgorithm;
+ Digest d;
+
+ if (TlsUtils.isTLSv12(context))
+ {
+ signatureAndHashAlgorithm = serverCredentials.getSignatureAndHashAlgorithm();
+ if (signatureAndHashAlgorithm == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ d = TlsUtils.createHash(signatureAndHashAlgorithm.getHash());
+ }
+ else
+ {
+ signatureAndHashAlgorithm = null;
+ d = new CombinedHash();
+ }
+
+ SecurityParameters securityParameters = context.getSecurityParameters();
+ d.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+ d.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+ buf.updateDigest(d);
+
+ byte[] hash = new byte[d.getDigestSize()];
+ d.doFinal(hash, 0);
+
+ byte[] signature = serverCredentials.generateCertificateSignature(hash);
+
+ DigitallySigned signed_params = new DigitallySigned(signatureAndHashAlgorithm, signature);
+ signed_params.encode(buf);
+
+ return buf.toByteArray();
+ }
+
+ public void processServerKeyExchange(InputStream input)
+ throws IOException
+ {
+ SecurityParameters securityParameters = context.getSecurityParameters();
+
+ SignerInputBuffer buf = new SignerInputBuffer();
+ InputStream teeIn = new TeeInputStream(input, buf);
+
+ ServerDHParams params = ServerDHParams.parse(teeIn);
+
+ DigitallySigned signed_params = DigitallySigned.parse(context, input);
+
+ Signer signer = initVerifyer(tlsSigner, signed_params.getAlgorithm(), securityParameters);
+ buf.updateSigner(signer);
+ if (!signer.verifySignature(signed_params.getSignature()))
+ {
+ throw new TlsFatalAlert(AlertDescription.decrypt_error);
+ }
+
+ this.dhAgreeServerPublicKey = TlsDHUtils.validateDHPublicKey(params.getPublicKey());
+ }
+
+ protected Signer initVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, SecurityParameters securityParameters)
+ {
+ Signer signer = tlsSigner.createVerifyer(algorithm, this.serverPublicKey);
+ signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+ signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+ return signer;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsDHKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsDHKeyExchange.java
new file mode 100644
index 00000000..b4948d9d
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsDHKeyExchange.java
@@ -0,0 +1,208 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.Vector;
+
+import org.spongycastle.asn1.x509.KeyUsage;
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.DHParameters;
+import org.spongycastle.crypto.params.DHPrivateKeyParameters;
+import org.spongycastle.crypto.params.DHPublicKeyParameters;
+import org.spongycastle.crypto.util.PublicKeyFactory;
+
+/**
+ * TLS 1.0/1.1 DH key exchange.
+ */
+public class TlsDHKeyExchange
+ extends AbstractTlsKeyExchange
+{
+ protected static final BigInteger ONE = BigInteger.valueOf(1);
+ protected static final BigInteger TWO = BigInteger.valueOf(2);
+
+ protected TlsSigner tlsSigner;
+ protected DHParameters dhParameters;
+
+ protected AsymmetricKeyParameter serverPublicKey;
+ protected DHPublicKeyParameters dhAgreeServerPublicKey;
+ protected TlsAgreementCredentials agreementCredentials;
+ protected DHPrivateKeyParameters dhAgreeClientPrivateKey;
+
+ protected DHPrivateKeyParameters dhAgreeServerPrivateKey;
+ protected DHPublicKeyParameters dhAgreeClientPublicKey;
+
+ public TlsDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, DHParameters dhParameters)
+ {
+ super(keyExchange, supportedSignatureAlgorithms);
+
+ switch (keyExchange)
+ {
+ case KeyExchangeAlgorithm.DH_RSA:
+ case KeyExchangeAlgorithm.DH_DSS:
+ this.tlsSigner = null;
+ break;
+ case KeyExchangeAlgorithm.DHE_RSA:
+ this.tlsSigner = new TlsRSASigner();
+ break;
+ case KeyExchangeAlgorithm.DHE_DSS:
+ this.tlsSigner = new TlsDSSSigner();
+ break;
+ default:
+ throw new IllegalArgumentException("unsupported key exchange algorithm");
+ }
+
+ this.dhParameters = dhParameters;
+ }
+
+ public void init(TlsContext context)
+ {
+ super.init(context);
+
+ if (this.tlsSigner != null)
+ {
+ this.tlsSigner.init(context);
+ }
+ }
+
+ public void skipServerCredentials()
+ throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ public void processServerCertificate(Certificate serverCertificate)
+ throws IOException
+ {
+ if (serverCertificate.isEmpty())
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_certificate);
+ }
+
+ org.spongycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+ SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+ try
+ {
+ this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+ }
+ catch (RuntimeException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+ }
+
+ if (tlsSigner == null)
+ {
+ try
+ {
+ this.dhAgreeServerPublicKey = TlsDHUtils.validateDHPublicKey((DHPublicKeyParameters)this.serverPublicKey);
+ }
+ catch (ClassCastException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+ }
+
+ TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyAgreement);
+ }
+ else
+ {
+ if (!tlsSigner.isValidPublicKey(this.serverPublicKey))
+ {
+ throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+ }
+
+ TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+ }
+
+ super.processServerCertificate(serverCertificate);
+ }
+
+ public boolean requiresServerKeyExchange()
+ {
+ switch (keyExchange)
+ {
+ case KeyExchangeAlgorithm.DHE_DSS:
+ case KeyExchangeAlgorithm.DHE_RSA:
+ case KeyExchangeAlgorithm.DH_anon:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public void validateCertificateRequest(CertificateRequest certificateRequest)
+ throws IOException
+ {
+ short[] types = certificateRequest.getCertificateTypes();
+ for (int i = 0; i < types.length; ++i)
+ {
+ switch (types[i])
+ {
+ case ClientCertificateType.rsa_sign:
+ case ClientCertificateType.dss_sign:
+ case ClientCertificateType.rsa_fixed_dh:
+ case ClientCertificateType.dss_fixed_dh:
+ case ClientCertificateType.ecdsa_sign:
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+ }
+
+ public void processClientCredentials(TlsCredentials clientCredentials)
+ throws IOException
+ {
+ if (clientCredentials instanceof TlsAgreementCredentials)
+ {
+ // TODO Validate client cert has matching parameters (see 'areCompatibleParameters')?
+
+ this.agreementCredentials = (TlsAgreementCredentials)clientCredentials;
+ }
+ else if (clientCredentials instanceof TlsSignerCredentials)
+ {
+ // OK
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public void generateClientKeyExchange(OutputStream output)
+ throws IOException
+ {
+ /*
+ * RFC 2246 7.4.7.2 If the client certificate already contains a suitable Diffie-Hellman
+ * key, then Yc is implicit and does not need to be sent again. In this case, the Client Key
+ * Exchange message will be sent, but will be empty.
+ */
+ if (agreementCredentials == null)
+ {
+ this.dhAgreeClientPrivateKey = TlsDHUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(),
+ dhAgreeServerPublicKey.getParameters(), output);
+ }
+ }
+
+ public byte[] generatePremasterSecret()
+ throws IOException
+ {
+ if (agreementCredentials != null)
+ {
+ return agreementCredentials.generateAgreement(dhAgreeServerPublicKey);
+ }
+
+ if (dhAgreeServerPrivateKey != null)
+ {
+ return TlsDHUtils.calculateDHBasicAgreement(dhAgreeClientPublicKey, dhAgreeServerPrivateKey);
+ }
+
+ if (dhAgreeClientPrivateKey != null)
+ {
+ return TlsDHUtils.calculateDHBasicAgreement(dhAgreeServerPublicKey, dhAgreeClientPrivateKey);
+ }
+
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsDHUtils.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsDHUtils.java
new file mode 100644
index 00000000..9a7a2218
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsDHUtils.java
@@ -0,0 +1,105 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+
+import org.spongycastle.crypto.AsymmetricCipherKeyPair;
+import org.spongycastle.crypto.agreement.DHBasicAgreement;
+import org.spongycastle.crypto.generators.DHBasicKeyPairGenerator;
+import org.spongycastle.crypto.params.DHKeyGenerationParameters;
+import org.spongycastle.crypto.params.DHParameters;
+import org.spongycastle.crypto.params.DHPrivateKeyParameters;
+import org.spongycastle.crypto.params.DHPublicKeyParameters;
+import org.spongycastle.util.BigIntegers;
+
+public class TlsDHUtils
+{
+ static final BigInteger ONE = BigInteger.valueOf(1);
+ static final BigInteger TWO = BigInteger.valueOf(2);
+
+ public static boolean areCompatibleParameters(DHParameters a, DHParameters b)
+ {
+ return a.getP().equals(b.getP()) && a.getG().equals(b.getG());
+ }
+
+ public static byte[] calculateDHBasicAgreement(DHPublicKeyParameters publicKey, DHPrivateKeyParameters privateKey)
+ {
+ DHBasicAgreement basicAgreement = new DHBasicAgreement();
+ basicAgreement.init(privateKey);
+ BigInteger agreementValue = basicAgreement.calculateAgreement(publicKey);
+
+ /*
+ * RFC 5246 8.1.2. Leading bytes of Z that contain all zero bits are stripped before it is
+ * used as the pre_master_secret.
+ */
+ return BigIntegers.asUnsignedByteArray(agreementValue);
+ }
+
+ public static AsymmetricCipherKeyPair generateDHKeyPair(SecureRandom random, DHParameters dhParams)
+ {
+ DHBasicKeyPairGenerator dhGen = new DHBasicKeyPairGenerator();
+ dhGen.init(new DHKeyGenerationParameters(random, dhParams));
+ return dhGen.generateKeyPair();
+ }
+
+ public static DHPrivateKeyParameters generateEphemeralClientKeyExchange(SecureRandom random, DHParameters dhParams,
+ OutputStream output) throws IOException
+ {
+ AsymmetricCipherKeyPair kp = generateDHKeyPair(random, dhParams);
+
+ DHPublicKeyParameters dh_public = (DHPublicKeyParameters) kp.getPublic();
+ writeDHParameter(dh_public.getY(), output);
+
+ return (DHPrivateKeyParameters) kp.getPrivate();
+ }
+
+ public static DHPrivateKeyParameters generateEphemeralServerKeyExchange(SecureRandom random, DHParameters dhParams,
+ OutputStream output) throws IOException
+ {
+ AsymmetricCipherKeyPair kp = TlsDHUtils.generateDHKeyPair(random, dhParams);
+
+ DHPublicKeyParameters dhPublicKey = (DHPublicKeyParameters)kp.getPublic();
+ ServerDHParams params = new ServerDHParams(dhPublicKey);
+ params.encode(output);
+
+ return (DHPrivateKeyParameters)kp.getPrivate();
+ }
+
+ public static DHPublicKeyParameters validateDHPublicKey(DHPublicKeyParameters key) throws IOException
+ {
+ BigInteger Y = key.getY();
+ DHParameters params = key.getParameters();
+ BigInteger p = params.getP();
+ BigInteger g = params.getG();
+
+ if (!p.isProbablePrime(2))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ if (g.compareTo(TWO) < 0 || g.compareTo(p.subtract(TWO)) > 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ if (Y.compareTo(TWO) < 0 || Y.compareTo(p.subtract(ONE)) > 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ // TODO See RFC 2631 for more discussion of Diffie-Hellman validation
+
+ return key;
+ }
+
+ public static BigInteger readDHParameter(InputStream input) throws IOException
+ {
+ return new BigInteger(1, TlsUtils.readOpaque16(input));
+ }
+
+ public static void writeDHParameter(BigInteger x, OutputStream output) throws IOException
+ {
+ TlsUtils.writeOpaque16(BigIntegers.asUnsignedByteArray(x), output);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsDSASigner.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsDSASigner.java
new file mode 100644
index 00000000..ef4a8e47
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsDSASigner.java
@@ -0,0 +1,92 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.digests.NullDigest;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.crypto.signers.DSADigestSigner;
+
+public abstract class TlsDSASigner
+ extends AbstractTlsSigner
+{
+ public byte[] generateRawSignature(SignatureAndHashAlgorithm algorithm,
+ AsymmetricKeyParameter privateKey, byte[] hash)
+ throws CryptoException
+ {
+ Signer signer = makeSigner(algorithm, true, true,
+ new ParametersWithRandom(privateKey, this.context.getSecureRandom()));
+ if (algorithm == null)
+ {
+ // Note: Only use the SHA1 part of the (MD5/SHA1) hash
+ signer.update(hash, 16, 20);
+ }
+ else
+ {
+ signer.update(hash, 0, hash.length);
+ }
+ return signer.generateSignature();
+ }
+
+ public boolean verifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes,
+ AsymmetricKeyParameter publicKey, byte[] hash)
+ throws CryptoException
+ {
+ Signer signer = makeSigner(algorithm, true, false, publicKey);
+ if (algorithm == null)
+ {
+ // Note: Only use the SHA1 part of the (MD5/SHA1) hash
+ signer.update(hash, 16, 20);
+ }
+ else
+ {
+ signer.update(hash, 0, hash.length);
+ }
+ return signer.verifySignature(sigBytes);
+ }
+
+ public Signer createSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey)
+ {
+ return makeSigner(algorithm, false, true, privateKey);
+ }
+
+ public Signer createVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey)
+ {
+ return makeSigner(algorithm, false, false, publicKey);
+ }
+
+ protected CipherParameters makeInitParameters(boolean forSigning, CipherParameters cp)
+ {
+ return cp;
+ }
+
+ protected Signer makeSigner(SignatureAndHashAlgorithm algorithm, boolean raw, boolean forSigning,
+ CipherParameters cp)
+ {
+ if ((algorithm != null) != TlsUtils.isTLSv12(context))
+ {
+ throw new IllegalStateException();
+ }
+
+ // TODO For TLS 1.2+, lift the SHA-1 restriction here
+ if (algorithm != null
+ && (algorithm.getHash() != HashAlgorithm.sha1 || algorithm.getSignature() != getSignatureAlgorithm()))
+ {
+ throw new IllegalStateException();
+ }
+
+ short hashAlgorithm = algorithm == null ? HashAlgorithm.sha1 : algorithm.getHash();
+ Digest d = raw ? new NullDigest() : TlsUtils.createHash(hashAlgorithm);
+
+ Signer s = new DSADigestSigner(createDSAImpl(hashAlgorithm), d);
+ s.init(forSigning, makeInitParameters(forSigning, cp));
+ return s;
+ }
+
+ protected abstract short getSignatureAlgorithm();
+
+ protected abstract DSA createDSAImpl(short hashAlgorithm);
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsDSSSigner.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsDSSSigner.java
new file mode 100644
index 00000000..bc26c036
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsDSSSigner.java
@@ -0,0 +1,26 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.DSAPublicKeyParameters;
+import org.spongycastle.crypto.signers.DSASigner;
+import org.spongycastle.crypto.signers.HMacDSAKCalculator;
+
+public class TlsDSSSigner
+ extends TlsDSASigner
+{
+ public boolean isValidPublicKey(AsymmetricKeyParameter publicKey)
+ {
+ return publicKey instanceof DSAPublicKeyParameters;
+ }
+
+ protected DSA createDSAImpl(short hashAlgorithm)
+ {
+ return new DSASigner(new HMacDSAKCalculator(TlsUtils.createHash(hashAlgorithm)));
+ }
+
+ protected short getSignatureAlgorithm()
+ {
+ return SignatureAlgorithm.dsa;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsECCUtils.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsECCUtils.java
new file mode 100644
index 00000000..5f9adffb
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsECCUtils.java
@@ -0,0 +1,690 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.security.SecureRandom;
+import java.util.Hashtable;
+
+import org.spongycastle.asn1.x9.ECNamedCurveTable;
+import org.spongycastle.asn1.x9.X9ECParameters;
+import org.spongycastle.crypto.AsymmetricCipherKeyPair;
+import org.spongycastle.crypto.agreement.ECDHBasicAgreement;
+import org.spongycastle.crypto.ec.CustomNamedCurves;
+import org.spongycastle.crypto.generators.ECKeyPairGenerator;
+import org.spongycastle.crypto.params.ECDomainParameters;
+import org.spongycastle.crypto.params.ECKeyGenerationParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.math.ec.ECAlgorithms;
+import org.spongycastle.math.ec.ECCurve;
+import org.spongycastle.math.ec.ECFieldElement;
+import org.spongycastle.math.ec.ECPoint;
+import org.spongycastle.math.field.PolynomialExtensionField;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.BigIntegers;
+import org.spongycastle.util.Integers;
+
+public class TlsECCUtils
+{
+ public static final Integer EXT_elliptic_curves = Integers.valueOf(ExtensionType.elliptic_curves);
+ public static final Integer EXT_ec_point_formats = Integers.valueOf(ExtensionType.ec_point_formats);
+
+ private static final 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"};
+
+ public static void addSupportedEllipticCurvesExtension(Hashtable extensions, int[] namedCurves) throws IOException
+ {
+ extensions.put(EXT_elliptic_curves, createSupportedEllipticCurvesExtension(namedCurves));
+ }
+
+ public static void addSupportedPointFormatsExtension(Hashtable extensions, short[] ecPointFormats)
+ throws IOException
+ {
+ extensions.put(EXT_ec_point_formats, createSupportedPointFormatsExtension(ecPointFormats));
+ }
+
+ public static int[] getSupportedEllipticCurvesExtension(Hashtable extensions) throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_elliptic_curves);
+ return extensionData == null ? null : readSupportedEllipticCurvesExtension(extensionData);
+ }
+
+ public static short[] getSupportedPointFormatsExtension(Hashtable extensions) throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_ec_point_formats);
+ return extensionData == null ? null : readSupportedPointFormatsExtension(extensionData);
+ }
+
+ public static byte[] createSupportedEllipticCurvesExtension(int[] namedCurves) throws IOException
+ {
+ if (namedCurves == null || namedCurves.length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ return TlsUtils.encodeUint16ArrayWithUint16Length(namedCurves);
+ }
+
+ public static byte[] createSupportedPointFormatsExtension(short[] ecPointFormats) throws IOException
+ {
+ 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 end (lowest preference)
+ ecPointFormats = Arrays.append(ecPointFormats, ECPointFormat.uncompressed);
+ }
+
+ return TlsUtils.encodeUint8ArrayWithUint8Length(ecPointFormats);
+ }
+
+ public static int[] readSupportedEllipticCurvesExtension(byte[] extensionData) throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extensionData);
+
+ int length = TlsUtils.readUint16(buf);
+ if (length < 2 || (length & 1) != 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ int[] namedCurves = TlsUtils.readUint16Array(length / 2, buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ return namedCurves;
+ }
+
+ public static short[] readSupportedPointFormatsExtension(byte[] extensionData) throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extensionData);
+
+ short length = TlsUtils.readUint8(buf);
+ if (length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ short[] ecPointFormats = TlsUtils.readUint8Array(length, buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ 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 String getNameOfNamedCurve(int namedCurve)
+ {
+ return isSupportedNamedCurve(namedCurve) ? curveNames[namedCurve - 1] : null;
+ }
+
+ public static ECDomainParameters getParametersForNamedCurve(int namedCurve)
+ {
+ String curveName = getNameOfNamedCurve(namedCurve);
+ if (curveName == null)
+ {
+ return null;
+ }
+
+ // Parameters are lazily created the first time a particular curve is accessed
+
+ 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.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed());
+ }
+
+ public static boolean hasAnySupportedNamedCurves()
+ {
+ return curveNames.length > 0;
+ }
+
+ public static boolean containsECCCipherSuites(int[] cipherSuites)
+ {
+ for (int i = 0; i < cipherSuites.length; ++i)
+ {
+ if (isECCCipherSuite(cipherSuites[i]))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isECCCipherSuite(int cipherSuite)
+ {
+ switch (cipherSuite)
+ {
+ /*
+ * RFC 4492
+ */
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_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_ECDHE_ECDSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_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_ECDH_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_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_ECDHE_RSA_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_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_ECDH_anon_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_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:
+
+ /*
+ * RFC 5289
+ */
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384:
+
+ /*
+ * RFC 5489
+ */
+ 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_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA256:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_NULL_SHA384:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA:
+
+ /*
+ * RFC 6367
+ */
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384:
+
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_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_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CAMELLIA_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_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+
+ /*
+ * draft-agl-tls-chacha20poly1305-04
+ */
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+
+ /*
+ * draft-josefsson-salsa20-tls-04
+ */
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ public static boolean areOnSameCurve(ECDomainParameters a, ECDomainParameters b)
+ {
+ // TODO Move to ECDomainParameters.equals() or other utility method?
+ return a.getCurve().equals(b.getCurve()) && a.getG().equals(b.getG()) && a.getN().equals(b.getN())
+ && a.getH().equals(b.getH());
+ }
+
+ public static boolean isSupportedNamedCurve(int namedCurve)
+ {
+ return (namedCurve > 0 && namedCurve <= curveNames.length);
+ }
+
+ public static boolean isCompressionPreferred(short[] ecPointFormats, short compressionFormat)
+ {
+ if (ecPointFormats == null)
+ {
+ return false;
+ }
+ for (int i = 0; i < ecPointFormats.length; ++i)
+ {
+ short ecPointFormat = ecPointFormats[i];
+ if (ecPointFormat == ECPointFormat.uncompressed)
+ {
+ return false;
+ }
+ if (ecPointFormat == compressionFormat)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static byte[] serializeECFieldElement(int fieldSize, BigInteger x) throws IOException
+ {
+ return BigIntegers.asUnsignedByteArray((fieldSize + 7) / 8, x);
+ }
+
+ public static byte[] serializeECPoint(short[] ecPointFormats, ECPoint point) throws IOException
+ {
+ ECCurve curve = point.getCurve();
+
+ /*
+ * RFC 4492 5.7. ...an elliptic curve point in uncompressed or compressed format. Here, the
+ * format MUST conform to what the server has requested through a Supported Point Formats
+ * Extension if this extension was used, and MUST be uncompressed if this extension was not
+ * used.
+ */
+ boolean compressed = false;
+ if (ECAlgorithms.isFpCurve(curve))
+ {
+ compressed = isCompressionPreferred(ecPointFormats, ECPointFormat.ansiX962_compressed_prime);
+ }
+ else if (ECAlgorithms.isF2mCurve(curve))
+ {
+ compressed = isCompressionPreferred(ecPointFormats, ECPointFormat.ansiX962_compressed_char2);
+ }
+ return point.getEncoded(compressed);
+ }
+
+ public static byte[] serializeECPublicKey(short[] ecPointFormats, ECPublicKeyParameters keyParameters)
+ throws IOException
+ {
+ return serializeECPoint(ecPointFormats, keyParameters.getQ());
+ }
+
+ public static BigInteger deserializeECFieldElement(int fieldSize, byte[] encoding) throws IOException
+ {
+ int requiredLength = (fieldSize + 7) / 8;
+ if (encoding.length != requiredLength)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+ return new BigInteger(1, encoding);
+ }
+
+ public static ECPoint deserializeECPoint(short[] ecPointFormats, ECCurve curve, byte[] encoding) throws IOException
+ {
+ if (encoding == null || encoding.length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ short actualFormat;
+ switch (encoding[0])
+ {
+ case 0x02: // compressed
+ case 0x03: // compressed
+ {
+ if (ECAlgorithms.isF2mCurve(curve))
+ {
+ actualFormat = ECPointFormat.ansiX962_compressed_char2;
+ }
+ else if (ECAlgorithms.isFpCurve(curve))
+ {
+ actualFormat = ECPointFormat.ansiX962_compressed_prime;
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ break;
+ }
+ case 0x04: // uncompressed
+ {
+ actualFormat = ECPointFormat.uncompressed;
+ break;
+ }
+ case 0x00: // infinity
+ case 0x06: // hybrid
+ case 0x07: // hybrid
+ default:
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ if (!Arrays.contains(ecPointFormats, actualFormat))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return curve.decodePoint(encoding);
+ }
+
+ public static ECPublicKeyParameters deserializeECPublicKey(short[] ecPointFormats, ECDomainParameters curve_params,
+ byte[] encoding) throws IOException
+ {
+ try
+ {
+ ECPoint Y = deserializeECPoint(ecPointFormats, curve_params.getCurve(), encoding);
+ return new ECPublicKeyParameters(Y, curve_params);
+ }
+ catch (RuntimeException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ public static byte[] calculateECDHBasicAgreement(ECPublicKeyParameters publicKey, ECPrivateKeyParameters privateKey)
+ {
+ 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.
+ */
+ return BigIntegers.asUnsignedByteArray(basicAgreement.getFieldSize(), agreementValue);
+ }
+
+ public static AsymmetricCipherKeyPair generateECKeyPair(SecureRandom random, ECDomainParameters ecParams)
+ {
+ ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
+ keyPairGenerator.init(new ECKeyGenerationParameters(ecParams, random));
+ return keyPairGenerator.generateKeyPair();
+ }
+
+ public static ECPrivateKeyParameters generateEphemeralClientKeyExchange(SecureRandom random, short[] ecPointFormats,
+ ECDomainParameters ecParams, OutputStream output) throws IOException
+ {
+ AsymmetricCipherKeyPair kp = generateECKeyPair(random, ecParams);
+
+ ECPublicKeyParameters ecPublicKey = (ECPublicKeyParameters) kp.getPublic();
+ writeECPoint(ecPointFormats, ecPublicKey.getQ(), output);
+
+ return (ECPrivateKeyParameters) kp.getPrivate();
+ }
+
+ public static ECPublicKeyParameters validateECPublicKey(ECPublicKeyParameters key) throws IOException
+ {
+ // TODO Check RFC 4492 for validation
+ return key;
+ }
+
+ public static int readECExponent(int fieldSize, InputStream input) throws IOException
+ {
+ BigInteger K = readECParameter(input);
+ if (K.bitLength() < 32)
+ {
+ int k = K.intValue();
+ if (k > 0 && k < fieldSize)
+ {
+ return k;
+ }
+ }
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ public static BigInteger readECFieldElement(int fieldSize, InputStream input) throws IOException
+ {
+ return deserializeECFieldElement(fieldSize, TlsUtils.readOpaque8(input));
+ }
+
+ public static BigInteger readECParameter(InputStream input) throws IOException
+ {
+ // TODO Are leading zeroes okay here?
+ return new BigInteger(1, TlsUtils.readOpaque8(input));
+ }
+
+ public static ECDomainParameters readECParameters(int[] namedCurves, short[] ecPointFormats, InputStream input)
+ throws IOException
+ {
+ try
+ {
+ short curveType = TlsUtils.readUint8(input);
+
+ switch (curveType)
+ {
+ case ECCurveType.explicit_prime:
+ {
+ checkNamedCurve(namedCurves, NamedCurve.arbitrary_explicit_prime_curves);
+
+ BigInteger prime_p = readECParameter(input);
+ BigInteger a = readECFieldElement(prime_p.bitLength(), input);
+ BigInteger b = readECFieldElement(prime_p.bitLength(), input);
+ byte[] baseEncoding = TlsUtils.readOpaque8(input);
+ BigInteger order = readECParameter(input);
+ BigInteger cofactor = readECParameter(input);
+ ECCurve curve = new ECCurve.Fp(prime_p, a, b, order, cofactor);
+ ECPoint base = deserializeECPoint(ecPointFormats, curve, baseEncoding);
+ return new ECDomainParameters(curve, base, order, cofactor);
+ }
+ case ECCurveType.explicit_char2:
+ {
+ checkNamedCurve(namedCurves, NamedCurve.arbitrary_explicit_char2_curves);
+
+ int m = TlsUtils.readUint16(input);
+ short basis = TlsUtils.readUint8(input);
+ if (!ECBasisType.isValid(basis))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ int k1 = readECExponent(m, input), k2 = -1, k3 = -1;
+ if (basis == ECBasisType.ec_basis_pentanomial)
+ {
+ k2 = readECExponent(m, input);
+ k3 = readECExponent(m, input);
+ }
+
+ BigInteger a = readECFieldElement(m, input);
+ BigInteger b = readECFieldElement(m, input);
+ byte[] baseEncoding = TlsUtils.readOpaque8(input);
+ BigInteger order = readECParameter(input);
+ BigInteger cofactor = readECParameter(input);
+
+ ECCurve curve = (basis == ECBasisType.ec_basis_pentanomial)
+ ? new ECCurve.F2m(m, k1, k2, k3, a, b, order, cofactor)
+ : new ECCurve.F2m(m, k1, a, b, order, cofactor);
+
+ ECPoint base = deserializeECPoint(ecPointFormats, curve, baseEncoding);
+
+ return new ECDomainParameters(curve, base, order, cofactor);
+ }
+ case ECCurveType.named_curve:
+ {
+ int namedCurve = TlsUtils.readUint16(input);
+ if (!NamedCurve.refersToASpecificNamedCurve(namedCurve))
+ {
+ /*
+ * 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.illegal_parameter);
+ }
+
+ checkNamedCurve(namedCurves, namedCurve);
+
+ return getParametersForNamedCurve(namedCurve);
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+ catch (RuntimeException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ private static void checkNamedCurve(int[] namedCurves, int namedCurve) throws IOException
+ {
+ if (namedCurves != null && !Arrays.contains(namedCurves, namedCurve))
+ {
+ /*
+ * RFC 4492 4. [...] servers MUST NOT negotiate the use of an ECC cipher suite
+ * unless they can complete the handshake while respecting the choice of curves
+ * and compression techniques specified by the client.
+ */
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ public static void writeECExponent(int k, OutputStream output) throws IOException
+ {
+ BigInteger K = BigInteger.valueOf(k);
+ writeECParameter(K, output);
+ }
+
+ public static void writeECFieldElement(ECFieldElement x, OutputStream output) throws IOException
+ {
+ TlsUtils.writeOpaque8(x.getEncoded(), output);
+ }
+
+ public static void writeECFieldElement(int fieldSize, BigInteger x, OutputStream output) throws IOException
+ {
+ TlsUtils.writeOpaque8(serializeECFieldElement(fieldSize, x), output);
+ }
+
+ public static void writeECParameter(BigInteger x, OutputStream output) throws IOException
+ {
+ TlsUtils.writeOpaque8(BigIntegers.asUnsignedByteArray(x), output);
+ }
+
+ public static void writeExplicitECParameters(short[] ecPointFormats, ECDomainParameters ecParameters,
+ OutputStream output) throws IOException
+ {
+ ECCurve curve = ecParameters.getCurve();
+
+ if (ECAlgorithms.isFpCurve(curve))
+ {
+ TlsUtils.writeUint8(ECCurveType.explicit_prime, output);
+
+ writeECParameter(curve.getField().getCharacteristic(), output);
+ }
+ else if (ECAlgorithms.isF2mCurve(curve))
+ {
+ PolynomialExtensionField field = (PolynomialExtensionField)curve.getField();
+ int[] exponents = field.getMinimalPolynomial().getExponentsPresent();
+
+ TlsUtils.writeUint8(ECCurveType.explicit_char2, output);
+
+ int m = exponents[exponents.length - 1];
+ TlsUtils.checkUint16(m);
+ TlsUtils.writeUint16(m, output);
+
+ if (exponents.length == 3)
+ {
+ TlsUtils.writeUint8(ECBasisType.ec_basis_trinomial, output);
+ writeECExponent(exponents[1], output);
+ }
+ else if (exponents.length == 5)
+ {
+ TlsUtils.writeUint8(ECBasisType.ec_basis_pentanomial, output);
+ writeECExponent(exponents[1], output);
+ writeECExponent(exponents[2], output);
+ writeECExponent(exponents[3], output);
+ }
+ else
+ {
+ throw new IllegalArgumentException("Only trinomial and pentomial curves are supported");
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("'ecParameters' not a known curve type");
+ }
+
+ writeECFieldElement(curve.getA(), output);
+ writeECFieldElement(curve.getB(), output);
+ TlsUtils.writeOpaque8(serializeECPoint(ecPointFormats, ecParameters.getG()), output);
+ writeECParameter(ecParameters.getN(), output);
+ writeECParameter(ecParameters.getH(), output);
+ }
+
+ public static void writeECPoint(short[] ecPointFormats, ECPoint point, OutputStream output) throws IOException
+ {
+ TlsUtils.writeOpaque8(serializeECPoint(ecPointFormats, point), output);
+ }
+
+ public static void writeNamedECParameters(int namedCurve, OutputStream output) throws IOException
+ {
+ if (!NamedCurve.refersToASpecificNamedCurve(namedCurve))
+ {
+ /*
+ * 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);
+ }
+
+ TlsUtils.writeUint8(ECCurveType.named_curve, output);
+ TlsUtils.checkUint16(namedCurve);
+ TlsUtils.writeUint16(namedCurve, output);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsECDHEKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsECDHEKeyExchange.java
new file mode 100644
index 00000000..d159368d
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsECDHEKeyExchange.java
@@ -0,0 +1,221 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Vector;
+
+import org.spongycastle.crypto.AsymmetricCipherKeyPair;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.params.ECDomainParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.io.TeeInputStream;
+
+/**
+ * ECDHE key exchange (see RFC 4492)
+ */
+public class TlsECDHEKeyExchange
+ extends TlsECDHKeyExchange
+{
+ protected TlsSignerCredentials serverCredentials = null;
+
+ public TlsECDHEKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves,
+ short[] clientECPointFormats, short[] serverECPointFormats)
+ {
+ super(keyExchange, supportedSignatureAlgorithms, namedCurves, clientECPointFormats, serverECPointFormats);
+ }
+
+ public void processServerCredentials(TlsCredentials serverCredentials)
+ throws IOException
+ {
+ if (!(serverCredentials instanceof TlsSignerCredentials))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ processServerCertificate(serverCredentials.getCertificate());
+
+ this.serverCredentials = (TlsSignerCredentials)serverCredentials;
+ }
+
+ public byte[] generateServerKeyExchange()
+ throws IOException
+ {
+ /*
+ * First we try to find a supported named curve from the client's list.
+ */
+ int namedCurve = -1;
+ if (namedCurves == null)
+ {
+ // TODO Let the peer choose the default named curve
+ namedCurve = NamedCurve.secp256r1;
+ }
+ else
+ {
+ for (int i = 0; i < namedCurves.length; ++i)
+ {
+ int entry = namedCurves[i];
+ if (NamedCurve.isValid(entry) && TlsECCUtils.isSupportedNamedCurve(entry))
+ {
+ namedCurve = entry;
+ break;
+ }
+ }
+ }
+
+ ECDomainParameters curve_params = null;
+ if (namedCurve >= 0)
+ {
+ curve_params = TlsECCUtils.getParametersForNamedCurve(namedCurve);
+ }
+ else
+ {
+ /*
+ * If no named curves are suitable, check if the client supports explicit curves.
+ */
+ if (Arrays.contains(namedCurves, NamedCurve.arbitrary_explicit_prime_curves))
+ {
+ curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.secp256r1);
+ }
+ else if (Arrays.contains(namedCurves, NamedCurve.arbitrary_explicit_char2_curves))
+ {
+ curve_params = TlsECCUtils.getParametersForNamedCurve(NamedCurve.sect283r1);
+ }
+ }
+
+ if (curve_params == null)
+ {
+ /*
+ * NOTE: We shouldn't have negotiated ECDHE key exchange since we apparently can't find
+ * a suitable curve.
+ */
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ AsymmetricCipherKeyPair kp = TlsECCUtils.generateECKeyPair(context.getSecureRandom(), curve_params);
+ this.ecAgreePrivateKey = (ECPrivateKeyParameters)kp.getPrivate();
+
+ DigestInputBuffer buf = new DigestInputBuffer();
+
+ if (namedCurve < 0)
+ {
+ TlsECCUtils.writeExplicitECParameters(clientECPointFormats, curve_params, buf);
+ }
+ else
+ {
+ TlsECCUtils.writeNamedECParameters(namedCurve, buf);
+ }
+
+ ECPublicKeyParameters ecPublicKey = (ECPublicKeyParameters) kp.getPublic();
+ TlsECCUtils.writeECPoint(clientECPointFormats, ecPublicKey.getQ(), buf);
+
+ /*
+ * RFC 5246 4.7. digitally-signed element needs SignatureAndHashAlgorithm from TLS 1.2
+ */
+ SignatureAndHashAlgorithm signatureAndHashAlgorithm;
+ Digest d;
+
+ if (TlsUtils.isTLSv12(context))
+ {
+ signatureAndHashAlgorithm = serverCredentials.getSignatureAndHashAlgorithm();
+ if (signatureAndHashAlgorithm == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ d = TlsUtils.createHash(signatureAndHashAlgorithm.getHash());
+ }
+ else
+ {
+ signatureAndHashAlgorithm = null;
+ d = new CombinedHash();
+ }
+
+ SecurityParameters securityParameters = context.getSecurityParameters();
+ d.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+ d.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+ buf.updateDigest(d);
+
+ byte[] hash = new byte[d.getDigestSize()];
+ d.doFinal(hash, 0);
+
+ byte[] signature = serverCredentials.generateCertificateSignature(hash);
+
+ DigitallySigned signed_params = new DigitallySigned(signatureAndHashAlgorithm, signature);
+ signed_params.encode(buf);
+
+ return buf.toByteArray();
+ }
+
+ public void processServerKeyExchange(InputStream input)
+ throws IOException
+ {
+ SecurityParameters securityParameters = context.getSecurityParameters();
+
+ SignerInputBuffer buf = new SignerInputBuffer();
+ InputStream teeIn = new TeeInputStream(input, buf);
+
+ ECDomainParameters curve_params = TlsECCUtils.readECParameters(namedCurves, clientECPointFormats, teeIn);
+
+ byte[] point = TlsUtils.readOpaque8(teeIn);
+
+ DigitallySigned signed_params = DigitallySigned.parse(context, input);
+
+ Signer signer = initVerifyer(tlsSigner, signed_params.getAlgorithm(), securityParameters);
+ buf.updateSigner(signer);
+ if (!signer.verifySignature(signed_params.getSignature()))
+ {
+ throw new TlsFatalAlert(AlertDescription.decrypt_error);
+ }
+
+ this.ecAgreePublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey(
+ clientECPointFormats, curve_params, point));
+ }
+
+ public void validateCertificateRequest(CertificateRequest certificateRequest)
+ throws IOException
+ {
+ /*
+ * 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.
+ */
+ short[] types = certificateRequest.getCertificateTypes();
+ for (int i = 0; i < types.length; ++i)
+ {
+ switch (types[i])
+ {
+ case ClientCertificateType.rsa_sign:
+ case ClientCertificateType.dss_sign:
+ case ClientCertificateType.ecdsa_sign:
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+ }
+
+ public void processClientCredentials(TlsCredentials clientCredentials)
+ throws IOException
+ {
+ if (clientCredentials instanceof TlsSignerCredentials)
+ {
+ // OK
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ protected Signer initVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, SecurityParameters securityParameters)
+ {
+ Signer signer = tlsSigner.createVerifyer(algorithm, this.serverPublicKey);
+ signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+ signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+ return signer;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsECDHKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsECDHKeyExchange.java
new file mode 100644
index 00000000..87ecaedb
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsECDHKeyExchange.java
@@ -0,0 +1,219 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.spongycastle.asn1.x509.KeyUsage;
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ECDomainParameters;
+import org.spongycastle.crypto.params.ECPrivateKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.util.PublicKeyFactory;
+
+/**
+ * ECDH key exchange (see RFC 4492)
+ */
+public class TlsECDHKeyExchange extends AbstractTlsKeyExchange
+{
+ protected TlsSigner tlsSigner;
+ protected int[] namedCurves;
+ protected short[] clientECPointFormats, serverECPointFormats;
+
+ protected AsymmetricKeyParameter serverPublicKey;
+ protected TlsAgreementCredentials agreementCredentials;
+
+ protected ECPrivateKeyParameters ecAgreePrivateKey;
+ protected ECPublicKeyParameters ecAgreePublicKey;
+
+ public TlsECDHKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, int[] namedCurves,
+ short[] clientECPointFormats, short[] serverECPointFormats)
+ {
+ super(keyExchange, supportedSignatureAlgorithms);
+
+ switch (keyExchange)
+ {
+ case KeyExchangeAlgorithm.ECDHE_RSA:
+ this.tlsSigner = new TlsRSASigner();
+ break;
+ case KeyExchangeAlgorithm.ECDHE_ECDSA:
+ this.tlsSigner = new TlsECDSASigner();
+ break;
+ case KeyExchangeAlgorithm.ECDH_RSA:
+ case KeyExchangeAlgorithm.ECDH_ECDSA:
+ this.tlsSigner = null;
+ break;
+ default:
+ throw new IllegalArgumentException("unsupported key exchange algorithm");
+ }
+
+ this.keyExchange = keyExchange;
+ this.namedCurves = namedCurves;
+ this.clientECPointFormats = clientECPointFormats;
+ this.serverECPointFormats = serverECPointFormats;
+ }
+
+ public void init(TlsContext context)
+ {
+ super.init(context);
+
+ if (this.tlsSigner != null)
+ {
+ this.tlsSigner.init(context);
+ }
+ }
+
+ public void skipServerCredentials() throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ public void processServerCertificate(Certificate serverCertificate) throws IOException
+ {
+ if (serverCertificate.isEmpty())
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_certificate);
+ }
+
+ org.spongycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+ SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+ try
+ {
+ this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+ }
+ catch (RuntimeException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+ }
+
+ if (tlsSigner == null)
+ {
+ try
+ {
+ this.ecAgreePublicKey = TlsECCUtils.validateECPublicKey((ECPublicKeyParameters) this.serverPublicKey);
+ }
+ catch (ClassCastException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+ }
+
+ TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyAgreement);
+ }
+ else
+ {
+ if (!tlsSigner.isValidPublicKey(this.serverPublicKey))
+ {
+ throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+ }
+
+ TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+ }
+
+ super.processServerCertificate(serverCertificate);
+ }
+
+ public boolean requiresServerKeyExchange()
+ {
+ switch (keyExchange)
+ {
+ case KeyExchangeAlgorithm.ECDHE_ECDSA:
+ case KeyExchangeAlgorithm.ECDHE_RSA:
+ case KeyExchangeAlgorithm.ECDH_anon:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public void validateCertificateRequest(CertificateRequest certificateRequest) throws IOException
+ {
+ /*
+ * 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.
+ */
+ short[] types = certificateRequest.getCertificateTypes();
+ for (int i = 0; i < types.length; ++i)
+ {
+ switch (types[i])
+ {
+ case ClientCertificateType.rsa_sign:
+ case ClientCertificateType.dss_sign:
+ case ClientCertificateType.ecdsa_sign:
+ case ClientCertificateType.rsa_fixed_ecdh:
+ case ClientCertificateType.ecdsa_fixed_ecdh:
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+ }
+
+ public void processClientCredentials(TlsCredentials clientCredentials) throws IOException
+ {
+ if (clientCredentials instanceof TlsAgreementCredentials)
+ {
+ // TODO Validate client cert has matching parameters (see 'TlsECCUtils.areOnSameCurve')?
+
+ this.agreementCredentials = (TlsAgreementCredentials) clientCredentials;
+ }
+ else if (clientCredentials instanceof TlsSignerCredentials)
+ {
+ // OK
+ }
+ else
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public void generateClientKeyExchange(OutputStream output) throws IOException
+ {
+ if (agreementCredentials == null)
+ {
+ this.ecAgreePrivateKey = TlsECCUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(),
+ serverECPointFormats, ecAgreePublicKey.getParameters(), output);
+ }
+ }
+
+ public void processClientCertificate(Certificate clientCertificate) throws IOException
+ {
+ // TODO Extract the public key
+ // TODO If the certificate is 'fixed', take the public key as ecAgreeClientPublicKey
+ }
+
+ public void processClientKeyExchange(InputStream input) throws IOException
+ {
+ if (ecAgreePublicKey != null)
+ {
+ // For ecdsa_fixed_ecdh and rsa_fixed_ecdh, the key arrived in the client certificate
+ return;
+ }
+
+ byte[] point = TlsUtils.readOpaque8(input);
+
+ ECDomainParameters curve_params = this.ecAgreePrivateKey.getParameters();
+
+ this.ecAgreePublicKey = TlsECCUtils.validateECPublicKey(TlsECCUtils.deserializeECPublicKey(
+ serverECPointFormats, curve_params, point));
+ }
+
+ public byte[] generatePremasterSecret() throws IOException
+ {
+ if (agreementCredentials != null)
+ {
+ return agreementCredentials.generateAgreement(ecAgreePublicKey);
+ }
+
+ if (ecAgreePrivateKey != null)
+ {
+ return TlsECCUtils.calculateECDHBasicAgreement(ecAgreePublicKey, ecAgreePrivateKey);
+ }
+
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsECDSASigner.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsECDSASigner.java
new file mode 100644
index 00000000..308aabf0
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsECDSASigner.java
@@ -0,0 +1,26 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.DSA;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.signers.ECDSASigner;
+import org.spongycastle.crypto.signers.HMacDSAKCalculator;
+
+public class TlsECDSASigner
+ extends TlsDSASigner
+{
+ public boolean isValidPublicKey(AsymmetricKeyParameter publicKey)
+ {
+ return publicKey instanceof ECPublicKeyParameters;
+ }
+
+ protected DSA createDSAImpl(short hashAlgorithm)
+ {
+ return new ECDSASigner(new HMacDSAKCalculator(TlsUtils.createHash(hashAlgorithm)));
+ }
+
+ protected short getSignatureAlgorithm()
+ {
+ return SignatureAlgorithm.ecdsa;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsEncryptionCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsEncryptionCredentials.java
new file mode 100644
index 00000000..c4736382
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsEncryptionCredentials.java
@@ -0,0 +1,9 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsEncryptionCredentials extends TlsCredentials
+{
+ byte[] decryptPreMasterSecret(byte[] encryptedPreMasterSecret)
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsExtensionsUtils.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsExtensionsUtils.java
new file mode 100644
index 00000000..70393e0f
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsExtensionsUtils.java
@@ -0,0 +1,267 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.spongycastle.util.Integers;
+
+public class TlsExtensionsUtils
+{
+ public static final Integer EXT_encrypt_then_mac = Integers.valueOf(ExtensionType.encrypt_then_mac);
+ public static final Integer EXT_heartbeat = Integers.valueOf(ExtensionType.heartbeat);
+ public static final Integer EXT_max_fragment_length = Integers.valueOf(ExtensionType.max_fragment_length);
+ public static final Integer EXT_server_name = Integers.valueOf(ExtensionType.server_name);
+ public static final Integer EXT_status_request = Integers.valueOf(ExtensionType.status_request);
+ public static final Integer EXT_truncated_hmac = Integers.valueOf(ExtensionType.truncated_hmac);
+
+ public static Hashtable ensureExtensionsInitialised(Hashtable extensions)
+ {
+ return extensions == null ? new Hashtable() : extensions;
+ }
+
+ public static void addEncryptThenMACExtension(Hashtable extensions)
+ {
+ extensions.put(EXT_encrypt_then_mac, createEncryptThenMACExtension());
+ }
+
+ public static void addHeartbeatExtension(Hashtable extensions, HeartbeatExtension heartbeatExtension)
+ throws IOException
+ {
+ extensions.put(EXT_heartbeat, createHeartbeatExtension(heartbeatExtension));
+ }
+
+ public static void addMaxFragmentLengthExtension(Hashtable extensions, short maxFragmentLength)
+ throws IOException
+ {
+ extensions.put(EXT_max_fragment_length, createMaxFragmentLengthExtension(maxFragmentLength));
+ }
+
+ public static void addServerNameExtension(Hashtable extensions, ServerNameList serverNameList)
+ throws IOException
+ {
+ extensions.put(EXT_server_name, createServerNameExtension(serverNameList));
+ }
+
+ public static void addStatusRequestExtension(Hashtable extensions, CertificateStatusRequest statusRequest)
+ throws IOException
+ {
+ extensions.put(EXT_status_request, createStatusRequestExtension(statusRequest));
+ }
+
+ public static void addTruncatedHMacExtension(Hashtable extensions)
+ {
+ extensions.put(EXT_truncated_hmac, createTruncatedHMacExtension());
+ }
+
+ public static HeartbeatExtension getHeartbeatExtension(Hashtable extensions)
+ throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_heartbeat);
+ return extensionData == null ? null : readHeartbeatExtension(extensionData);
+ }
+
+ public static short getMaxFragmentLengthExtension(Hashtable extensions)
+ throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_max_fragment_length);
+ return extensionData == null ? -1 : readMaxFragmentLengthExtension(extensionData);
+ }
+
+ public static ServerNameList getServerNameExtension(Hashtable extensions)
+ throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_server_name);
+ return extensionData == null ? null : readServerNameExtension(extensionData);
+ }
+
+ public static CertificateStatusRequest getStatusRequestExtension(Hashtable extensions)
+ throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_status_request);
+ return extensionData == null ? null : readStatusRequestExtension(extensionData);
+ }
+
+ public static boolean hasEncryptThenMACExtension(Hashtable extensions) throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_encrypt_then_mac);
+ return extensionData == null ? false : readEncryptThenMACExtension(extensionData);
+ }
+
+ public static boolean hasTruncatedHMacExtension(Hashtable extensions) throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_truncated_hmac);
+ return extensionData == null ? false : readTruncatedHMacExtension(extensionData);
+ }
+
+ public static byte[] createEmptyExtensionData()
+ {
+ return TlsUtils.EMPTY_BYTES;
+ }
+
+ public static byte[] createEncryptThenMACExtension()
+ {
+ return createEmptyExtensionData();
+ }
+
+ public static byte[] createHeartbeatExtension(HeartbeatExtension heartbeatExtension)
+ throws IOException
+ {
+ if (heartbeatExtension == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ heartbeatExtension.encode(buf);
+
+ return buf.toByteArray();
+ }
+
+ public static byte[] createMaxFragmentLengthExtension(short maxFragmentLength)
+ throws IOException
+ {
+ if (!MaxFragmentLength.isValid(maxFragmentLength))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ return new byte[]{ (byte)maxFragmentLength };
+ }
+
+ public static byte[] createServerNameExtension(ServerNameList serverNameList)
+ throws IOException
+ {
+ if (serverNameList == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ serverNameList.encode(buf);
+
+ return buf.toByteArray();
+ }
+
+ public static byte[] createStatusRequestExtension(CertificateStatusRequest statusRequest)
+ throws IOException
+ {
+ if (statusRequest == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ statusRequest.encode(buf);
+
+ return buf.toByteArray();
+ }
+
+ public static byte[] createTruncatedHMacExtension()
+ {
+ return createEmptyExtensionData();
+ }
+
+ private static boolean readEmptyExtensionData(byte[] extensionData) throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ if (extensionData.length != 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return true;
+ }
+
+ public static boolean readEncryptThenMACExtension(byte[] extensionData) throws IOException
+ {
+ return readEmptyExtensionData(extensionData);
+ }
+
+ public static HeartbeatExtension readHeartbeatExtension(byte[] extensionData)
+ throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extensionData);
+
+ HeartbeatExtension heartbeatExtension = HeartbeatExtension.parse(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ return heartbeatExtension;
+ }
+
+ public static short readMaxFragmentLengthExtension(byte[] extensionData)
+ throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ if (extensionData.length != 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ short maxFragmentLength = (short)extensionData[0];
+
+ if (!MaxFragmentLength.isValid(maxFragmentLength))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return maxFragmentLength;
+ }
+
+ public static ServerNameList readServerNameExtension(byte[] extensionData)
+ throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extensionData);
+
+ ServerNameList serverNameList = ServerNameList.parse(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ return serverNameList;
+ }
+
+ public static CertificateStatusRequest readStatusRequestExtension(byte[] extensionData)
+ throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extensionData);
+
+ CertificateStatusRequest statusRequest = CertificateStatusRequest.parse(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ return statusRequest;
+ }
+
+ public static boolean readTruncatedHMacExtension(byte[] extensionData) throws IOException
+ {
+ return readEmptyExtensionData(extensionData);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsFatalAlert.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsFatalAlert.java
new file mode 100644
index 00000000..7e633809
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsFatalAlert.java
@@ -0,0 +1,21 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public class TlsFatalAlert
+ extends IOException
+{
+ private static final long serialVersionUID = 3584313123679111168L;
+
+ private short alertDescription;
+
+ public TlsFatalAlert(short alertDescription)
+ {
+ this.alertDescription = alertDescription;
+ }
+
+ public short getAlertDescription()
+ {
+ return alertDescription;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsHandshakeHash.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsHandshakeHash.java
new file mode 100644
index 00000000..4fc82ffd
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsHandshakeHash.java
@@ -0,0 +1,21 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.Digest;
+
+public interface TlsHandshakeHash
+ extends Digest
+{
+ void init(TlsContext context);
+
+ TlsHandshakeHash notifyPRFDetermined();
+
+ void trackHashAlgorithm(short hashAlgorithm);
+
+ void sealHashAlgorithms();
+
+ TlsHandshakeHash stopTracking();
+
+ Digest forkPRFHash();
+
+ byte[] getFinalHash(short hashAlgorithm);
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsInputStream.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsInputStream.java
new file mode 100644
index 00000000..e2c3e281
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsInputStream.java
@@ -0,0 +1,47 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An InputStream for an TLS 1.0 connection.
+ */
+class TlsInputStream
+ extends InputStream
+{
+ private byte[] buf = new byte[1];
+ private TlsProtocol handler = null;
+
+ TlsInputStream(TlsProtocol handler)
+ {
+ this.handler = handler;
+ }
+
+ public int available()
+ throws IOException
+ {
+ return this.handler.applicationDataAvailable();
+ }
+
+ public int read(byte[] buf, int offset, int len)
+ throws IOException
+ {
+ return this.handler.readApplicationData(buf, offset, len);
+ }
+
+ public int read()
+ throws IOException
+ {
+ if (this.read(buf) < 0)
+ {
+ return -1;
+ }
+ return buf[0] & 0xff;
+ }
+
+ public void close()
+ throws IOException
+ {
+ handler.close();
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsKeyExchange.java
new file mode 100644
index 00000000..82902170
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsKeyExchange.java
@@ -0,0 +1,54 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A generic interface for key exchange implementations in TLS 1.0/1.1.
+ */
+public interface TlsKeyExchange
+{
+ void init(TlsContext context);
+
+ void skipServerCredentials()
+ throws IOException;
+
+ void processServerCredentials(TlsCredentials serverCredentials)
+ throws IOException;
+
+ void processServerCertificate(Certificate serverCertificate)
+ throws IOException;
+
+ boolean requiresServerKeyExchange();
+
+ byte[] generateServerKeyExchange()
+ throws IOException;
+
+ void skipServerKeyExchange()
+ throws IOException;
+
+ void processServerKeyExchange(InputStream input)
+ throws IOException;
+
+ void validateCertificateRequest(CertificateRequest certificateRequest)
+ throws IOException;
+
+ void skipClientCredentials()
+ throws IOException;
+
+ void processClientCredentials(TlsCredentials clientCredentials)
+ throws IOException;
+
+ void processClientCertificate(Certificate clientCertificate)
+ throws IOException;
+
+ void generateClientKeyExchange(OutputStream output)
+ throws IOException;
+
+ void processClientKeyExchange(InputStream input)
+ throws IOException;
+
+ byte[] generatePremasterSecret()
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsMac.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsMac.java
new file mode 100644
index 00000000..6390b699
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsMac.java
@@ -0,0 +1,172 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Mac;
+import org.spongycastle.crypto.digests.LongDigest;
+import org.spongycastle.crypto.macs.HMac;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.util.Arrays;
+
+/**
+ * A generic TLS MAC implementation, acting as an HMAC based on some underlying Digest.
+ */
+public class TlsMac
+{
+ protected TlsContext context;
+ protected byte[] secret;
+ protected Mac mac;
+ protected int digestBlockSize;
+ protected int digestOverhead;
+ protected int macLength;
+
+ /**
+ * Generate a new instance of an TlsMac.
+ *
+ * @param context the TLS client context
+ * @param digest The digest to use.
+ * @param key A byte-array where the key for this MAC is located.
+ * @param keyOff The number of bytes to skip, before the key starts in the buffer.
+ * @param keyLen The length of the key.
+ */
+ public TlsMac(TlsContext context, Digest digest, byte[] key, int keyOff, int keyLen)
+ {
+ this.context = context;
+
+ KeyParameter keyParameter = new KeyParameter(key, keyOff, keyLen);
+
+ this.secret = Arrays.clone(keyParameter.getKey());
+
+ // TODO This should check the actual algorithm, not rely on the engine type
+ if (digest instanceof LongDigest)
+ {
+ this.digestBlockSize = 128;
+ this.digestOverhead = 16;
+ }
+ else
+ {
+ this.digestBlockSize = 64;
+ this.digestOverhead = 8;
+ }
+
+ if (TlsUtils.isSSL(context))
+ {
+ this.mac = new SSL3Mac(digest);
+
+ // TODO This should check the actual algorithm, not assume based on the digest size
+ if (digest.getDigestSize() == 20)
+ {
+ /*
+ * NOTE: When SHA-1 is used with the SSL 3.0 MAC, the secret + input pad is not
+ * digest block-aligned.
+ */
+ this.digestOverhead = 4;
+ }
+ }
+ else
+ {
+ this.mac = new HMac(digest);
+
+ // NOTE: The input pad for HMAC is always a full digest block
+ }
+
+ this.mac.init(keyParameter);
+
+ this.macLength = mac.getMacSize();
+ if (context.getSecurityParameters().truncatedHMac)
+ {
+ this.macLength = Math.min(this.macLength, 10);
+ }
+ }
+
+ /**
+ * @return the MAC write secret
+ */
+ public byte[] getMACSecret()
+ {
+ return this.secret;
+ }
+
+ /**
+ * @return The output length of this MAC.
+ */
+ public int getSize()
+ {
+ return macLength;
+ }
+
+ /**
+ * Calculate the MAC for some given data.
+ *
+ * @param type The message type of the message.
+ * @param message A byte-buffer containing the message.
+ * @param offset The number of bytes to skip, before the message starts.
+ * @param length The length of the message.
+ * @return A new byte-buffer containing the MAC value.
+ */
+ public byte[] calculateMac(long seqNo, short type, byte[] message, int offset, int length)
+ {
+ ProtocolVersion serverVersion = context.getServerVersion();
+ boolean isSSL = serverVersion.isSSL();
+
+ byte[] macHeader = new byte[isSSL ? 11 : 13];
+ TlsUtils.writeUint64(seqNo, macHeader, 0);
+ TlsUtils.writeUint8(type, macHeader, 8);
+ if (!isSSL)
+ {
+ TlsUtils.writeVersion(serverVersion, macHeader, 9);
+ }
+ TlsUtils.writeUint16(length, macHeader, macHeader.length - 2);
+
+ mac.update(macHeader, 0, macHeader.length);
+ mac.update(message, offset, length);
+
+ byte[] result = new byte[mac.getMacSize()];
+ mac.doFinal(result, 0);
+ return truncate(result);
+ }
+
+ public byte[] calculateMacConstantTime(long seqNo, short type, byte[] message, int offset, int length,
+ int fullLength, byte[] dummyData)
+ {
+ /*
+ * Actual MAC only calculated on 'length' bytes...
+ */
+ byte[] result = calculateMac(seqNo, type, message, offset, length);
+
+ /*
+ * ...but ensure a constant number of complete digest blocks are processed (as many as would
+ * be needed for 'fullLength' bytes of input).
+ */
+ int headerLength = TlsUtils.isSSL(context) ? 11 : 13;
+
+ // How many extra full blocks do we need to calculate?
+ int extra = getDigestBlockCount(headerLength + fullLength) - getDigestBlockCount(headerLength + length);
+
+ while (--extra >= 0)
+ {
+ mac.update(dummyData, 0, digestBlockSize);
+ }
+
+ // One more byte in case the implementation is "lazy" about processing blocks
+ mac.update(dummyData[0]);
+ mac.reset();
+
+ return result;
+ }
+
+ protected int getDigestBlockCount(int inputLength)
+ {
+ // NOTE: This calculation assumes a minimum of 1 pad byte
+ return (inputLength + digestOverhead) / digestBlockSize;
+ }
+
+ protected byte[] truncate(byte[] bs)
+ {
+ if (bs.length <= macLength)
+ {
+ return bs;
+ }
+
+ return Arrays.copyOf(bs, macLength);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsNullCipher.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsNullCipher.java
new file mode 100644
index 00000000..6972cea6
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsNullCipher.java
@@ -0,0 +1,123 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.util.Arrays;
+
+/**
+ * A NULL CipherSuite with optional MAC
+ */
+public class TlsNullCipher
+ implements TlsCipher
+{
+ protected TlsContext context;
+
+ protected TlsMac writeMac;
+ protected TlsMac readMac;
+
+ public TlsNullCipher(TlsContext context)
+ {
+ this.context = context;
+ this.writeMac = null;
+ this.readMac = null;
+ }
+
+ public TlsNullCipher(TlsContext context, Digest clientWriteDigest, Digest serverWriteDigest)
+ throws IOException
+ {
+ if ((clientWriteDigest == null) != (serverWriteDigest == null))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ this.context = context;
+
+ TlsMac clientWriteMac = null, serverWriteMac = null;
+
+ if (clientWriteDigest != null)
+ {
+ int key_block_size = clientWriteDigest.getDigestSize()
+ + serverWriteDigest.getDigestSize();
+ byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size);
+
+ int offset = 0;
+
+ clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+ clientWriteDigest.getDigestSize());
+ offset += clientWriteDigest.getDigestSize();
+
+ serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+ serverWriteDigest.getDigestSize());
+ offset += serverWriteDigest.getDigestSize();
+
+ if (offset != key_block_size)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ if (context.isServer())
+ {
+ writeMac = serverWriteMac;
+ readMac = clientWriteMac;
+ }
+ else
+ {
+ writeMac = clientWriteMac;
+ readMac = serverWriteMac;
+ }
+ }
+
+ public int getPlaintextLimit(int ciphertextLimit)
+ {
+ int result = ciphertextLimit;
+ if (writeMac != null)
+ {
+ result -= writeMac.getSize();
+ }
+ return result;
+ }
+
+ public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+ throws IOException
+ {
+ if (writeMac == null)
+ {
+ return Arrays.copyOfRange(plaintext, offset, offset + len);
+ }
+
+ byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len);
+ byte[] ciphertext = new byte[len + mac.length];
+ System.arraycopy(plaintext, offset, ciphertext, 0, len);
+ System.arraycopy(mac, 0, ciphertext, len, mac.length);
+ return ciphertext;
+ }
+
+ public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+ throws IOException
+ {
+ if (readMac == null)
+ {
+ return Arrays.copyOfRange(ciphertext, offset, offset + len);
+ }
+
+ int macSize = readMac.getSize();
+ if (len < macSize)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ int macInputLen = len - macSize;
+
+ byte[] receivedMac = Arrays.copyOfRange(ciphertext, offset + macInputLen, offset + len);
+ byte[] computedMac = readMac.calculateMac(seqNo, type, ciphertext, offset, macInputLen);
+
+ if (!Arrays.constantTimeAreEqual(receivedMac, computedMac))
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+ }
+
+ return Arrays.copyOfRange(ciphertext, offset, offset + macInputLen);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsNullCompression.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsNullCompression.java
new file mode 100644
index 00000000..724d93b6
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsNullCompression.java
@@ -0,0 +1,17 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.OutputStream;
+
+public class TlsNullCompression
+ implements TlsCompression
+{
+ public OutputStream compress(OutputStream output)
+ {
+ return output;
+ }
+
+ public OutputStream decompress(OutputStream output)
+ {
+ return output;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsOutputStream.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsOutputStream.java
new file mode 100644
index 00000000..d4b6de66
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsOutputStream.java
@@ -0,0 +1,44 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An OutputStream for an TLS connection.
+ */
+class TlsOutputStream
+ extends OutputStream
+{
+ private byte[] buf = new byte[1];
+ private TlsProtocol handler;
+
+ TlsOutputStream(TlsProtocol handler)
+ {
+ this.handler = handler;
+ }
+
+ public void write(byte buf[], int offset, int len)
+ throws IOException
+ {
+ this.handler.writeData(buf, offset, len);
+ }
+
+ public void write(int arg0)
+ throws IOException
+ {
+ buf[0] = (byte)arg0;
+ this.write(buf, 0, 1);
+ }
+
+ public void close()
+ throws IOException
+ {
+ handler.close();
+ }
+
+ public void flush()
+ throws IOException
+ {
+ handler.flush();
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsPSKIdentity.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsPSKIdentity.java
new file mode 100644
index 00000000..a271ddce
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsPSKIdentity.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+public interface TlsPSKIdentity
+{
+ void skipIdentityHint();
+
+ void notifyIdentityHint(byte[] psk_identity_hint);
+
+ byte[] getPSKIdentity();
+
+ byte[] getPSK();
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsPSKKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsPSKKeyExchange.java
new file mode 100644
index 00000000..c3431bb4
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsPSKKeyExchange.java
@@ -0,0 +1,285 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.spongycastle.asn1.x509.KeyUsage;
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.DHParameters;
+import org.spongycastle.crypto.params.DHPrivateKeyParameters;
+import org.spongycastle.crypto.params.DHPublicKeyParameters;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+import org.spongycastle.crypto.util.PublicKeyFactory;
+
+/**
+ * TLS 1.0 PSK key exchange (RFC 4279).
+ */
+public class TlsPSKKeyExchange
+ extends AbstractTlsKeyExchange
+{
+ protected TlsPSKIdentity pskIdentity;
+ protected DHParameters dhParameters;
+ protected int[] namedCurves;
+ protected short[] clientECPointFormats, serverECPointFormats;
+
+ protected byte[] psk_identity_hint = null;
+
+ protected DHPrivateKeyParameters dhAgreePrivateKey = null;
+ protected DHPublicKeyParameters dhAgreePublicKey = null;
+
+ protected AsymmetricKeyParameter serverPublicKey = null;
+ protected RSAKeyParameters rsaServerPublicKey = null;
+ protected TlsEncryptionCredentials serverCredentials = null;
+ protected byte[] premasterSecret;
+
+ public TlsPSKKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, TlsPSKIdentity pskIdentity,
+ DHParameters dhParameters, int[] namedCurves, short[] clientECPointFormats, short[] serverECPointFormats)
+ {
+ super(keyExchange, supportedSignatureAlgorithms);
+
+ switch (keyExchange)
+ {
+ case KeyExchangeAlgorithm.DHE_PSK:
+ case KeyExchangeAlgorithm.ECDHE_PSK:
+ case KeyExchangeAlgorithm.PSK:
+ case KeyExchangeAlgorithm.RSA_PSK:
+ break;
+ default:
+ throw new IllegalArgumentException("unsupported key exchange algorithm");
+ }
+
+ this.pskIdentity = pskIdentity;
+ this.dhParameters = dhParameters;
+ this.namedCurves = namedCurves;
+ this.clientECPointFormats = clientECPointFormats;
+ this.serverECPointFormats = serverECPointFormats;
+ }
+
+ public void skipServerCredentials()
+ throws IOException
+ {
+ if (keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ public void processServerCredentials(TlsCredentials serverCredentials)
+ throws IOException
+ {
+ if (!(serverCredentials instanceof TlsEncryptionCredentials))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ processServerCertificate(serverCredentials.getCertificate());
+
+ this.serverCredentials = (TlsEncryptionCredentials)serverCredentials;
+ }
+
+ public byte[] generateServerKeyExchange() throws IOException
+ {
+ // TODO[RFC 4279] Need a server-side PSK API to determine hint and resolve identities to keys
+ this.psk_identity_hint = null;
+
+ if (this.psk_identity_hint == null && !requiresServerKeyExchange())
+ {
+ return null;
+ }
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ if (this.psk_identity_hint == null)
+ {
+ TlsUtils.writeOpaque16(TlsUtils.EMPTY_BYTES, buf);
+ }
+ else
+ {
+ TlsUtils.writeOpaque16(this.psk_identity_hint, buf);
+ }
+
+ if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+ {
+ if (this.dhParameters == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ this.dhAgreePrivateKey = TlsDHUtils.generateEphemeralServerKeyExchange(context.getSecureRandom(),
+ this.dhParameters, buf);
+ }
+ else if (this.keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+ {
+ // TODO[RFC 5489]
+ }
+
+ return buf.toByteArray();
+ }
+
+ public void processServerCertificate(Certificate serverCertificate)
+ throws IOException
+ {
+ if (keyExchange != KeyExchangeAlgorithm.RSA_PSK)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ if (serverCertificate.isEmpty())
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_certificate);
+ }
+
+ org.spongycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+ SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+ try
+ {
+ this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+ }
+ catch (RuntimeException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+ }
+
+ // Sanity check the PublicKeyFactory
+ if (this.serverPublicKey.isPrivate())
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ this.rsaServerPublicKey = validateRSAPublicKey((RSAKeyParameters)this.serverPublicKey);
+
+ TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyEncipherment);
+
+ super.processServerCertificate(serverCertificate);
+ }
+
+ public boolean requiresServerKeyExchange()
+ {
+ switch (keyExchange)
+ {
+ case KeyExchangeAlgorithm.DHE_PSK:
+ case KeyExchangeAlgorithm.ECDHE_PSK:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public void processServerKeyExchange(InputStream input)
+ throws IOException
+ {
+ this.psk_identity_hint = TlsUtils.readOpaque16(input);
+
+ if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+ {
+ ServerDHParams serverDHParams = ServerDHParams.parse(input);
+
+ this.dhAgreePublicKey = TlsDHUtils.validateDHPublicKey(serverDHParams.getPublicKey());
+ }
+ else if (this.keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+ {
+ // TODO[RFC 5489]
+ }
+ }
+
+ public void validateCertificateRequest(CertificateRequest certificateRequest)
+ throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ public void processClientCredentials(TlsCredentials clientCredentials)
+ throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ public void generateClientKeyExchange(OutputStream output)
+ throws IOException
+ {
+ if (psk_identity_hint == null)
+ {
+ pskIdentity.skipIdentityHint();
+ }
+ else
+ {
+ pskIdentity.notifyIdentityHint(psk_identity_hint);
+ }
+
+ byte[] psk_identity = pskIdentity.getPSKIdentity();
+
+ TlsUtils.writeOpaque16(psk_identity, output);
+
+ if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+ {
+ this.dhAgreePrivateKey = TlsDHUtils.generateEphemeralClientKeyExchange(context.getSecureRandom(),
+ dhAgreePublicKey.getParameters(), output);
+ }
+ else if (this.keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+ {
+ // TODO[RFC 5489]
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ else if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+ {
+ this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, this.rsaServerPublicKey,
+ output);
+ }
+ }
+
+ public byte[] generatePremasterSecret()
+ throws IOException
+ {
+ byte[] psk = pskIdentity.getPSK();
+ byte[] other_secret = generateOtherSecret(psk.length);
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream(4 + other_secret.length + psk.length);
+ TlsUtils.writeOpaque16(other_secret, buf);
+ TlsUtils.writeOpaque16(psk, buf);
+ return buf.toByteArray();
+ }
+
+ protected byte[] generateOtherSecret(int pskLength) throws IOException
+ {
+ if (this.keyExchange == KeyExchangeAlgorithm.DHE_PSK)
+ {
+ if (dhAgreePrivateKey != null)
+ {
+ return TlsDHUtils.calculateDHBasicAgreement(dhAgreePublicKey, dhAgreePrivateKey);
+ }
+
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ if (this.keyExchange == KeyExchangeAlgorithm.ECDHE_PSK)
+ {
+ // TODO[RFC 5489]
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ if (this.keyExchange == KeyExchangeAlgorithm.RSA_PSK)
+ {
+ return this.premasterSecret;
+ }
+
+ return new byte[pskLength];
+ }
+
+ protected RSAKeyParameters validateRSAPublicKey(RSAKeyParameters key)
+ throws IOException
+ {
+ // TODO What is the minimum bit length required?
+ // key.getModulus().bitLength();
+
+ if (!key.getExponent().isProbablePrime(2))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return key;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsPeer.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsPeer.java
new file mode 100644
index 00000000..62ed0f71
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsPeer.java
@@ -0,0 +1,46 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsPeer
+{
+ /**
+ * 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."
+ *
+ * @return <code>true</code> if the current time should be used in the gmt_unix_time field of
+ * Random, or <code>false</code> if gmt_unix_time should contain a cryptographically
+ * random value.
+ */
+ boolean shouldUseGMTUnixTime();
+
+ void notifySecureRenegotiation(boolean secureNegotiation) throws IOException;
+
+ TlsCompression getCompression() throws IOException;
+
+ TlsCipher getCipher() throws IOException;
+
+ /**
+ * This method will be called when an alert is raised by the protocol.
+ *
+ * @param alertLevel {@link AlertLevel}
+ * @param alertDescription {@link AlertDescription}
+ * @param message A human-readable message explaining what caused this alert. May be null.
+ * @param cause The exception 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.
+ *
+ * @param alertLevel {@link AlertLevel}
+ * @param alertDescription {@link AlertDescription}
+ */
+ void notifyAlertReceived(short alertLevel, short alertDescription);
+
+ /**
+ * Notifies the peer that the handshake has been successfully completed.
+ */
+ void notifyHandshakeComplete() throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java
new file mode 100644
index 00000000..96aff625
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocol.java
@@ -0,0 +1,1184 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.prng.RandomGenerator;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.Integers;
+
+/**
+ * An implementation of all high level protocols in TLS 1.0/1.1.
+ */
+public abstract class TlsProtocol
+{
+ protected static final Integer EXT_RenegotiationInfo = Integers.valueOf(ExtensionType.renegotiation_info);
+ protected static final Integer EXT_SessionTicket = Integers.valueOf(ExtensionType.session_ticket);
+
+ private static final String TLS_ERROR_MESSAGE = "Internal TLS error, this could be an attack";
+
+ /*
+ * Our Connection states
+ */
+ protected static final short CS_START = 0;
+ protected static final short CS_CLIENT_HELLO = 1;
+ protected static final short CS_SERVER_HELLO = 2;
+ protected static final short CS_SERVER_SUPPLEMENTAL_DATA = 3;
+ protected static final short CS_SERVER_CERTIFICATE = 4;
+ protected static final short CS_CERTIFICATE_STATUS = 5;
+ protected static final short CS_SERVER_KEY_EXCHANGE = 6;
+ protected static final short CS_CERTIFICATE_REQUEST = 7;
+ protected static final short CS_SERVER_HELLO_DONE = 8;
+ protected static final short CS_CLIENT_SUPPLEMENTAL_DATA = 9;
+ protected static final short CS_CLIENT_CERTIFICATE = 10;
+ protected static final short CS_CLIENT_KEY_EXCHANGE = 11;
+ protected static final short CS_CERTIFICATE_VERIFY = 12;
+ protected static final short CS_CLIENT_FINISHED = 13;
+ protected static final short CS_SERVER_SESSION_TICKET = 14;
+ protected static final short CS_SERVER_FINISHED = 15;
+ protected static final short CS_END = 16;
+
+ /*
+ * Queues for data from some protocols.
+ */
+ private ByteQueue applicationDataQueue = new ByteQueue();
+ private ByteQueue alertQueue = new ByteQueue(2);
+ private ByteQueue handshakeQueue = new ByteQueue();
+// private ByteQueue heartbeatQueue = new ByteQueue();
+
+ /*
+ * The Record Stream we use
+ */
+ protected RecordStream recordStream;
+ protected SecureRandom secureRandom;
+
+ private TlsInputStream tlsInputStream = null;
+ private TlsOutputStream tlsOutputStream = null;
+
+ private volatile boolean closed = false;
+ private volatile boolean failedWithError = false;
+ private volatile boolean appDataReady = false;
+ private volatile boolean splitApplicationDataRecords = true;
+ private byte[] expected_verify_data = null;
+
+ protected TlsSession tlsSession = null;
+ protected SessionParameters sessionParameters = null;
+ protected SecurityParameters securityParameters = null;
+ protected Certificate peerCertificate = null;
+
+ protected int[] offeredCipherSuites = null;
+ protected short[] offeredCompressionMethods = null;
+ protected Hashtable clientExtensions = null;
+ protected Hashtable serverExtensions = null;
+
+ protected short connection_state = CS_START;
+ protected boolean resumedSession = false;
+ protected boolean receivedChangeCipherSpec = false;
+ protected boolean secure_renegotiation = false;
+ protected boolean allowCertificateStatus = false;
+ protected boolean expectSessionTicket = false;
+
+ public TlsProtocol(InputStream input, OutputStream output, SecureRandom secureRandom)
+ {
+ this.recordStream = new RecordStream(this, input, output);
+ this.secureRandom = secureRandom;
+ }
+
+ protected abstract AbstractTlsContext getContext();
+
+ protected abstract TlsPeer getPeer();
+
+ protected void handleChangeCipherSpecMessage() throws IOException
+ {
+ }
+
+ protected abstract void handleHandshakeMessage(short type, byte[] buf)
+ throws IOException;
+
+ protected void handleWarningMessage(short description)
+ throws IOException
+ {
+ }
+
+ protected void cleanupHandshake()
+ {
+ if (this.expected_verify_data != null)
+ {
+ Arrays.fill(this.expected_verify_data, (byte)0);
+ this.expected_verify_data = null;
+ }
+
+ this.securityParameters.clear();
+ this.peerCertificate = null;
+
+ this.offeredCipherSuites = null;
+ this.offeredCompressionMethods = null;
+ this.clientExtensions = null;
+ this.serverExtensions = null;
+
+ this.resumedSession = false;
+ this.receivedChangeCipherSpec = false;
+ this.secure_renegotiation = false;
+ this.allowCertificateStatus = false;
+ this.expectSessionTicket = false;
+ }
+
+ protected void completeHandshake()
+ throws IOException
+ {
+ try
+ {
+ /*
+ * We will now read data, until we have completed the handshake.
+ */
+ while (this.connection_state != CS_END)
+ {
+ if (this.closed)
+ {
+ // TODO What kind of exception/alert?
+ }
+
+ safeReadRecord();
+ }
+
+ this.recordStream.finaliseHandshake();
+
+ this.splitApplicationDataRecords = !TlsUtils.isTLSv11(getContext());
+
+ /*
+ * If this was an initial handshake, we are now ready to send and receive application data.
+ */
+ if (!appDataReady)
+ {
+ this.appDataReady = true;
+
+ this.tlsInputStream = new TlsInputStream(this);
+ this.tlsOutputStream = new TlsOutputStream(this);
+ }
+
+ if (this.tlsSession != null)
+ {
+ if (this.sessionParameters == null)
+ {
+ this.sessionParameters = new SessionParameters.Builder()
+ .setCipherSuite(this.securityParameters.cipherSuite)
+ .setCompressionAlgorithm(this.securityParameters.compressionAlgorithm)
+ .setMasterSecret(this.securityParameters.masterSecret)
+ .setPeerCertificate(this.peerCertificate)
+ // TODO Consider filtering extensions that aren't relevant to resumed sessions
+ .setServerExtensions(this.serverExtensions)
+ .build();
+
+ this.tlsSession = new TlsSessionImpl(this.tlsSession.getSessionID(), this.sessionParameters);
+ }
+
+ getContext().setResumableSession(this.tlsSession);
+ }
+
+ getPeer().notifyHandshakeComplete();
+ }
+ finally
+ {
+ cleanupHandshake();
+ }
+ }
+
+ protected void processRecord(short protocol, byte[] buf, int offset, int len)
+ throws IOException
+ {
+ /*
+ * Have a look at the protocol type, and add it to the correct queue.
+ */
+ switch (protocol)
+ {
+ case ContentType.alert:
+ {
+ alertQueue.addData(buf, offset, len);
+ processAlert();
+ break;
+ }
+ case ContentType.application_data:
+ {
+ if (!appDataReady)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ applicationDataQueue.addData(buf, offset, len);
+ processApplicationData();
+ break;
+ }
+ case ContentType.change_cipher_spec:
+ {
+ processChangeCipherSpec(buf, offset, len);
+ break;
+ }
+ case ContentType.handshake:
+ {
+ handshakeQueue.addData(buf, offset, len);
+ processHandshake();
+ break;
+ }
+ case ContentType.heartbeat:
+ {
+ if (!appDataReady)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ // TODO[RFC 6520]
+// heartbeatQueue.addData(buf, offset, len);
+// processHeartbeat();
+ }
+ default:
+ /*
+ * Uh, we don't know this protocol.
+ *
+ * RFC2246 defines on page 13, that we should ignore this.
+ */
+ }
+ }
+
+ private void processHandshake()
+ throws IOException
+ {
+ boolean read;
+ do
+ {
+ read = false;
+ /*
+ * We need the first 4 bytes, they contain type and length of the message.
+ */
+ if (handshakeQueue.size() >= 4)
+ {
+ byte[] beginning = new byte[4];
+ handshakeQueue.read(beginning, 0, 4, 0);
+ ByteArrayInputStream bis = new ByteArrayInputStream(beginning);
+ short type = TlsUtils.readUint8(bis);
+ int len = TlsUtils.readUint24(bis);
+
+ /*
+ * Check if we have enough bytes in the buffer to read the full message.
+ */
+ if (handshakeQueue.size() >= (len + 4))
+ {
+ /*
+ * Read the message.
+ */
+ byte[] buf = handshakeQueue.removeData(len, 4);
+
+ /*
+ * RFC 2246 7.4.9. The value handshake_messages includes all handshake messages
+ * starting at client hello up to, but not including, this finished message.
+ * [..] Note: [Also,] Hello Request messages are omitted from handshake hashes.
+ */
+ switch (type)
+ {
+ case HandshakeType.hello_request:
+ break;
+ case HandshakeType.finished:
+ {
+ if (this.expected_verify_data == null)
+ {
+ this.expected_verify_data = createVerifyData(!getContext().isServer());
+ }
+
+ // NB: Fall through to next case label
+ }
+ default:
+ recordStream.updateHandshakeData(beginning, 0, 4);
+ recordStream.updateHandshakeData(buf, 0, len);
+ break;
+ }
+
+ /*
+ * Now, parse the message.
+ */
+ handleHandshakeMessage(type, buf);
+ read = true;
+ }
+ }
+ }
+ while (read);
+ }
+
+ private void processApplicationData()
+ {
+ /*
+ * There is nothing we need to do here.
+ *
+ * This function could be used for callbacks when application data arrives in the future.
+ */
+ }
+
+ private void processAlert()
+ throws IOException
+ {
+ while (alertQueue.size() >= 2)
+ {
+ /*
+ * An alert is always 2 bytes. Read the alert.
+ */
+ byte[] tmp = alertQueue.removeData(2, 0);
+ short level = tmp[0];
+ short description = tmp[1];
+
+ getPeer().notifyAlertReceived(level, description);
+
+ if (level == AlertLevel.fatal)
+ {
+ /*
+ * RFC 2246 7.2.1. The session becomes unresumable if any connection is terminated
+ * without proper close_notify messages with level equal to warning.
+ */
+ invalidateSession();
+
+ this.failedWithError = true;
+ this.closed = true;
+
+ recordStream.safeClose();
+
+ throw new IOException(TLS_ERROR_MESSAGE);
+ }
+ else
+ {
+
+ /*
+ * 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.
+ */
+ // TODO Can close_notify be a fatal alert?
+ if (description == AlertDescription.close_notify)
+ {
+ handleClose(false);
+ }
+
+ /*
+ * If it is just a warning, we continue.
+ */
+ handleWarningMessage(description);
+ }
+ }
+ }
+
+ /**
+ * This method is called, when a change cipher spec message is received.
+ *
+ * @throws IOException If the message has an invalid content or the handshake is not in the correct
+ * state.
+ */
+ private void processChangeCipherSpec(byte[] buf, int off, int len)
+ throws IOException
+ {
+ for (int i = 0; i < len; ++i)
+ {
+ short message = TlsUtils.readUint8(buf, off + i);
+
+ if (message != ChangeCipherSpec.change_cipher_spec)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ if (this.receivedChangeCipherSpec
+ || alertQueue.size() > 0
+ || handshakeQueue.size() > 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ recordStream.receivedReadCipherSpec();
+
+ this.receivedChangeCipherSpec = true;
+
+ handleChangeCipherSpecMessage();
+ }
+ }
+
+ protected int applicationDataAvailable()
+ throws IOException
+ {
+ return applicationDataQueue.size();
+ }
+
+ /**
+ * Read data from the network. The method will return immediately, if there is still some data
+ * left in the buffer, or block until some application data has been read from the network.
+ *
+ * @param buf The buffer where the data will be copied to.
+ * @param offset The position where the data will be placed in the buffer.
+ * @param len The maximum number of bytes to read.
+ * @return The number of bytes read.
+ * @throws IOException If something goes wrong during reading data.
+ */
+ protected int readApplicationData(byte[] buf, int offset, int len)
+ throws IOException
+ {
+ if (len < 1)
+ {
+ return 0;
+ }
+
+ while (applicationDataQueue.size() == 0)
+ {
+ /*
+ * We need to read some data.
+ */
+ if (this.closed)
+ {
+ if (this.failedWithError)
+ {
+ /*
+ * Something went terribly wrong, we should throw an IOException
+ */
+ throw new IOException(TLS_ERROR_MESSAGE);
+ }
+
+ /*
+ * Connection has been closed, there is no more data to read.
+ */
+ return -1;
+ }
+
+ safeReadRecord();
+ }
+
+ len = Math.min(len, applicationDataQueue.size());
+ applicationDataQueue.removeData(buf, offset, len, 0);
+ return len;
+ }
+
+ protected void safeReadRecord()
+ throws IOException
+ {
+ try
+ {
+ if (!recordStream.readRecord())
+ {
+ // TODO It would be nicer to allow graceful connection close if between records
+// this.failWithError(AlertLevel.warning, AlertDescription.close_notify);
+ throw new EOFException();
+ }
+ }
+ catch (TlsFatalAlert e)
+ {
+ if (!this.closed)
+ {
+ this.failWithError(AlertLevel.fatal, e.getAlertDescription(), "Failed to read record", e);
+ }
+ throw e;
+ }
+ catch (IOException e)
+ {
+ if (!this.closed)
+ {
+ this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e);
+ }
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ if (!this.closed)
+ {
+ this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to read record", e);
+ }
+ throw e;
+ }
+ }
+
+ protected void safeWriteRecord(short type, byte[] buf, int offset, int len)
+ throws IOException
+ {
+ try
+ {
+ recordStream.writeRecord(type, buf, offset, len);
+ }
+ catch (TlsFatalAlert e)
+ {
+ if (!this.closed)
+ {
+ this.failWithError(AlertLevel.fatal, e.getAlertDescription(), "Failed to write record", e);
+ }
+ throw e;
+ }
+ catch (IOException e)
+ {
+ if (!closed)
+ {
+ this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to write record", e);
+ }
+ throw e;
+ }
+ catch (RuntimeException e)
+ {
+ if (!closed)
+ {
+ this.failWithError(AlertLevel.fatal, AlertDescription.internal_error, "Failed to write record", e);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Send some application data to the remote system.
+ * <p/>
+ * The method will handle fragmentation internally.
+ *
+ * @param buf The buffer with the data.
+ * @param offset The position in the buffer where the data is placed.
+ * @param len The length of the data.
+ * @throws IOException If something goes wrong during sending.
+ */
+ protected void writeData(byte[] buf, int offset, int len)
+ throws IOException
+ {
+ if (this.closed)
+ {
+ if (this.failedWithError)
+ {
+ throw new IOException(TLS_ERROR_MESSAGE);
+ }
+
+ throw new IOException("Sorry, connection has been closed, you cannot write more data");
+ }
+
+ while (len > 0)
+ {
+ /*
+ * 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 (this.splitApplicationDataRecords)
+ {
+ /*
+ * Protect against known IV attack!
+ *
+ * DO NOT REMOVE THIS CODE, EXCEPT YOU KNOW EXACTLY WHAT YOU ARE DOING HERE.
+ */
+ safeWriteRecord(ContentType.application_data, buf, offset, 1);
+ ++offset;
+ --len;
+ }
+
+ if (len > 0)
+ {
+ // Fragment data according to the current fragment limit.
+ int toWrite = Math.min(len, recordStream.getPlaintextLimit());
+ safeWriteRecord(ContentType.application_data, buf, offset, toWrite);
+ offset += toWrite;
+ len -= toWrite;
+ }
+ }
+ }
+
+ protected void writeHandshakeMessage(byte[] buf, int off, int len) throws IOException
+ {
+ while (len > 0)
+ {
+ // Fragment data according to the current fragment limit.
+ int toWrite = Math.min(len, recordStream.getPlaintextLimit());
+ safeWriteRecord(ContentType.handshake, buf, off, toWrite);
+ off += toWrite;
+ len -= toWrite;
+ }
+ }
+
+ /**
+ * @return An OutputStream which can be used to send data.
+ */
+ public OutputStream getOutputStream()
+ {
+ return this.tlsOutputStream;
+ }
+
+ /**
+ * @return An InputStream which can be used to read data.
+ */
+ public InputStream getInputStream()
+ {
+ return this.tlsInputStream;
+ }
+
+ /**
+ * Terminate this connection with an alert. Can be used for normal closure too.
+ *
+ * @param alertLevel
+ * See {@link AlertLevel} for values.
+ * @param alertDescription
+ * See {@link AlertDescription} for values.
+ * @throws IOException
+ * If alert was fatal.
+ */
+ protected void failWithError(short alertLevel, short alertDescription, String message, Exception cause)
+ throws IOException
+ {
+ /*
+ * Check if the connection is still open.
+ */
+ if (!closed)
+ {
+ /*
+ * Prepare the message
+ */
+ this.closed = true;
+
+ if (alertLevel == AlertLevel.fatal)
+ {
+ /*
+ * 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();
+
+ this.failedWithError = true;
+ }
+ raiseAlert(alertLevel, alertDescription, message, cause);
+ recordStream.safeClose();
+ if (alertLevel != AlertLevel.fatal)
+ {
+ return;
+ }
+ }
+
+ throw new IOException(TLS_ERROR_MESSAGE);
+ }
+
+ protected void invalidateSession()
+ {
+ if (this.sessionParameters != null)
+ {
+ this.sessionParameters.clear();
+ this.sessionParameters = null;
+ }
+
+ if (this.tlsSession != null)
+ {
+ this.tlsSession.invalidate();
+ this.tlsSession = null;
+ }
+ }
+
+ protected void processFinishedMessage(ByteArrayInputStream buf)
+ throws IOException
+ {
+ byte[] verify_data = TlsUtils.readFully(expected_verify_data.length, buf);
+
+ assertEmpty(buf);
+
+ /*
+ * Compare both checksums.
+ */
+ if (!Arrays.constantTimeAreEqual(expected_verify_data, verify_data))
+ {
+ /*
+ * Wrong checksum in the finished message.
+ */
+ throw new TlsFatalAlert(AlertDescription.decrypt_error);
+ }
+ }
+
+ protected void raiseAlert(short alertLevel, short alertDescription, String message, Exception cause)
+ throws IOException
+ {
+ getPeer().notifyAlertRaised(alertLevel, alertDescription, message, cause);
+
+ byte[] error = new byte[2];
+ error[0] = (byte)alertLevel;
+ error[1] = (byte)alertDescription;
+
+ safeWriteRecord(ContentType.alert, error, 0, 2);
+ }
+
+ protected void raiseWarning(short alertDescription, String message)
+ throws IOException
+ {
+ raiseAlert(AlertLevel.warning, alertDescription, message, null);
+ }
+
+ protected void sendCertificateMessage(Certificate certificate)
+ throws IOException
+ {
+ if (certificate == null)
+ {
+ certificate = Certificate.EMPTY_CHAIN;
+ }
+
+ if (certificate.getLength() == 0)
+ {
+ TlsContext context = getContext();
+ if (!context.isServer())
+ {
+ ProtocolVersion serverVersion = getContext().getServerVersion();
+ if (serverVersion.isSSL())
+ {
+ String message = serverVersion.toString() + " client didn't provide credentials";
+ raiseWarning(AlertDescription.no_certificate, message);
+ return;
+ }
+ }
+ }
+
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate);
+
+ certificate.encode(message);
+
+ message.writeToRecordStream();
+ }
+
+ protected void sendChangeCipherSpecMessage()
+ throws IOException
+ {
+ byte[] message = new byte[]{ 1 };
+ safeWriteRecord(ContentType.change_cipher_spec, message, 0, message.length);
+ recordStream.sentWriteCipherSpec();
+ }
+
+ protected void sendFinishedMessage()
+ throws IOException
+ {
+ byte[] verify_data = createVerifyData(getContext().isServer());
+
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.finished, verify_data.length);
+
+ message.write(verify_data);
+
+ message.writeToRecordStream();
+ }
+
+ protected void sendSupplementalDataMessage(Vector supplementalData)
+ throws IOException
+ {
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.supplemental_data);
+
+ writeSupplementalData(message, supplementalData);
+
+ message.writeToRecordStream();
+ }
+
+ protected byte[] createVerifyData(boolean isServer)
+ {
+ TlsContext context = getContext();
+
+ if (isServer)
+ {
+ return TlsUtils.calculateVerifyData(context, ExporterLabel.server_finished,
+ getCurrentPRFHash(getContext(), recordStream.getHandshakeHash(), TlsUtils.SSL_SERVER));
+ }
+
+ return TlsUtils.calculateVerifyData(context, ExporterLabel.client_finished,
+ getCurrentPRFHash(getContext(), recordStream.getHandshakeHash(), TlsUtils.SSL_CLIENT));
+ }
+
+ /**
+ * Closes this connection.
+ *
+ * @throws IOException If something goes wrong during closing.
+ */
+ public void close()
+ throws IOException
+ {
+ handleClose(true);
+ }
+
+ protected void handleClose(boolean user_canceled)
+ throws IOException
+ {
+ if (!closed)
+ {
+ if (user_canceled && !appDataReady)
+ {
+ raiseWarning(AlertDescription.user_canceled, "User canceled handshake");
+ }
+ this.failWithError(AlertLevel.warning, AlertDescription.close_notify, "Connection closed", null);
+ }
+ }
+
+ protected void flush()
+ throws IOException
+ {
+ recordStream.flush();
+ }
+
+ protected short processMaxFragmentLengthExtension(Hashtable clientExtensions, Hashtable serverExtensions, short alertDescription)
+ throws IOException
+ {
+ short maxFragmentLength = TlsExtensionsUtils.getMaxFragmentLengthExtension(serverExtensions);
+ if (maxFragmentLength >= 0 && !this.resumedSession)
+ {
+ if (maxFragmentLength != TlsExtensionsUtils.getMaxFragmentLengthExtension(clientExtensions))
+ {
+ throw new TlsFatalAlert(alertDescription);
+ }
+ }
+ return maxFragmentLength;
+ }
+
+ /**
+ * Make sure the InputStream 'buf' now empty. Fail otherwise.
+ *
+ * @param buf The InputStream to check.
+ * @throws IOException If 'buf' is not empty.
+ */
+ protected static void assertEmpty(ByteArrayInputStream buf)
+ throws IOException
+ {
+ if (buf.available() > 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+ }
+
+ protected static byte[] createRandomBlock(boolean useGMTUnixTime, RandomGenerator randomGenerator)
+ {
+ byte[] result = new byte[32];
+ randomGenerator.nextBytes(result);
+
+ if (useGMTUnixTime)
+ {
+ TlsUtils.writeGMTUnixTime(result, 0);
+ }
+
+ return result;
+ }
+
+ protected static byte[] createRenegotiationInfo(byte[] renegotiated_connection)
+ throws IOException
+ {
+ return TlsUtils.encodeOpaque8(renegotiated_connection);
+ }
+
+ protected static void establishMasterSecret(TlsContext context, TlsKeyExchange keyExchange)
+ throws IOException
+ {
+ byte[] pre_master_secret = keyExchange.generatePremasterSecret();
+
+ try
+ {
+ context.getSecurityParameters().masterSecret = TlsUtils.calculateMasterSecret(context, pre_master_secret);
+ }
+ finally
+ {
+ // TODO Is there a way to ensure the data is really overwritten?
+ /*
+ * RFC 2246 8.1. The pre_master_secret should be deleted from memory once the
+ * master_secret has been computed.
+ */
+ if (pre_master_secret != null)
+ {
+ Arrays.fill(pre_master_secret, (byte)0);
+ }
+ }
+ }
+
+ /**
+ * 'sender' only relevant to SSLv3
+ */
+ protected static byte[] getCurrentPRFHash(TlsContext context, TlsHandshakeHash handshakeHash, byte[] sslSender)
+ {
+ Digest d = handshakeHash.forkPRFHash();
+
+ if (sslSender != null && TlsUtils.isSSL(context))
+ {
+ d.update(sslSender, 0, sslSender.length);
+ }
+
+ byte[] bs = new byte[d.getDigestSize()];
+ d.doFinal(bs, 0);
+ return bs;
+ }
+
+ protected static Hashtable readExtensions(ByteArrayInputStream input)
+ throws IOException
+ {
+ if (input.available() < 1)
+ {
+ return null;
+ }
+
+ byte[] extBytes = TlsUtils.readOpaque16(input);
+
+ assertEmpty(input);
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extBytes);
+
+ // Integer -> byte[]
+ Hashtable extensions = new Hashtable();
+
+ while (buf.available() > 0)
+ {
+ Integer extension_type = Integers.valueOf(TlsUtils.readUint16(buf));
+ byte[] extension_data = TlsUtils.readOpaque16(buf);
+
+ /*
+ * RFC 3546 2.3 There MUST NOT be more than one extension of the same type.
+ */
+ if (null != extensions.put(extension_type, extension_data))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ return extensions;
+ }
+
+ protected static Vector readSupplementalDataMessage(ByteArrayInputStream input)
+ throws IOException
+ {
+ byte[] supp_data = TlsUtils.readOpaque24(input);
+
+ assertEmpty(input);
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(supp_data);
+
+ Vector supplementalData = new Vector();
+
+ while (buf.available() > 0)
+ {
+ int supp_data_type = TlsUtils.readUint16(buf);
+ byte[] data = TlsUtils.readOpaque16(buf);
+
+ supplementalData.addElement(new SupplementalDataEntry(supp_data_type, data));
+ }
+
+ return supplementalData;
+ }
+
+ protected static void writeExtensions(OutputStream output, Hashtable extensions)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ Enumeration keys = extensions.keys();
+ while (keys.hasMoreElements())
+ {
+ Integer key = (Integer)keys.nextElement();
+ int extension_type = key.intValue();
+ byte[] extension_data = (byte[])extensions.get(key);
+
+ TlsUtils.checkUint16(extension_type);
+ TlsUtils.writeUint16(extension_type, buf);
+ TlsUtils.writeOpaque16(extension_data, buf);
+ }
+
+ byte[] extBytes = buf.toByteArray();
+
+ TlsUtils.writeOpaque16(extBytes, output);
+ }
+
+ protected static void writeSupplementalData(OutputStream output, Vector supplementalData)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ for (int i = 0; i < supplementalData.size(); ++i)
+ {
+ SupplementalDataEntry entry = (SupplementalDataEntry)supplementalData.elementAt(i);
+
+ int supp_data_type = entry.getDataType();
+ TlsUtils.checkUint16(supp_data_type);
+ TlsUtils.writeUint16(supp_data_type, buf);
+ TlsUtils.writeOpaque16(entry.getData(), buf);
+ }
+
+ byte[] supp_data = buf.toByteArray();
+
+ TlsUtils.writeOpaque24(supp_data, output);
+ }
+
+ protected static int getPRFAlgorithm(TlsContext context, int ciphersuite) throws IOException
+ {
+ boolean isTLSv12 = TlsUtils.isTLSv12(context);
+
+ switch (ciphersuite)
+ {
+ 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_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_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_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_AES_256_CCM:
+ case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_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_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_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_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_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_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_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_AES_256_CCM:
+ case CipherSuite.TLS_PSK_WITH_AES_256_CCM_8:
+ 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_CAMELLIA_128_GCM_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_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 (isTLSv12)
+ {
+ return PRFAlgorithm.tls_prf_sha256;
+ }
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ 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_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_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_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_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_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_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_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_CAMELLIA_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_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_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_CAMELLIA_256_GCM_SHA384:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_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_CAMELLIA_256_GCM_SHA384:
+ {
+ if (isTLSv12)
+ {
+ 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 (isTLSv12)
+ {
+ return PRFAlgorithm.tls_prf_sha384;
+ }
+ return PRFAlgorithm.tls_prf_legacy;
+ }
+
+ default:
+ {
+ if (isTLSv12)
+ {
+ return PRFAlgorithm.tls_prf_sha256;
+ }
+ return PRFAlgorithm.tls_prf_legacy;
+ }
+ }
+ }
+
+ class HandshakeMessage extends ByteArrayOutputStream
+ {
+ HandshakeMessage(short handshakeType) throws IOException
+ {
+ this(handshakeType, 60);
+ }
+
+ HandshakeMessage(short handshakeType, int length) throws IOException
+ {
+ super(length + 4);
+ TlsUtils.writeUint8(handshakeType, this);
+ // Reserve space for length
+ count += 3;
+ }
+
+ void writeToRecordStream() throws IOException
+ {
+ // Patch actual length back in
+ int length = count - 4;
+ TlsUtils.checkUint24(length);
+ TlsUtils.writeUint24(length, buf, 1);
+ writeHandshakeMessage(buf, 0, count);
+ buf = null;
+ }
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocolHandler.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocolHandler.java
new file mode 100644
index 00000000..49dceb23
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsProtocolHandler.java
@@ -0,0 +1,22 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+
+/**
+ * @deprecated use TlsClientProtocol instead
+ */
+public class TlsProtocolHandler
+ extends TlsClientProtocol
+{
+ public TlsProtocolHandler(InputStream is, OutputStream os)
+ {
+ super(is, os);
+ }
+
+ public TlsProtocolHandler(InputStream is, OutputStream os, SecureRandom sr)
+ {
+ super(is, os, sr);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsRSAKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsRSAKeyExchange.java
new file mode 100644
index 00000000..10cace4b
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsRSAKeyExchange.java
@@ -0,0 +1,191 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Vector;
+
+import org.spongycastle.asn1.x509.KeyUsage;
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+import org.spongycastle.crypto.util.PublicKeyFactory;
+import org.spongycastle.util.io.Streams;
+
+/**
+ * TLS 1.0/1.1 and SSLv3 RSA key exchange.
+ */
+public class TlsRSAKeyExchange
+ extends AbstractTlsKeyExchange
+{
+ protected AsymmetricKeyParameter serverPublicKey = null;
+
+ protected RSAKeyParameters rsaServerPublicKey = null;
+
+ protected TlsEncryptionCredentials serverCredentials = null;
+
+ protected byte[] premasterSecret;
+
+ public TlsRSAKeyExchange(Vector supportedSignatureAlgorithms)
+ {
+ super(KeyExchangeAlgorithm.RSA, supportedSignatureAlgorithms);
+ }
+
+ public void skipServerCredentials()
+ throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ public void processServerCredentials(TlsCredentials serverCredentials)
+ throws IOException
+ {
+ if (!(serverCredentials instanceof TlsEncryptionCredentials))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ processServerCertificate(serverCredentials.getCertificate());
+
+ this.serverCredentials = (TlsEncryptionCredentials)serverCredentials;
+ }
+
+ public void processServerCertificate(Certificate serverCertificate)
+ throws IOException
+ {
+ if (serverCertificate.isEmpty())
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_certificate);
+ }
+
+ org.spongycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+ SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+ try
+ {
+ this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+ }
+ catch (RuntimeException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+ }
+
+ // Sanity check the PublicKeyFactory
+ if (this.serverPublicKey.isPrivate())
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ this.rsaServerPublicKey = validateRSAPublicKey((RSAKeyParameters)this.serverPublicKey);
+
+ TlsUtils.validateKeyUsage(x509Cert, KeyUsage.keyEncipherment);
+
+ super.processServerCertificate(serverCertificate);
+ }
+
+ public void validateCertificateRequest(CertificateRequest certificateRequest)
+ throws IOException
+ {
+ short[] types = certificateRequest.getCertificateTypes();
+ for (int i = 0; i < types.length; ++i)
+ {
+ switch (types[i])
+ {
+ case ClientCertificateType.rsa_sign:
+ case ClientCertificateType.dss_sign:
+ case ClientCertificateType.ecdsa_sign:
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+ }
+
+ public void processClientCredentials(TlsCredentials clientCredentials)
+ throws IOException
+ {
+ if (!(clientCredentials instanceof TlsSignerCredentials))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public void generateClientKeyExchange(OutputStream output)
+ throws IOException
+ {
+ this.premasterSecret = TlsRSAUtils.generateEncryptedPreMasterSecret(context, rsaServerPublicKey, output);
+ }
+
+ public void processClientKeyExchange(InputStream input)
+ throws IOException
+ {
+ byte[] encryptedPreMasterSecret;
+ if (TlsUtils.isSSL(context))
+ {
+ // TODO Do any SSLv3 clients actually include the length?
+ encryptedPreMasterSecret = Streams.readAll(input);
+ }
+ else
+ {
+ encryptedPreMasterSecret = TlsUtils.readOpaque16(input);
+ }
+
+ this.premasterSecret = serverCredentials.decryptPreMasterSecret(encryptedPreMasterSecret);
+ }
+
+ public byte[] generatePremasterSecret()
+ throws IOException
+ {
+ if (this.premasterSecret == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ byte[] tmp = this.premasterSecret;
+ this.premasterSecret = null;
+ return tmp;
+ }
+
+ // Would be needed to process RSA_EXPORT server key exchange
+ // protected void processRSAServerKeyExchange(InputStream is, Signer signer) throws IOException
+ // {
+ // InputStream sigIn = is;
+ // if (signer != null)
+ // {
+ // sigIn = new SignerInputStream(is, signer);
+ // }
+ //
+ // byte[] modulusBytes = TlsUtils.readOpaque16(sigIn);
+ // byte[] exponentBytes = TlsUtils.readOpaque16(sigIn);
+ //
+ // if (signer != null)
+ // {
+ // byte[] sigByte = TlsUtils.readOpaque16(is);
+ //
+ // if (!signer.verifySignature(sigByte))
+ // {
+ // handler.failWithError(AlertLevel.fatal, AlertDescription.bad_certificate);
+ // }
+ // }
+ //
+ // BigInteger modulus = new BigInteger(1, modulusBytes);
+ // BigInteger exponent = new BigInteger(1, exponentBytes);
+ //
+ // this.rsaServerPublicKey = validateRSAPublicKey(new RSAKeyParameters(false, modulus,
+ // exponent));
+ // }
+
+ protected RSAKeyParameters validateRSAPublicKey(RSAKeyParameters key)
+ throws IOException
+ {
+ // TODO What is the minimum bit length required?
+ // key.getModulus().bitLength();
+
+ if (!key.getExponent().isProbablePrime(2))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return key;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsRSASigner.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsRSASigner.java
new file mode 100644
index 00000000..9511b078
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsRSASigner.java
@@ -0,0 +1,112 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.AsymmetricBlockCipher;
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.digests.NullDigest;
+import org.spongycastle.crypto.encodings.PKCS1Encoding;
+import org.spongycastle.crypto.engines.RSABlindedEngine;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+import org.spongycastle.crypto.signers.GenericSigner;
+import org.spongycastle.crypto.signers.RSADigestSigner;
+
+public class TlsRSASigner
+ extends AbstractTlsSigner
+{
+ public byte[] generateRawSignature(SignatureAndHashAlgorithm algorithm,
+ AsymmetricKeyParameter privateKey, byte[] hash)
+ throws CryptoException
+ {
+ Signer signer = makeSigner(algorithm, true, true,
+ new ParametersWithRandom(privateKey, this.context.getSecureRandom()));
+ signer.update(hash, 0, hash.length);
+ return signer.generateSignature();
+ }
+
+ public boolean verifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes,
+ AsymmetricKeyParameter publicKey, byte[] hash)
+ throws CryptoException
+ {
+ Signer signer = makeSigner(algorithm, true, false, publicKey);
+ signer.update(hash, 0, hash.length);
+ return signer.verifySignature(sigBytes);
+ }
+
+ public Signer createSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey)
+ {
+ return makeSigner(algorithm, false, true, new ParametersWithRandom(privateKey, this.context.getSecureRandom()));
+ }
+
+ public Signer createVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey)
+ {
+ return makeSigner(algorithm, false, false, publicKey);
+ }
+
+ public boolean isValidPublicKey(AsymmetricKeyParameter publicKey)
+ {
+ return publicKey instanceof RSAKeyParameters && !publicKey.isPrivate();
+ }
+
+ protected Signer makeSigner(SignatureAndHashAlgorithm algorithm, boolean raw, boolean forSigning,
+ CipherParameters cp)
+ {
+ if ((algorithm != null) != TlsUtils.isTLSv12(context))
+ {
+ throw new IllegalStateException();
+ }
+
+ if (algorithm != null && algorithm.getSignature() != SignatureAlgorithm.rsa)
+ {
+ throw new IllegalStateException();
+ }
+
+ Digest d;
+ if (raw)
+ {
+ d = new NullDigest();
+ }
+ else if (algorithm == null)
+ {
+ d = new CombinedHash();
+ }
+ else
+ {
+ d = TlsUtils.createHash(algorithm.getHash());
+ }
+
+ Signer s;
+ if (algorithm != null)
+ {
+ /*
+ * 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].
+ */
+ s = new RSADigestSigner(d, TlsUtils.getOIDForHashAlgorithm(algorithm.getHash()));
+ }
+ else
+ {
+ /*
+ * RFC 5246 4.7. Note that earlier versions of TLS used a different RSA signature scheme
+ * that did not include a DigestInfo encoding.
+ */
+ s = new GenericSigner(createRSAImpl(), d);
+ }
+ s.init(forSigning, cp);
+ return s;
+ }
+
+ protected AsymmetricBlockCipher createRSAImpl()
+ {
+ /*
+ * RFC 5264 7.4.7.1. Implementation note: It is now known that remote timing-based attacks
+ * on TLS are possible, at least when the client and server are on the same LAN.
+ * Accordingly, implementations that use static RSA keys MUST use RSA blinding or some other
+ * anti-timing technique, as described in [TIMING].
+ */
+ return new PKCS1Encoding(new RSABlindedEngine());
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsRSAUtils.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsRSAUtils.java
new file mode 100644
index 00000000..ecc058b5
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsRSAUtils.java
@@ -0,0 +1,140 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.spongycastle.crypto.InvalidCipherTextException;
+import org.spongycastle.crypto.encodings.PKCS1Encoding;
+import org.spongycastle.crypto.engines.RSABlindedEngine;
+import org.spongycastle.crypto.params.ParametersWithRandom;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+import org.spongycastle.util.Arrays;
+
+public class TlsRSAUtils
+{
+ public static byte[] generateEncryptedPreMasterSecret(TlsContext context, RSAKeyParameters rsaServerPublicKey,
+ OutputStream output) throws IOException
+ {
+ /*
+ * Choose a PremasterSecret and send it encrypted to the server
+ */
+ byte[] premasterSecret = new byte[48];
+ context.getSecureRandom().nextBytes(premasterSecret);
+ TlsUtils.writeVersion(context.getClientVersion(), premasterSecret, 0);
+
+ PKCS1Encoding encoding = new PKCS1Encoding(new RSABlindedEngine());
+ encoding.init(true, new ParametersWithRandom(rsaServerPublicKey, context.getSecureRandom()));
+
+ try
+ {
+ byte[] encryptedPreMasterSecret = encoding.processBlock(premasterSecret, 0, premasterSecret.length);
+
+ if (TlsUtils.isSSL(context))
+ {
+ // TODO Do any SSLv3 servers actually expect the length?
+ output.write(encryptedPreMasterSecret);
+ }
+ else
+ {
+ TlsUtils.writeOpaque16(encryptedPreMasterSecret, output);
+ }
+ }
+ catch (InvalidCipherTextException e)
+ {
+ /*
+ * This should never happen, only during decryption.
+ */
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ return premasterSecret;
+ }
+
+ /**
+ * @deprecated {@link TlsEncryptionCredentials#decryptPreMasterSecret(byte[])} is expected to decrypt safely
+ */
+ public static byte[] safeDecryptPreMasterSecret(TlsContext context, TlsEncryptionCredentials encryptionCredentials,
+ byte[] encryptedPreMasterSecret) throws IOException
+ {
+ return encryptionCredentials.decryptPreMasterSecret(encryptedPreMasterSecret);
+ }
+
+ public static byte[] safeDecryptPreMasterSecret(TlsContext context, RSAKeyParameters rsaServerPrivateKey,
+ byte[] encryptedPreMasterSecret)
+ {
+ /*
+ * RFC 5246 7.4.7.1.
+ */
+ ProtocolVersion clientVersion = context.getClientVersion();
+
+ // TODO Provide as configuration option?
+ boolean 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];
+ context.getSecureRandom().nextBytes(fallback);
+
+ byte[] M = Arrays.clone(fallback);
+ try
+ {
+ PKCS1Encoding encoding = new PKCS1Encoding(new RSABlindedEngine(), fallback);
+ encoding.init(false,
+ new ParametersWithRandom(rsaServerPrivateKey, context.getSecureRandom()));
+
+ M = encoding.processBlock(encryptedPreMasterSecret, 0, encryptedPreMasterSecret.length);
+ }
+ catch (Exception e)
+ {
+ /*
+ * 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.client_version is TLS 1.1 or higher, server implementations MUST
+ * check the version number [..].
+ */
+ if (versionNumberCheckDisabled && clientVersion.isEqualOrEarlierVersionOf(ProtocolVersion.TLSv10))
+ {
+ /*
+ * 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.
+ *
+ * So there is nothing to do here.
+ */
+ }
+ else
+ {
+ /*
+ * OK, we need to compare the version number in the decrypted Pre-Master-Secret with the
+ * clientVersion received during the handshake. If they don't match, we replace the
+ * decrypted Pre-Master-Secret with a random one.
+ */
+ int correct = (clientVersion.getMajorVersion() ^ (M[0] & 0xff))
+ | (clientVersion.getMinorVersion() ^ (M[1] & 0xff));
+ correct |= correct >> 1;
+ correct |= correct >> 2;
+ correct |= correct >> 4;
+ int mask = ~((correct & 1) - 1);
+
+ /*
+ * mask will be all bits set to 0xff if the version number differed.
+ */
+ for (int i = 0; i < 48; i++)
+ {
+ M[i] = (byte)((M[i] & (~mask)) | (fallback[i] & mask));
+ }
+ }
+ return M;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsSRPKeyExchange.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsSRPKeyExchange.java
new file mode 100644
index 00000000..6e9072ed
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsSRPKeyExchange.java
@@ -0,0 +1,205 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.Vector;
+
+import org.spongycastle.asn1.x509.KeyUsage;
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.agreement.srp.SRP6Client;
+import org.spongycastle.crypto.agreement.srp.SRP6Util;
+import org.spongycastle.crypto.digests.SHA1Digest;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.util.PublicKeyFactory;
+import org.spongycastle.util.BigIntegers;
+import org.spongycastle.util.io.TeeInputStream;
+
+/**
+ * TLS 1.1 SRP key exchange (RFC 5054).
+ */
+public class TlsSRPKeyExchange extends AbstractTlsKeyExchange
+{
+ protected TlsSigner tlsSigner;
+ protected byte[] identity;
+ protected byte[] password;
+
+ protected AsymmetricKeyParameter serverPublicKey = null;
+
+ protected byte[] s = null;
+ protected BigInteger B = null;
+ protected SRP6Client srpClient = new SRP6Client();
+
+ public TlsSRPKeyExchange(int keyExchange, Vector supportedSignatureAlgorithms, byte[] identity, byte[] password)
+ {
+ super(keyExchange, supportedSignatureAlgorithms);
+
+ switch (keyExchange)
+ {
+ case KeyExchangeAlgorithm.SRP:
+ this.tlsSigner = null;
+ break;
+ case KeyExchangeAlgorithm.SRP_RSA:
+ this.tlsSigner = new TlsRSASigner();
+ break;
+ case KeyExchangeAlgorithm.SRP_DSS:
+ this.tlsSigner = new TlsDSSSigner();
+ break;
+ default:
+ throw new IllegalArgumentException("unsupported key exchange algorithm");
+ }
+
+ this.keyExchange = keyExchange;
+ this.identity = identity;
+ this.password = password;
+ }
+
+ public void init(TlsContext context)
+ {
+ super.init(context);
+
+ if (this.tlsSigner != null) {
+ this.tlsSigner.init(context);
+ }
+ }
+
+ public void skipServerCredentials() throws IOException
+ {
+ if (tlsSigner != null)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ public void processServerCertificate(Certificate serverCertificate) throws IOException
+ {
+ if (tlsSigner == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ if (serverCertificate.isEmpty())
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_certificate);
+ }
+
+ org.spongycastle.asn1.x509.Certificate x509Cert = serverCertificate.getCertificateAt(0);
+
+ SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+ try
+ {
+ this.serverPublicKey = PublicKeyFactory.createKey(keyInfo);
+ }
+ catch (RuntimeException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+ }
+
+ if (!tlsSigner.isValidPublicKey(this.serverPublicKey))
+ {
+ throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+ }
+
+ TlsUtils.validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+
+ super.processServerCertificate(serverCertificate);
+ }
+
+ public boolean requiresServerKeyExchange()
+ {
+ return true;
+ }
+
+ public void processServerKeyExchange(InputStream input) throws IOException
+ {
+ SecurityParameters securityParameters = context.getSecurityParameters();
+
+ SignerInputBuffer buf = null;
+ InputStream teeIn = input;
+
+ if (tlsSigner != null)
+ {
+ buf = new SignerInputBuffer();
+ teeIn = new TeeInputStream(input, buf);
+ }
+
+ byte[] NBytes = TlsUtils.readOpaque16(teeIn);
+ byte[] gBytes = TlsUtils.readOpaque16(teeIn);
+ byte[] sBytes = TlsUtils.readOpaque8(teeIn);
+ byte[] BBytes = TlsUtils.readOpaque16(teeIn);
+
+ if (buf != null)
+ {
+ DigitallySigned signed_params = DigitallySigned.parse(context, input);
+
+ Signer signer = initVerifyer(tlsSigner, signed_params.getAlgorithm(), securityParameters);
+ buf.updateSigner(signer);
+ if (!signer.verifySignature(signed_params.getSignature()))
+ {
+ throw new TlsFatalAlert(AlertDescription.decrypt_error);
+ }
+ }
+
+ BigInteger N = new BigInteger(1, NBytes);
+ BigInteger g = new BigInteger(1, gBytes);
+
+ // TODO Validate group parameters (see RFC 5054)
+// throw new TlsFatalAlert(AlertDescription.insufficient_security);
+
+ this.s = sBytes;
+
+ /*
+ * RFC 5054 2.5.3: The client MUST abort the handshake with an "illegal_parameter" alert if
+ * B % N = 0.
+ */
+ try
+ {
+ this.B = SRP6Util.validatePublicValue(N, new BigInteger(1, BBytes));
+ }
+ catch (CryptoException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ this.srpClient.init(N, g, TlsUtils.createHash(HashAlgorithm.sha1), context.getSecureRandom());
+ }
+
+ public void validateCertificateRequest(CertificateRequest certificateRequest) throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ public void processClientCredentials(TlsCredentials clientCredentials) throws IOException
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ public void generateClientKeyExchange(OutputStream output) throws IOException
+ {
+ BigInteger A = srpClient.generateClientCredentials(s, this.identity, this.password);
+ TlsUtils.writeOpaque16(BigIntegers.asUnsignedByteArray(A), output);
+ }
+
+ public byte[] generatePremasterSecret() throws IOException
+ {
+ try
+ {
+ // TODO Check if this needs to be a fixed size
+ return BigIntegers.asUnsignedByteArray(srpClient.calculateSecret(B));
+ }
+ catch (CryptoException e)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ }
+
+ protected Signer initVerifyer(TlsSigner tlsSigner, SignatureAndHashAlgorithm algorithm, SecurityParameters securityParameters)
+ {
+ Signer signer = tlsSigner.createVerifyer(algorithm, this.serverPublicKey);
+ signer.update(securityParameters.clientRandom, 0, securityParameters.clientRandom.length);
+ signer.update(securityParameters.serverRandom, 0, securityParameters.serverRandom.length);
+ return signer;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsSRPUtils.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsSRPUtils.java
new file mode 100644
index 00000000..48ddefb7
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsSRPUtils.java
@@ -0,0 +1,48 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.spongycastle.util.Integers;
+
+public class TlsSRPUtils
+{
+ public static final Integer EXT_SRP = Integers.valueOf(ExtensionType.srp);
+
+ public static void addSRPExtension(Hashtable extensions, byte[] identity) throws IOException
+ {
+ extensions.put(EXT_SRP, createSRPExtension(identity));
+ }
+
+ public static byte[] getSRPExtension(Hashtable extensions) throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_SRP);
+ return extensionData == null ? null : readSRPExtension(extensionData);
+ }
+
+ public static byte[] createSRPExtension(byte[] identity) throws IOException
+ {
+ if (identity == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ return TlsUtils.encodeOpaque8(identity);
+ }
+
+ public static byte[] readSRPExtension(byte[] extensionData) throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extensionData);
+ byte[] identity = TlsUtils.readOpaque8(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ return identity;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsSRTPUtils.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsSRTPUtils.java
new file mode 100644
index 00000000..edc48795
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsSRTPUtils.java
@@ -0,0 +1,74 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Hashtable;
+
+import org.spongycastle.util.Integers;
+
+/**
+ * RFC 5764 DTLS Extension to Establish Keys for SRTP.
+ */
+public class TlsSRTPUtils
+{
+ public static final Integer EXT_use_srtp = Integers.valueOf(ExtensionType.use_srtp);
+
+ public static void addUseSRTPExtension(Hashtable extensions, UseSRTPData useSRTPData)
+ throws IOException
+ {
+ extensions.put(EXT_use_srtp, createUseSRTPExtension(useSRTPData));
+ }
+
+ public static UseSRTPData getUseSRTPExtension(Hashtable extensions)
+ throws IOException
+ {
+ byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_use_srtp);
+ return extensionData == null ? null : readUseSRTPExtension(extensionData);
+ }
+
+ public static byte[] createUseSRTPExtension(UseSRTPData useSRTPData)
+ throws IOException
+ {
+ if (useSRTPData == null)
+ {
+ throw new IllegalArgumentException("'useSRTPData' cannot be null");
+ }
+
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ // SRTPProtectionProfiles
+ TlsUtils.writeUint16ArrayWithUint16Length(useSRTPData.getProtectionProfiles(), buf);
+
+ // srtp_mki
+ TlsUtils.writeOpaque8(useSRTPData.getMki(), buf);
+
+ return buf.toByteArray();
+ }
+
+ public static UseSRTPData readUseSRTPExtension(byte[] extensionData)
+ throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extensionData);
+
+ // SRTPProtectionProfiles
+ int length = TlsUtils.readUint16(buf);
+ if (length < 2 || (length & 1) != 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+ int[] protectionProfiles = TlsUtils.readUint16Array(length / 2, buf);
+
+ // srtp_mki
+ byte[] mki = TlsUtils.readOpaque8(buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ return new UseSRTPData(protectionProfiles, mki);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsServer.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsServer.java
new file mode 100644
index 00000000..225963f3
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsServer.java
@@ -0,0 +1,90 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.util.Hashtable;
+import java.util.Vector;
+
+public interface TlsServer
+ extends TlsPeer
+{
+ void init(TlsServerContext context);
+
+ void notifyClientVersion(ProtocolVersion clientVersion) throws IOException;
+
+ void notifyOfferedCipherSuites(int[] offeredCipherSuites)
+ throws IOException;
+
+ void notifyOfferedCompressionMethods(short[] offeredCompressionMethods)
+ throws IOException;
+
+ // Hashtable is (Integer -> byte[])
+ void processClientExtensions(Hashtable clientExtensions)
+ throws IOException;
+
+ ProtocolVersion getServerVersion()
+ throws IOException;
+
+ int getSelectedCipherSuite()
+ throws IOException;
+
+ short getSelectedCompressionMethod()
+ throws IOException;
+
+ // Hashtable is (Integer -> byte[])
+ Hashtable getServerExtensions()
+ throws IOException;
+
+ // Vector is (SupplementalDataEntry)
+ Vector getServerSupplementalData()
+ throws IOException;
+
+ TlsCredentials getCredentials()
+ throws IOException;
+
+ /**
+ * This method will be called (only) if the server included an extension of type
+ * "status_request" with empty "extension_data" in the extended server hello. See <i>RFC 3546
+ * 3.6. Certificate Status Request</i>. If a non-null {@link CertificateStatus} is returned, it
+ * is sent to the client as a handshake message of type "certificate_status".
+ *
+ * @return A {@link CertificateStatus} to be sent to the client (or null for none).
+ * @throws IOException
+ */
+ CertificateStatus getCertificateStatus()
+ throws IOException;
+
+ TlsKeyExchange getKeyExchange()
+ throws IOException;
+
+ CertificateRequest getCertificateRequest()
+ throws IOException;
+
+ // Vector is (SupplementalDataEntry)
+ void processClientSupplementalData(Vector clientSupplementalData)
+ throws IOException;
+
+ /**
+ * Called by the protocol handler to report the client certificate, only if
+ * {@link #getCertificateRequest()} returned non-null.
+ *
+ * Note: this method is responsible for certificate verification and validation.
+ *
+ * @param clientCertificate
+ * the effective client certificate (may be an empty chain).
+ * @throws IOException
+ */
+ void notifyClientCertificate(Certificate clientCertificate)
+ throws IOException;
+
+ /**
+ * RFC 5077 3.3. NewSessionTicket Handshake Message.
+ * <p>
+ * This method will be called (only) if a NewSessionTicket extension was sent by the server. See
+ * <i>RFC 5077 4. Recommended Ticket Construction</i> for recommended format and protection.
+ *
+ * @return The ticket.
+ * @throws IOException
+ */
+ NewSessionTicket getNewSessionTicket()
+ throws IOException;
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsServerContext.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsServerContext.java
new file mode 100644
index 00000000..48480f36
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsServerContext.java
@@ -0,0 +1,6 @@
+package org.spongycastle.crypto.tls;
+
+public interface TlsServerContext
+ extends TlsContext
+{
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsServerContextImpl.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsServerContextImpl.java
new file mode 100644
index 00000000..27063350
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsServerContextImpl.java
@@ -0,0 +1,18 @@
+package org.spongycastle.crypto.tls;
+
+import java.security.SecureRandom;
+
+class TlsServerContextImpl
+ extends AbstractTlsContext
+ implements TlsServerContext
+{
+ TlsServerContextImpl(SecureRandom secureRandom, SecurityParameters securityParameters)
+ {
+ super(secureRandom, securityParameters);
+ }
+
+ public boolean isServer()
+ {
+ return true;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsServerProtocol.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsServerProtocol.java
new file mode 100644
index 00000000..b4e3b04c
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsServerProtocol.java
@@ -0,0 +1,778 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Vector;
+
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.util.PublicKeyFactory;
+import org.spongycastle.util.Arrays;
+
+public class TlsServerProtocol
+ extends TlsProtocol
+{
+ protected TlsServer tlsServer = null;
+ protected TlsServerContextImpl tlsServerContext = null;
+
+ protected TlsKeyExchange keyExchange = null;
+ protected TlsCredentials serverCredentials = null;
+ protected CertificateRequest certificateRequest = null;
+
+ protected short clientCertificateType = -1;
+ protected TlsHandshakeHash prepareFinishHash = null;
+
+ public TlsServerProtocol(InputStream input, OutputStream output, SecureRandom secureRandom)
+ {
+ super(input, output, secureRandom);
+ }
+
+ /**
+ * Receives a TLS handshake in the role of server
+ *
+ * @param tlsServer
+ * @throws IOException If handshake was not successful.
+ */
+ public void accept(TlsServer tlsServer)
+ throws IOException
+ {
+ if (tlsServer == null)
+ {
+ throw new IllegalArgumentException("'tlsServer' cannot be null");
+ }
+ if (this.tlsServer != null)
+ {
+ throw new IllegalStateException("'accept' can only be called once");
+ }
+
+ this.tlsServer = tlsServer;
+
+ this.securityParameters = new SecurityParameters();
+ this.securityParameters.entity = ConnectionEnd.server;
+
+ this.tlsServerContext = new TlsServerContextImpl(secureRandom, securityParameters);
+
+ this.securityParameters.serverRandom = createRandomBlock(tlsServer.shouldUseGMTUnixTime(),
+ tlsServerContext.getNonceRandomGenerator());
+
+ this.tlsServer.init(tlsServerContext);
+ this.recordStream.init(tlsServerContext);
+
+ this.recordStream.setRestrictReadVersion(false);
+
+ completeHandshake();
+ }
+
+ protected void cleanupHandshake()
+ {
+ super.cleanupHandshake();
+
+ this.keyExchange = null;
+ this.serverCredentials = null;
+ this.certificateRequest = null;
+ this.prepareFinishHash = null;
+ }
+
+ protected AbstractTlsContext getContext()
+ {
+ return tlsServerContext;
+ }
+
+ protected TlsPeer getPeer()
+ {
+ return tlsServer;
+ }
+
+ protected void handleHandshakeMessage(short type, byte[] data)
+ throws IOException
+ {
+ ByteArrayInputStream buf = new ByteArrayInputStream(data);
+
+ switch (type)
+ {
+ case HandshakeType.client_hello:
+ {
+ switch (this.connection_state)
+ {
+ case CS_START:
+ {
+ receiveClientHelloMessage(buf);
+ this.connection_state = CS_CLIENT_HELLO;
+
+ sendServerHelloMessage();
+ this.connection_state = CS_SERVER_HELLO;
+
+ Vector serverSupplementalData = tlsServer.getServerSupplementalData();
+ if (serverSupplementalData != null)
+ {
+ sendSupplementalDataMessage(serverSupplementalData);
+ }
+ this.connection_state = CS_SERVER_SUPPLEMENTAL_DATA;
+
+ this.keyExchange = tlsServer.getKeyExchange();
+ this.keyExchange.init(getContext());
+
+ this.serverCredentials = tlsServer.getCredentials();
+
+ Certificate serverCertificate = null;
+
+ if (this.serverCredentials == null)
+ {
+ this.keyExchange.skipServerCredentials();
+ }
+ else
+ {
+ this.keyExchange.processServerCredentials(this.serverCredentials);
+
+ serverCertificate = this.serverCredentials.getCertificate();
+ sendCertificateMessage(serverCertificate);
+ }
+ this.connection_state = CS_SERVER_CERTIFICATE;
+
+ // TODO[RFC 3546] Check whether empty certificates is possible, allowed, or excludes CertificateStatus
+ if (serverCertificate == null || serverCertificate.isEmpty())
+ {
+ this.allowCertificateStatus = false;
+ }
+
+ if (this.allowCertificateStatus)
+ {
+ CertificateStatus certificateStatus = tlsServer.getCertificateStatus();
+ if (certificateStatus != null)
+ {
+ sendCertificateStatusMessage(certificateStatus);
+ }
+ }
+
+ this.connection_state = CS_CERTIFICATE_STATUS;
+
+ byte[] serverKeyExchange = this.keyExchange.generateServerKeyExchange();
+ if (serverKeyExchange != null)
+ {
+ sendServerKeyExchangeMessage(serverKeyExchange);
+ }
+ this.connection_state = CS_SERVER_KEY_EXCHANGE;
+
+ if (this.serverCredentials != null)
+ {
+ this.certificateRequest = tlsServer.getCertificateRequest();
+ if (this.certificateRequest != null)
+ {
+ this.keyExchange.validateCertificateRequest(certificateRequest);
+
+ sendCertificateRequestMessage(certificateRequest);
+
+ TlsUtils.trackHashAlgorithms(this.recordStream.getHandshakeHash(),
+ this.certificateRequest.getSupportedSignatureAlgorithms());
+ }
+ }
+ this.connection_state = CS_CERTIFICATE_REQUEST;
+
+ sendServerHelloDoneMessage();
+ this.connection_state = CS_SERVER_HELLO_DONE;
+
+ this.recordStream.getHandshakeHash().sealHashAlgorithms();
+
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.supplemental_data:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_HELLO_DONE:
+ {
+ tlsServer.processClientSupplementalData(readSupplementalDataMessage(buf));
+ this.connection_state = CS_CLIENT_SUPPLEMENTAL_DATA;
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.certificate:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_HELLO_DONE:
+ {
+ tlsServer.processClientSupplementalData(null);
+ // NB: Fall through to next case label
+ }
+ case CS_CLIENT_SUPPLEMENTAL_DATA:
+ {
+ if (this.certificateRequest == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ receiveCertificateMessage(buf);
+ this.connection_state = CS_CLIENT_CERTIFICATE;
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.client_key_exchange:
+ {
+ switch (this.connection_state)
+ {
+ case CS_SERVER_HELLO_DONE:
+ {
+ tlsServer.processClientSupplementalData(null);
+ // NB: Fall through to next case label
+ }
+ case CS_CLIENT_SUPPLEMENTAL_DATA:
+ {
+ if (this.certificateRequest == null)
+ {
+ this.keyExchange.skipClientCredentials();
+ }
+ else
+ {
+ if (TlsUtils.isTLSv12(getContext()))
+ {
+ /*
+ * 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 (TlsUtils.isSSL(getContext()))
+ {
+ if (this.peerCertificate == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+ else
+ {
+ notifyClientCertificate(Certificate.EMPTY_CHAIN);
+ }
+ }
+ // NB: Fall through to next case label
+ }
+ case CS_CLIENT_CERTIFICATE:
+ {
+ receiveClientKeyExchangeMessage(buf);
+ this.connection_state = CS_CLIENT_KEY_EXCHANGE;
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.certificate_verify:
+ {
+ switch (this.connection_state)
+ {
+ case CS_CLIENT_KEY_EXCHANGE:
+ {
+ /*
+ * RFC 5246 7.4.8 This message is only sent following a client certificate that has
+ * signing capability (i.e., all certificates except those containing fixed
+ * Diffie-Hellman parameters).
+ */
+ if (!expectCertificateVerifyMessage())
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ receiveCertificateVerifyMessage(buf);
+ this.connection_state = CS_CERTIFICATE_VERIFY;
+
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.finished:
+ {
+ switch (this.connection_state)
+ {
+ case CS_CLIENT_KEY_EXCHANGE:
+ {
+ if (expectCertificateVerifyMessage())
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ // NB: Fall through to next case label
+ }
+ case CS_CERTIFICATE_VERIFY:
+ {
+ processFinishedMessage(buf);
+ this.connection_state = CS_CLIENT_FINISHED;
+
+ if (this.expectSessionTicket)
+ {
+ sendNewSessionTicketMessage(tlsServer.getNewSessionTicket());
+ sendChangeCipherSpecMessage();
+ }
+ this.connection_state = CS_SERVER_SESSION_TICKET;
+
+ sendFinishedMessage();
+ this.connection_state = CS_SERVER_FINISHED;
+ this.connection_state = CS_END;
+ break;
+ }
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ break;
+ }
+ case HandshakeType.hello_request:
+ case HandshakeType.hello_verify_request:
+ case HandshakeType.server_hello:
+ case HandshakeType.server_key_exchange:
+ case HandshakeType.certificate_request:
+ case HandshakeType.server_hello_done:
+ case HandshakeType.session_ticket:
+ default:
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+ }
+
+ protected void handleWarningMessage(short description)
+ throws IOException
+ {
+ switch (description)
+ {
+ case AlertDescription.no_certificate:
+ {
+ /*
+ * SSL 3.0 If the server has sent a certificate request Message, the client must send
+ * either the certificate message or a no_certificate alert.
+ */
+ if (TlsUtils.isSSL(getContext()) && certificateRequest != null)
+ {
+ notifyClientCertificate(Certificate.EMPTY_CHAIN);
+ }
+ break;
+ }
+ default:
+ {
+ super.handleWarningMessage(description);
+ }
+ }
+ }
+
+ protected void notifyClientCertificate(Certificate clientCertificate)
+ throws IOException
+ {
+ if (certificateRequest == null)
+ {
+ throw new IllegalStateException();
+ }
+
+ if (this.peerCertificate != null)
+ {
+ throw new TlsFatalAlert(AlertDescription.unexpected_message);
+ }
+
+ this.peerCertificate = clientCertificate;
+
+ if (clientCertificate.isEmpty())
+ {
+ this.keyExchange.skipClientCredentials();
+ }
+ else
+ {
+
+ /*
+ * TODO RFC 5246 7.4.6. If the certificate_authorities list in the certificate request
+ * message was non-empty, one of the certificates in the certificate chain SHOULD be
+ * issued by one of the listed CAs.
+ */
+
+ this.clientCertificateType = TlsUtils.getClientCertificateType(clientCertificate,
+ this.serverCredentials.getCertificate());
+
+ this.keyExchange.processClientCertificate(clientCertificate);
+ }
+
+ /*
+ * RFC 5246 7.4.6. If the client does not send any certificates, the server MAY at its
+ * discretion either continue the handshake without client authentication, or respond with a
+ * fatal handshake_failure alert. Also, if some aspect of the certificate chain was
+ * unacceptable (e.g., it was not signed by a known, trusted CA), the server MAY at its
+ * discretion either continue the handshake (considering the client unauthenticated) or send
+ * a fatal alert.
+ */
+ this.tlsServer.notifyClientCertificate(clientCertificate);
+ }
+
+ protected void receiveCertificateMessage(ByteArrayInputStream buf)
+ throws IOException
+ {
+ Certificate clientCertificate = Certificate.parse(buf);
+
+ assertEmpty(buf);
+
+ notifyClientCertificate(clientCertificate);
+ }
+
+ protected void receiveCertificateVerifyMessage(ByteArrayInputStream buf)
+ throws IOException
+ {
+ DigitallySigned clientCertificateVerify = DigitallySigned.parse(getContext(), buf);
+
+ assertEmpty(buf);
+
+ // Verify the CertificateVerify message contains a correct signature.
+ boolean verified = false;
+ try
+ {
+ byte[] certificateVerifyHash;
+ if (TlsUtils.isTLSv12(getContext()))
+ {
+ certificateVerifyHash = prepareFinishHash.getFinalHash(clientCertificateVerify.getAlgorithm().getHash());
+ }
+ else
+ {
+ certificateVerifyHash = TlsProtocol.getCurrentPRFHash(getContext(), prepareFinishHash, null);
+ }
+
+ org.spongycastle.asn1.x509.Certificate x509Cert = this.peerCertificate.getCertificateAt(0);
+ SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+ AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo);
+
+ TlsSigner tlsSigner = TlsUtils.createTlsSigner(this.clientCertificateType);
+ tlsSigner.init(getContext());
+ verified = tlsSigner.verifyRawSignature(clientCertificateVerify.getAlgorithm(),
+ clientCertificateVerify.getSignature(), publicKey, certificateVerifyHash);
+ }
+ catch (Exception e)
+ {
+ }
+
+ if (!verified)
+ {
+ throw new TlsFatalAlert(AlertDescription.decrypt_error);
+ }
+ }
+
+ protected void receiveClientHelloMessage(ByteArrayInputStream buf)
+ throws IOException
+ {
+ ProtocolVersion client_version = TlsUtils.readVersion(buf);
+ if (client_version.isDTLS())
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ byte[] client_random = TlsUtils.readFully(32, buf);
+
+ /*
+ * TODO RFC 5077 3.4. If a ticket is presented by the client, the server MUST NOT attempt to
+ * use the Session ID in the ClientHello for stateful session resumption.
+ */
+ byte[] sessionID = TlsUtils.readOpaque8(buf);
+ if (sessionID.length > 32)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ /*
+ * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session
+ * resumption request), this vector MUST include at least the cipher_suite from that
+ * session.
+ */
+ int cipher_suites_length = TlsUtils.readUint16(buf);
+ if (cipher_suites_length < 2 || (cipher_suites_length & 1) != 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+ this.offeredCipherSuites = TlsUtils.readUint16Array(cipher_suites_length / 2, buf);
+
+ /*
+ * TODO RFC 5246 7.4.1.2. If the session_id field is not empty (implying a session
+ * resumption request), it MUST include the compression_method from that session.
+ */
+ int compression_methods_length = TlsUtils.readUint8(buf);
+ if (compression_methods_length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ this.offeredCompressionMethods = TlsUtils.readUint8Array(compression_methods_length, buf);
+
+ /*
+ * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+ * extensions appearing in the client hello, and send a server hello containing no
+ * extensions.
+ */
+ this.clientExtensions = readExtensions(buf);
+
+ getContext().setClientVersion(client_version);
+
+ tlsServer.notifyClientVersion(client_version);
+
+ securityParameters.clientRandom = client_random;
+
+ tlsServer.notifyOfferedCipherSuites(offeredCipherSuites);
+ tlsServer.notifyOfferedCompressionMethods(offeredCompressionMethods);
+
+ /*
+ * 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(offeredCipherSuites, CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV))
+ {
+ this.secure_renegotiation = true;
+ }
+
+ /*
+ * The server MUST check if the "renegotiation_info" extension is included in the
+ * ClientHello.
+ */
+ byte[] renegExtData = TlsUtils.getExtensionData(clientExtensions, EXT_RenegotiationInfo);
+ if (renegExtData != null)
+ {
+ /*
+ * If the extension is present, set secure_renegotiation flag to TRUE. The
+ * server MUST then verify that the length of the "renegotiated_connection"
+ * field is zero, and if it is not, MUST abort the handshake.
+ */
+ this.secure_renegotiation = true;
+
+ if (!Arrays.constantTimeAreEqual(renegExtData, createRenegotiationInfo(TlsUtils.EMPTY_BYTES)))
+ {
+ throw new TlsFatalAlert(AlertDescription.handshake_failure);
+ }
+ }
+ }
+
+ tlsServer.notifySecureRenegotiation(this.secure_renegotiation);
+
+ if (clientExtensions != null)
+ {
+ tlsServer.processClientExtensions(clientExtensions);
+ }
+ }
+
+ protected void receiveClientKeyExchangeMessage(ByteArrayInputStream buf)
+ throws IOException
+ {
+ this.keyExchange.processClientKeyExchange(buf);
+
+ assertEmpty(buf);
+
+ establishMasterSecret(getContext(), keyExchange);
+ recordStream.setPendingConnectionState(getPeer().getCompression(), getPeer().getCipher());
+
+ this.prepareFinishHash = recordStream.prepareToFinish();
+
+ if (!expectSessionTicket)
+ {
+ sendChangeCipherSpecMessage();
+ }
+ }
+
+ protected void sendCertificateRequestMessage(CertificateRequest certificateRequest)
+ throws IOException
+ {
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_request);
+
+ certificateRequest.encode(message);
+
+ message.writeToRecordStream();
+ }
+
+ protected void sendCertificateStatusMessage(CertificateStatus certificateStatus)
+ throws IOException
+ {
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.certificate_status);
+
+ certificateStatus.encode(message);
+
+ message.writeToRecordStream();
+ }
+
+ protected void sendNewSessionTicketMessage(NewSessionTicket newSessionTicket)
+ throws IOException
+ {
+ if (newSessionTicket == null)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.session_ticket);
+
+ newSessionTicket.encode(message);
+
+ message.writeToRecordStream();
+ }
+
+ protected void sendServerHelloMessage()
+ throws IOException
+ {
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.server_hello);
+
+ ProtocolVersion server_version = tlsServer.getServerVersion();
+ if (!server_version.isEqualOrEarlierVersionOf(getContext().getClientVersion()))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ recordStream.setReadVersion(server_version);
+ recordStream.setWriteVersion(server_version);
+ recordStream.setRestrictReadVersion(true);
+ getContext().setServerVersion(server_version);
+
+ TlsUtils.writeVersion(server_version, message);
+
+ message.write(this.securityParameters.serverRandom);
+
+ /*
+ * The server may return an empty session_id to indicate that the session will not be cached
+ * and therefore cannot be resumed.
+ */
+ TlsUtils.writeOpaque8(TlsUtils.EMPTY_BYTES, message);
+
+ int selectedCipherSuite = tlsServer.getSelectedCipherSuite();
+ if (!Arrays.contains(this.offeredCipherSuites, selectedCipherSuite)
+ || selectedCipherSuite == CipherSuite.TLS_NULL_WITH_NULL_NULL
+ || selectedCipherSuite == CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV
+ || !TlsUtils.isValidCipherSuiteForVersion(selectedCipherSuite, server_version))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ securityParameters.cipherSuite = selectedCipherSuite;
+
+ short selectedCompressionMethod = tlsServer.getSelectedCompressionMethod();
+ if (!Arrays.contains(this.offeredCompressionMethods, selectedCompressionMethod))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ securityParameters.compressionAlgorithm = selectedCompressionMethod;
+
+ TlsUtils.writeUint16(selectedCipherSuite, message);
+ TlsUtils.writeUint8(selectedCompressionMethod, message);
+
+ this.serverExtensions = tlsServer.getServerExtensions();
+
+ /*
+ * RFC 5746 3.6. Server Behavior: Initial Handshake
+ */
+ if (this.secure_renegotiation)
+ {
+ byte[] renegExtData = TlsUtils.getExtensionData(this.serverExtensions, EXT_RenegotiationInfo);
+ boolean noRenegExt = (null == renegExtData);
+
+ if (noRenegExt)
+ {
+ /*
+ * Note that sending a "renegotiation_info" extension in response to a ClientHello
+ * containing only the SCSV is an explicit exception to the prohibition in RFC 5246,
+ * Section 7.4.1.4, on the server sending unsolicited extensions and is only allowed
+ * because the client is signaling its willingness to receive the extension via the
+ * TLS_EMPTY_RENEGOTIATION_INFO_SCSV SCSV.
+ */
+
+ /*
+ * If the secure_renegotiation flag is set to TRUE, the server MUST include an empty
+ * "renegotiation_info" extension in the ServerHello message.
+ */
+ this.serverExtensions = TlsExtensionsUtils.ensureExtensionsInitialised(this.serverExtensions);
+ this.serverExtensions.put(EXT_RenegotiationInfo, createRenegotiationInfo(TlsUtils.EMPTY_BYTES));
+ }
+ }
+
+ /*
+ * TODO RFC 3546 2.3 If [...] the older session is resumed, then the server MUST ignore
+ * extensions appearing in the client hello, and send a server hello containing no
+ * extensions.
+ */
+
+ if (this.serverExtensions != null)
+ {
+ this.securityParameters.encryptThenMAC = TlsExtensionsUtils.hasEncryptThenMACExtension(this.serverExtensions);
+
+ this.securityParameters.maxFragmentLength = processMaxFragmentLengthExtension(clientExtensions,
+ this.serverExtensions, AlertDescription.internal_error);
+
+ this.securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(this.serverExtensions);
+
+ /*
+ * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in
+ * a session resumption handshake.
+ */
+ this.allowCertificateStatus = !this.resumedSession
+ && TlsUtils.hasExpectedEmptyExtensionData(this.serverExtensions, TlsExtensionsUtils.EXT_status_request,
+ AlertDescription.internal_error);
+
+ this.expectSessionTicket = !this.resumedSession
+ && TlsUtils.hasExpectedEmptyExtensionData(this.serverExtensions, TlsProtocol.EXT_SessionTicket,
+ AlertDescription.internal_error);
+
+ writeExtensions(message, this.serverExtensions);
+ }
+
+ if (this.securityParameters.maxFragmentLength >= 0)
+ {
+ int plainTextLimit = 1 << (8 + this.securityParameters.maxFragmentLength);
+ recordStream.setPlaintextLimit(plainTextLimit);
+ }
+
+ securityParameters.prfAlgorithm = getPRFAlgorithm(getContext(), securityParameters.getCipherSuite());
+
+ /*
+ * RFC 5264 7.4.9. Any cipher suite which does not explicitly specify verify_data_length has
+ * a verify_data_length equal to 12. This includes all existing cipher suites.
+ */
+ securityParameters.verifyDataLength = 12;
+
+ message.writeToRecordStream();
+
+ this.recordStream.notifyHelloComplete();
+ }
+
+ protected void sendServerHelloDoneMessage()
+ throws IOException
+ {
+ byte[] message = new byte[4];
+ TlsUtils.writeUint8(HandshakeType.server_hello_done, message, 0);
+ TlsUtils.writeUint24(0, message, 1);
+
+ writeHandshakeMessage(message, 0, message.length);
+ }
+
+ protected void sendServerKeyExchangeMessage(byte[] serverKeyExchange)
+ throws IOException
+ {
+ HandshakeMessage message = new HandshakeMessage(HandshakeType.server_key_exchange, serverKeyExchange.length);
+
+ message.write(serverKeyExchange);
+
+ message.writeToRecordStream();
+ }
+
+ protected boolean expectCertificateVerifyMessage()
+ {
+ return this.clientCertificateType >= 0 && TlsUtils.hasSigningCapability(this.clientCertificateType);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsSession.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsSession.java
new file mode 100644
index 00000000..6d30e442
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsSession.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+public interface TlsSession
+{
+ SessionParameters exportSessionParameters();
+
+ byte[] getSessionID();
+
+ void invalidate();
+
+ boolean isResumable();
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsSessionImpl.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsSessionImpl.java
new file mode 100644
index 00000000..af11bf08
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsSessionImpl.java
@@ -0,0 +1,48 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.util.Arrays;
+
+class TlsSessionImpl implements TlsSession
+{
+ final byte[] sessionID;
+ SessionParameters sessionParameters;
+
+ TlsSessionImpl(byte[] sessionID, SessionParameters sessionParameters)
+ {
+ if (sessionID == null)
+ {
+ throw new IllegalArgumentException("'sessionID' cannot be null");
+ }
+ if (sessionID.length < 1 || sessionID.length > 32)
+ {
+ throw new IllegalArgumentException("'sessionID' must have length between 1 and 32 bytes, inclusive");
+ }
+
+ this.sessionID = Arrays.clone(sessionID);
+ this.sessionParameters = sessionParameters;
+ }
+
+ public synchronized SessionParameters exportSessionParameters()
+ {
+ return this.sessionParameters == null ? null : this.sessionParameters.copy();
+ }
+
+ public synchronized byte[] getSessionID()
+ {
+ return sessionID;
+ }
+
+ public synchronized void invalidate()
+ {
+ if (this.sessionParameters != null)
+ {
+ this.sessionParameters.clear();
+ this.sessionParameters = null;
+ }
+ }
+
+ public synchronized boolean isResumable()
+ {
+ return this.sessionParameters != null;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsSigner.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsSigner.java
new file mode 100644
index 00000000..021155e3
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsSigner.java
@@ -0,0 +1,34 @@
+package org.spongycastle.crypto.tls;
+
+import org.spongycastle.crypto.CryptoException;
+import org.spongycastle.crypto.Signer;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+
+public interface TlsSigner
+{
+ void init(TlsContext context);
+
+ byte[] generateRawSignature(AsymmetricKeyParameter privateKey, byte[] md5AndSha1)
+ throws CryptoException;
+
+ byte[] generateRawSignature(SignatureAndHashAlgorithm algorithm,
+ AsymmetricKeyParameter privateKey, byte[] hash)
+ throws CryptoException;
+
+ boolean verifyRawSignature(byte[] sigBytes, AsymmetricKeyParameter publicKey, byte[] md5AndSha1)
+ throws CryptoException;
+
+ boolean verifyRawSignature(SignatureAndHashAlgorithm algorithm, byte[] sigBytes,
+ AsymmetricKeyParameter publicKey, byte[] hash)
+ throws CryptoException;
+
+ Signer createSigner(AsymmetricKeyParameter privateKey);
+
+ Signer createSigner(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter privateKey);
+
+ Signer createVerifyer(AsymmetricKeyParameter publicKey);
+
+ Signer createVerifyer(SignatureAndHashAlgorithm algorithm, AsymmetricKeyParameter publicKey);
+
+ boolean isValidPublicKey(AsymmetricKeyParameter publicKey);
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsSignerCredentials.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsSignerCredentials.java
new file mode 100644
index 00000000..da1a91cd
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsSignerCredentials.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+public interface TlsSignerCredentials
+ extends TlsCredentials
+{
+ byte[] generateCertificateSignature(byte[] hash)
+ throws IOException;
+
+ SignatureAndHashAlgorithm getSignatureAndHashAlgorithm();
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsStreamCipher.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsStreamCipher.java
new file mode 100644
index 00000000..6394b391
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsStreamCipher.java
@@ -0,0 +1,178 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+
+import org.spongycastle.crypto.CipherParameters;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.StreamCipher;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.crypto.params.ParametersWithIV;
+import org.spongycastle.util.Arrays;
+
+public class TlsStreamCipher
+ implements TlsCipher
+{
+ protected TlsContext context;
+
+ protected StreamCipher encryptCipher;
+ protected StreamCipher decryptCipher;
+
+ protected TlsMac writeMac;
+ protected TlsMac readMac;
+
+ protected boolean usesNonce;
+
+ /**
+ * @deprecated Use version with additional 'usesNonce' argument
+ */
+ public TlsStreamCipher(TlsContext context, StreamCipher clientWriteCipher,
+ StreamCipher serverWriteCipher, Digest clientWriteDigest, Digest serverWriteDigest,
+ int cipherKeySize) throws IOException
+ {
+ this(context, clientWriteCipher, serverWriteCipher, clientWriteDigest, serverWriteDigest, cipherKeySize, false);
+ }
+
+ public TlsStreamCipher(TlsContext context, StreamCipher clientWriteCipher,
+ StreamCipher serverWriteCipher, Digest clientWriteDigest, Digest serverWriteDigest,
+ int cipherKeySize, boolean usesNonce) throws IOException
+ {
+ boolean isServer = context.isServer();
+
+ this.context = context;
+ this.usesNonce = usesNonce;
+
+ this.encryptCipher = clientWriteCipher;
+ this.decryptCipher = serverWriteCipher;
+
+ int key_block_size = (2 * cipherKeySize) + clientWriteDigest.getDigestSize()
+ + serverWriteDigest.getDigestSize();
+
+ byte[] key_block = TlsUtils.calculateKeyBlock(context, key_block_size);
+
+ int offset = 0;
+
+ // Init MACs
+ TlsMac clientWriteMac = new TlsMac(context, clientWriteDigest, key_block, offset,
+ clientWriteDigest.getDigestSize());
+ offset += clientWriteDigest.getDigestSize();
+ TlsMac serverWriteMac = new TlsMac(context, serverWriteDigest, key_block, offset,
+ serverWriteDigest.getDigestSize());
+ offset += serverWriteDigest.getDigestSize();
+
+ // Build keys
+ KeyParameter clientWriteKey = new KeyParameter(key_block, offset, cipherKeySize);
+ offset += cipherKeySize;
+ KeyParameter serverWriteKey = new KeyParameter(key_block, offset, cipherKeySize);
+ offset += cipherKeySize;
+
+ if (offset != key_block_size)
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ CipherParameters encryptParams, decryptParams;
+ if (isServer)
+ {
+ this.writeMac = serverWriteMac;
+ this.readMac = clientWriteMac;
+ this.encryptCipher = serverWriteCipher;
+ this.decryptCipher = clientWriteCipher;
+ encryptParams = serverWriteKey;
+ decryptParams = clientWriteKey;
+ }
+ else
+ {
+ this.writeMac = clientWriteMac;
+ this.readMac = serverWriteMac;
+ this.encryptCipher = clientWriteCipher;
+ this.decryptCipher = serverWriteCipher;
+ encryptParams = clientWriteKey;
+ decryptParams = serverWriteKey;
+ }
+
+ if (usesNonce)
+ {
+ byte[] dummyNonce = new byte[8];
+ encryptParams = new ParametersWithIV(encryptParams, dummyNonce);
+ decryptParams = new ParametersWithIV(decryptParams, dummyNonce);
+ }
+
+ this.encryptCipher.init(true, encryptParams);
+ this.decryptCipher.init(false, decryptParams);
+ }
+
+ public int getPlaintextLimit(int ciphertextLimit)
+ {
+ return ciphertextLimit - writeMac.getSize();
+ }
+
+ public byte[] encodePlaintext(long seqNo, short type, byte[] plaintext, int offset, int len)
+ {
+ /*
+ * draft-josefsson-salsa20-tls-04 2.1 Note that Salsa20 requires a 64-bit nonce. That
+ * nonce is updated on the encryption of every TLS record, and is set to be the 64-bit TLS
+ * record sequence number. In case of DTLS the 64-bit nonce is formed as the concatenation
+ * of the 16-bit epoch with the 48-bit sequence number.
+ */
+ if (usesNonce)
+ {
+ updateIV(encryptCipher, true, seqNo);
+ }
+
+ byte[] outBuf = new byte[len + writeMac.getSize()];
+
+ encryptCipher.processBytes(plaintext, offset, len, outBuf, 0);
+
+ byte[] mac = writeMac.calculateMac(seqNo, type, plaintext, offset, len);
+ encryptCipher.processBytes(mac, 0, mac.length, outBuf, len);
+
+ return outBuf;
+ }
+
+ public byte[] decodeCiphertext(long seqNo, short type, byte[] ciphertext, int offset, int len)
+ throws IOException
+ {
+ /*
+ * draft-josefsson-salsa20-tls-04 2.1 Note that Salsa20 requires a 64-bit nonce. That
+ * nonce is updated on the encryption of every TLS record, and is set to be the 64-bit TLS
+ * record sequence number. In case of DTLS the 64-bit nonce is formed as the concatenation
+ * of the 16-bit epoch with the 48-bit sequence number.
+ */
+ if (usesNonce)
+ {
+ updateIV(decryptCipher, false, seqNo);
+ }
+
+ int macSize = readMac.getSize();
+ if (len < macSize)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+
+ int plaintextLength = len - macSize;
+
+ byte[] deciphered = new byte[len];
+ decryptCipher.processBytes(ciphertext, offset, len, deciphered, 0);
+ checkMAC(seqNo, type, deciphered, plaintextLength, len, deciphered, 0, plaintextLength);
+ return Arrays.copyOfRange(deciphered, 0, plaintextLength);
+ }
+
+ private void checkMAC(long seqNo, short type, byte[] recBuf, int recStart, int recEnd, byte[] calcBuf, int calcOff, int calcLen)
+ throws IOException
+ {
+ byte[] receivedMac = Arrays.copyOfRange(recBuf, recStart, recEnd);
+ byte[] computedMac = readMac.calculateMac(seqNo, type, calcBuf, calcOff, calcLen);
+
+ if (!Arrays.constantTimeAreEqual(receivedMac, computedMac))
+ {
+ throw new TlsFatalAlert(AlertDescription.bad_record_mac);
+ }
+ }
+
+ private void updateIV(StreamCipher cipher, boolean forEncryption, long seqNo)
+ {
+ byte[] nonce = new byte[8];
+ TlsUtils.writeUint64(seqNo, nonce, 0);
+ cipher.init(forEncryption, new ParametersWithIV(null, nonce));
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/TlsUtils.java b/core/src/main/java/org/spongycastle/crypto/tls/TlsUtils.java
new file mode 100644
index 00000000..118af480
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/TlsUtils.java
@@ -0,0 +1,1780 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import org.spongycastle.asn1.ASN1Encoding;
+import org.spongycastle.asn1.ASN1InputStream;
+import org.spongycastle.asn1.ASN1ObjectIdentifier;
+import org.spongycastle.asn1.ASN1Primitive;
+import org.spongycastle.asn1.nist.NISTObjectIdentifiers;
+import org.spongycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.spongycastle.asn1.x509.Extensions;
+import org.spongycastle.asn1.x509.KeyUsage;
+import org.spongycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.spongycastle.asn1.x509.X509ObjectIdentifiers;
+import org.spongycastle.crypto.Digest;
+import org.spongycastle.crypto.digests.MD5Digest;
+import org.spongycastle.crypto.digests.SHA1Digest;
+import org.spongycastle.crypto.digests.SHA224Digest;
+import org.spongycastle.crypto.digests.SHA256Digest;
+import org.spongycastle.crypto.digests.SHA384Digest;
+import org.spongycastle.crypto.digests.SHA512Digest;
+import org.spongycastle.crypto.macs.HMac;
+import org.spongycastle.crypto.params.AsymmetricKeyParameter;
+import org.spongycastle.crypto.params.DSAPublicKeyParameters;
+import org.spongycastle.crypto.params.ECPublicKeyParameters;
+import org.spongycastle.crypto.params.KeyParameter;
+import org.spongycastle.crypto.params.RSAKeyParameters;
+import org.spongycastle.crypto.util.PublicKeyFactory;
+import org.spongycastle.util.Arrays;
+import org.spongycastle.util.Integers;
+import org.spongycastle.util.Strings;
+import org.spongycastle.util.io.Streams;
+
+/**
+ * Some helper functions for MicroTLS.
+ */
+public class TlsUtils
+{
+ public static final byte[] EMPTY_BYTES = new byte[0];
+
+ public static final Integer EXT_signature_algorithms = Integers.valueOf(ExtensionType.signature_algorithms);
+
+ public static void checkUint8(short i) throws IOException
+ {
+ if (!isValidUint8(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint8(int i) throws IOException
+ {
+ if (!isValidUint8(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint8(long i) throws IOException
+ {
+ if (!isValidUint8(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint16(int i) throws IOException
+ {
+ if (!isValidUint16(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint16(long i) throws IOException
+ {
+ if (!isValidUint16(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint24(int i) throws IOException
+ {
+ if (!isValidUint24(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint24(long i) throws IOException
+ {
+ if (!isValidUint24(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint32(long i) throws IOException
+ {
+ if (!isValidUint32(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint48(long i) throws IOException
+ {
+ if (!isValidUint48(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static void checkUint64(long i) throws IOException
+ {
+ if (!isValidUint64(i))
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static boolean isValidUint8(short i)
+ {
+ return (i & 0xFF) == i;
+ }
+
+ public static boolean isValidUint8(int i)
+ {
+ return (i & 0xFF) == i;
+ }
+
+ public static boolean isValidUint8(long i)
+ {
+ return (i & 0xFFL) == i;
+ }
+
+ public static boolean isValidUint16(int i)
+ {
+ return (i & 0xFFFF) == i;
+ }
+
+ public static boolean isValidUint16(long i)
+ {
+ return (i & 0xFFFFL) == i;
+ }
+
+ public static boolean isValidUint24(int i)
+ {
+ return (i & 0xFFFFFF) == i;
+ }
+
+ public static boolean isValidUint24(long i)
+ {
+ return (i & 0xFFFFFFL) == i;
+ }
+
+ public static boolean isValidUint32(long i)
+ {
+ return (i & 0xFFFFFFFFL) == i;
+ }
+
+ public static boolean isValidUint48(long i)
+ {
+ return (i & 0xFFFFFFFFFFFFL) == i;
+ }
+
+ public static boolean isValidUint64(long i)
+ {
+ return true;
+ }
+
+ public static boolean isSSL(TlsContext context)
+ {
+ return context.getServerVersion().isSSL();
+ }
+
+ public static boolean isTLSv11(TlsContext context)
+ {
+ return ProtocolVersion.TLSv11.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion());
+ }
+
+ public static boolean isTLSv12(TlsContext context)
+ {
+ return ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(context.getServerVersion().getEquivalentTLSVersion());
+ }
+
+ public static void writeUint8(short i, OutputStream output)
+ throws IOException
+ {
+ output.write(i);
+ }
+
+ public static void writeUint8(int i, OutputStream output)
+ throws IOException
+ {
+ output.write(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, OutputStream output)
+ throws IOException
+ {
+ output.write(i >>> 8);
+ output.write(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, OutputStream output)
+ throws IOException
+ {
+ output.write((byte)(i >>> 16));
+ output.write((byte)(i >>> 8));
+ output.write((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, OutputStream output)
+ throws IOException
+ {
+ output.write((byte)(i >>> 24));
+ output.write((byte)(i >>> 16));
+ output.write((byte)(i >>> 8));
+ output.write((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, OutputStream output)
+ throws IOException
+ {
+ output.write((byte)(i >>> 40));
+ output.write((byte)(i >>> 32));
+ output.write((byte)(i >>> 24));
+ output.write((byte)(i >>> 16));
+ output.write((byte)(i >>> 8));
+ output.write((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, OutputStream output)
+ throws IOException
+ {
+ output.write((byte)(i >>> 56));
+ output.write((byte)(i >>> 48));
+ output.write((byte)(i >>> 40));
+ output.write((byte)(i >>> 32));
+ output.write((byte)(i >>> 24));
+ output.write((byte)(i >>> 16));
+ output.write((byte)(i >>> 8));
+ output.write((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, OutputStream output)
+ throws IOException
+ {
+ checkUint8(buf.length);
+ writeUint8(buf.length, output);
+ output.write(buf);
+ }
+
+ public static void writeOpaque16(byte[] buf, OutputStream output)
+ throws IOException
+ {
+ checkUint16(buf.length);
+ writeUint16(buf.length, output);
+ output.write(buf);
+ }
+
+ public static void writeOpaque24(byte[] buf, OutputStream output)
+ throws IOException
+ {
+ checkUint24(buf.length);
+ writeUint24(buf.length, output);
+ output.write(buf);
+ }
+
+ public static void writeUint8Array(short[] uints, OutputStream output)
+ throws IOException
+ {
+ for (int i = 0; i < uints.length; ++i)
+ {
+ writeUint8(uints[i], output);
+ }
+ }
+
+ public static void writeUint8Array(short[] uints, byte[] buf, int offset)
+ throws IOException
+ {
+ for (int i = 0; i < uints.length; ++i)
+ {
+ writeUint8(uints[i], buf, offset);
+ ++offset;
+ }
+ }
+
+ public static void writeUint8ArrayWithUint8Length(short[] uints, OutputStream output)
+ throws IOException
+ {
+ checkUint8(uints.length);
+ writeUint8(uints.length, output);
+ writeUint8Array(uints, output);
+ }
+
+ public static void writeUint8ArrayWithUint8Length(short[] uints, byte[] buf, int offset)
+ throws IOException
+ {
+ checkUint8(uints.length);
+ writeUint8(uints.length, buf, offset);
+ writeUint8Array(uints, buf, offset + 1);
+ }
+
+ public static void writeUint16Array(int[] uints, OutputStream output)
+ throws IOException
+ {
+ for (int i = 0; i < uints.length; ++i)
+ {
+ writeUint16(uints[i], output);
+ }
+ }
+
+ public static void writeUint16Array(int[] uints, byte[] buf, int offset)
+ throws IOException
+ {
+ for (int i = 0; i < uints.length; ++i)
+ {
+ writeUint16(uints[i], buf, offset);
+ offset += 2;
+ }
+ }
+
+ public static void writeUint16ArrayWithUint16Length(int[] uints, OutputStream output)
+ throws IOException
+ {
+ int length = 2 * uints.length;
+ checkUint16(length);
+ writeUint16(length, output);
+ writeUint16Array(uints, output);
+ }
+
+ public static void writeUint16ArrayWithUint16Length(int[] uints, byte[] buf, int offset)
+ throws IOException
+ {
+ int length = 2 * uints.length;
+ checkUint16(length);
+ writeUint16(length, buf, offset);
+ writeUint16Array(uints, buf, offset + 2);
+ }
+
+ public static byte[] encodeOpaque8(byte[] buf)
+ throws IOException
+ {
+ checkUint8(buf.length);
+ return Arrays.prepend(buf, (byte)buf.length);
+ }
+
+ public static byte[] encodeUint8ArrayWithUint8Length(short[] uints) throws IOException
+ {
+ byte[] result = new byte[1 + uints.length];
+ writeUint8ArrayWithUint8Length(uints, result, 0);
+ return result;
+ }
+
+ public static byte[] encodeUint16ArrayWithUint16Length(int[] uints) throws IOException
+ {
+ int length = 2 * uints.length;
+ byte[] result = new byte[2 + length];
+ writeUint16ArrayWithUint16Length(uints, result, 0);
+ return result;
+ }
+
+ public static short readUint8(InputStream input)
+ throws IOException
+ {
+ int i = input.read();
+ if (i < 0)
+ {
+ throw new EOFException();
+ }
+ return (short)i;
+ }
+
+ public static short readUint8(byte[] buf, int offset)
+ {
+ return (short)buf[offset];
+ }
+
+ public static int readUint16(InputStream input)
+ throws IOException
+ {
+ int i1 = input.read();
+ int i2 = input.read();
+ if (i2 < 0)
+ {
+ throw new EOFException();
+ }
+ 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(InputStream input)
+ throws IOException
+ {
+ int i1 = input.read();
+ int i2 = input.read();
+ int i3 = input.read();
+ if (i3 < 0)
+ {
+ throw new EOFException();
+ }
+ 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(InputStream input)
+ throws IOException
+ {
+ int i1 = input.read();
+ int i2 = input.read();
+ int i3 = input.read();
+ int i4 = input.read();
+ if (i4 < 0)
+ {
+ throw new EOFException();
+ }
+ return ((i1 << 2) | (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(InputStream input)
+ throws IOException
+ {
+ 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, InputStream input)
+ throws IOException
+ {
+ if (length < 1)
+ {
+ return EMPTY_BYTES;
+ }
+ byte[] buf = new byte[length];
+ int read = Streams.readFully(input, buf);
+ if (read == 0)
+ {
+ return null;
+ }
+ if (read != length)
+ {
+ throw new EOFException();
+ }
+ return buf;
+ }
+
+ public static byte[] readFully(int length, InputStream input)
+ throws IOException
+ {
+ if (length < 1)
+ {
+ return EMPTY_BYTES;
+ }
+ byte[] buf = new byte[length];
+ if (length != Streams.readFully(input, buf))
+ {
+ throw new EOFException();
+ }
+ return buf;
+ }
+
+ public static void readFully(byte[] buf, InputStream input)
+ throws IOException
+ {
+ int length = buf.length;
+ if (length > 0 && length != Streams.readFully(input, buf))
+ {
+ throw new EOFException();
+ }
+ }
+
+ public static byte[] readOpaque8(InputStream input)
+ throws IOException
+ {
+ short length = readUint8(input);
+ return readFully(length, input);
+ }
+
+ public static byte[] readOpaque16(InputStream input)
+ throws IOException
+ {
+ int length = readUint16(input);
+ return readFully(length, input);
+ }
+
+ public static byte[] readOpaque24(InputStream input)
+ throws IOException
+ {
+ int length = readUint24(input);
+ return readFully(length, input);
+ }
+
+ public static short[] readUint8Array(int count, InputStream input)
+ throws IOException
+ {
+ short[] uints = new short[count];
+ for (int i = 0; i < count; ++i)
+ {
+ uints[i] = readUint8(input);
+ }
+ return uints;
+ }
+
+ public static int[] readUint16Array(int count, InputStream input)
+ throws IOException
+ {
+ 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)
+ throws IOException
+ {
+ return ProtocolVersion.get(buf[offset] & 0xFF, buf[offset + 1] & 0xFF);
+ }
+
+ public static ProtocolVersion readVersion(InputStream input)
+ throws IOException
+ {
+ int i1 = input.read();
+ int i2 = input.read();
+ if (i2 < 0)
+ {
+ throw new EOFException();
+ }
+ return ProtocolVersion.get(i1, i2);
+ }
+
+ public static int readVersionRaw(byte[] buf, int offset)
+ throws IOException
+ {
+ return (buf[offset] << 8) | buf[offset + 1];
+ }
+
+ public static int readVersionRaw(InputStream input)
+ throws IOException
+ {
+ int i1 = input.read();
+ int i2 = input.read();
+ if (i2 < 0)
+ {
+ throw new EOFException();
+ }
+ return (i1 << 8) | i2;
+ }
+
+ public static ASN1Primitive readASN1Object(byte[] encoding) throws IOException
+ {
+ ASN1InputStream asn1 = new ASN1InputStream(encoding);
+ ASN1Primitive 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 ASN1Primitive readDERObject(byte[] encoding) throws IOException
+ {
+ /*
+ * 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.
+ */
+ ASN1Primitive result = readASN1Object(encoding);
+ byte[] check = result.getEncoded(ASN1Encoding.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)(System.currentTimeMillis() / 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, OutputStream output)
+ throws IOException
+ {
+ output.write(version.getMajorVersion());
+ output.write(version.getMinorVersion());
+ }
+
+ public static void writeVersion(ProtocolVersion version, byte[] buf, int offset)
+ {
+ buf[offset] = (byte)version.getMajorVersion();
+ buf[offset + 1] = (byte)version.getMinorVersion();
+ }
+
+ public static Vector getDefaultDSSSignatureAlgorithms()
+ {
+ return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.dsa));
+ }
+
+ public static Vector getDefaultECDSASignatureAlgorithms()
+ {
+ return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.ecdsa));
+ }
+
+ public static Vector getDefaultRSASignatureAlgorithms()
+ {
+ return vectorOfOne(new SignatureAndHashAlgorithm(HashAlgorithm.sha1, SignatureAlgorithm.rsa));
+ }
+
+ public static byte[] getExtensionData(Hashtable extensions, Integer extensionType)
+ {
+ return extensions == null ? null : (byte[])extensions.get(extensionType);
+ }
+
+ public static boolean hasExpectedEmptyExtensionData(Hashtable extensions, Integer extensionType,
+ short alertDescription) throws IOException
+ {
+ 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);
+ }
+
+ public static boolean isSignatureAlgorithmsExtensionAllowed(ProtocolVersion clientVersion)
+ {
+ return ProtocolVersion.TLSv12.isEqualOrEarlierVersionOf(clientVersion.getEquivalentTLSVersion());
+ }
+
+ /**
+ * Add a 'signature_algorithms' extension to existing extensions.
+ *
+ * @param extensions A {@link Hashtable} to add the extension to.
+ * @param supportedSignatureAlgorithms {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}.
+ * @throws IOException
+ */
+ public static void addSignatureAlgorithmsExtension(Hashtable extensions, Vector supportedSignatureAlgorithms)
+ throws IOException
+ {
+ extensions.put(EXT_signature_algorithms, createSignatureAlgorithmsExtension(supportedSignatureAlgorithms));
+ }
+
+ /**
+ * Get a 'signature_algorithms' extension from extensions.
+ *
+ * @param extensions A {@link Hashtable} to get the extension from, if it is present.
+ * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}, or null.
+ * @throws IOException
+ */
+ public static Vector getSignatureAlgorithmsExtension(Hashtable extensions)
+ throws IOException
+ {
+ byte[] extensionData = getExtensionData(extensions, EXT_signature_algorithms);
+ return extensionData == null ? null : readSignatureAlgorithmsExtension(extensionData);
+ }
+
+ /**
+ * Create a 'signature_algorithms' extension value.
+ *
+ * @param supportedSignatureAlgorithms A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}.
+ * @return A byte array suitable for use as an extension value.
+ * @throws IOException
+ */
+ public static byte[] createSignatureAlgorithmsExtension(Vector supportedSignatureAlgorithms)
+ throws IOException
+ {
+ ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ // supported_signature_algorithms
+ encodeSupportedSignatureAlgorithms(supportedSignatureAlgorithms, false, buf);
+
+ return buf.toByteArray();
+ }
+
+ /**
+ * Read 'signature_algorithms' extension data.
+ *
+ * @param extensionData The extension data.
+ * @return A {@link Vector} containing at least 1 {@link SignatureAndHashAlgorithm}.
+ * @throws IOException
+ */
+ public static Vector readSignatureAlgorithmsExtension(byte[] extensionData)
+ throws IOException
+ {
+ if (extensionData == null)
+ {
+ throw new IllegalArgumentException("'extensionData' cannot be null");
+ }
+
+ ByteArrayInputStream buf = new ByteArrayInputStream(extensionData);
+
+ // supported_signature_algorithms
+ Vector supported_signature_algorithms = parseSupportedSignatureAlgorithms(false, buf);
+
+ TlsProtocol.assertEmpty(buf);
+
+ return supported_signature_algorithms;
+ }
+
+ public static void encodeSupportedSignatureAlgorithms(Vector supportedSignatureAlgorithms, boolean allowAnonymous,
+ OutputStream output) throws IOException
+ {
+ if (supportedSignatureAlgorithms == null || supportedSignatureAlgorithms.size() < 1
+ || supportedSignatureAlgorithms.size() >= (1 << 15))
+ {
+ throw new IllegalArgumentException(
+ "'supportedSignatureAlgorithms' must have length from 1 to (2^15 - 1)");
+ }
+
+ // supported_signature_algorithms
+ int length = 2 * supportedSignatureAlgorithms.size();
+ TlsUtils.checkUint16(length);
+ TlsUtils.writeUint16(length, output);
+ for (int i = 0; i < supportedSignatureAlgorithms.size(); ++i)
+ {
+ SignatureAndHashAlgorithm entry = (SignatureAndHashAlgorithm)supportedSignatureAlgorithms.elementAt(i);
+ if (!allowAnonymous && entry.getSignature() == 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 IllegalArgumentException(
+ "SignatureAlgorithm.anonymous MUST NOT appear in the signature_algorithms extension");
+ }
+ entry.encode(output);
+ }
+ }
+
+ public static Vector parseSupportedSignatureAlgorithms(boolean allowAnonymous, InputStream input)
+ throws IOException
+ {
+ // supported_signature_algorithms
+ int length = TlsUtils.readUint16(input);
+ if (length < 2 || (length & 1) != 0)
+ {
+ throw new TlsFatalAlert(AlertDescription.decode_error);
+ }
+ int count = length / 2;
+ Vector supportedSignatureAlgorithms = new Vector(count);
+ for (int i = 0; i < count; ++i)
+ {
+ SignatureAndHashAlgorithm entry = SignatureAndHashAlgorithm.parse(input);
+ if (!allowAnonymous && entry.getSignature() == 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 TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ supportedSignatureAlgorithms.addElement(entry);
+ }
+ return supportedSignatureAlgorithms;
+ }
+
+ public static byte[] PRF(TlsContext context, byte[] secret, String asciiLabel, byte[] seed, int size)
+ {
+ ProtocolVersion version = context.getServerVersion();
+
+ if (version.isSSL())
+ {
+ throw new IllegalStateException("No PRF available for SSLv3 session");
+ }
+
+ byte[] label = Strings.toByteArray(asciiLabel);
+ byte[] labelSeed = concat(label, seed);
+
+ int prfAlgorithm = context.getSecurityParameters().getPrfAlgorithm();
+
+ if (prfAlgorithm == PRFAlgorithm.tls_prf_legacy)
+ {
+ return PRF_legacy(secret, label, labelSeed, size);
+ }
+
+ Digest prfDigest = createPRFHash(prfAlgorithm);
+ byte[] buf = new byte[size];
+ hmac_hash(prfDigest, secret, labelSeed, buf);
+ return buf;
+ }
+
+ public static byte[] PRF_legacy(byte[] secret, String asciiLabel, byte[] seed, int size)
+ {
+ byte[] label = Strings.toByteArray(asciiLabel);
+ byte[] labelSeed = concat(label, seed);
+
+ return PRF_legacy(secret, label, labelSeed, size);
+ }
+
+ static byte[] PRF_legacy(byte[] secret, byte[] label, byte[] labelSeed, int size)
+ {
+ int s_half = (secret.length + 1) / 2;
+ byte[] s1 = new byte[s_half];
+ byte[] s2 = new byte[s_half];
+ System.arraycopy(secret, 0, s1, 0, s_half);
+ System.arraycopy(secret, secret.length - s_half, s2, 0, s_half);
+
+ byte[] b1 = new byte[size];
+ byte[] b2 = new byte[size];
+ hmac_hash(createHash(HashAlgorithm.md5), s1, labelSeed, b1);
+ hmac_hash(createHash(HashAlgorithm.sha1), s2, labelSeed, b2);
+ for (int i = 0; i < size; i++)
+ {
+ b1[i] ^= b2[i];
+ }
+ return b1;
+ }
+
+ static byte[] concat(byte[] a, byte[] b)
+ {
+ byte[] c = new byte[a.length + b.length];
+ System.arraycopy(a, 0, c, 0, a.length);
+ System.arraycopy(b, 0, c, a.length, b.length);
+ return c;
+ }
+
+ static void hmac_hash(Digest digest, byte[] secret, byte[] seed, byte[] out)
+ {
+ HMac mac = new HMac(digest);
+ mac.init(new KeyParameter(secret));
+ byte[] a = seed;
+ int size = digest.getDigestSize();
+ int iterations = (out.length + size - 1) / size;
+ byte[] buf = new byte[mac.getMacSize()];
+ byte[] buf2 = new byte[mac.getMacSize()];
+ for (int i = 0; i < iterations; i++)
+ {
+ mac.update(a, 0, a.length);
+ mac.doFinal(buf, 0);
+ a = buf;
+ mac.update(a, 0, a.length);
+ mac.update(seed, 0, seed.length);
+ mac.doFinal(buf2, 0);
+ System.arraycopy(buf2, 0, out, (size * i), Math.min(size, out.length - (size * i)));
+ }
+ }
+
+ static void validateKeyUsage(org.spongycastle.asn1.x509.Certificate c, int keyUsageBits)
+ throws IOException
+ {
+ Extensions exts = c.getTBSCertificate().getExtensions();
+ if (exts != null)
+ {
+ KeyUsage ku = KeyUsage.fromExtensions(exts);
+ if (ku != null)
+ {
+ int bits = ku.getBytes()[0] & 0xff;
+ if ((bits & keyUsageBits) != keyUsageBits)
+ {
+ throw new TlsFatalAlert(AlertDescription.certificate_unknown);
+ }
+ }
+ }
+ }
+
+ static byte[] calculateKeyBlock(TlsContext context, int size)
+ {
+ SecurityParameters securityParameters = context.getSecurityParameters();
+ byte[] master_secret = securityParameters.getMasterSecret();
+ byte[] seed = concat(securityParameters.getServerRandom(),
+ securityParameters.getClientRandom());
+
+ if (isSSL(context))
+ {
+ return calculateKeyBlock_SSL(master_secret, seed, size);
+ }
+
+ return PRF(context, master_secret, ExporterLabel.key_expansion, seed, size);
+ }
+
+ static byte[] calculateKeyBlock_SSL(byte[] master_secret, byte[] random, int size)
+ {
+ Digest md5 = createHash(HashAlgorithm.md5);
+ Digest sha1 = createHash(HashAlgorithm.sha1);
+ int md5Size = md5.getDigestSize();
+ byte[] shatmp = new byte[sha1.getDigestSize()];
+ byte[] tmp = new byte[size + md5Size];
+
+ int i = 0, pos = 0;
+ while (pos < size)
+ {
+ byte[] ssl3Const = SSL3_CONST[i];
+
+ sha1.update(ssl3Const, 0, ssl3Const.length);
+ sha1.update(master_secret, 0, master_secret.length);
+ sha1.update(random, 0, random.length);
+ sha1.doFinal(shatmp, 0);
+
+ md5.update(master_secret, 0, master_secret.length);
+ md5.update(shatmp, 0, shatmp.length);
+ md5.doFinal(tmp, pos);
+
+ pos += md5Size;
+ ++i;
+ }
+
+ byte rval[] = new byte[size];
+ System.arraycopy(tmp, 0, rval, 0, size);
+ return rval;
+ }
+
+ static byte[] calculateMasterSecret(TlsContext context, byte[] pre_master_secret)
+ {
+ SecurityParameters securityParameters = context.getSecurityParameters();
+ byte[] seed = concat(securityParameters.getClientRandom(), securityParameters.getServerRandom());
+
+ if (isSSL(context))
+ {
+ return calculateMasterSecret_SSL(pre_master_secret, seed);
+ }
+
+ return PRF(context, pre_master_secret, ExporterLabel.master_secret, seed, 48);
+ }
+
+ static byte[] calculateMasterSecret_SSL(byte[] pre_master_secret, byte[] random)
+ {
+ Digest md5 = createHash(HashAlgorithm.md5);
+ Digest sha1 = createHash(HashAlgorithm.sha1);
+ int md5Size = md5.getDigestSize();
+ byte[] shatmp = new byte[sha1.getDigestSize()];
+
+ byte[] rval = new byte[md5Size * 3];
+ int pos = 0;
+
+ for (int i = 0; i < 3; ++i)
+ {
+ byte[] ssl3Const = SSL3_CONST[i];
+
+ sha1.update(ssl3Const, 0, ssl3Const.length);
+ sha1.update(pre_master_secret, 0, pre_master_secret.length);
+ sha1.update(random, 0, random.length);
+ sha1.doFinal(shatmp, 0);
+
+ md5.update(pre_master_secret, 0, pre_master_secret.length);
+ md5.update(shatmp, 0, shatmp.length);
+ md5.doFinal(rval, pos);
+
+ pos += md5Size;
+ }
+
+ return rval;
+ }
+
+ static byte[] calculateVerifyData(TlsContext context, String asciiLabel, byte[] handshakeHash)
+ {
+ if (isSSL(context))
+ {
+ return handshakeHash;
+ }
+
+ SecurityParameters securityParameters = context.getSecurityParameters();
+ byte[] master_secret = securityParameters.getMasterSecret();
+ int verify_data_length = securityParameters.getVerifyDataLength();
+
+ return PRF(context, master_secret, asciiLabel, handshakeHash, verify_data_length);
+ }
+
+ public static Digest createHash(short hashAlgorithm)
+ {
+ switch (hashAlgorithm)
+ {
+ case HashAlgorithm.md5:
+ return new MD5Digest();
+ case HashAlgorithm.sha1:
+ return new SHA1Digest();
+ case HashAlgorithm.sha224:
+ return new SHA224Digest();
+ case HashAlgorithm.sha256:
+ return new SHA256Digest();
+ case HashAlgorithm.sha384:
+ return new SHA384Digest();
+ case HashAlgorithm.sha512:
+ return new SHA512Digest();
+ default:
+ throw new IllegalArgumentException("unknown HashAlgorithm");
+ }
+ }
+
+ public static Digest cloneHash(short hashAlgorithm, Digest hash)
+ {
+ switch (hashAlgorithm)
+ {
+ case HashAlgorithm.md5:
+ return new MD5Digest((MD5Digest)hash);
+ case HashAlgorithm.sha1:
+ return new SHA1Digest((SHA1Digest)hash);
+ case HashAlgorithm.sha224:
+ return new SHA224Digest((SHA224Digest)hash);
+ case HashAlgorithm.sha256:
+ return new SHA256Digest((SHA256Digest)hash);
+ case HashAlgorithm.sha384:
+ return new SHA384Digest((SHA384Digest)hash);
+ case HashAlgorithm.sha512:
+ return new SHA512Digest((SHA512Digest)hash);
+ default:
+ throw new IllegalArgumentException("unknown HashAlgorithm");
+ }
+ }
+
+ public static Digest createPRFHash(int prfAlgorithm)
+ {
+ switch (prfAlgorithm)
+ {
+ case PRFAlgorithm.tls_prf_legacy:
+ return new CombinedHash();
+ default:
+ return createHash(getHashAlgorithmForPRFAlgorithm(prfAlgorithm));
+ }
+ }
+
+ public static Digest clonePRFHash(int prfAlgorithm, Digest hash)
+ {
+ switch (prfAlgorithm)
+ {
+ case PRFAlgorithm.tls_prf_legacy:
+ return new CombinedHash((CombinedHash)hash);
+ default:
+ return cloneHash(getHashAlgorithmForPRFAlgorithm(prfAlgorithm), hash);
+ }
+ }
+
+ public static short getHashAlgorithmForPRFAlgorithm(int prfAlgorithm)
+ {
+ switch (prfAlgorithm)
+ {
+ case PRFAlgorithm.tls_prf_legacy:
+ throw new IllegalArgumentException("legacy PRF not a valid algorithm");
+ case PRFAlgorithm.tls_prf_sha256:
+ return HashAlgorithm.sha256;
+ case PRFAlgorithm.tls_prf_sha384:
+ return HashAlgorithm.sha384;
+ default:
+ throw new IllegalArgumentException("unknown PRFAlgorithm");
+ }
+ }
+
+ public static ASN1ObjectIdentifier getOIDForHashAlgorithm(short hashAlgorithm)
+ {
+ switch (hashAlgorithm)
+ {
+ case HashAlgorithm.md5:
+ return PKCSObjectIdentifiers.md5;
+ case HashAlgorithm.sha1:
+ return X509ObjectIdentifiers.id_SHA1;
+ case HashAlgorithm.sha224:
+ return NISTObjectIdentifiers.id_sha224;
+ case HashAlgorithm.sha256:
+ return NISTObjectIdentifiers.id_sha256;
+ case HashAlgorithm.sha384:
+ return NISTObjectIdentifiers.id_sha384;
+ case HashAlgorithm.sha512:
+ return NISTObjectIdentifiers.id_sha512;
+ default:
+ throw new IllegalArgumentException("unknown HashAlgorithm");
+ }
+ }
+
+ static short getClientCertificateType(Certificate clientCertificate, Certificate serverCertificate)
+ throws IOException
+ {
+ if (clientCertificate.isEmpty())
+ {
+ return -1;
+ }
+
+ org.spongycastle.asn1.x509.Certificate x509Cert = clientCertificate.getCertificateAt(0);
+ SubjectPublicKeyInfo keyInfo = x509Cert.getSubjectPublicKeyInfo();
+ try
+ {
+ AsymmetricKeyParameter publicKey = PublicKeyFactory.createKey(keyInfo);
+ if (publicKey.isPrivate())
+ {
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ /*
+ * TODO RFC 5246 7.4.6. The certificates MUST be signed using an acceptable hash/
+ * signature algorithm pair, as described in Section 7.4.4. Note that this relaxes the
+ * constraints on certificate-signing algorithms found in prior versions of TLS.
+ */
+
+ /*
+ * 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 instanceof RSAKeyParameters)
+ {
+ validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+ return ClientCertificateType.rsa_sign;
+ }
+
+ /*
+ * 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 instanceof DSAPublicKeyParameters)
+ {
+ validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+ return ClientCertificateType.dss_sign;
+ }
+
+ /*
+ * 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 instanceof ECPublicKeyParameters)
+ {
+ validateKeyUsage(x509Cert, KeyUsage.digitalSignature);
+ // TODO Check the curve and point format
+ return ClientCertificateType.ecdsa_sign;
+ }
+
+ // TODO Add support for ClientCertificateType.*_fixed_*
+
+ }
+ catch (Exception e)
+ {
+ }
+
+ throw new TlsFatalAlert(AlertDescription.unsupported_certificate);
+ }
+
+ static void trackHashAlgorithms(TlsHandshakeHash handshakeHash, Vector supportedSignatureAlgorithms)
+ {
+ if (supportedSignatureAlgorithms != null)
+ {
+ for (int i = 0; i < supportedSignatureAlgorithms.size(); ++i)
+ {
+ SignatureAndHashAlgorithm signatureAndHashAlgorithm = (SignatureAndHashAlgorithm)
+ supportedSignatureAlgorithms.elementAt(i);
+ short hashAlgorithm = signatureAndHashAlgorithm.getHash();
+ handshakeHash.trackHashAlgorithm(hashAlgorithm);
+ }
+ }
+ }
+
+ public static boolean hasSigningCapability(short clientCertificateType)
+ {
+ switch (clientCertificateType)
+ {
+ case ClientCertificateType.dss_sign:
+ case ClientCertificateType.ecdsa_sign:
+ case ClientCertificateType.rsa_sign:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static TlsSigner createTlsSigner(short clientCertificateType)
+ {
+ switch (clientCertificateType)
+ {
+ case ClientCertificateType.dss_sign:
+ return new TlsDSSSigner();
+ case ClientCertificateType.ecdsa_sign:
+ return new TlsECDSASigner();
+ case ClientCertificateType.rsa_sign:
+ return new TlsRSASigner();
+ default:
+ throw new IllegalArgumentException("'clientCertificateType' is not a type with signing capability");
+ }
+ }
+
+ static final byte[] SSL_CLIENT = {0x43, 0x4C, 0x4E, 0x54};
+ static final byte[] SSL_SERVER = {0x53, 0x52, 0x56, 0x52};
+
+ // SSL3 magic mix constants ("A", "BB", "CCC", ...)
+ static final byte[][] SSL3_CONST = genConst();
+
+ private static byte[][] genConst()
+ {
+ int n = 10;
+ byte[][] arr = new byte[n][];
+ for (int i = 0; i < n; i++)
+ {
+ byte[] b = new byte[i + 1];
+ Arrays.fill(b, (byte)('A' + i));
+ arr[i] = b;
+ }
+ return arr;
+ }
+
+ private static Vector vectorOfOne(Object obj)
+ {
+ Vector v = new Vector(1);
+ v.addElement(obj);
+ return v;
+ }
+
+ public static int getCipherType(int ciphersuite) throws IOException
+ {
+ switch (getEncryptionAlgorithm(ciphersuite))
+ {
+ case EncryptionAlgorithm.AES_128_GCM:
+ case EncryptionAlgorithm.AES_256_GCM:
+ case EncryptionAlgorithm.AES_128_CCM:
+ case EncryptionAlgorithm.AES_128_CCM_8:
+ case EncryptionAlgorithm.AES_256_CCM:
+ case EncryptionAlgorithm.AES_256_CCM_8:
+ case EncryptionAlgorithm.CAMELLIA_128_GCM:
+ case EncryptionAlgorithm.CAMELLIA_256_GCM:
+ case EncryptionAlgorithm.AEAD_CHACHA20_POLY1305:
+ return CipherType.aead;
+
+ case EncryptionAlgorithm.RC2_CBC_40:
+ case EncryptionAlgorithm.IDEA_CBC:
+ case EncryptionAlgorithm.DES40_CBC:
+ case EncryptionAlgorithm.DES_CBC:
+ case EncryptionAlgorithm._3DES_EDE_CBC:
+ case EncryptionAlgorithm.AES_128_CBC:
+ case EncryptionAlgorithm.AES_256_CBC:
+ case EncryptionAlgorithm.CAMELLIA_128_CBC:
+ case EncryptionAlgorithm.CAMELLIA_256_CBC:
+ case EncryptionAlgorithm.SEED_CBC:
+ return CipherType.block;
+
+ case EncryptionAlgorithm.RC4_40:
+ case EncryptionAlgorithm.RC4_128:
+ case EncryptionAlgorithm.ESTREAM_SALSA20:
+ case EncryptionAlgorithm.SALSA20:
+ return CipherType.stream;
+
+ default:
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static int getEncryptionAlgorithm(int ciphersuite) throws IOException
+ {
+ switch (ciphersuite)
+ {
+ 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_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._3DES_EDE_CBC;
+
+ case CipherSuite.TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ return EncryptionAlgorithm.AEAD_CHACHA20_POLY1305;
+
+ case CipherSuite.TLS_DH_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA:
+ 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_DH_DSS_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256:
+ return EncryptionAlgorithm.AES_128_CBC;
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_128_CCM:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM:
+ case CipherSuite.TLS_PSK_WITH_AES_128_CCM:
+ case CipherSuite.TLS_RSA_WITH_AES_128_CCM:
+ return EncryptionAlgorithm.AES_128_CCM;
+
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_128_CCM_8:
+ 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_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_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_DSS_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA:
+ 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_DH_DSS_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256:
+ return EncryptionAlgorithm.AES_256_CBC;
+
+ case CipherSuite.TLS_DHE_PSK_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_PSK_WITH_AES_256_CBC_SHA384:
+ case CipherSuite.TLS_RSA_PSK_WITH_AES_256_CBC_SHA384:
+ 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_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_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_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_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_DSS_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA:
+ return EncryptionAlgorithm.CAMELLIA_128_CBC;
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ 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_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_PSK_WITH_CAMELLIA_128_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256:
+ return EncryptionAlgorithm.CAMELLIA_128_CBC;
+
+ 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_DSS_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA:
+ return EncryptionAlgorithm.CAMELLIA_256_CBC;
+
+ case CipherSuite.TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ case CipherSuite.TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256:
+ return EncryptionAlgorithm.CAMELLIA_256_CBC;
+
+ case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+ 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_PSK_WITH_CAMELLIA_256_CBC_SHA384:
+ return EncryptionAlgorithm.CAMELLIA_256_CBC;
+
+ 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_DHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_DHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_PSK_WITH_ESTREAM_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_ESTREAM_SALSA20_SHA1:
+ return EncryptionAlgorithm.ESTREAM_SALSA20;
+
+ case CipherSuite.TLS_RSA_WITH_NULL_MD5:
+ return EncryptionAlgorithm.NULL;
+
+ case CipherSuite.TLS_DHE_PSK_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_RC4_128_MD5:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_MD5:
+ return EncryptionAlgorithm.RC4_128;
+
+ case CipherSuite.TLS_DHE_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDH_anon_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDH_ECDSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDH_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_PSK_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_WITH_RC4_128_SHA:
+ case CipherSuite.TLS_RSA_PSK_WITH_RC4_128_SHA:
+ return EncryptionAlgorithm.RC4_128;
+
+ case CipherSuite.TLS_DHE_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_DHE_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_ECDSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_ECDHE_RSA_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_PSK_WITH_SALSA20_SHA1:
+ case CipherSuite.TLS_RSA_WITH_SALSA20_SHA1:
+ return EncryptionAlgorithm.SALSA20;
+
+ 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;
+
+ default:
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+ }
+
+ public static ProtocolVersion getMinimumVersion(int ciphersuite)
+ {
+ switch (ciphersuite)
+ {
+ 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_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_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_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_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+ 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_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_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_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_GCM_SHA256:
+ 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_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_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_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_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+ 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_CAMELLIA_128_GCM_SHA256:
+ case CipherSuite.TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384:
+ 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_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 boolean isAEADCipherSuite(int ciphersuite) throws IOException
+ {
+ return CipherType.aead == getCipherType(ciphersuite);
+ }
+
+ public static boolean isBlockCipherSuite(int ciphersuite) throws IOException
+ {
+ return CipherType.block == getCipherType(ciphersuite);
+ }
+
+ public static boolean isStreamCipherSuite(int ciphersuite) throws IOException
+ {
+ return CipherType.stream == getCipherType(ciphersuite);
+ }
+
+ public static boolean isValidCipherSuiteForVersion(int cipherSuite, ProtocolVersion serverVersion)
+ {
+ return getMinimumVersion(cipherSuite).isEqualOrEarlierVersionOf(serverVersion.getEquivalentTLSVersion());
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/UDPTransport.java b/core/src/main/java/org/spongycastle/crypto/tls/UDPTransport.java
new file mode 100644
index 00000000..63aca852
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/UDPTransport.java
@@ -0,0 +1,75 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+
+public class UDPTransport
+ implements DatagramTransport
+{
+ protected final static int MIN_IP_OVERHEAD = 20;
+ protected final static int MAX_IP_OVERHEAD = MIN_IP_OVERHEAD + 64;
+ protected final static int UDP_OVERHEAD = 8;
+
+ protected final DatagramSocket socket;
+ protected final int receiveLimit, sendLimit;
+
+ public UDPTransport(DatagramSocket socket, int mtu)
+ throws IOException
+ {
+ if (!socket.isBound() || !socket.isConnected())
+ {
+ throw new IllegalArgumentException("'socket' must be bound and connected");
+ }
+
+ this.socket = socket;
+
+ // NOTE: As of JDK 1.6, can use NetworkInterface.getMTU
+
+ this.receiveLimit = mtu - MIN_IP_OVERHEAD - UDP_OVERHEAD;
+ this.sendLimit = mtu - MAX_IP_OVERHEAD - UDP_OVERHEAD;
+ }
+
+ public int getReceiveLimit()
+ {
+ return receiveLimit;
+ }
+
+ public int getSendLimit()
+ {
+ // TODO[DTLS] Implement Path-MTU discovery?
+ return sendLimit;
+ }
+
+ public int receive(byte[] buf, int off, int len, int waitMillis)
+ throws IOException
+ {
+ socket.setSoTimeout(waitMillis);
+ DatagramPacket packet = new DatagramPacket(buf, off, len);
+ socket.receive(packet);
+ return packet.getLength();
+ }
+
+ public void send(byte[] buf, int off, int len)
+ throws IOException
+ {
+ if (len > getSendLimit())
+ {
+ /*
+ * RFC 4347 4.1.1. "If the application attempts to send a record larger than the MTU,
+ * the DTLS implementation SHOULD generate an error, thus avoiding sending a packet
+ * which will be fragmented."
+ */
+ throw new TlsFatalAlert(AlertDescription.internal_error);
+ }
+
+ DatagramPacket packet = new DatagramPacket(buf, off, len);
+ socket.send(packet);
+ }
+
+ public void close()
+ throws IOException
+ {
+ socket.close();
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/URLAndHash.java b/core/src/main/java/org/spongycastle/crypto/tls/URLAndHash.java
new file mode 100644
index 00000000..05407842
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/URLAndHash.java
@@ -0,0 +1,104 @@
+package org.spongycastle.crypto.tls;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.spongycastle.util.Strings;
+
+/**
+ * RFC 6066 5.
+ */
+public class URLAndHash
+{
+ protected String url;
+ protected byte[] sha1Hash;
+
+ public URLAndHash(String url, byte[] sha1Hash)
+ {
+ if (url == null || url.length() < 1 || url.length() >= (1 << 16))
+ {
+ throw new IllegalArgumentException("'url' must have length from 1 to (2^16 - 1)");
+ }
+ if (sha1Hash != null && sha1Hash.length != 20)
+ {
+ throw new IllegalArgumentException("'sha1Hash' must have length == 20, if present");
+ }
+
+ this.url = url;
+ this.sha1Hash = sha1Hash;
+ }
+
+ public String getURL()
+ {
+ return url;
+ }
+
+ public byte[] getSHA1Hash()
+ {
+ return sha1Hash;
+ }
+
+ /**
+ * Encode this {@link URLAndHash} to an {@link OutputStream}.
+ *
+ * @param output the {@link OutputStream} to encode to.
+ * @throws IOException
+ */
+ public void encode(OutputStream output)
+ throws IOException
+ {
+ byte[] urlEncoding = Strings.toByteArray(this.url);
+ TlsUtils.writeOpaque16(urlEncoding, output);
+
+ if (this.sha1Hash == null)
+ {
+ TlsUtils.writeUint8(0, output);
+ }
+ else
+ {
+ TlsUtils.writeUint8(1, output);
+ output.write(this.sha1Hash);
+ }
+ }
+
+ /**
+ * Parse a {@link URLAndHash} from an {@link InputStream}.
+ *
+ * @param context
+ * the {@link TlsContext} of the current connection.
+ * @param input
+ * the {@link InputStream} to parse from.
+ * @return a {@link URLAndHash} object.
+ * @throws IOException
+ */
+ public static URLAndHash parse(TlsContext context, InputStream input)
+ throws IOException
+ {
+ byte[] urlEncoding = TlsUtils.readOpaque16(input);
+ if (urlEncoding.length < 1)
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ String url = Strings.fromByteArray(urlEncoding);
+
+ byte[] sha1Hash = null;
+ short padding = TlsUtils.readUint8(input);
+ switch (padding)
+ {
+ case 0:
+ if (TlsUtils.isTLSv12(context))
+ {
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+ break;
+ case 1:
+ sha1Hash = TlsUtils.readFully(20, input);
+ break;
+ default:
+ throw new TlsFatalAlert(AlertDescription.illegal_parameter);
+ }
+
+ return new URLAndHash(url, sha1Hash);
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/UseSRTPData.java b/core/src/main/java/org/spongycastle/crypto/tls/UseSRTPData.java
new file mode 100644
index 00000000..8202dd1e
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/UseSRTPData.java
@@ -0,0 +1,52 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 5764 4.1.1
+ */
+public class UseSRTPData
+{
+ private int[] protectionProfiles;
+ private byte[] mki;
+
+ /**
+ * @param protectionProfiles see {@link SRTPProtectionProfile} for valid constants.
+ * @param mki valid lengths from 0 to 255.
+ */
+ public UseSRTPData(int[] protectionProfiles, byte[] mki)
+ {
+ if (protectionProfiles == null || protectionProfiles.length < 1
+ || protectionProfiles.length >= (1 << 15))
+ {
+ throw new IllegalArgumentException(
+ "'protectionProfiles' must have length from 1 to (2^15 - 1)");
+ }
+
+ if (mki == null)
+ {
+ mki = TlsUtils.EMPTY_BYTES;
+ }
+ else if (mki.length > 255)
+ {
+ throw new IllegalArgumentException("'mki' cannot be longer than 255 bytes");
+ }
+
+ this.protectionProfiles = protectionProfiles;
+ this.mki = mki;
+ }
+
+ /**
+ * @return see {@link SRTPProtectionProfile} for valid constants.
+ */
+ public int[] getProtectionProfiles()
+ {
+ return protectionProfiles;
+ }
+
+ /**
+ * @return valid lengths from 0 to 255.
+ */
+ public byte[] getMki()
+ {
+ return mki;
+ }
+}
diff --git a/core/src/main/java/org/spongycastle/crypto/tls/UserMappingType.java b/core/src/main/java/org/spongycastle/crypto/tls/UserMappingType.java
new file mode 100644
index 00000000..f2fc21f2
--- /dev/null
+++ b/core/src/main/java/org/spongycastle/crypto/tls/UserMappingType.java
@@ -0,0 +1,12 @@
+package org.spongycastle.crypto.tls;
+
+/**
+ * RFC 4681
+ */
+public class UserMappingType
+{
+ /*
+ * RFC 4681
+ */
+ public static final short upn_domain_hint = 64;
+}