diff options
Diffstat (limited to 'core/src/main/java/org/spongycastle/crypto/tls')
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<2^24-1>; + * + * struct { + * ASN.1Cert certificate_list<0..2^24-1>; + * } 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<1..2^8-1>; + * DistinguishedName certificate_authorities<3..2^16-1>; + * } 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; +} |