Skip to content

Instantly share code, notes, and snippets.

@mcarbonneaux
Forked from ogarrett/README.md
Last active February 13, 2025 13:15
Show Gist options
  • Save mcarbonneaux/898914b580d98c7794eacb0145bf5e25 to your computer and use it in GitHub Desktop.
Save mcarbonneaux/898914b580d98c7794eacb0145bf5e25 to your computer and use it in GitHub Desktop.
libHMAC and libJWT: TrafficScript library to calculate HMAC hashes and JWT Signed verification
# Performance and correctness tests for libHMAC
#
# Apply this rule to an HTTP virtual server and submit a request to run these tests
import libHMAC as hmac;
# Correctness tests
sub testcorrectness() {
$r = "Test cases from http://tools.ietf.org/html/draft-cheng-hmac-test-cases-00:\n\n";
$r .= testcorrectness( 1, "MD5",
"0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"Hi There",
"0x9294727a3638bb1c13f48ef8158bfc9d" );
$r .= testcorrectness( 2, "MD5",
"Jefe",
"what do ya want for nothing?",
"0x750c783e6ab0b503eaa86e310a5db738" );
$a = ""; for( $i = 0; $i < 50; $i++ ) $a .= "\xdd";
$r .= testcorrectness( 3, "MD5",
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
$a,
"0x56be34521d144c88dbb8c733f0e8b3f6" );
$a = ""; for( $i = 0; $i < 50; $i++ ) $a .= "\xcd";
$r .= testcorrectness( 4, "MD5",
"0x0102030405060708090a0b0c0d0e0f10111213141516171819",
$a,
"0x697eaf0aca3a3aea3a75164746ffaa79" );
$r .= testcorrectness( 5, "MD5",
"0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
"Test With Truncation",
"0x56461ef2342edc00f9bab995690efd4c" );
$a = ""; for( $i = 0; $i < 80; $i++ ) $a .= "\xaa";
$r .= testcorrectness( 6, "MD5",
$a,
"Test Using Larger Than Block-Size Key - Hash Key First",
"0x6b1ab7fe4bd7bf8f0b62e6ce61b9d0cd" );
$r .= testcorrectness( 7, "MD5",
$a,
"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
"0x6f630fad67cda0ee1fb1f562db3aa53e" );
$r .= testcorrectness( 1, "SHA1",
"0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"Hi There",
"0xb617318655057264e28bc0b6fb378c8ef146be00" );
$r .= testcorrectness( 2, "SHA1",
"Jefe",
"what do ya want for nothing?",
"0xeffcdf6ae5eb2fa2d27416d5f184df9c259a7c79" );
$a = ""; for( $i = 0; $i < 50; $i++ ) $a .= "\xdd";
$r .= testcorrectness( 3, "SHA1",
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
$a,
"0x125d7342b9ac11cd91a39af48aa17b4f63f175d3" );
$a = ""; for( $i = 0; $i < 50; $i++ ) $a .= "\xcd";
$r .= testcorrectness( 4, "SHA1",
"0x0102030405060708090a0b0c0d0e0f10111213141516171819",
$a,
"0x4c9007f4026250c6bc8414f9bf50c86c2d7235da" );
$r .= testcorrectness( 5, "SHA1",
"0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
"Test With Truncation",
"0x4c1a03424b55e07fe7f27be1d58bb9324a9a5a04" );
$a = ""; for( $i = 0; $i < 80; $i++ ) $a .= "\xaa";
$r .= testcorrectness( 6, "SHA1",
$a,
"Test Using Larger Than Block-Size Key - Hash Key First",
"0xaa4ae5e15272d00e95705637ce8a3b55ed402112" );
$r .= testcorrectness( 7, "SHA1",
$a,
"Test Using Larger Than Block-Size Key and Larger Than One Block-Size Data",
"0xe8e99d0f45237d786d6bbaa7965c7808bbff1a91" );
$r .= "\n\nTest cases from https://datatracker.ietf.org/doc/html/rfc4231#section-4.1\n\n";
# https://datatracker.ietf.org/doc/html/rfc4231#section-4.1
# https://datatracker.ietf.org/doc/html/rfc4231#section-4.2
$a = "0x0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"; # (20 bytes)
$b = "0x4869205468657265"; # ("Hi There")
$n=1;
$r .= testcorrectness( $n, "SHA256",
$a,
$b,
"0xb0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" );
$r .= testcorrectness( $n, "SHA384",
$a,
$b,
"0xafd03944d84895626b0825f4ab46907f15f9dadbe4101ec682aa034c7cebc59cfaea9ea9076ede7f4af152e8b2fa9cb6" );
$r .= testcorrectness( $n, "SHA512",
$a,
$b,
"0x87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854" );
# https://datatracker.ietf.org/doc/html/rfc4231#section-4.3
# Test with a key shorter than the length of the HMAC output.
$a = "0x4a656665"; # ("Jefe")
$b = "0x7768617420646f2079612077616e7420666f72206e6f7468696e673f"; # ("what do ya want for nothing?")
$n++;
$r .= testcorrectness( $n, "SHA256",
$a,
$b,
"0x5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843" );
$r .= testcorrectness( $n, "SHA384",
$a,
$b,
"0xaf45d2e376484031617f78d2b58a6b1b9c7ef464f5a01b47e42ec3736322445e8e2240ca5e69e2c78b3239ecfab21649" );
$r .= testcorrectness( $n, "SHA512",
$a,
$b,
"0x164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737" );
# https://datatracker.ietf.org/doc/html/rfc4231#section-4.4
# Test with a combined length of key and data that is larger than 64
# bytes (= block-size of SHA-224 and SHA-256).
$a = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; # (20 bytes)
$b = "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"; # (50 bytes)
$n++;
$r .= testcorrectness( $n, "SHA256",
$a,
$b,
"0x773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe" );
$r .= testcorrectness( $n, "SHA384",
$a,
$b,
"0x88062608d3e6ad8a0aa2ace014c8a86f0aa635d947ac9febe83ef4e55966144b2a5ab39dc13814b94e3ab6e101a34f27" );
$r .= testcorrectness( $n, "SHA512",
$a,
$b,
"0xfa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb" );
# https://datatracker.ietf.org/doc/html/rfc4231#section-4.5
# Test with a combined length of key and data that is larger than 64
# bytes (= block-size of SHA-224 and SHA-256).
$a = "0x0102030405060708090a0b0c0d0e0f10111213141516171819"; # (25 bytes)
$b = "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"; # (50 bytes)
$n++;
$r .= testcorrectness( $n, "SHA256",
$a,
$b,
"0x82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b" );
$r .= testcorrectness( $n, "SHA384",
$a,
$b,
"0x3e8a69b7783c25851933ab6290af6ca77a9981480850009cc5577c6e1f573b4e6801dd23c4a7d679ccf8a386c674cffb" );
$r .= testcorrectness( $n, "SHA512",
$a,
$b,
"0xb0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd" );
# https://datatracker.ietf.org/doc/html/rfc4231#section-4.6
# Test with a truncation of output to 128 bits.
$a = "0x0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c"; # (20 bytes)
$b = "0x546573742057697468205472756e636174696f6e"; # ("Test With Truncation")
$n++;
$r .= testcorrectness( $n, "SHA256",
$a,
$b,
"0xa3b6167473100ee06e0c796c2955552b" );
$r .= testcorrectness( $n, "SHA384",
$a,
$b,
"0x3abf34c3503b2a23a46efc619baef897" );
$r .= testcorrectness( $n, "SHA512",
$a,
$b,
"0x415fad6271580a531d4179bc891d87a6" );
# https://datatracker.ietf.org/doc/html/rfc4231#section-4.7
# Test with a key larger than 128 bytes (= block-size of SHA-384 and
# SHA-512).
$a = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
$a .= "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
$a .= "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
$a .= "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
$a .= "aaaaaa";
# (131 bytes)
$b ="0x54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a";
$b .="65204b6579202d2048617368204b6579204669727374";
# ("Test Using Larger Than Block-Size Key - Hash Key First")
$n++;
$r .= testcorrectness( $n, "SHA256",
$a,
$b,
"0x60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54" );
$r .= testcorrectness( $n, "SHA384",
$a,
$b,
"0x4ece084485813e9088d2c63a041bc5b44f9ef1012a2b588f3cd11f05033ac4c60c2ef6ab4030fe8296248df163f44952" );
$r .= testcorrectness( $n, "SHA512",
$a,
$b,
"0x80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598" );
# https://datatracker.ietf.org/doc/html/rfc4231#section-4.8
# Test with a key and data that is larger than 128 bytes (= block-size
# of SHA-384 and SHA-512).
$a = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
$a .= "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
$a .= "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
$a .= "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
$a .= "aaaaaa";
# (131 bytes)
$b = "0x5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b65";
$b .= "7920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565";
$b .= "647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c";
$b .= "676f726974686d2e";
# ("This is a test using a larger than block-size key and a larger than block-size data. The key needs to be hashed before being used by the HMAC algorithm.")
$n++;
$r .= testcorrectness( $n, "SHA256",
$a,
$b,
"0x9b09ffa71b942fcb27635fbcd5b0e944bfdc63644f0713938a7f51535c3a35e2" );
$r .= testcorrectness( $n, "SHA384",
$a,
$b,
"0x6617178e941f020d351e2f254e8fd32c602420feb0b8fb9adccebb82461e99c5a678cc31e799176d3860e6110c46523e" );
$r .= testcorrectness( $n, "SHA512",
$a,
$b,
"0xe37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58" );
http.sendResponse( 200, "text/plain", $r, "" );
}
sub testperf() {
$r = "Performance tests (running on single core):\n\n";
$r .= testperformance( "MD5" );
$r .= testperformance( "SHA1" );
http.sendResponse( 200, "text/plain", $r, "" );
}
#---------------------------------
sub testcorrectness( $num, $method, $k, $d, $h ) {
if( string.startsWith( $k, "0x" )) $k = string.hexdecode( string.skip( $k, 2 ));
if( string.startsWith( $d, "0x" )) $d = string.hexdecode( string.skip( $d, 2 ));
if( string.startsWith( $h, "0x" )) $h = string.hexdecode( string.skip( $h, 2 ));
$r = "Test case #".$num.":";
$result = hmac.get($method, $k, $d );
$r .= " Testing HMAC-".$method." ... ";
if( string.startsWith($result,$h)) {
$r .= "passed ";
} else {
$r .= "failed ";
}
$pad = hmac.getPad(" ",string.len($r));
$r .= " hashed result = ".string.hexencode($result)."\n";
$r .= $pad." expected = ".string.hexencode($h)."\n";
return $r;
}
sub testperformance( $method ) {
$k = "This is my key";
$d = "This is the data";
$r = "";
$r .= "Testing performance for HMAC-".$method."\n";
# How long do 1000 hash operations take?
$start = sys.time.highres();
if( $method == "MD5" ) {
for( $i = 0; $i < 1000; $i++ ) hmac.get("MD5", $k, $d );
} else if( $method == "SHA1" ) {
for( $i = 0; $i < 1000; $i++ ) hmac.get("SHA1", $k, $d );
}
$elapsed = sys.time.highres() - $start;
$r .= " 1000 operations took ".$elapsed."s\n";
# Estimate how many operations we can perform in 5 seconds
$count = math.rint( 5*1000/$elapsed );
$start = sys.time.highres();
if( $method == "MD5" ) {
for( $i = 0; $i < $count; $i++ ) hmac.get("MD5", $k, $d );
} else if( $method == "SHA1" ) {
for( $i = 0; $i < $count; $i++ ) hmac.get("SHA1", $k, $d );
}
$elapsed = sys.time.highres() - $start;
$r .= " ".$count." operations took ".$elapsed." s\n";
$r .= " Estimated rate: " . ( $count / $elapsed ) . " HMAC-".$method."/s per core\n\n";
return $r;
}
# Ivanti TrafficScript HMAC library
#
# Function exports:
# get( hash, key, data )
# hash value: MD5, SHA1, SHA256, SHA384, SHA512
sub get( $hash, $key, $data ) {
$blocksize = getHash2BlockSize($hash); # hash blocksize
if( string.length( $key ) > $blocksize ) $key = hash( $hash, $key );
if( string.length( $key ) < $blocksize ) $key = string.replaceBytes( getPad("\x00", $blocksize) , $key, 0 );
$o_key_pad = string_xor( getPad("\x5c", $blocksize), $key );
$i_key_pad = string_xor( getPad("\x36", $blocksize), $key );
$h = hash( $hash, $o_key_pad . hash( $hash, $i_key_pad . $data ));
return $h;
}
sub getPad($char,$size) {
$pad="";
for($i=0; $i <$size; $i++) $pad.=$char;
return $pad;
}
sub getHash2BlockSize( $hash ) {
switch( $hash ) {
case "MD5", "SHA1", "SHA256":
return 64;
case "SHA512", "SHA384":
return 128;
}
return 0;
}
sub hash( $hash, $key) {
switch( $hash ) {
case "MD5":
return string.hashMD5( $key );
case "SHA1":
return string.hashSHA1( $key );
case "SHA256":
return string.hashSHA256( $key );
case "SHA512":
return string.hashSHA512( $key );
case "SHA384":
return string.hashSHA384( $key );
}
return "";
}
sub string_xor( $a, $b ) {
$r = "";
while( string.length( $a ) ) {
$a1 = string.left( $a, 1 ); $a = string.skip( $a, 1 );
$b1 = string.left( $b, 1 ); $b = string.skip( $b, 1 );
$r = $r . chr( ord( $a1 ) ^ ord ( $b1 ) );
}
return $r;
}
import libHMAC as hmac;
# jwt signed only (HS only)
# jwt signed supported algorythm : HS256, HS384, HS512
sub validate($jwt,$secret) {
$returnval = [ "valide" => false,
"payload" => "",
"headers" => "",
"error" => ""];
$debug="Validation of the jwt:".$jwt."\n";
$jwtArray = string.split($jwt,".");
if (array.length($jwtArray)!=3) {
$returnval["error"]=$debug."The jwt as not 3 dot sections dot !";
return $returnval; # on doit avoir 3 sections
}
$debug.="Jwt as 3 dot sections!\n";
# on decode en base64 la section header
$jwtRawHeader = base64urldecode($jwtArray[0]);
$JwtHeaders = json.deserialize( $jwtRawHeader );
if ($JwtHeaders=="") {
$returnval["error"]=$debug."The jwt header is not json or bad base64url encoded !";
return $returnval; # si header json invalide
}
$debug.="The jwt header is json!\n";
$debug.="The jwt header:".$jwtRawHeader."!\n";
$jwtAlgo = $JwtHeaders["alg"]; # on recupere l'algorythm
$jwtType = $JwtHeaders["typ"]; # et le type qui dois toujours etre JWT
$hash=""; # on verifie les algorythm supporter
switch( $jwtAlgo ) {
case "HS256":
$hash="SHA256";
break;
case "HS384":
$hash="SHA384";
break;
case "HS512":
$hash="SHA512";
break;
}
$debug.="the jwt algo is ".$jwtAlgo."\n";
if ($hash=="") {
$returnval["error"]=$debug."the jwt header hash not supported : ".$jwtAlgo." !";
return $returnval; # si l'algo pas supporter
}
$debug.="the jwt use hash:".$hash."\n";
if ($jwtType!="JWT") {
$returnval["error"]=$debug."The jwt header type must be JWT :".$jwtType."!";
return $returnval; # si type pas JWT
}
$debug.="the jwt type is ok\n";
# on calule le hash avec le bon algo de hash
$hashresult=hmac.get($hash, $secret, array.join([$jwtArray[0], $jwtArray[1]], ".") );
$jwtsigned = base64urldecode($jwtArray[2]);
if ($hashresult!=$jwtsigned) {
$returnval["error"]=$debug."the jwt signature ".$hash." is invalid - Jwtcalculed =".string.hexencode($hashresult).
", Jwtsigned =".string.hexencode($jwtsigned)." !";
return $returnval; # signature invalide
}
$jwtRawPayload = base64urldecode($jwtArray[1]);
$JwtPayload = json.deserialize( $jwtRawPayload );
if ($JwtPayload =="") {
$returnval["error"]=$debug."The jwt payload are not json ou or bad base64url encoded :".$jwtRawPayload."!";
return $returnval; # payload json invalide
}
$debug.="the jwt paylaod is json!\n";
$debug.="The jwt header:".$jwtRawPayload."!\n";
$now = sys.time();
$iat = $JwtPayload['iat'];
$exp = $JwtPayload['exp'];
if ($now<$iat) {
$returnval["error"]=$debug."JWT issued in the futur";
return $returnval;
}
if ($now>$exp) {
$returnval["error"]=$debug."JWT as expired";
return $returnval;
}
$debug.="the jwt iat and exp are ok, the jwt are not expired or in futur\n";
$returnval["valide"]=true;
$returnval["payload"]=$JwtPayload;
$returnval["headers"]=$JwtHeaders;
return $returnval;
}
# https://base64.guru/standards/base64url
sub base64urldecode($encoded) {
$e = string.replaceAll($encoded, "-", "+");
$e = string.replaceAll($e, "_", "/");
return string.base64decode($e);
}
sub base64urlencode($decoded) {
$d = string.base64decode($decoded);
$d = string.replaceAll($d, "+", "-");
$d = string.replaceAll($d, "/", "_");
$d = string.replaceAll($d, "=", "");
return $d;
}
@mcarbonneaux
Copy link
Author

mcarbonneaux commented Feb 13, 2025

to use libhmac create rule and add import:

import libHMAC as hmac;
$hash_mac_result = hmac.get("SHA256", "my_super_secret", "my data to sign" );

to use libJWT create rule and add import:

import libJWT as JWT;
$jwt = http.getCookie( "my_jwt_session" );
if ($jwt!="") {
    $output = "<pre>";
    $output .= "jwt cookie \"my_jwt_session\" raw: ".$jwt."\n";
    $retval = JWT.validate($jwt,"my_super_secret_used_to_sign_jwt");
    if ($retval["valide"]!=0) {
       $output .= "jwt is valid !\n\n";
       $output .= "jwt payload User: ".$retval["payload"]["User"]."\n";
       $output .= "jwt payload Fqdn: ".$retval["payload"]["Fqdn"]."\n";
       $iat=$retval["payload"]["iat"];
       $exp=$retval["payload"]["exp"];
       if ($iat !="")
          $output .= "jwt payload iat: ".sys.timeToString($iat)."\n";
       if ($exp!="")
          $output .= "jwt payload exp: ".sys.timeToString($exp)."\n";
    }
    else {
       $output .= "jwt is invalid!\n";         
       if ($retval["error"]!="")
          $output .= "jwt log error : \n----------------------\n".$retval["error"]."\n----------------------\n";
    }
    http.sendResponse( "200 OK", "text/html", $output, "" );
} else {
  http.redirect( "/to/endpoint/that/auth/and/set/jwtsession" );
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment