diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java index c9f72876..6a429862 100644 --- a/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java @@ -18,14 +18,19 @@ package io.openvidu.java.client; import com.google.gson.JsonObject; +import org.apache.commons.codec.binary.Base64; import org.apache.commons.validator.routines.DomainValidator; import org.apache.commons.validator.routines.InetAddressValidator; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; import java.net.Inet6Address; import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.*; /** * See @@ -89,6 +94,7 @@ public class IceServerProperties { private String url; private String username; private String credential; + private String staticAuthSecret; /** * Set the url for the ICE Server you want to use. @@ -121,6 +127,23 @@ public class IceServerProperties { return this; } + /** + * Secret for TURN authentication based on: + * - https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 + * - https://www.ietf.org/proceedings/87/slides/slides-87-behave-10.pdf + * This will generate credentials valid for 24 hours which is the recommended value + */ + public IceServerProperties.Builder staticAuthSecret(String staticAuthSecret) { + this.staticAuthSecret = staticAuthSecret; + return this; + } + + public IceServerProperties.Builder clone() { + return new Builder().url(this.url) + .username(this.username) + .credential(this.credential) + .staticAuthSecret(this.staticAuthSecret); + } /** * Builder for {@link io.openvidu.java.client.RecordingProperties} @@ -137,6 +160,16 @@ public class IceServerProperties { } this.checkValidStunTurn(this.url); if (this.url.startsWith("turn")) { + if (this.staticAuthSecret != null) { + if (this.username != null || this.credential != null) { + throw new IllegalArgumentException("You can't define username or credential if staticAuthSecret is defined"); + } + try { + this.generateTURNCredentials(); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage()); + } + } if ((this.username == null || this.credential == null)) { throw new IllegalArgumentException("Credentials must be defined while using turn"); } @@ -281,6 +314,35 @@ public class IceServerProperties { this.checkHost(uri, host); this.checkPort(uri, port); } + + + private void generateTURNCredentials() throws NoSuchAlgorithmException, InvalidKeyException { + // 1. Generate random username + char[] ALPHANUMERIC =("abcdefghijklmnopqrstuvwxyzABCDEFGHIJK " + + "LMNOPQRSTUVWXYZ0123456789").toCharArray(); + int MAX_LENGTH = 8; + StringBuilder randomUsername = new StringBuilder(); + for(int i =0; i < MAX_LENGTH; i++) { + int index = new SecureRandom().nextInt(ALPHANUMERIC.length); + randomUsername.append(ALPHANUMERIC[index]); + } + // 2. Get unix timestamp adding 24 hours to define max credential valid time + String unixTimestamp = Long.toString((System.currentTimeMillis() / 1000) + 24*3600); + + // 3. Generate TURN username + String username = unixTimestamp + ":" + randomUsername; + System.out.println(username); + + // 4. Generate HMAC SHA-1 password + SecretKeySpec signingKey = new SecretKeySpec(staticAuthSecret.getBytes(), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(signingKey); + String credential = new String(Base64.encodeBase64(mac.doFinal(username.getBytes()))); + + // Set credentials in builder + this.username = username; + this.credential = credential; + } } } diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java index 9be26840..bc8e66c7 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java @@ -225,7 +225,7 @@ public class OpenviduConfig { private boolean webrtcSimulcast = false; - private List webrtcIceServers; + private List webrtcIceServersBuilders; // Plain config properties getters @@ -293,8 +293,8 @@ public class OpenviduConfig { return this.webrtcSimulcast; } - public List getWebrtcIceServers() { - return webrtcIceServers; + public List getWebrtcIceServersBuilders() { + return webrtcIceServersBuilders; } public String getOpenViduRecordingPath() { @@ -640,7 +640,7 @@ public class OpenviduConfig { checkCertificateType(); - webrtcIceServers = loadWebrtcIceServers("OPENVIDU_WEBRTC_ICE_SERVERS"); + webrtcIceServersBuilders = loadWebrtcIceServers("OPENVIDU_WEBRTC_ICE_SERVERS"); } @@ -1170,16 +1170,16 @@ public class OpenviduConfig { } } - private List loadWebrtcIceServers(String property) { + private List loadWebrtcIceServers(String property) { String rawIceServers = asOptionalString(property); - List webrtcIceServers = new ArrayList<>(); + List webrtcIceServers = new ArrayList<>(); if (rawIceServers == null || rawIceServers.isEmpty()) { return webrtcIceServers; } List arrayIceServers = asJsonStringsArray(property); for (String iceServerString : arrayIceServers) { try { - IceServerProperties iceServerProperties = readIceServer(property, iceServerString); + IceServerProperties.Builder iceServerProperties = readIceServer(property, iceServerString); webrtcIceServers.add(iceServerProperties); } catch (Exception e) { addError(property, iceServerString + " is not a valid webrtc ice server: " + e.getMessage()); @@ -1188,8 +1188,8 @@ public class OpenviduConfig { return webrtcIceServers; } - private IceServerProperties readIceServer(String property, String iceServerString) { - String url = null, username = null, credential = null; + private IceServerProperties.Builder readIceServer(String property, String iceServerString) { + String url = null, username = null, credential = null, staticAuthSecret = null; String[] iceServerPropList = iceServerString.split(","); for (String iceServerProp: iceServerPropList) { if (iceServerProp.startsWith("url=")) { @@ -1198,13 +1198,29 @@ public class OpenviduConfig { username = StringUtils.substringAfter(iceServerProp, "username="); } else if (iceServerProp.startsWith("credential=")) { credential = StringUtils.substringAfter(iceServerProp, "credential="); + } else if (iceServerProp.startsWith("staticAuthSecret=")) { + staticAuthSecret = StringUtils.substringAfter(iceServerProp, "staticAuthSecret="); } else { addError(property, "Wrong parameter: " + iceServerProp); } } - IceServerProperties iceServerProperties = new IceServerProperties.Builder() - .url(url).username(username).credential(credential).build(); - return iceServerProperties; + IceServerProperties.Builder builder = new IceServerProperties.Builder().url(url); + IceServerProperties.Builder builderCheck = new IceServerProperties.Builder().url(url); + if (staticAuthSecret != null) { + builder.staticAuthSecret(staticAuthSecret); + builderCheck.staticAuthSecret(staticAuthSecret); + } + if (username != null) { + builder.username(username); + builderCheck.username(username); + } + if (credential != null) { + builder.credential(credential); + builderCheck.credential(credential); + } + // Validate config input + builderCheck.build(); + return builder; } } diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java index 121ed0dd..461c389f 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java @@ -922,6 +922,9 @@ public class SessionRestController { JsonObject customIceServerJson = customIceServersJsonArray.get(i).getAsJsonObject(); IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder(); iceServerPropertiesBuilder.url(customIceServerJson.get("url").getAsString()); + if (customIceServerJson.has("staticAuthSecret")) { + iceServerPropertiesBuilder.staticAuthSecret(customIceServerJson.get("staticAuthSecret").getAsString()); + } if (customIceServerJson.has("username")) { iceServerPropertiesBuilder.username(customIceServerJson.get("username").getAsString()); } @@ -934,10 +937,11 @@ public class SessionRestController { } catch (Exception e) { throw new Exception("Type error in some parameter of 'customIceServers': " + e.getMessage()); } - } else if(!openviduConfig.getWebrtcIceServers().isEmpty()){ + } else if(!openviduConfig.getWebrtcIceServersBuilders().isEmpty()){ // If not defined in connection, check if defined in openvidu config - for (IceServerProperties iceServerProperties: openviduConfig.getWebrtcIceServers()) { - builder.addCustomIceServer(iceServerProperties); + for (IceServerProperties.Builder iceServerPropertiesBuilder: openviduConfig.getWebrtcIceServersBuilders()) { + IceServerProperties.Builder configIceBuilder = iceServerPropertiesBuilder.clone(); + builder.addCustomIceServer(configIceBuilder.build()); } }