diff --git a/tls/src/main/java/org/bouncycastle/tls/AbstractTlsClient.java b/tls/src/main/java/org/bouncycastle/tls/AbstractTlsClient.java index e384ee46a6..5cfcf1516e 100644 --- a/tls/src/main/java/org/bouncycastle/tls/AbstractTlsClient.java +++ b/tls/src/main/java/org/bouncycastle/tls/AbstractTlsClient.java @@ -228,6 +228,10 @@ public Hashtable getClientExtensions() TlsExtensionsUtils.addSupportedGroupsExtension(clientExtensions, supportedGroups); } + //add TokenBinding Extension + TokenBindingExtension tokenBindingExtension = new TokenBindingExtension(); + TlsExtensionsUtils.addTokenBindingExtension(clientExtensions, tokenBindingExtension); + return clientExtensions; } diff --git a/tls/src/main/java/org/bouncycastle/tls/ExporterLabel.java b/tls/src/main/java/org/bouncycastle/tls/ExporterLabel.java index 7141172d32..0c47b4e9ae 100644 --- a/tls/src/main/java/org/bouncycastle/tls/ExporterLabel.java +++ b/tls/src/main/java/org/bouncycastle/tls/ExporterLabel.java @@ -33,4 +33,9 @@ public class ExporterLabel * draft-ietf-tls-session-hash-04 */ public static final String extended_master_secret = "extended master secret"; + + /* + * draft-ietf-tokbind-protocol-16 + */ + public static final String token_binding = "EXPORTER-Token-Binding"; } diff --git a/tls/src/main/java/org/bouncycastle/tls/NegotiatedTokenBinding.java b/tls/src/main/java/org/bouncycastle/tls/NegotiatedTokenBinding.java new file mode 100644 index 0000000000..c17e6fb1fb --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/tls/NegotiatedTokenBinding.java @@ -0,0 +1,68 @@ +package org.bouncycastle.tls; + +/** + * This class captures the negotiated parameters from the TLS handshake + */ +public class NegotiatedTokenBinding { + + private String selectedKeyParameter; + protected String RSA2048_PCKS15 = "rsa2048_pcks15"; + protected String RSA2048_PSS = "rsa2048_pss"; + protected String RSA2048_ECDSAP256 = "rsa2048_ecdsap256"; + + public byte[] exportKeyingMaterial; + + public byte[] getExportKeyingMaterial() { + return exportKeyingMaterial; + } + + public void setExportKeyingMaterial(byte[] exportKeyingMaterial) { + this.exportKeyingMaterial = exportKeyingMaterial; + } + + public int MajorProtocolVerison = 0; + public int MinorProtocolVerison = 13; + + public String getSelectedKeyParameter() { + return selectedKeyParameter; + } + + public void setSelectedKeyParameter(String selectedKeyParameter) { + this.selectedKeyParameter = selectedKeyParameter; + } + + public int getMajorProtocolVerison() { + return MajorProtocolVerison; + } + + public void setMajorProtocolVerison(int majorProtocolVerison) { + MajorProtocolVerison = majorProtocolVerison; + } + + public int getMinorProtocolVerison() { + return MinorProtocolVerison; + } + + public void setMinorProtocolVerison(int minorProtocolVerison) { + MinorProtocolVerison = minorProtocolVerison; + } + + public NegotiatedTokenBinding decode(int[] serverdata) throws TlsFatalAlert { + + if (serverdata.length != 4) { + throw new TlsFatalAlert(AlertDescription.unsupported_extension); + } + this.setMajorProtocolVerison(serverdata[0]); + this.setMinorProtocolVerison(serverdata[1]); + if (serverdata[3] == 0) { + this.setSelectedKeyParameter(RSA2048_PCKS15); + } else if (serverdata[3] == 1) { + this.setSelectedKeyParameter(RSA2048_PSS); + } else if (serverdata[3] == 2) { + this.setSelectedKeyParameter(RSA2048_ECDSAP256); + } else { + throw new TlsFatalAlert(AlertDescription.unsupported_extension); + } + return this; + } +} diff --git a/tls/src/main/java/org/bouncycastle/tls/SecurityParameters.java b/tls/src/main/java/org/bouncycastle/tls/SecurityParameters.java index e5e524419e..fd0d445c4f 100644 --- a/tls/src/main/java/org/bouncycastle/tls/SecurityParameters.java +++ b/tls/src/main/java/org/bouncycastle/tls/SecurityParameters.java @@ -20,6 +20,7 @@ public class SecurityParameters boolean encryptThenMAC = false; boolean extendedMasterSecret = false; boolean truncatedHMac = false; + NegotiatedTokenBinding negotiatedTokenBinding =null; void clear() { @@ -132,4 +133,12 @@ public boolean isTruncatedHMac() { return truncatedHMac; } + + public NegotiatedTokenBinding getNegotiatedTokenBinding() { + return negotiatedTokenBinding; + } + + public void setNegotiatedTokenBinding(NegotiatedTokenBinding negotiatedTokenBinding) { + this.negotiatedTokenBinding = negotiatedTokenBinding; + } } diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java b/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java index 8fcd7e603b..39b06271f9 100644 --- a/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java +++ b/tls/src/main/java/org/bouncycastle/tls/TlsClientProtocol.java @@ -386,6 +386,12 @@ protected void handleHandshakeMessage(short type, ByteArrayInputStream buf) establishMasterSecret(getContext(), keyExchange); + if (this.securityParameters.negotiatedTokenBinding != null && this.securityParameters.masterSecret != + null ){ + this.securityParameters.negotiatedTokenBinding.setExportKeyingMaterial(this.tlsClientContext + .exportKeyingMaterial(ExporterLabel.token_binding,null,32)); + } + recordStream.setPendingConnectionState(getPeer().getCompression(), getPeer().getCipher()); if (credentialedSigner != null) @@ -776,6 +782,7 @@ protected void receiveServerHelloMessage(ByteArrayInputStream buf) sessionServerExtensions, AlertDescription.illegal_parameter); this.securityParameters.truncatedHMac = TlsExtensionsUtils.hasTruncatedHMacExtension(sessionServerExtensions); + this.securityParameters.negotiatedTokenBinding=processTokenBindingExtension(sessionServerExtensions); /* * TODO It's surprising that there's no provision to allow a 'fresh' CertificateStatus to be sent in diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsExtensionsUtils.java b/tls/src/main/java/org/bouncycastle/tls/TlsExtensionsUtils.java index 8ddd225348..326c59032d 100644 --- a/tls/src/main/java/org/bouncycastle/tls/TlsExtensionsUtils.java +++ b/tls/src/main/java/org/bouncycastle/tls/TlsExtensionsUtils.java @@ -23,6 +23,7 @@ public class TlsExtensionsUtils public static final Integer EXT_supported_groups = Integers.valueOf(ExtensionType.supported_groups); public static final Integer EXT_truncated_hmac = Integers.valueOf(ExtensionType.truncated_hmac); public static final Integer EXT_trusted_ca_keys = Integers.valueOf(ExtensionType.trusted_ca_keys); + public static final Integer EXT_token_binding = Integers.valueOf(ExtensionType.DRAFT_token_binding); public static Hashtable ensureExtensionsInitialised(Hashtable extensions) { @@ -119,6 +120,12 @@ public static void addTrustedCAKeysExtensionServer(Hashtable extensions) extensions.put(EXT_trusted_ca_keys, createTrustedCAKeysExtensionServer()); } + public static void addTokenBindingExtension(Hashtable extensions, TokenBindingExtension tokenBindingExtension) + throws IOException { + extensions.put(EXT_token_binding, createTokenBindingExtension(tokenBindingExtension)); + } + + public static short[] getClientCertificateTypeExtensionClient(Hashtable extensions) throws IOException { @@ -195,6 +202,13 @@ public static Vector getTrustedCAKeysExtensionClient(Hashtable extensions) return extensionData == null ? null : readTrustedCAKeysExtensionClient(extensionData); } + public static NegotiatedTokenBinding getTokenBindingExtension(Hashtable extensions) + throws IOException + { + byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_token_binding); + return extensionData == null ? null : readTokenBindingExtension(extensionData); + } + public static boolean hasClientCertificateURLExtension(Hashtable extensions) throws IOException { byte[] extensionData = TlsUtils.getExtensionData(extensions, EXT_client_certificate_url); @@ -225,6 +239,15 @@ public static boolean hasTrustedCAKeysExtensionServer(Hashtable extensions) thro return extensionData == null ? false : readTrustedCAKeysExtensionServer(extensionData); } + private static byte[] createTokenBindingExtension(TokenBindingExtension tokenBindingExtension) throws IOException { + if (tokenBindingExtension == null) { + throw new TlsFatalAlert(AlertDescription.internal_error); + } + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + tokenBindingExtension.encode(buf); + return buf.toByteArray(); + } + public static byte[] createCertificateTypeExtensionClient(short[] certificateTypes) throws IOException { if (certificateTypes == null || certificateTypes.length < 1 || certificateTypes.length > 255) @@ -366,6 +389,14 @@ public static byte[] createTrustedCAKeysExtensionServer() return createEmptyExtensionData(); } + public static NegotiatedTokenBinding readTokenBindingExtension(byte[] extensionData) + throws IOException { + int[] serverData = TlsUtils.decodeUint8ArrayWithUint16Length(extensionData); + NegotiatedTokenBinding tokenBinding = new NegotiatedTokenBinding(); + tokenBinding.decode(serverData); + return tokenBinding; + } + private static boolean readEmptyExtensionData(byte[] extensionData) throws IOException { if (extensionData == null) diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java b/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java index 9b37f0c279..c2b435020d 100644 --- a/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java +++ b/tls/src/main/java/org/bouncycastle/tls/TlsProtocol.java @@ -1189,6 +1189,26 @@ protected void refuseRenegotiation() throws IOException raiseAlertWarning(AlertDescription.no_renegotiation, "Renegotiation not supported"); } + /** + * This method creates NegotiatedTokenBindingClass and checks the negotiated parameters. + * + * @param serverExtensions + * @return + * @throws IOException + */ + protected NegotiatedTokenBinding processTokenBindingExtension(Hashtable serverExtensions) throws IOException { + NegotiatedTokenBinding tokenBinding = TlsExtensionsUtils.getTokenBindingExtension(serverExtensions); + if (tokenBinding != null) { + if (tokenBinding.getMajorProtocolVerison() > TokenBindingExtension.getMajorProtocolVerison()) { + throw new TlsFatalAlert(AlertDescription.unsupported_extension); + } + if (tokenBinding.getMinorProtocolVerison() > TokenBindingExtension.getMinorProtocolVerison()) { + throw new TlsFatalAlert(AlertDescription.unsupported_extension); + } + } + return tokenBinding; + } + /** * Make sure the InputStream 'buf' now empty. Fail otherwise. * diff --git a/tls/src/main/java/org/bouncycastle/tls/TlsUtils.java b/tls/src/main/java/org/bouncycastle/tls/TlsUtils.java index f850757e0d..f2cf065c56 100644 --- a/tls/src/main/java/org/bouncycastle/tls/TlsUtils.java +++ b/tls/src/main/java/org/bouncycastle/tls/TlsUtils.java @@ -450,6 +450,21 @@ public static short[] decodeUint8ArrayWithUint8Length(byte[] buf) throws IOExcep return uints; } + public static int[] decodeUint8ArrayWithUint16Length(byte[] buf) throws IOException + { + if (buf == null) + { + throw new IllegalArgumentException("'buf' cannot be null"); + } + + int[] uints = new int[buf.length]; + for (int i = 0; i < buf.length; ++i) + { + uints[i] = readUint8(buf, i ); + } + return uints; + } + public static byte[] encodeOpaque8(byte[] buf) throws IOException { diff --git a/tls/src/main/java/org/bouncycastle/tls/TokenBindingExtension.java b/tls/src/main/java/org/bouncycastle/tls/TokenBindingExtension.java new file mode 100644 index 0000000000..9ec7295609 --- /dev/null +++ b/tls/src/main/java/org/bouncycastle/tls/TokenBindingExtension.java @@ -0,0 +1,74 @@ +package org.bouncycastle.tls; + +import org.bouncycastle.util.io.Streams; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Token binding (draft) extension to TLS + */ +public class TokenBindingExtension { + + public static final Integer rsa2048_pcks15 = 0; + public static final Integer rsa2048_pss = 1; + public static final Integer rsa2048_ecdsap256 = 2; + + List TokenBindingKeyParameters = new ArrayList(); + + private static int MajorProtocolVerison = 0; + + public static int getMajorProtocolVerison() { + return MajorProtocolVerison; + } + + public static int getMinorProtocolVerison() { + return MinorProtocolVerison; + } + + private static int MinorProtocolVerison = 13; + + public static void setMajorProtocolVerison(int majorProtocolVerison) { + MajorProtocolVerison = majorProtocolVerison; + } + + public static void setMinorProtocolVerison(int minorProtocolVerison) { + MinorProtocolVerison = minorProtocolVerison; + } + + public void addTokenbindingKeyParameters(int parameter) { + TokenBindingKeyParameters.add(parameter); + } + + public List getTokenBindingKeyParameters() { + if (TokenBindingKeyParameters.size() < 1) { + TokenBindingKeyParameters.add(rsa2048_pcks15); + } + Collections.sort(TokenBindingKeyParameters, Collections.reverseOrder()); + return TokenBindingKeyParameters; + } + + public void encode(OutputStream output) throws IOException { + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + TlsUtils.checkUint8(MajorProtocolVerison); + TlsUtils.checkUint8(MinorProtocolVerison); + TlsUtils.writeUint8(MajorProtocolVerison, output); + TlsUtils.writeUint8(MinorProtocolVerison, output); + + for (Integer param : this.getTokenBindingKeyParameters()) { + TlsUtils.checkUint8(param); + TlsUtils.writeUint8(param, buf); + } + + TlsUtils.checkUint8(buf.size()); + TlsUtils.writeUint8(buf.size(), output); + Streams.writeBufTo(buf, output); + + } + +}