Wowza Community

Custom stream authorization and expiration module (all methods?)

well I added catch (ArrayIndexOutOfBoundsException e) {

getLogger().info(“HTTP Cupertino Authorization failed!”);

This appears to take care of it.

An easy fix but thought I should add it for anyone having this issue and looking to use this solution.

good code…

but I would use …

[PHP]

public void play(IClient client, RequestFunction function, AMFDataList params)

{

try

{

String[] data = null;

data = getParamString(params, PARAM1).toLowerCase().split("&");

String filename = data[0];

String timestamp = data[1];

String signature = data[2];

try

{

long currenttime = System.currentTimeMillis()/1000;

long futuretime = Long.parseLong(timestamp.trim()) + seconds;

Integer comparetime = new Long(currenttime).compareTo(new Long(futuretime));

if ( comparetime <= 0 )

{

String hashcheck = filename + timestamp + secret_code;

MessageDigest m;

m = MessageDigest.getInstance(“MD5”);

m.reset();

m.update(hashcheck.getBytes());

byte[] digest = m.digest();

BigInteger bigInt = new BigInteger(1,digest);

String hashtext = bigInt.toString(16);

while(hashtext.length() < 32 )

{

hashtext = “0”+hashtext;

}

if ( !signature.equals(hashtext) )

{

sendClientOnStatusError(client, “NetConnection.Connect.Rejected”, “Access denied: bad token.”);

client.rejectConnection();

getLogger().info(“RTMP Authorization rejected, bad token”);

return;

}

}

else

{

sendClientOnStatusError(client, “NetConnection.Connect.Rejected”, “Access denied: time bad.”);

client.rejectConnection();

getLogger().info(“RTMP Authorization rejected, time bad”);

return;

}

} catch (NoSuchAlgorithmException e) {

getLogger().info(“Authorization algorithm failed!”);

sendClientOnStatusError(client, “NetConnection.Connect.Rejected”, “Access denied: server failed.”);

return;

}

} catch (NullPointerException e) {

getLogger().info(“RTMP Authorization failed!”);

sendClientOnStatusError(client, “NetConnection.Connect.Rejected”, “Access denied: server failed.”);

return;

}

this.invokePrevious(client, function, params);

}

[/PHP]

and

[PHP]

//…

nc.connect(“rtmp://localhost/live”);

//…

nsPlay.play(“thestream&thetimestamp&thesignature”);

[/PHP]

sorry for my bad english

code bad

change to

[PHP]

try {

// get URL variables

String filename = session.getStreamName();

String querystring = session.getStream().getQueryStr();

if(querystring.length < 1)

{

getLogger().info(“HTTP Cupertino Fail! querystring not params”);

}

String[] queryarray = querystring.split("[&]");

if(queryarray.length < 2)

{

getLogger().info(“HTTP Cupertino Fail! queryarray not params”);

}

String[] temparray = queryarray[0].split("[=]");

if(temparray.length < 2)

{

getLogger().info(“HTTP Cupertino Fail! temparray1 not params”);

}

String timestamp = temparray[1];

temparray = queryarray[1].split("[=]");

if(temparray.length < 2)

{

getLogger().info(“HTTP Cupertino Fail! temparray2 not params”);

}

String signature = temparray[1];

// check authorization

authorized = authorize(filename,timestamp,signature);

}

[/PHP]

your code not fix the problem…

capostrike93,

I am currently using my code, and it is working fine (although I’m sure it could be cleaner and more efficient). Your code seems to add more specific logging, which may be desired, but I don’t think it improves the functionality.

what your code is simply display a message when these errors occur (IndexOutOfBounds… etc)

but NOT prevent the error…

my code prevent generating the error which is very different.

thought to prefer prevent it from generating error than delete the message of error and change it to another message…

http://en.wikipedia.org/wiki/Exception_handling

This script no work if a request with bad hash the connecion isnt rejected

i have this error

ERROR server comment 2010-08-17 07:44:48 - - - - - 4.967 - - - - - - - - invoke(onConnect): java.lang.NumberFormatException: For input string: “4c5dd7826981d7082e24eec5c2c1c5da”: java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

Not Working it same log

I think you need to add the “AllowEncoder” property to address this issue. Add this the Application.xml /Properties list, at bottom of the file:

<Property>
	<Name>AllowEncoder</Name>
	<Value>FM</Value>
</Property>

“FM” should work as the userAgent of the encoder, if not, look in the access log and you should see the userAgent of the encoder. Copy that to the AllowEncoder Property Value.

Salvadore

i stream no authentication

		<Modules>
			<Module>
				<Name>base</Name>
				<Description>Base</Description>
				<Class>com.wowza.wms.module.ModuleCore</Class>
			</Module>
			<Module>
				<Name>properties</Name>
				<Description>Properties</Description>
				<Class>com.wowza.wms.module.ModuleProperties</Class>
			</Module>
			<Module>
				<Name>logging</Name>
				<Description>Client Logging</Description>
				<Class>com.wowza.wms.module.ModuleClientLogging</Class>
			</Module>
			<Module>
				<Name>flvplayback</Name>
				<Description>FLVPlayback</Description>
				<Class>com.wowza.wms.module.ModuleFLVPlayback</Class>
			</Module>
            <Module>
				<Name>Authorization</Name>
				<Description>Stream authorization and expiration</Description>
				<Class>com.unite.wms.module.Authorization</Class>
            </Module>			
		</Modules>
		<!-- Properties defined here will be added to the IApplication.getProperties() and IApplicationInstance.getProperties() collections -->
		<Properties>
<Property>
	<Name>AllowEncoder</Name>
	<Value>WIN</Value>
</Property>
		</Properties>
	</Application>
</Root>

What type of authentication are you using? Can you get it to stream with no authentication?

Salvadore

Yes, it work

Does it work if you comment out the Authorization module like this?

<!-- 
<Module>
	<Name>Authorization</Name>
	<Description>Stream authorization and expiration</Description>
	<Class>com.unite.wms.module.Authorization</Class>
</Module>
-->	

Salvadore

Salvadore , not working same last time

Great!

Here is an article that describes how to enable user authentication to publish from an RTMP/RTSP based encoder to Wowza:

How to enable username/password authentication for RTMP and RTSP publishing (ModuleRTMPAuthenticate)

Salvadore

Hello,

thanks for all the info. Can you please share the .jar compiled for the authorization module? Thanks

Can anyone send the modified jw player? as my flash skills are bellow zero.

thanks

I’ve extended this somewhat for my own purposes which may help others in securing their streams.

This extended module requires the installation of a separate LAMP server, you will still need to make the modifications to JWplayer in order to secure RTMP streaming (securing iOS and Android will work out of the box).

The process for authentication works as follows:

User authenticates onto a Web Server running a LAMP stack, the generated signature, connection time, requested streams are stored within a mysql table.

The authenticated web page loads JWplayer with the requested stream.

When the user plays the stream the wowza module then connects back to the webserver with the signature and verifies if it exists and how long ago it was placed in the table for an expiry threshold.

PHPAuth code is as follows:

package com.blackcat;
import com.wowza.wms.application.*;
import com.wowza.wms.amf.*;
import com.wowza.wms.client.*;
import com.wowza.wms.module.*;
import com.wowza.wms.request.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.rtp.model.*;
import com.wowza.wms.httpstreamer.model.*;
import com.wowza.wms.httpstreamer.cupertinostreaming.httpstreamer.*;
import com.wowza.wms.httpstreamer.smoothstreaming.httpstreamer.*;
// PHPAuth Version 
import java.lang.String;
//import java.io.InputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.security.*;
import java.math.*;
public class PHPAuth extends ModuleBase {
	
	// Define the same secret code here that you used in the PHP page
	
	// Define how long you want the stream to be valid, in seconds
	public long seconds = 60;
	
	// For RTMP stream requests
	public void onConnect(IClient client, RequestFunction function, AMFDataList params)
	{
		String logTitle = "RTMP Authorization onConnect - ";
		
		// default to not isAuthorized
		Boolean isAuthorized = false;
		
		try {
			
			String phpAuthFileName = getParamString(params, PARAM1);
			String phpAuthTimeStamp = getParamString(params, PARAM2);
			String phpAuthSignature = getParamString(params, PARAM3);
			String phpAuthQueryString = client.getQueryStr();
			String phpAuthCallBackAddressProperty = client.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			String phpAuthSecretCode = client.getAppInstance().getProperties().getPropertyStr("phpAuthSecretCode");
			
			int phpAuthClientId = client.getClientId();
			
			if (phpAuthFileName == null) {
				getLogger().info(logTitle + "Client seems to librtmp");
				String[] arrQueryString = phpAuthQueryString.split("[&]");	
				String[] arrTemp = arrQueryString[0].split("[=]");
				phpAuthTimeStamp = arrTemp[1];
				
				arrTemp = arrQueryString[1].split("[=]");
				phpAuthSignature = arrTemp[1];
				
				arrTemp = arrQueryString[2].split("[=]");
				phpAuthFileName = arrTemp[1];
				phpAuthFileName = phpAuthFileName.replace("%", "/");
			}
			else
			{
				getLogger().info(logTitle + "Client seems to be Flash player");
			}
			
			getLogger().info(logTitle + "phpAuthFileName:       " + phpAuthFileName);
			getLogger().info(logTitle + "phpAuthQueryString:    " + phpAuthQueryString);
			getLogger().info(logTitle + "phpAuthTimeStamp:      " + phpAuthTimeStamp);
			getLogger().info(logTitle + "phpAuthSignature:      " + phpAuthSignature);
			getLogger().info(logTitle + "phpAuthClientId:       " + phpAuthClientId);
			getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
			getLogger().info(logTitle + "phpAuthSecretCode:       " + phpAuthSecretCode);
			
			isAuthorized = authorize(phpAuthFileName,phpAuthTimeStamp,phpAuthSignature, phpAuthClientId, phpAuthCallBackAddressProperty, phpAuthSecretCode);
		} catch (NullPointerException e) {
			getLogger().info(logTitle + "RTMP Authorization failed! - null exception");
		} catch (ArrayIndexOutOfBoundsException e) {
			getLogger().info(logTitle + "RTMP Authorization failed! - array index out of bounds");
		}
		// log status and reject connection, if necessary
		if (!isAuthorized)
		{
			getLogger().info(logTitle + "RTMP Authorization rejected");
			sendClientOnStatusError(client, "NetConnection.Connect.Rejected", "Access denied: please reload the video page.");
			client.rejectConnection();
		} else
		{
			getLogger().info(logTitle + "RTMP Authorization approved");
		}
	}
	
	public void onDisconnect(IClient client)
	{
		String logTitle = "RTMP Authorization onDisconnect - ";
		
		int phpAuthClientId = client.getClientId();
		String phpAuthCallBackAddressProperty = client.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
		
		getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
		getLogger().info(logTitle + "phpAuthCallBackAddressProperty: " + phpAuthCallBackAddressProperty);
		
		String phpAuthHttpCallBack = phpAuthCallBackAddressProperty + "?action=stop&phpAuthClientId="+phpAuthClientId;
		String findContent = "###STOP###";
		
		phpConnect(phpAuthHttpCallBack, findContent);
	}
	
	// For HTTP Cupertino stream requests
	public void onHTTPCupertinoStreamingSessionCreate(HTTPStreamerSessionCupertino session)
	{
		String logTitle = "HLS Authorization onHTTPCupertinoStreamingSessionCreate - ";
		
		// default to not isAuthorized		
		Boolean isAuthorized = false;
		
		try {
			// get URL variables
			String phpAuthFileName = session.getStreamName();
			String phpAuthQueryString = session.getQueryStr();
			
			String[] arrQueryString = phpAuthQueryString.split("[&]");
			String[] arrTemp = arrQueryString[0].split("[=]");
			String phpAuthTimeStamp = arrTemp[1];
			arrTemp = arrQueryString[1].split("[=]");
			String phpAuthSignature = arrTemp[1];
			
			String clientId = session.getSessionId();
			int phpAuthClientId = Integer.parseInt(clientId);
			
			String phpAuthCallBackAddressProperty = session.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			String phpAuthSecretCode = session.getAppInstance().getProperties().getPropertyStr("phpAuthSecretCode");
			
			getLogger().info(logTitle + "phpAuthFileName: " + phpAuthFileName);
			getLogger().info(logTitle + "phpAuthTimeStamp: " + phpAuthTimeStamp);
			getLogger().info(logTitle + "phpAuthSignature: " + phpAuthSignature);
			getLogger().info(logTitle + "phpAuthQueryString: " + phpAuthQueryString);
			getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
			getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
			getLogger().info(logTitle + "phpAuthSecretCode:       " + phpAuthSecretCode);
			
			// check authorization
			isAuthorized = authorize(phpAuthFileName, phpAuthTimeStamp,phpAuthSignature, phpAuthClientId, phpAuthCallBackAddressProperty, phpAuthSecretCode);
		} catch (NullPointerException e) {
			getLogger().info(logTitle + " nullPointerException : HTTP Cupertino Authorization failed!");
		} catch (ArrayIndexOutOfBoundsException e) {
			getLogger().info(logTitle + " ArrayIndexOutOfBounds : HTTP Cupertino Authorization failed!");
		}
		// log status and reject connection, if necessary
		if (!isAuthorized)
		{
			getLogger().info(logTitle + " Authorization rejected.");
			session.rejectSession();
		} else
		{
			getLogger().info(logTitle + " Authorization approved");
		}
	}
	
	public void onHTTPCupertinoStreamingSessionDestroy(HTTPStreamerSessionCupertino session)
	{
		String logTitle = "HLS Authorization onHTTPCupertinoStreamingSessionDestroy - ";
		
		String clientId = session.getSessionId();
		int phpAuthClientId = Integer.parseInt(clientId);
		String phpAuthCallBackAddressProperty = session.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
	
		getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
		getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
		
		String phpAuthHttpCallBack = phpAuthCallBackAddressProperty + "?action=stop&phpAuthClientId="+phpAuthClientId;
		String findContent = "###STOP###";
		
		phpConnect(phpAuthHttpCallBack, findContent);
		
	}
	// for HTTP Smooth Streaming stream requests
	public void onHTTPSmoothStreamingSessionCreate(HTTPStreamerSessionSmoothStreamer session)
	{
		String logTitle = "HLS Authorization onHTTPSmoothStreamingSessionCreate - ";
		
		String clientId = session.getSessionId();
		int phpAuthClientId = Integer.parseInt(clientId);
		
		// default to not isAuthorized		
		Boolean isAuthorized = false;
		
		try {
			// get URL variables
			String phpAuthFileName = session.getStreamName();
			String phpAuthQueryString = session.getStream().getQueryStr();
			String[] arrQueryString = phpAuthQueryString.split("[&]");
			String[] arrTemp = arrQueryString[0].split("[=]");
			String phpAuthTimeStamp = arrTemp[1];
			arrTemp = arrQueryString[1].split("[=]");
			String phpAuthSignature = arrTemp[1];
			
			String phpAuthCallBackAddressProperty = session.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			String phpAuthSecretCode = session.getAppInstance().getProperties().getPropertyStr("phpAuthSecretCode");
			getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
			getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
			
			// check authorization
			isAuthorized = authorize(phpAuthFileName,phpAuthTimeStamp,phpAuthSignature, phpAuthClientId, phpAuthCallBackAddressProperty, phpAuthSecretCode);
		} catch (NullPointerException e) {
			getLogger().info(logTitle + "Authorization failed!");
		} catch (ArrayIndexOutOfBoundsException e) {
			getLogger().info(logTitle + "Authorization failed!");
		}
		// log status and reject connection, if necessary
		if (!isAuthorized)
		{
			getLogger().info(logTitle + "Authorization rejected");
			session.rejectSession();
		} else
		{
			getLogger().info(logTitle + "Streaming approved");
		}
	}
	
	
	// For RTP/RTSP stream requests
	public void onRTPSessionCreate(RTPSession rtspSession)
	{
		String logTitle = "Android Authorization onRTPSessionCreate - ";
		
		// default to not isAuthorized
		Boolean isAuthorized = false;
		
		try {
	
			String phpAuthFileName = rtspSession.getUri();
			String phpAuthQueryString = rtspSession.getQueryStr();
			String uriStr = rtspSession.getUri();
			String phpAuthCallBackAddressProperty = rtspSession.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			String phpAuthSecretCode = rtspSession.getAppInstance().getProperties().getPropertyStr("phpAuthSecretCode");
			
			getLogger().info("#####  onRTPSessionCreate:"+uriStr+"\nQuerryString:"+phpAuthQueryString);
			
			String[] arrQueryString = phpAuthQueryString.split("[&]");
			String[] arrTemp = arrQueryString[0].split("[=]");
			String phpAuthTimeStamp = arrTemp[1];
			arrTemp = arrQueryString[1].split("[=]");
			String phpAuthSignature = arrTemp[1];
			
			String clientId = rtspSession.getSessionId();
			int phpAuthClientId = Integer.parseInt(clientId);
			
			getLogger().info(logTitle + "phpAuthFileName: " + phpAuthFileName);
			getLogger().info(logTitle + "phpAuthTimeStamp: " + phpAuthTimeStamp);
			getLogger().info(logTitle + "phpAuthSignature: " + phpAuthSignature);
			getLogger().info(logTitle + "phpAuthQueryString: " + phpAuthQueryString);
			getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
			getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
			getLogger().info(logTitle + "phpAuthSecretCode:       " + phpAuthSecretCode);
			
			// check authorization
			isAuthorized = authorize(phpAuthFileName,phpAuthTimeStamp,phpAuthSignature,phpAuthClientId, phpAuthCallBackAddressProperty,phpAuthSecretCode);
		} catch (NullPointerException e) {
			getLogger().info(logTitle + "Authorization failed! NullPointerException");
		} catch (ArrayIndexOutOfBoundsException e) {
			getLogger().info(logTitle + "Authorization failed! ArrayIndexOutOfBoundsException");
		}
		// log status and reject connection, if necessary
		if (!isAuthorized)
		{
			getLogger().info(logTitle + "Authorization rejected");
			rtspSession.rejectSession();
		} else
		{
			getLogger().info(logTitle + "Streaming approved");
		}
	}
	
	// For RTP/RTSP stream requests
		public void onRTPSessionDestroy(RTPSession rtspSession)
		{
			String logTitle = "Android Authorization onRTPSessionDestroy - ";
			
			// default to not isAuthorized
			String phpAuthCallBackAddressProperty = rtspSession.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			
			try {
				String clientId = rtspSession.getSessionId();
				int phpAuthClientId = Integer.parseInt(clientId);
				
				getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
				getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
				
				String phpAuthHttpCallBack = phpAuthCallBackAddressProperty + "?action=stop&phpAuthClientId="+phpAuthClientId;
				String findContent = "###STOP###";
				
				phpConnect(phpAuthHttpCallBack, findContent);
				
			} catch (NullPointerException e) {
				getLogger().info("RTP onRTPSessionDestroy - NullPointerException");
			} catch (ArrayIndexOutOfBoundsException e) {
				getLogger().info("RTP onRTPSessionDestroy - ArrayIndexOutOfBoundsException");
			}
		}
	
	
	
	// main authorize function
	public boolean authorize(String phpAuthFileName, String phpAuthTimeStamp, String phpAuthSignature, int phpAuthClientId, String phpCallBackUrl, String secretCode)
	{
		String logTitle = "authorize ";
		
		Boolean isAuthorized = false;
		
		String hashcheck = phpAuthFileName + phpAuthTimeStamp + secretCode;
		
		
		// Regenerate the signature from the input parameters - they should match, otherwise something fishy is going on.
		try {
			MessageDigest m;
			m = MessageDigest.getInstance("MD5");
			m.reset();
			m.update(hashcheck.getBytes());
			byte[] digest = m.digest();
			BigInteger bigInt = new BigInteger(1,digest);
			String hashtext = bigInt.toString(16);
			while(hashtext.length() < 32 ){
				hashtext = "0"+hashtext;
			}
	
			getLogger().info(logTitle + "phpAuthSignature: "  + phpAuthSignature);
			getLogger().info(logTitle + "hashtext: "  + hashtext);
			
			//if ( phpAuthSignature.equals(hashtext) ) {
				String phpAuthHttpCallBack = phpCallBackUrl + "?action=play&phpAuthSignature="+phpAuthSignature+"&phpAuthClientId="+phpAuthClientId;
				String findContent = "###ALLOWED###";
				isAuthorized = phpConnect(phpAuthHttpCallBack, findContent);
			//}
			//else {
				//getLogger().info(logTitle + "HASHES DO NOT MATCH --- SOMETHING FISHY GOING ON");
			//}
			
			
		} catch (NoSuchAlgorithmException e) {
			getLogger().info("Authorization algorithm failed!");
		}
		
		return isAuthorized;
	}
	
	// Make a http connection and return return true or false based on the content being found.
	public boolean phpConnect(String webURL, String Content)
	{
		// default to not isAuthorized
		Boolean contentExists = false;
		
		  HttpURLConnection connection = null;
	      OutputStreamWriter wr = null;
	      BufferedReader rd  = null;
	      StringBuilder sb = null;
	      String line = null;
	    
	      URL phpAuthHttpCallBack = null;
	    
	      try {
	          phpAuthHttpCallBack = new URL(webURL);
	          getLogger().info(phpAuthHttpCallBack);
	          
	          //set up out communications stuff
	          connection = null;
	        
	          //Set up the initial connection
	          connection = (HttpURLConnection)phpAuthHttpCallBack.openConnection();
	          connection.setRequestMethod("GET");
	          connection.setDoOutput(true);
	          connection.setReadTimeout(10000);
	                    
	          connection.connect();
	        
	          //read the result from the server
	          rd  = new BufferedReader(new InputStreamReader(connection.getInputStream()));
	          sb = new StringBuilder();
	        
	          while ((line = rd.readLine()) != null)
	          {
	        	  if (line.matches(Content)) {
	        		  contentExists = true;
	        	  }
	              sb.append(line + '\n');
	          }
	        
	          getLogger().info(sb.toString());
	          //System.out.println(sb.toString());
	                    
	      } catch (MalformedURLException e) {
	          e.printStackTrace();
	      } catch (ProtocolException e) {
	          e.printStackTrace();
	      } catch (IOException e) {
	          e.printStackTrace();
	      }
	      finally
	      {
	          //close the connection, set all objects to null
	          connection.disconnect();
	          rd = null;
	          sb = null;
	          wr = null;
	          connection = null;
	      }
		return contentExists;
	}
}

In the modules section of your Application.xml insert the block below.

<Module>
                                <Name>PHPAuth</Name>
                                <Description>PHPAuthModule</Description>
                                <Class>com.blackcat.PHPAuth</Class>
                        </Module>

In the properties section of the Application.xml

 <Property>
                                <Name>phpAuthCallBackAddressProperty</Name>
                                <Value>http://your.lampserver.com/phpauth/wowzaSecurity.php</Value>
                        </Property>
                        <Property>
                                <Name>phpAuthSecretCode</Name>
                                <Value>S3cretC0de</Value>                       <!-- is the secret token used to generate the signature -->
                        </Property>
                        <Property>
                             <Name>secureTokenSharedSecret</Name>
                             <Value>S3cureToken</Value>                        <!-- This is the secure token compiled into JWplayer -->
                        </Property>

In the WowzaSecurity.php script you either accept or reject the stream simply by outputting ###ALLOWED### or ###REJECTED###.

As an additional level of security I have obfuscated the secure token in the compiled JWplayer as it’s a well known fact that you can use things like showmycode.com to reveal AS3 code - I can post this code as well if people would like it.

My background is more on the PHP side of things and I’ve done this as a learning curve to Java, Wowza and ActionScript 3 - so please try to ignore my poor Java, or if you’re feeling generous please improve the module.

I will certainly try and assist you if you have problems getting this to work.

Graeme

P.S. I should point out that this module is for the latest version of WMS (3.6.2) - i just realised it’s in the Wowza Version 2 forums.

Hello,

Any chance you could send the .sql and wowza php file? wowzaSecurity.php in this case.

thanks

I Can’t livestream with Adobe Flash Media Live Encoder

Help Please :frowning:

What do you mean you can’t stream with FMLE?

Take a look at this guide to get started:

How to set up live streaming using an RTMP-based encoder

Salvadore

I think you need to add the “AllowEncoder” property to address this issue. Add this the Application.xml /Properties list, at bottom of the file:

<Property>
	<Name>AllowEncoder</Name>
	<Value>FM</Value>
</Property>

“FM” should work as the userAgent of the encoder, if not, look in the access log and you should see the userAgent of the encoder. Copy that to the AllowEncoder Property Value.

Salvadore

What type of authentication are you using? Can you get it to stream with no authentication?

Salvadore

Does it work if you comment out the Authorization module like this?

<!-- 
<Module>
	<Name>Authorization</Name>
	<Description>Stream authorization and expiration</Description>
	<Class>com.unite.wms.module.Authorization</Class>
</Module>
-->	

Salvadore

Great!

Here is an article that describes how to enable user authentication to publish from an RTMP/RTSP based encoder to Wowza:

How to enable username/password authentication for RTMP and RTSP publishing (ModuleRTMPAuthenticate)

Salvadore

I am not sure what the Authorization module is but it seems to be messing things up.

What version of Wowza are you running? If you are running Wowza Media Server 3.5.0, the ModuleRTMPAuthenticate module is built-in, and you would use:

<Module>
	<Name>ModuleRTMPAuthenticate</Name>
	<Description>ModuleRTMPAuthenticate</Description>
	<Class>com.wowza.wms.security.ModuleRTMPAuthenticate</Class>
</Module>

If you’re using an earlier version of Wowza Media Server, you must download the MediaSecurity Addon to get the module. Download and unzip the MediaSecurity Addon package, copy the wms-plugin-security.jar file from the package /lib folder to the Wowza Media Server /lib folder, and then restart Wowza Media Server.

And you would use:

<Module>
     <Name>ModuleRTMPAuthenticate</Name>
     <Description>ModuleRTMPAuthenticate</Description>
     <Class>com.wowza.wms.plugin.security.ModuleRTMPAuthenticate</Class>
</Module>

But you should leave the Authorization module commented out for now or remove it all together.

Salvadore

I’ve extended this somewhat for my own purposes which may help others in securing their streams.

This extended module requires the installation of a separate LAMP server, you will still need to make the modifications to JWplayer in order to secure RTMP streaming (securing iOS and Android will work out of the box).

The process for authentication works as follows:

User authenticates onto a Web Server running a LAMP stack, the generated signature, connection time, requested streams are stored within a mysql table.

The authenticated web page loads JWplayer with the requested stream.

When the user plays the stream the wowza module then connects back to the webserver with the signature and verifies if it exists and how long ago it was placed in the table for an expiry threshold.

PHPAuth code is as follows:

package com.blackcat;
import com.wowza.wms.application.*;
import com.wowza.wms.amf.*;
import com.wowza.wms.client.*;
import com.wowza.wms.module.*;
import com.wowza.wms.request.*;
import com.wowza.wms.stream.*;
import com.wowza.wms.rtp.model.*;
import com.wowza.wms.httpstreamer.model.*;
import com.wowza.wms.httpstreamer.cupertinostreaming.httpstreamer.*;
import com.wowza.wms.httpstreamer.smoothstreaming.httpstreamer.*;
// PHPAuth Version 
import java.lang.String;
//import java.io.InputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.security.*;
import java.math.*;
public class PHPAuth extends ModuleBase {
	
	// Define the same secret code here that you used in the PHP page
	
	// Define how long you want the stream to be valid, in seconds
	public long seconds = 60;
	
	// For RTMP stream requests
	public void onConnect(IClient client, RequestFunction function, AMFDataList params)
	{
		String logTitle = "RTMP Authorization onConnect - ";
		
		// default to not isAuthorized
		Boolean isAuthorized = false;
		
		try {
			
			String phpAuthFileName = getParamString(params, PARAM1);
			String phpAuthTimeStamp = getParamString(params, PARAM2);
			String phpAuthSignature = getParamString(params, PARAM3);
			String phpAuthQueryString = client.getQueryStr();
			String phpAuthCallBackAddressProperty = client.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			String phpAuthSecretCode = client.getAppInstance().getProperties().getPropertyStr("phpAuthSecretCode");
			
			int phpAuthClientId = client.getClientId();
			
			if (phpAuthFileName == null) {
				getLogger().info(logTitle + "Client seems to librtmp");
				String[] arrQueryString = phpAuthQueryString.split("[&]");	
				String[] arrTemp = arrQueryString[0].split("[=]");
				phpAuthTimeStamp = arrTemp[1];
				
				arrTemp = arrQueryString[1].split("[=]");
				phpAuthSignature = arrTemp[1];
				
				arrTemp = arrQueryString[2].split("[=]");
				phpAuthFileName = arrTemp[1];
				phpAuthFileName = phpAuthFileName.replace("%", "/");
			}
			else
			{
				getLogger().info(logTitle + "Client seems to be Flash player");
			}
			
			getLogger().info(logTitle + "phpAuthFileName:       " + phpAuthFileName);
			getLogger().info(logTitle + "phpAuthQueryString:    " + phpAuthQueryString);
			getLogger().info(logTitle + "phpAuthTimeStamp:      " + phpAuthTimeStamp);
			getLogger().info(logTitle + "phpAuthSignature:      " + phpAuthSignature);
			getLogger().info(logTitle + "phpAuthClientId:       " + phpAuthClientId);
			getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
			getLogger().info(logTitle + "phpAuthSecretCode:       " + phpAuthSecretCode);
			
			isAuthorized = authorize(phpAuthFileName,phpAuthTimeStamp,phpAuthSignature, phpAuthClientId, phpAuthCallBackAddressProperty, phpAuthSecretCode);
		} catch (NullPointerException e) {
			getLogger().info(logTitle + "RTMP Authorization failed! - null exception");
		} catch (ArrayIndexOutOfBoundsException e) {
			getLogger().info(logTitle + "RTMP Authorization failed! - array index out of bounds");
		}
		// log status and reject connection, if necessary
		if (!isAuthorized)
		{
			getLogger().info(logTitle + "RTMP Authorization rejected");
			sendClientOnStatusError(client, "NetConnection.Connect.Rejected", "Access denied: please reload the video page.");
			client.rejectConnection();
		} else
		{
			getLogger().info(logTitle + "RTMP Authorization approved");
		}
	}
	
	public void onDisconnect(IClient client)
	{
		String logTitle = "RTMP Authorization onDisconnect - ";
		
		int phpAuthClientId = client.getClientId();
		String phpAuthCallBackAddressProperty = client.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
		
		getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
		getLogger().info(logTitle + "phpAuthCallBackAddressProperty: " + phpAuthCallBackAddressProperty);
		
		String phpAuthHttpCallBack = phpAuthCallBackAddressProperty + "?action=stop&phpAuthClientId="+phpAuthClientId;
		String findContent = "###STOP###";
		
		phpConnect(phpAuthHttpCallBack, findContent);
	}
	
	// For HTTP Cupertino stream requests
	public void onHTTPCupertinoStreamingSessionCreate(HTTPStreamerSessionCupertino session)
	{
		String logTitle = "HLS Authorization onHTTPCupertinoStreamingSessionCreate - ";
		
		// default to not isAuthorized		
		Boolean isAuthorized = false;
		
		try {
			// get URL variables
			String phpAuthFileName = session.getStreamName();
			String phpAuthQueryString = session.getQueryStr();
			
			String[] arrQueryString = phpAuthQueryString.split("[&]");
			String[] arrTemp = arrQueryString[0].split("[=]");
			String phpAuthTimeStamp = arrTemp[1];
			arrTemp = arrQueryString[1].split("[=]");
			String phpAuthSignature = arrTemp[1];
			
			String clientId = session.getSessionId();
			int phpAuthClientId = Integer.parseInt(clientId);
			
			String phpAuthCallBackAddressProperty = session.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			String phpAuthSecretCode = session.getAppInstance().getProperties().getPropertyStr("phpAuthSecretCode");
			
			getLogger().info(logTitle + "phpAuthFileName: " + phpAuthFileName);
			getLogger().info(logTitle + "phpAuthTimeStamp: " + phpAuthTimeStamp);
			getLogger().info(logTitle + "phpAuthSignature: " + phpAuthSignature);
			getLogger().info(logTitle + "phpAuthQueryString: " + phpAuthQueryString);
			getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
			getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
			getLogger().info(logTitle + "phpAuthSecretCode:       " + phpAuthSecretCode);
			
			// check authorization
			isAuthorized = authorize(phpAuthFileName, phpAuthTimeStamp,phpAuthSignature, phpAuthClientId, phpAuthCallBackAddressProperty, phpAuthSecretCode);
		} catch (NullPointerException e) {
			getLogger().info(logTitle + " nullPointerException : HTTP Cupertino Authorization failed!");
		} catch (ArrayIndexOutOfBoundsException e) {
			getLogger().info(logTitle + " ArrayIndexOutOfBounds : HTTP Cupertino Authorization failed!");
		}
		// log status and reject connection, if necessary
		if (!isAuthorized)
		{
			getLogger().info(logTitle + " Authorization rejected.");
			session.rejectSession();
		} else
		{
			getLogger().info(logTitle + " Authorization approved");
		}
	}
	
	public void onHTTPCupertinoStreamingSessionDestroy(HTTPStreamerSessionCupertino session)
	{
		String logTitle = "HLS Authorization onHTTPCupertinoStreamingSessionDestroy - ";
		
		String clientId = session.getSessionId();
		int phpAuthClientId = Integer.parseInt(clientId);
		String phpAuthCallBackAddressProperty = session.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
	
		getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
		getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
		
		String phpAuthHttpCallBack = phpAuthCallBackAddressProperty + "?action=stop&phpAuthClientId="+phpAuthClientId;
		String findContent = "###STOP###";
		
		phpConnect(phpAuthHttpCallBack, findContent);
		
	}
	// for HTTP Smooth Streaming stream requests
	public void onHTTPSmoothStreamingSessionCreate(HTTPStreamerSessionSmoothStreamer session)
	{
		String logTitle = "HLS Authorization onHTTPSmoothStreamingSessionCreate - ";
		
		String clientId = session.getSessionId();
		int phpAuthClientId = Integer.parseInt(clientId);
		
		// default to not isAuthorized		
		Boolean isAuthorized = false;
		
		try {
			// get URL variables
			String phpAuthFileName = session.getStreamName();
			String phpAuthQueryString = session.getStream().getQueryStr();
			String[] arrQueryString = phpAuthQueryString.split("[&]");
			String[] arrTemp = arrQueryString[0].split("[=]");
			String phpAuthTimeStamp = arrTemp[1];
			arrTemp = arrQueryString[1].split("[=]");
			String phpAuthSignature = arrTemp[1];
			
			String phpAuthCallBackAddressProperty = session.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			String phpAuthSecretCode = session.getAppInstance().getProperties().getPropertyStr("phpAuthSecretCode");
			getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
			getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
			
			// check authorization
			isAuthorized = authorize(phpAuthFileName,phpAuthTimeStamp,phpAuthSignature, phpAuthClientId, phpAuthCallBackAddressProperty, phpAuthSecretCode);
		} catch (NullPointerException e) {
			getLogger().info(logTitle + "Authorization failed!");
		} catch (ArrayIndexOutOfBoundsException e) {
			getLogger().info(logTitle + "Authorization failed!");
		}
		// log status and reject connection, if necessary
		if (!isAuthorized)
		{
			getLogger().info(logTitle + "Authorization rejected");
			session.rejectSession();
		} else
		{
			getLogger().info(logTitle + "Streaming approved");
		}
	}
	
	
	// For RTP/RTSP stream requests
	public void onRTPSessionCreate(RTPSession rtspSession)
	{
		String logTitle = "Android Authorization onRTPSessionCreate - ";
		
		// default to not isAuthorized
		Boolean isAuthorized = false;
		
		try {
	
			String phpAuthFileName = rtspSession.getUri();
			String phpAuthQueryString = rtspSession.getQueryStr();
			String uriStr = rtspSession.getUri();
			String phpAuthCallBackAddressProperty = rtspSession.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			String phpAuthSecretCode = rtspSession.getAppInstance().getProperties().getPropertyStr("phpAuthSecretCode");
			
			getLogger().info("#####  onRTPSessionCreate:"+uriStr+"\nQuerryString:"+phpAuthQueryString);
			
			String[] arrQueryString = phpAuthQueryString.split("[&]");
			String[] arrTemp = arrQueryString[0].split("[=]");
			String phpAuthTimeStamp = arrTemp[1];
			arrTemp = arrQueryString[1].split("[=]");
			String phpAuthSignature = arrTemp[1];
			
			String clientId = rtspSession.getSessionId();
			int phpAuthClientId = Integer.parseInt(clientId);
			
			getLogger().info(logTitle + "phpAuthFileName: " + phpAuthFileName);
			getLogger().info(logTitle + "phpAuthTimeStamp: " + phpAuthTimeStamp);
			getLogger().info(logTitle + "phpAuthSignature: " + phpAuthSignature);
			getLogger().info(logTitle + "phpAuthQueryString: " + phpAuthQueryString);
			getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
			getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
			getLogger().info(logTitle + "phpAuthSecretCode:       " + phpAuthSecretCode);
			
			// check authorization
			isAuthorized = authorize(phpAuthFileName,phpAuthTimeStamp,phpAuthSignature,phpAuthClientId, phpAuthCallBackAddressProperty,phpAuthSecretCode);
		} catch (NullPointerException e) {
			getLogger().info(logTitle + "Authorization failed! NullPointerException");
		} catch (ArrayIndexOutOfBoundsException e) {
			getLogger().info(logTitle + "Authorization failed! ArrayIndexOutOfBoundsException");
		}
		// log status and reject connection, if necessary
		if (!isAuthorized)
		{
			getLogger().info(logTitle + "Authorization rejected");
			rtspSession.rejectSession();
		} else
		{
			getLogger().info(logTitle + "Streaming approved");
		}
	}
	
	// For RTP/RTSP stream requests
		public void onRTPSessionDestroy(RTPSession rtspSession)
		{
			String logTitle = "Android Authorization onRTPSessionDestroy - ";
			
			// default to not isAuthorized
			String phpAuthCallBackAddressProperty = rtspSession.getAppInstance().getProperties().getPropertyStr("phpAuthCallBackAddressProperty");
			
			try {
				String clientId = rtspSession.getSessionId();
				int phpAuthClientId = Integer.parseInt(clientId);
				
				getLogger().info(logTitle + "phpAuthClientId: " + phpAuthClientId);
				getLogger().info(logTitle + "phpAuthCallBackAddressProperty: "  + phpAuthCallBackAddressProperty);
				
				String phpAuthHttpCallBack = phpAuthCallBackAddressProperty + "?action=stop&phpAuthClientId="+phpAuthClientId;
				String findContent = "###STOP###";
				
				phpConnect(phpAuthHttpCallBack, findContent);
				
			} catch (NullPointerException e) {
				getLogger().info("RTP onRTPSessionDestroy - NullPointerException");
			} catch (ArrayIndexOutOfBoundsException e) {
				getLogger().info("RTP onRTPSessionDestroy - ArrayIndexOutOfBoundsException");
			}
		}
	
	
	
	// main authorize function
	public boolean authorize(String phpAuthFileName, String phpAuthTimeStamp, String phpAuthSignature, int phpAuthClientId, String phpCallBackUrl, String secretCode)
	{
		String logTitle = "authorize ";
		
		Boolean isAuthorized = false;
		
		String hashcheck = phpAuthFileName + phpAuthTimeStamp + secretCode;
		
		
		// Regenerate the signature from the input parameters - they should match, otherwise something fishy is going on.
		try {
			MessageDigest m;
			m = MessageDigest.getInstance("MD5");
			m.reset();
			m.update(hashcheck.getBytes());
			byte[] digest = m.digest();
			BigInteger bigInt = new BigInteger(1,digest);
			String hashtext = bigInt.toString(16);
			while(hashtext.length() < 32 ){
				hashtext = "0"+hashtext;
			}
	
			getLogger().info(logTitle + "phpAuthSignature: "  + phpAuthSignature);
			getLogger().info(logTitle + "hashtext: "  + hashtext);
			
			//if ( phpAuthSignature.equals(hashtext) ) {
				String phpAuthHttpCallBack = phpCallBackUrl + "?action=play&phpAuthSignature="+phpAuthSignature+"&phpAuthClientId="+phpAuthClientId;
				String findContent = "###ALLOWED###";
				isAuthorized = phpConnect(phpAuthHttpCallBack, findContent);
			//}
			//else {
				//getLogger().info(logTitle + "HASHES DO NOT MATCH --- SOMETHING FISHY GOING ON");
			//}
			
			
		} catch (NoSuchAlgorithmException e) {
			getLogger().info("Authorization algorithm failed!");
		}
		
		return isAuthorized;
	}
	
	// Make a http connection and return return true or false based on the content being found.
	public boolean phpConnect(String webURL, String Content)
	{
		// default to not isAuthorized
		Boolean contentExists = false;
		
		  HttpURLConnection connection = null;
	      OutputStreamWriter wr = null;
	      BufferedReader rd  = null;
	      StringBuilder sb = null;
	      String line = null;
	    
	      URL phpAuthHttpCallBack = null;
	    
	      try {
	          phpAuthHttpCallBack = new URL(webURL);
	          getLogger().info(phpAuthHttpCallBack);
	          
	          //set up out communications stuff
	          connection = null;
	        
	          //Set up the initial connection
	          connection = (HttpURLConnection)phpAuthHttpCallBack.openConnection();
	          connection.setRequestMethod("GET");
	          connection.setDoOutput(true);
	          connection.setReadTimeout(10000);
	                    
	          connection.connect();
	        
	          //read the result from the server
	          rd  = new BufferedReader(new InputStreamReader(connection.getInputStream()));
	          sb = new StringBuilder();
	        
	          while ((line = rd.readLine()) != null)
	          {
	        	  if (line.matches(Content)) {
	        		  contentExists = true;
	        	  }
	              sb.append(line + '\n');
	          }
	        
	          getLogger().info(sb.toString());
	          //System.out.println(sb.toString());
	                    
	      } catch (MalformedURLException e) {
	          e.printStackTrace();
	      } catch (ProtocolException e) {
	          e.printStackTrace();
	      } catch (IOException e) {
	          e.printStackTrace();
	      }
	      finally
	      {
	          //close the connection, set all objects to null
	          connection.disconnect();
	          rd = null;
	          sb = null;
	          wr = null;
	          connection = null;
	      }
		return contentExists;
	}
}

In the modules section of your Application.xml insert the block below.

<Module>
                                <Name>PHPAuth</Name>
                                <Description>PHPAuthModule</Description>
                                <Class>com.blackcat.PHPAuth</Class>
                        </Module>

In the properties section of the Application.xml

 <Property>
                                <Name>phpAuthCallBackAddressProperty</Name>
                                <Value>http://your.lampserver.com/phpauth/wowzaSecurity.php</Value>
                        </Property>
                        <Property>
                                <Name>phpAuthSecretCode</Name>
                                <Value>S3cretC0de</Value>                       <!-- is the secret token used to generate the signature -->
                        </Property>
                        <Property>
                             <Name>secureTokenSharedSecret</Name>
                             <Value>S3cureToken</Value>                        <!-- This is the secure token compiled into JWplayer -->
                        </Property>

In the WowzaSecurity.php script you either accept or reject the stream simply by outputting ###ALLOWED### or ###REJECTED###.

As an additional level of security I have obfuscated the secure token in the compiled JWplayer as it’s a well known fact that you can use things like showmycode.com to reveal AS3 code - I can post this code as well if people would like it.

My background is more on the PHP side of things and I’ve done this as a learning curve to Java, Wowza and ActionScript 3 - so please try to ignore my poor Java, or if you’re feeling generous please improve the module.

I will certainly try and assist you if you have problems getting this to work.

Graeme

P.S. I should point out that this module is for the latest version of WMS (3.6.2) - i just realised it’s in the Wowza Version 2 forums.

Hi, I’ve tried the same steps as mentioned above. But the ‘onHTTPCupertinoStreamingSessionCreate’ method is no triggered by wowza, even though I have included the module in Application.xml. Is there anything more to do, to trigger that method(something like adding a custom property at vod, for eg: cupertinoEncryptionAPIBased on that lines…).