Manually insert TURN users (Coturn) into a database - stun

I'm trying to set up a TURN server for a project using Coturn but am finding that documentation is sketchy at best...
I realise that there is a turnadmin tool that will do this for you, but I would greatly prefer to just run queries on my database directly. This is an app with potentially many users and their shared keys (hmackey in turnusers_lt) are subject to change (in order to not share passwords with the app the app uses a 'fake' password which is a hash of certain volatile user parameters that aren't so secret).
I can gather from the scant docs that the hmackey is computed using the realm, username and password:
$ turnadmin -k -u myusername -r my.realm.org -p my-password
> e.g. 0x7a69b0e2b747a4560045f79d171b78c0
Given that my code will know these three parameters, how do I build the hmac hash? E.g. in PHP I have
string hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = false ] )
$algo here should be SHA1, but what values would go into $data (e.g. concat of user/pass) and $key (e.g. realm)?
There's also a turn_secret table listing a 'value' for a realm, I was guessing this should be used as the $key in the above example, but adding and modifying the keys still give the same result when I call turnadmin.
Essentially, what I want to do is (pseudo-code):
// user registers
// pseudo-code, this is of course computed using php's password_hash function
$hashed_pw = hash($pw);
$db->query('insert into usertable (name, pass) values ($name, $hashed_pw)');
// this is implemented somewhere...
$coturn_pw = get_secret_hash($name);
// this needs implementing...
$HAMC = calc_hmac($name, $coturn_pw, 'my.realm.com');
$turndb->query('insert into turnusers_lt values (...)');
// on update, delete also update turnusers_lt
...and then in the client, I should now be able to connect to the TURN server using $name and $coturn_pw as credentials for my.realm.com.
Or am I over-thinking this and should I just use a generic user for my app, hardcode the password and let Coturn figure out who is talking to who?

How to build the HMAC key is described in RFC 5389:
key = MD5(username ":" realm ":" SASLprep(password))
where MD5 is defined in RFC 1321 and SASLprep() is defined in RFC 4013
The only table you need to update is turnusers_lt. The turn_secret table and SHA1 algorithm is used for generating time-limited credentials.
INSERT INTO turnusers_lt (realm, name, hmackey) VALUES (:realm, :username, :key);
And of course, use prepared statements rather than building the SQL string manually.

OrangeDog answer is correct.
With node.js:
const crypto= require("crypto");
const username= "foo";
const realm= "here";
const password= "secret";
const hmac = crypto
.createHash("md5")
.update(`${username}:${realm}:${password}`)
.digest("hex")
;

Related

Is it "secure" to store a password and username in a .env file in a server to validate an admin API endpoint against?

Context
I've build a RESTful API server in Actix-Web with Rust that's hosted on a Heroku paid plan. It has n amount of publicly available endpoints to access content, alongside 3 strictly admin-only endpoints (for creating, editing, and deleting public content).
I am the only developer who'd ever need to access the admin-only endpoints - and infrequently at that. Several random users will be using the publicly available endpoints daily.
Normally, I'd implement an authentication/authorization strategy akin to this using JWTs (but obviously in Rust for my case). However, the added complexity that comes with this "more common" solution seems overkill for my simple use-case.
My theorized solution
Could I add a username and password field to the .env file in my project like so in order to match against a username and password passed in the admin-only handler functions?
... OTHER KEYS ...
USERNAME = my_really_long_random_username
PASSWORD = my_really_long_random_password
At first glance I'm storing passwords in plain text... but, there's only 1 and it's in my .env file, which is private by default.
All I'd do for the admin-only routes then is this (pseudo-code):
pub fn router_handler(passed_data) -> HttpResponse {
if passed_data.username == env.username && passed_data.password == env.password {
// CONSIDER THEM ADMIN
} else {
// BLOCK THEM AS THEY'RE NOT AUTHENTICATED
}
}
What I've tried
I have yet to try this strategy, but I'm curious about your opinions on it.
Question
Is my theorized solution secure? Does it seem reasonable given my use-case?
Response to question: jthulhu - is this what I do?
So, my .env file should look something like this:
... OTHER KEYS ...
USERNAME = a98ysnrn938qwyanr9c8yQden
PASSWORD = aosdf83h282huciquhr8291h91
where both of those hashes are the results of running my pre-determined username and password through my to_hash function which I added below (likely using a lib like this).
Then, my handler should be like this (psuedo-code):
pub fn router_handler(passed_data) -> HttpResponse {
if to_hash(passed_data.username) == env.username && to_hash(passed_data.password) == env.password {
// CONSIDER THEM ADMIN
} else {
// BLOCK THEM AS THEY'RE NOT AUTHENTICATED
}
}
You should never store passwords in plain text in a server, because if someones breaks in your server, and can read that file, they now have access to everything (whereas they might previously not). Not only that, but most people tend to reuse passwords, so storing one password in plain text means exposing several services where that password is used.
Instead, you should hash the passwords and store the hash. To perform a login, check if the hash of the given password corresponds to the one stored. This mechanism can be used with files or with databases alike, and is pretty much independent on how you actually store the hashes.

