diff --git a/smradius/modules/authentication/mod_auth_mschap.pm b/smradius/modules/authentication/mod_auth_mschap.pm index b0f28a38f106fcc9957f6b73d76fbf0471042d5c..b10dddb5deb4b2ba0e7b934e36bf494eae601177 100644 --- a/smradius/modules/authentication/mod_auth_mschap.pm +++ b/smradius/modules/authentication/mod_auth_mschap.pm @@ -1,11 +1,13 @@ # Microsoft CHAP version 1 and 2 support -# Copyright (C) 2007-2009, AllWorldIT +# Copyright (C) 2007-2010, AllWorldIT # # References: # RFC1994 - PPP Challenge Handshake Authentication Protocol (CHAP) # RFC2443 - Microsoft PPP CHAP Extensions # RFC2759 - Microsoft PPP CHAP Extensions, Version 2 # RFC2548 - Microsoft Vendor-specific RADIUS Attributes +# RFC3079 - Deriving Keys for use with Microsoft Point-to-Point +# Encryption (MPPE) # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -38,6 +40,7 @@ use Crypt::DES; use Crypt::RC4; use Digest::SHA1; use Digest::MD4 qw( md4 ); +use Digest::MD5 qw( ); # Don't use unicode use bytes; @@ -48,6 +51,8 @@ require Exporter; our (@ISA,@EXPORT,@EXPORT_OK); @ISA = qw(Exporter); @EXPORT = qw( +); +@EXPORT_OK = qw( GenerateNTResponse ChallengeHash NtPasswordHash @@ -57,8 +62,6 @@ our (@ISA,@EXPORT,@EXPORT_OK); CheckAuthenticatorResponse NtChallengeResponse ); -@EXPORT_OK = qw( -); use constant { @@ -142,22 +145,29 @@ sub authenticate my $challenge = @{$rawChallenge}[0]; my $response = substr(@{$rawResponse}[0],2); -# print(STDERR "RECEIVED\n"); -# print(STDERR "Challenge: len = ".length($challenge).", hex = ".unpack("H*",$challenge)."\n"); -# print(STDERR "Reponse : len = ".length($response).", hex = ".unpack("H*",$response)."\n"); -# print(STDERR "\n\n"); - -# print(STDERR "CHOPPED OFFF!!\n"); # Chop off NtResponse my $NtResponse = substr($response,24,24); -# print(STDERR "NTRespons: len = ".length($NtResponse).", hex = ".unpack("H*",$NtResponse)."\n"); -# print(STDERR "\n\n"); -# print(STDERR "TEST\n"); # Generate our response my $ourResponse = NtChallengeResponse($challenge,$unicodePassword); -# print(STDERR "Calculate: len = ".length($ourResponse).", hex = ".unpack("H*",$ourResponse)."\n"); -# print(STDERR "\n\n"); + + +# MPPE Code +################## + my $NtPasswordHash = NtPasswordHash($unicodePassword); + my $HashNtPasswordHash = HashNtPasswordHash($NtPasswordHash); + + my $sendkey = pack("x8a16",$HashNtPasswordHash); + my $recvkey; + + setReplyVAttribute($server,$user->{'ReplyVAttributes'}, { + 'Vendor' => 311, + 'Name' => 'MS-CHAP-MPPE-Keys', + 'Operator' => ":=", + 'Value' => $sendkey + }); +################## + # Check responses match if ($NtResponse eq $ourResponse) { @@ -174,29 +184,13 @@ sub authenticate my $ident = unpack("C", substr(@{$rawResponse2}[0],0,1)); my $response = substr(@{$rawResponse2}[0],2); -# print(STDERR "RECEIVED\n"); -# print(STDERR "Challenge: len = ".length($challenge).", hex = ".unpack("H*",$challenge)."\n"); -# print(STDERR "Ident : $ident\n"); -# print(STDERR "Response : len = ".length($response).", hex = ".unpack("H*",$response)."\n"); -# print(STDERR "\n\n"); - - - -# print(STDERR "CHOPPED OFFF!!\n"); # Grab peer challenge and response my $peerChallenge = substr($response,0,16); my $NtResponse = substr($response,24,24); -# print(STDERR "PeerChallenge: len = ".length($peerChallenge).", hex = ".unpack("H*",$peerChallenge)."\n"); -# print(STDERR "NTResponse: len = ".length($NtResponse).", hex = ".unpack("H*",$NtResponse)."\n"); -# print(STDERR "\n\n"); -# print(STDERR "TEST\n"); # Generate our challenge and our response my $ourChallenge = ChallengeHash($peerChallenge,$challenge,$username); my $ourResponse = NtChallengeResponse($ourChallenge,$unicodePassword); -# print(STDERR "OurChallenge: len = ".length($ourChallenge).", hex = ".unpack("H*",$ourChallenge)."\n"); -# print(STDERR "OurResponse: len = ".length($ourResponse).", hex = ".unpack("H*",$ourResponse)."\n"); -# print(STDERR "\n\n"); # Check response match if ($NtResponse eq $ourResponse) { @@ -204,8 +198,57 @@ sub authenticate my $authenticatorResponse = pack("C",$ident) . GenerateAuthenticatorResponse($unicodePassword,$ourResponse, $peerChallenge,$challenge,$username); -# print(STDERR "Authenticator Response: len = ".length($authenticatorResponse). -# ", hex = ".unpack("H*",$authenticatorResponse)."\n"); + # MPPE Code +################################ + + my $NtPasswordHash = NtPasswordHash($unicodePassword); + my $HashNtPasswordHash = HashNtPasswordHash($NtPasswordHash); + + # Create master key + my $MasterKey = GetMasterKey($HashNtPasswordHash,$NtResponse); + + # Create MPPE keys + my $mppe_sendKey = GetAsymmetricStartKey($MasterKey,16,1,1); + my $mppe_recvKey = GetAsymmetricStartKey($MasterKey,16,1,0); + + + # Generate salts ... this should be in its own module and salt_offset should be global + my $salt_offset = 0; + my $salt1 = pack("C2",(0x80 | ( (($salt_offset++) & 0x0f) << 3) | + (rand(255) & 0x07)),rand(255)); + my $salt2 = pack("C2",(0x80 | ( (($salt_offset++) & 0x0f) << 3) | + (rand(255) & 0x07)),rand(255)); + + # Encode keys + my $mppe_sendKey_e = mppe_encode_key( + getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"), + $packet->authenticator, + $salt1, + $mppe_sendKey + ); + my $mppe_recvKey_e = mppe_encode_key( + getAttributeValue($user->{'ConfigAttributes'},"SMRadius-Config-Secret"), + $packet->authenticator, + $salt2, + $mppe_recvKey + ); + + # Finally setup arguments + setReplyVAttribute($server,$user->{'ReplyVAttributes'}, { + 'Vendor' => 311, + 'Name' => 'MS-MPPE-Recv-Key', + 'Operator' => ":=", + 'Value' => $mppe_recvKey_e + }); + + setReplyVAttribute($server,$user->{'ReplyVAttributes'}, { + 'Vendor' => 311, + 'Name' => 'MS-MPPE-Send-Key', + 'Operator' => ":=", + 'Value' => $mppe_sendKey_e + }); +################################# + setReplyVAttribute($server,$user->{'ReplyVAttributes'}, { 'Vendor' => 311, @@ -769,5 +812,220 @@ sub NtChallengeResponse { +# +# RFC 3079 +# + +#GetMasterKey( +# IN 16-octet PasswordHashHash, +# IN 24-octet NTResponse, +# OUT 16-octet MasterKey ) +#{ +# 20-octet Digest +# +# ZeroMemory(Digest, sizeof(Digest)); +# +# /* +# * SHSInit(), SHSUpdate() and SHSFinal() +# * are an implementation of the Secure Hash Standard [7]. +# */ +# +# SHSInit(Context); +# SHSUpdate(Context, PasswordHashHash, 16); +# SHSUpdate(Context, NTResponse, 24); +# SHSUpdate(Context, Magic1, 27); +# SHSFinal(Context, Digest); +# +# MoveMemory(MasterKey, Digest, 16); +#} +sub GetMasterKey +{ + my ($PasswordHashHash,$NTResponse) = @_; + + + # "Magic" constants used in key derivations - in hex + my @Magic1 = + ("54", "68", "69", "73", "20", "69", "73", "20", "74", + "68", "65", "20", "4d", "50", "50", "45", "20", "4d", + "61", "73", "74", "65", "72", "20", "4b", "65", "79"); + + my $sha = Digest::SHA1->new(); + $sha->add($PasswordHashHash); + $sha->add($NTResponse); + foreach my $item (@Magic1) { + $sha->add(pack("H*",$item)); + } + my $Digest = $sha->digest(); + # Cut off MasterKey + my $MasterKey = substr($Digest,0,16); + + return $MasterKey; +} + + + + +#VOID +#GetAsymetricStartKey( +# IN 16-octet MasterKey, +# OUT 8-to-16 octet SessionKey, +# IN INTEGER SessionKeyLength, +# IN BOOLEAN IsSend, +# IN BOOLEAN IsServer ) +#{ +# +# 20-octet Digest; +# +# ZeroMemory(Digest, 20); +# +# if (IsSend) { +# if (IsServer) { +# s = Magic3 +# } else { +# s = Magic2 +# } +# } else { +# if (IsServer) { +# s = Magic2 +# } else { +# s = Magic3 +# } +# } +# +# /* +# * SHSInit(), SHSUpdate() and SHSFinal() +# * are an implementation of the Secure Hash Standard [7]. +# */ +# +# SHSInit(Context); +# SHSUpdate(Context, MasterKey, 16); +# SHSUpdate(Context, SHSpad1, 40); +# SHSUpdate(Context, s, 84); +# SHSUpdate(Context, SHSpad2, 40); +# SHSFinal(Context, Digest); +# +# MoveMemory(SessionKey, Digest, SessionKeyLength); +#} +sub GetAsymmetricStartKey +{ + my ($MasterKey,$SessionKeyLength,$IsSend,$IsServer) = @_; + + + # "Magic" constants used in key derivations - in hex + my @Magic2 = + ("4f", "6e", "20", "74", "68", "65", "20", "63", "6c", "69", + "65", "6e", "74", "20", "73", "69", "64", "65", "2c", "20", + "74", "68", "69", "73", "20", "69", "73", "20", "74", "68", + "65", "20", "73", "65", "6e", "64", "20", "6b", "65", "79", + "3b", "20", "6f", "6e", "20", "74", "68", "65", "20", "73", + "65", "72", "76", "65", "72", "20", "73", "69", "64", "65", + "2c", "20", "69", "74", "20", "69", "73", "20", "74", "68", + "65", "20", "72", "65", "63", "65", "69", "76", "65", "20", + "6b", "65", "79", "2e"); + + my @Magic3 = + ("4f", "6e", "20", "74", "68", "65", "20", "63", "6c", "69", + "65", "6e", "74", "20", "73", "69", "64", "65", "2c", "20", + "74", "68", "69", "73", "20", "69", "73", "20", "74", "68", + "65", "20", "72", "65", "63", "65", "69", "76", "65", "20", + "6b", "65", "79", "3b", "20", "6f", "6e", "20", "74", "68", + "65", "20", "73", "65", "72", "76", "65", "72", "20", "73", + "69", "64", "65", "2c", "20", "69", "74", "20", "69", "73", + "20", "74", "68", "65", "20", "73", "65", "6e", "64", "20", + "6b", "65", "79", "2e"); + + # Pads used in key derivation - in hex + my @SHSpad1 = + ("00", "00", "00", "00", "00", "00", "00", "00", "00", "00", + "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", + "00", "00", "00", "00", "00", "00", "00", "00", "00", "00", + "00", "00", "00", "00", "00", "00", "00", "00", "00", "00"); + + my @SHSpad2 = + ("f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", + "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", + "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", + "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2", "f2"); + + my @s; + if ($IsSend) { + if ($IsServer) { + @s = @Magic3; + } else { + @s = @Magic2; + } + } else { + if ($IsServer) { + @s = @Magic2; + } else { + @s = @Magic3; + } + } + + my $sha = Digest::SHA1->new(); + $sha->add($MasterKey); + foreach my $item (@SHSpad1) { + $sha->add(pack("H*",$item)); + } + foreach my $item (@s) { + $sha->add(pack("H*",$item)); + } + foreach my $item (@SHSpad2) { + $sha->add(pack("H*",$item)); + } + my $digest = $sha->digest(); + # Cut off SessionKey + my $SessionKey = substr($digest,0,$SessionKeyLength); + + return $SessionKey; +} + + +# Function to encode a key +sub mppe_encode_key +{ + my ($secret,$vector,$salt,$enckey) = @_; + + + # Ok, to do this we need the length of the key first + my @plain = ( + 16, # Length + unpack("C*",pack("a31",$enckey)) + ); + + # Create our first digest + my $sha = Digest::MD5->new(); + $sha->add($secret); + $sha->add($vector); + $sha->add($salt); + # Unpack digest for calculation + my @buf = unpack("C*",$sha->digest()); + + # Calculate + for(my $i=0; $i < 16; $i++) { + $plain[$i] ^= $buf[$i]; + } + + # Second round + $sha = Digest::MD5->new(); + $sha->add($secret); + # Add the values we calculated above + for (my $i = 0; $i < 16; $i++) { + $sha->add(pack("C",$plain[$i])); + } + # Unpack digest for calculation + @buf = unpack("C*",$sha->digest()); + + # Calculate + for (my $i = 0; $i < 16; $i++) { + $plain[$i+16] ^= $buf[$i]; + } + # Pack salt, and result + my $key = pack("a2C32",$salt,@plain); + + return $key; +} + + 1; # vim: ts=4