mirror of https://github.com/OpenVidu/openvidu.git
openvidu-server: External Turn REST API credentials: https://datatracker.ietf.org/doc/html/draft-uberti-rtcweb-turn-rest-00
parent
af5efc4de4
commit
c15d6170da
|
@ -18,14 +18,19 @@
|
||||||
package io.openvidu.java.client;
|
package io.openvidu.java.client;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
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.DomainValidator;
|
||||||
import org.apache.commons.validator.routines.InetAddressValidator;
|
import org.apache.commons.validator.routines.InetAddressValidator;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Arrays;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashSet;
|
import java.security.InvalidKeyException;
|
||||||
import java.util.Set;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See
|
* See
|
||||||
|
@ -89,6 +94,7 @@ public class IceServerProperties {
|
||||||
private String url;
|
private String url;
|
||||||
private String username;
|
private String username;
|
||||||
private String credential;
|
private String credential;
|
||||||
|
private String staticAuthSecret;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the url for the ICE Server you want to use.
|
* Set the url for the ICE Server you want to use.
|
||||||
|
@ -121,6 +127,23 @@ public class IceServerProperties {
|
||||||
return this;
|
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}
|
* Builder for {@link io.openvidu.java.client.RecordingProperties}
|
||||||
|
@ -137,6 +160,16 @@ public class IceServerProperties {
|
||||||
}
|
}
|
||||||
this.checkValidStunTurn(this.url);
|
this.checkValidStunTurn(this.url);
|
||||||
if (this.url.startsWith("turn")) {
|
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)) {
|
if ((this.username == null || this.credential == null)) {
|
||||||
throw new IllegalArgumentException("Credentials must be defined while using turn");
|
throw new IllegalArgumentException("Credentials must be defined while using turn");
|
||||||
}
|
}
|
||||||
|
@ -281,6 +314,35 @@ public class IceServerProperties {
|
||||||
this.checkHost(uri, host);
|
this.checkHost(uri, host);
|
||||||
this.checkPort(uri, port);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,7 +225,7 @@ public class OpenviduConfig {
|
||||||
|
|
||||||
private boolean webrtcSimulcast = false;
|
private boolean webrtcSimulcast = false;
|
||||||
|
|
||||||
private List<IceServerProperties> webrtcIceServers;
|
private List<IceServerProperties.Builder> webrtcIceServersBuilders;
|
||||||
|
|
||||||
// Plain config properties getters
|
// Plain config properties getters
|
||||||
|
|
||||||
|
@ -293,8 +293,8 @@ public class OpenviduConfig {
|
||||||
return this.webrtcSimulcast;
|
return this.webrtcSimulcast;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IceServerProperties> getWebrtcIceServers() {
|
public List<IceServerProperties.Builder> getWebrtcIceServersBuilders() {
|
||||||
return webrtcIceServers;
|
return webrtcIceServersBuilders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOpenViduRecordingPath() {
|
public String getOpenViduRecordingPath() {
|
||||||
|
@ -640,7 +640,7 @@ public class OpenviduConfig {
|
||||||
|
|
||||||
checkCertificateType();
|
checkCertificateType();
|
||||||
|
|
||||||
webrtcIceServers = loadWebrtcIceServers("OPENVIDU_WEBRTC_ICE_SERVERS");
|
webrtcIceServersBuilders = loadWebrtcIceServers("OPENVIDU_WEBRTC_ICE_SERVERS");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1170,16 +1170,16 @@ public class OpenviduConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<IceServerProperties> loadWebrtcIceServers(String property) {
|
private List<IceServerProperties.Builder> loadWebrtcIceServers(String property) {
|
||||||
String rawIceServers = asOptionalString(property);
|
String rawIceServers = asOptionalString(property);
|
||||||
List<IceServerProperties> webrtcIceServers = new ArrayList<>();
|
List<IceServerProperties.Builder> webrtcIceServers = new ArrayList<>();
|
||||||
if (rawIceServers == null || rawIceServers.isEmpty()) {
|
if (rawIceServers == null || rawIceServers.isEmpty()) {
|
||||||
return webrtcIceServers;
|
return webrtcIceServers;
|
||||||
}
|
}
|
||||||
List<String> arrayIceServers = asJsonStringsArray(property);
|
List<String> arrayIceServers = asJsonStringsArray(property);
|
||||||
for (String iceServerString : arrayIceServers) {
|
for (String iceServerString : arrayIceServers) {
|
||||||
try {
|
try {
|
||||||
IceServerProperties iceServerProperties = readIceServer(property, iceServerString);
|
IceServerProperties.Builder iceServerProperties = readIceServer(property, iceServerString);
|
||||||
webrtcIceServers.add(iceServerProperties);
|
webrtcIceServers.add(iceServerProperties);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
addError(property, iceServerString + " is not a valid webrtc ice server: " + e.getMessage());
|
addError(property, iceServerString + " is not a valid webrtc ice server: " + e.getMessage());
|
||||||
|
@ -1188,8 +1188,8 @@ public class OpenviduConfig {
|
||||||
return webrtcIceServers;
|
return webrtcIceServers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IceServerProperties readIceServer(String property, String iceServerString) {
|
private IceServerProperties.Builder readIceServer(String property, String iceServerString) {
|
||||||
String url = null, username = null, credential = null;
|
String url = null, username = null, credential = null, staticAuthSecret = null;
|
||||||
String[] iceServerPropList = iceServerString.split(",");
|
String[] iceServerPropList = iceServerString.split(",");
|
||||||
for (String iceServerProp: iceServerPropList) {
|
for (String iceServerProp: iceServerPropList) {
|
||||||
if (iceServerProp.startsWith("url=")) {
|
if (iceServerProp.startsWith("url=")) {
|
||||||
|
@ -1198,13 +1198,29 @@ public class OpenviduConfig {
|
||||||
username = StringUtils.substringAfter(iceServerProp, "username=");
|
username = StringUtils.substringAfter(iceServerProp, "username=");
|
||||||
} else if (iceServerProp.startsWith("credential=")) {
|
} else if (iceServerProp.startsWith("credential=")) {
|
||||||
credential = StringUtils.substringAfter(iceServerProp, "credential=");
|
credential = StringUtils.substringAfter(iceServerProp, "credential=");
|
||||||
|
} else if (iceServerProp.startsWith("staticAuthSecret=")) {
|
||||||
|
staticAuthSecret = StringUtils.substringAfter(iceServerProp, "staticAuthSecret=");
|
||||||
} else {
|
} else {
|
||||||
addError(property, "Wrong parameter: " + iceServerProp);
|
addError(property, "Wrong parameter: " + iceServerProp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IceServerProperties iceServerProperties = new IceServerProperties.Builder()
|
IceServerProperties.Builder builder = new IceServerProperties.Builder().url(url);
|
||||||
.url(url).username(username).credential(credential).build();
|
IceServerProperties.Builder builderCheck = new IceServerProperties.Builder().url(url);
|
||||||
return iceServerProperties;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -922,6 +922,9 @@ public class SessionRestController {
|
||||||
JsonObject customIceServerJson = customIceServersJsonArray.get(i).getAsJsonObject();
|
JsonObject customIceServerJson = customIceServersJsonArray.get(i).getAsJsonObject();
|
||||||
IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder();
|
IceServerProperties.Builder iceServerPropertiesBuilder = new IceServerProperties.Builder();
|
||||||
iceServerPropertiesBuilder.url(customIceServerJson.get("url").getAsString());
|
iceServerPropertiesBuilder.url(customIceServerJson.get("url").getAsString());
|
||||||
|
if (customIceServerJson.has("staticAuthSecret")) {
|
||||||
|
iceServerPropertiesBuilder.staticAuthSecret(customIceServerJson.get("staticAuthSecret").getAsString());
|
||||||
|
}
|
||||||
if (customIceServerJson.has("username")) {
|
if (customIceServerJson.has("username")) {
|
||||||
iceServerPropertiesBuilder.username(customIceServerJson.get("username").getAsString());
|
iceServerPropertiesBuilder.username(customIceServerJson.get("username").getAsString());
|
||||||
}
|
}
|
||||||
|
@ -934,10 +937,11 @@ public class SessionRestController {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new Exception("Type error in some parameter of 'customIceServers': " + e.getMessage());
|
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
|
// If not defined in connection, check if defined in openvidu config
|
||||||
for (IceServerProperties iceServerProperties: openviduConfig.getWebrtcIceServers()) {
|
for (IceServerProperties.Builder iceServerPropertiesBuilder: openviduConfig.getWebrtcIceServersBuilders()) {
|
||||||
builder.addCustomIceServer(iceServerProperties);
|
IceServerProperties.Builder configIceBuilder = iceServerPropertiesBuilder.clone();
|
||||||
|
builder.addCustomIceServer(configIceBuilder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue