aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer/Core/Secure.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/MasterServer/Core/Secure.pm')
-rwxr-xr-xlib/MasterServer/Core/Secure.pm210
1 files changed, 210 insertions, 0 deletions
diff --git a/lib/MasterServer/Core/Secure.pm b/lib/MasterServer/Core/Secure.pm
new file mode 100755
index 0000000..dbe9c1f
--- /dev/null
+++ b/lib/MasterServer/Core/Secure.pm
@@ -0,0 +1,210 @@
+
+package MasterServer::Core::Secure;
+
+use strict;
+use warnings;
+use POSIX qw/strftime/;
+use Exporter 'import';
+
+our @EXPORT = qw| secure_string validated_beacon validated_request validate_string charshift get_validate_string|;
+
+## generate a random string of 6 characters long for the \secure\ challenge
+sub secure_string {
+ # spit out a random string, only uppercase characters
+ my @c = ('A'..'Z');
+ my $s = "";
+ $s .= $c[rand @c] for 1..6;
+
+ # return random string
+ return $s;
+}
+
+## Check if beacon has a valid response.
+sub validated_beacon {
+ my ($self, $gamename, $secure, $enctype, $validate) = @_;
+
+ # debugging enabled? Then don't care about validation
+ return 1 if ($self->{debug_validate});
+
+ # enctype given?
+ $enctype = 0 unless $enctype;
+
+ if ($self->{ignore_beacon_key} =~ m/$gamename/i){
+ $self->log("secure", "Ignored beacon validation for $gamename.");
+ return 1;
+ }
+
+ # compare received response with challenge
+ return ($self->validate_string($gamename, $secure, $enctype) eq $validate) || 0;
+}
+
+## Check if request has valid response
+sub validated_request {
+ my ($self, $gamename, $secure, $enctype, $validate) = @_;
+
+ # debugging enabled? Then don't care about validation
+ return 1 if ($self->{debug_validate});
+
+ # enctype given?
+ $enctype = 0 unless $enctype;
+
+ # ignore games and beacons that are listed
+ if ($self->{ignore_browser_key} =~ m/$gamename/i){
+ $self->log("secure", "Ignored browser validation for $gamename.");
+ return 1;
+ }
+
+ # compare received response with challenge
+ return ($self->validate_string($gamename, $secure, $enctype) eq $validate) || 0;
+}
+
+################################################################################
+# calculate the \validate\ response for the \secure\ challenge.
+# args: gamename, secure_string, encryption type
+# returns: validate string (usually 8 characters long)
+# !! requires cipher hash to be configured in config! (imported or else)
+################################################################################
+sub validate_string {
+ my ($self, $game, $sec, $enc) = @_;
+
+ # get cipher from gamename
+ my $cip = $self->{game}->{$game}->{key} || "XXXXXX";
+
+ # don't accept challenge longer than 16 characters -- usually h@xx0rs
+ if (length $sec > 16) {
+ return "0"}
+
+ # check for valid encryption choises
+ my $enc_val = (defined $enc && 0 <= $enc && $enc <= 2) ? $enc : 0;
+
+ # calculate and return validate string
+ return $self->get_validate_string($cip, $sec, $enc_val);
+}
+
+################################################################################
+# rotate characters as part of the secure/validate algorithm.
+# arg and return: int (representing a character)
+################################################################################
+sub charshift {
+ my ($self, $reg) = @_;
+ return($reg + 65) if ($reg < 26);
+ return($reg + 71) if ($reg < 52);
+ return($reg - 4) if ($reg < 62);
+ return(43) if ($reg == 62);
+ return(47) if ($reg == 63);
+
+ # if all else fails
+ return(0);
+}
+
+################################################################################
+# algorithm to calculate the response to the secure/validate query. processes
+# the secure_string and returns the challenge_string with which GameSpy secure
+# protocol authenticates games.
+#
+# the following algorithm is based on gsmsalg.h in GSMSALG 0.3.3 by Luigi
+# Auriemma, aluigi@autistici.org, aluigi.org, copyright 2004-2008. GSMSALG 0.3.3
+# was released under the GNU General Public License, for more information, see
+# the original software at http://aluigi.altervista.org/papers.htm#gsmsalg
+#
+# conversion and modification of the algorithm by Darkelarious, June 2014.
+#
+# args: game cipher, 6-char challenge string, encryption type
+# returns: validate string (usually 8 characters long)
+# !! requires cipher hash to be configured in config! (imported or else)
+################################################################################
+sub get_validate_string {
+ my ($self, $cipher_string, $secure_string, $enctype) = @_;
+
+ # import pre-built rotations from config for enctype
+ # -- see GSMSALG 0.3.3 reference for copyright and more information
+ my @enc_chars = $self->{enc_chars};
+
+ # convert to array of characters
+ my @cip = split "", $cipher_string;
+ my @sec = split "", $secure_string;
+
+ # length of strings/arrays which should be 6
+ my $sec_len = scalar @sec;
+ my $cip_len = scalar @cip;
+
+ # from this point on, work with ordinal values
+ for (0..$sec_len-1) { $sec[$_] = ord $sec[$_]; }
+ for (0..$cip_len-1) { $cip[$_] = ord $cip[$_]; }
+
+ # helper vars
+ my ($i,$j,$k,$l,$m,$n,$p);
+
+ # too short or too long -- return empty string
+ return "" if ($sec_len <= 0 || $sec_len >= 32);
+ return "" if ($cip_len <= 0 || $cip_len >= 32);
+
+ # temporary array with ascii characters
+ my @enc;
+ for(0..255) {$enc[$_] = $_;}
+
+ $j = 0;
+ for(0..255) {
+ $j += $enc[$_] + $cip[$_ % $cip_len];
+ $j = $j % 256;
+ $l = $enc[$j];
+ $enc[$j] = $enc[$_];
+ $enc[$_] = $l;
+ }
+
+ # store temporary positions
+ my @tmp;
+
+ $j = 0;
+ $k = 0;
+ for($i = 0; $sec[$i]; $i++) {
+ $j += $sec[$i] + 1;
+ $j = $j % 256;
+ $l = $enc[$j];
+ $k += $l;
+ $k = $k % 256;
+ $m = $enc[$k];
+ $enc[$k] = $l;
+ $enc[$j] = $m;
+ $tmp[$i] = $sec[$i] ^ $enc[($l + $m) & 0xff];
+ }
+
+# part of the enctype 1-2 process
+ for($sec_len = $i; $sec_len % 3; $sec_len++) {
+ $tmp[$sec_len] = 0;
+ }
+
+ if ($enctype == 1) {
+ for (0..$sec_len-1) {
+ $tmp[$_] = $enc_chars[$tmp[$_]];
+ }
+ }
+ elsif ($enctype == 2) {
+ for (0..$sec_len-1) {
+ $tmp[$_] ^= $cip[$_ % $cip_len];
+ }
+ }
+
+ # parse the validate array
+ $p = 0;
+ my @val;
+ for($i = 0; $i < $sec_len; $i += 3) {
+ $l = $tmp[$i];
+ $m = $tmp[$i + 1];
+ $m = $m % 256;
+ $n = $tmp[$i + 2];
+ $n = $n % 256;
+ $val[$p++] = $self->charshift($l >> 2);
+ $val[$p++] = $self->charshift((($l & 3 ) << 4) | ($m >> 4));
+ $val[$p++] = $self->charshift((($m & 15) << 2) | ($n >> 6));
+ $val[$p++] = $self->charshift($n & 63);
+ }
+
+ # return to ascii characters
+ my $str = "";
+ for (@val) { $str .= chr $_}
+
+ return $str;
+}
+
+1;