aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer/Core/Secure.pm
blob: dbe9c1f9e550695061476e0f9ecc8f74127ef586 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
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;