ramsey/uuid - validate a name-based hashed has the name that we assign it to the uuid

Any reliable uuid package from Packagist that I can download and use it for Slim framework?
Ideally,I would an uuid that I can hash a type, e.g. php, into the string. then i can check if that uuid has 'php'.
EDIT:
How can I validate a name-based hashed has the name that we assign it to the uuid?
// Generate a version 3 (name-based and hashed with MD5) UUID object
$uuid3 = Uuid::uuid3(Uuid::NAMESPACE_DNS, 'php.net');
$uuid = $uuid3->toString();
For instance:
if ($uuid3->getName($uuid) === 'php.net') {
// do something
}
Is this possible?
The best UUID package is https://github.com/ramsey/uuid
Just use composer require ramsey/uuid to install it.
Ideally,I would an uuid that I can hash a type, e.g. php, into the string. then i can check if that uuid has 'php'.
I don't understand this. UUIDs create a unique string. From Wikipedia:
A universally unique identifier (UUID) is a 128-bit number used to identify information in computer systems. The term globally unique identifier (GUID) is also used.
When generated according to the standard methods, UUIDs are for practical purposes unique, without depending for their uniqueness on a central registration authority or coordination between the parties generating them
Edit.
To answer your new question…
You can't reverse a hash, so you need to hash again and compare.
$uuid = Uuid::uuid3(Uuid::NAMESPACE_DNS, 'php.net')->toString();
$domainFromUser = 'example.com';
$userUuid = Uuid::uuid3(Uuid::NAMESPACE_DNS, $domainFromUser)->toString();
if ($uuid === $userUuid) {
// user provided the right domain
}

CoTURN: How to use TURN REST API?

I have build coturn and run it successfully. ip:192.168.1.111. Now the question I faced is to get the Turn credential through REST API.
https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00 According to the passage the request format should be
GET /?service=turn&username=mbzrxpgjys
and response should be JSON. Now my question is:
a) How to configure and command TURN SERVER to make it run in REST API mode?
b) How to write a http request in the right format so TURN SERVER can reply correctly? could you give me an example?
Few things to be clarified here are:
GET /?service=turn&username=mbzrxpgjys which returns a JSON, is just a suggested uri for retrieving time-limited TURN credentials from the server, you do not have to follow that, your uri can be just /?giveMeCredentials. In fact, I use my socket connection to retrieve this data, not direct http call with json response. End of day, it does not matter how you( the client that uses said TURN) get those credentials as long as they are valid.
You do not make any requests to the TURN server directly, no rest api call to TURN server is under your control.
you allocate a secret key when you are starting the TURN server, this can be taken from a db(thus dynamically changable), but lazy that I am, just hard-coded, and gave it in the turn config file, also remember to enable REST API. As part of turn command, turnserver ... --use-auth-secret --static-auth-secret=MySecretKey
Now, in your application server, you would use the same secret key to generate credentials, for username, it is UNIX timestamp and some string( can be random or user id or something) seperated by : and the password would be HMAC of the username with your secret key.
about the UNIX timestamp, this has be the time in TURN server till which your credentials has to be valid, so which calculating this make sure you take into account of the clock time difference between your application server and your turn server.
Now some sample code taken from my answer to another question
command for stating TURN server:
turnserver -v --syslog -a -L xx.xxx.xx.xx -X yy.yyy.yyy.yy -E zz.zzz.zz.zzz --max-bps=3000000 -f -m 3 --min-port=32355 --max-port=65535 --use-auth-secret --static-auth-secret=my_secret --realm=north.gov --cert=turn_server_cert.pem --pkey=turn_server_pkey.pem --log-file=stdout -q 100 -Q 300 --cipher-list=ALL
node.js code for creating TURN credentials in application server:
var crypto = require('crypto');
function getTURNCredentials(name, secret){
var unixTimeStamp = parseInt(Date.now()/1000) + 24*3600, // this credential would be valid for the next 24 hours
username = [unixTimeStamp, name].join(':'),
password,
hmac = crypto.createHmac('sha1', secret);
hmac.setEncoding('base64');
hmac.write(username);
hmac.end();
password = hmac.read();
return {
username: username,
password: password
};
}
Browser code for using this:
...
iceServers:[
{
urls: "turn:turn_server_ip",
username: username,
credential:password
}
...
After (many) hours of frustration, #Mido's excellent answer here was the only thing that actually got CoTurn's REST API working for me.
My credential server is PHP and I use CoTurn's config file 'turnserver.conf' so here's a tested and working translation of Mido's work for that situation:
Assuming a 'shared secret' of '3575819665154b268af59efedee8826e', here are the relevant turnserver.conf entries:
lt-cred-mech
use-auth-secret
static-auth-secret=3575819665154b268af59efedee8826e
...and the PHP (which misled me for ages):
$ttl = 24 * 3600; // Time to live
$time = time() + $ttl;
$username = $time . ':' . $user;
$password = base64_encode(hash_hmac('sha1', $username, '3575819665154b268af59efedee8826e', true));
Building upon #Mido and #HeyHeyJC answers, here is the Python implementation to build credentials for coturn.
import hashlib
import hmac
import base64
from time import time
user = 'your-arbitrary-username'
secret = 'this-is-the-secret-configured-for-coturn-server'
ttl = 24 * 3600 # Time to live
timestamp = int(time()) + ttl
username = str(timestamp) + ':' + user
dig = hmac.new(secret.encode(), username.encode(), hashlib.sha1).digest()
password = base64.b64encode(dig).decode()
print('username: %s' % username)
print('password: %s' % password)
Here is a web application to test the login to your coturn server. Use turn:host.example.com as the server name.
I came across similar issue (getting REST API working with TURN server) recently and learned that TURN server doesn't support REST API calls at all and just provides support for an authentication format with shared secret when we enable REST API support in TURN config. The draft only provides info on things that we need to consider while implementing such REST API and WE need to create the API on our own or use something like turnhttp to generate the temporary username password combo.
As #mido detailed, you can implement the username/password generation part in the application itself. But if you have reasons to separate this from the application and want to implement it as an entirely different API service, instead of implementing a complete API as per the draft, I came across another post in which the OP provided a PHP script to generate temp username & password and this one works pretty well once you modify the hash_hmac() function to the following,
$turn_password = hash_hmac('sha1', $turn_user, $secret_key, true);
We need to base64 encode the RAW output of hash_hmac to get it working and I believe this is why it was not working for the OP in that link.
You should be able to test authentication using turnutils_uclient command to verify that the temp username/password combo is working as expected.
turnutils_uclient -y -u GENERATED_USERNAME -w GENERATED_PASSWORD yourturnserver.com
Once you have verified authentication and confirmed that it's working, you can setup webserver for the PHP script to make it available to your application and fetch the temporary username/password combo. Also, you would need to implement other security setup (authentication) to protect the API from unauthorized access.
I know this is an old post, just sharing my findings here hoping that it will be useful for someone someday.
Here is my c# implementation with TTL
public string[] GenerateTurnPassword(string username)
{
long ttl = 3600 * 6;
var time = DateTimeOffset.Now.ToUnixTimeSeconds() + ttl;
var newuser = time + ":" + username;
byte[] key = Encoding.UTF8.GetBytes("YOURSECRET");
HMACSHA1 hmacsha1 = new HMACSHA1(key);
byte[] buffer = Encoding.UTF8.GetBytes(newuser);
MemoryStream stream = new MemoryStream(buffer);
var hashValue = hmacsha1.ComputeHash(stream);
string[] arr = new string[2];
arr[0] = Convert.ToBase64String(hashValue);
arr[1] = newuser;
return arr;
}
Well #Augusto Destrero provided implementation will cause TypeError: key: expected bytes or bytearray, but got 'str' on Python 3.7.6, for anyone looking for another Python implementation, here is an example:
import time
import hmac
import hashlib
import base64
secret = b'abcdefghijkmln'
def generateTurnUsernamePwd():
username = "arbitry username here"
password = hmac.new(secret, bytes(username, 'UTF-8'), hashlib.sha1).digest()
passwordStr = base64.b64encode(password).decode("utf-8")
return username,passwordStr
print(generateTurnUsernamePwd())
The main difference is key and message keyword arguments in hmac lib has to be bytes in newer version , while in older versions, it requires str.
I thought it worthwhile to add to the answer the actual text of the documentation of coturn regardingg this topic and a link to it for those interested:
--auth-secret TURN REST API flag. Flag that sets a special WebRTC authorization option that is based upon authentication secret. The
feature purpose is to support "TURN Server REST API" as described
in the TURN REST API section below. This option uses timestamp
as part of combined username: usercombo -> "timestamp:username",
turn user -> usercombo, turn password ->
base64(hmac(input_buffer = usercombo, key = shared-secret)). This
allows TURN credentials to be accounted for a specific user id. If
you don't have a suitable id, the timestamp alone can be used. This
option is just turns on secret-based authentication. The actual
value of the secret is defined either by option static-auth-secret,
or can be found in the turn_secret table in the database.
Here is an example for go with ttl:
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"time"
)
const turnTokenTtl = time.Hour * 24
const turnSecret = "your secret"
func getTurnCredentials(name string) (string, string) {
timestamp := time.Now().Add(turnTokenTtl).Unix()
username := fmt.Sprintf("%d:%s", timestamp, name)
h := hmac.New(sha1.New, []byte(turnSecret))
h.Write([]byte(username))
credential := base64.StdEncoding.EncodeToString(h.Sum(nil))
return username, credential
}

How to use strong encryption for password field using grails with postgresql database?

I had to migrate a legacy database with clear text password to a PostgresSQL database. I've looked up what's the best way to encrypt password in a database and found the pgcrypto extension with slow algorithm. (see pgcrypto documentation for 8.4)
The migration is done for data and everything is working well.
Now I have to write a CRUD application to handle this data.
I'm wondering what's the best way to use this strong encryption with grails ?
In my model, I've used the afterInsert event to handle this :
def afterInsert() {
Compte.executeUpdate("update Compte set hashpass=crypt(hashpass, gen_salt('bf', 8)) where id = (:compteId)", [compteId: this.id])
}
I guess that I should also check if the hashpass field is modified whenever the model is saved. But before that, is there another (best) way to achieve my goal ?
Edit : I cannot use the Spring Security bcrypt plugin here. The CRUD application that I'm writing use SSO CAS so I don't need such a plugin. The CRUD application manages accounts for another application that I don't own. I just need to create a new account, modify or delete an existing one. This is very simple. The tricky part is to hack grails so that it takes into account the password field and use a specific sql to store it to a postgreSQL database.
Edit 2 :
I've come up with the following code but it doesn't work
def beforeInsert() {
hashpass = encodePassword(hashpass);
}
def encodePassword(cleartextpwd) {
// create a key generator based upon the Blowfish cipher
KeyGenerator keygenerator = KeyGenerator.getInstance("Blowfish");
// create a key
SecretKey secretkey = keygenerator.generateKey();
// create a cipher based upon Blowfish
Cipher cipher = Cipher.getInstance(ALGORITHM);
// initialise cipher to with secret key
cipher.init(Cipher.ENCRYPT_MODE, secretkey);
// get the text to encrypt
String inputText = cleartextpwd;
// encrypt message
byte[] encrypted = cipher.doFinal(inputText.getBytes("UTF-8"));
return Base64.encodeBase64String(encrypted);
}
I get a hash that is not a blowfish hash (beginning with $2a$08$ )
Edit 3 :
I've finally came up with a cleaner grails solution after reading this wiki page : grails.org/Simple+Dynamic+Password+Codec (not enough reputation to put more than 2 links so add http:// before) and the bug report jira.grails.org/browse/GRAILS-3620
Following advice from #lukelazarovic, I've also used the algorithm from the spring security plugin.
Here is my password encoder to put into grails/utils :
import grails.plugin.springsecurity.authentication.encoding.BCryptPasswordEncoder;
class BlowfishCodec {
static encode(target) {
// TODO need to put the logcount = 8 in configuration file
return new BCryptPasswordEncoder(8).encodePassword(
target, null)
}
}
I've updated my Compte model to call my password encoder before saving / updating the data :
def beforeInsert() {
hashpass = hashpass.encodeAsBlowfish();
}
def beforeUpdate() {
if(isDirty('hashpass')) {
hashpass = hashpass.encodeAsBlowfish();
}
}
The tricky part is to hack grails so that it takes into account the
password field and use a specific sql to store it to a postgreSQL
database.
Is there any particular reason to do the hashing in database?
IMHO it's better to hash the password in Grails, therefore have a code that is not database-specific and easier to read.
For hashing passwords using Blowfish algorithm using Java or Groovy see Encryption with BlowFish in Java
The resulting hash begins with algorithm specification, iteration count and salt, separated with dollar sign - '$'. So the hash may look like "$2a$08$saltCharacters" where 2a is a algorithm, 08 is iteration count, then follows salt and after salt is the hash itself.
For broader explanation see http://www.techrepublic.com/blog/australian-technology/securing-passwords-with-blowfish. Don't mind that it concerns to Blowfish in PHP, the principles applies for Java or Groovy as well.

User authentication failure /w Hash

And I need to secure some area's on my web store for admin use.
The problem is the authentication of the user: the salt + hash is failing.
This is my code for creating a password (using PHP5.x):
$salt = rand(0, 999999999999);<br>
$passEncr = sha1($pass1 + $salt);
This variable $passEncr is inserted into the database together with its salt.
At the login page I've got the following check:
$password = $_POST['password']; // hash+salt in the database
$storedSalt = $row['salt']; // salt from database<br>
if (sha1($password + $storedSalt) == $row['password'])
Now the problem I'm experiencing is that some hashes appear to be the same.
If I try to log in with an alphanumeric password, I succeed, no matter what the content of that password is.
Full login check here: http://pastebin.com/WjVnQ4aF
Can someone please explain what I'm doing wrong?
Well, SQL injection, using SHA for passwords instead of bcrypt are the first things I see, not using OpenId so you can get out of the business of storing passwords is another.
As for the passwords being the same, I would check the database -- see what you are storing, that will tell you where your problem lies.