#
# Authen::Challenge::Basic: Provides a trivial challenge/response protocol
#	to assist in authentication tasks. It provides for time-window
#	challenge/response sessions. 
#
# Using this module, it's possible to autenticate both endpoints of
# a transaction provided that a shared-secret was exchanged prior to
# the session among the endpoints. As timestamps are part of the
# protocol, some restrictions can be applied to the timing, to help
# prevent hijacked connections.
#
# This is free software. You can use at will provided that proper
# credit is given to the author(s). This module requires MD5.
#
# lem@cantv.net, 19980713 - Initial release
#
#############

package Authen::Challenge::Basic;
$VERSION='0.1';
require 5.000;

=head1 NAME

Authen::Challenge::Basic - A Basic challenge/response authentication scheme.

=head1 SYNOPSIS

	use Authen::Challenge::Basic;

	$server = Authen::Challenge->new ('Secret' => 'known2us',
					  'Timeout' => 30,
					  'Sync' => 10);

	$client = Authen::Challenge->new ('Secret' => 'known2us',
					  'Timeout' => 30,
					  'Sync' => 10);

	$challenge = $server->Challenge;
	$response = $client->Response($challenge);

	if ($server->Validate($challenge, $response)) {
		print "Hi master\n";
	}
	else {
		print "Impostor!\n";
	}

=head1 DESCRIPTION

Authen::Challenge::Basic provides a simple MD5-based challenge/response
protocol allowing for mutual peer authentication in a session. The
protocol includes timing information, so it is possible to introduce
time constraints in the session to help prevent attacks that rely on
adjusting the clock in one of the peers.

This protocol requires a shared secret to be known only to the peers.
The compromise of this secret also compromises this protocol, so 
it should be treated as a trusted password for that matter.

Challenge/response sessions are not 'replayable' provided that the 
attacker ignores the shared secret. The sessions are also associated
to the instance that produced the challenge. This means that it can
only be Validate()'d by the instance that produced the challenge in
the first place.

The built-in random number generator from perl is used in this module.
Hooks for better random number generators are planned soon to increase
the relative strength of this protocol. In any case, the main security
dependencies for this module are MD5 itself and the secrecy of the shared
secret.

The following functions are provided by this class.

new()

Creates a new instance of a challenge/response endpoint. It has three
parameters that influence its behavior. Those can be seen next

	$server = Authen::Challenge::Basic->new ('Secret' => 'known2us',
						 'Timeout' => 30,
						 'Sync' => 10);

'Secret' is used to indicate the shared secret to use in this session.

'Timeout' specifies the lifespan, in seconds, for this transaction.
This means that a succesful Validate() must occur within this many
seconds to have a chance to be acknowledged. Reducing this value
too much can cause problems as a slight ammount of load can make the
session fail. 30 seconds is a reasonable default for many uses. If
left unspecified, no timeout is enforced.

'Sync' indicates how strict are we with regard to the clock in our
peer. The parameter contains the maximum offset in seconds between the
clocks of the peers. The more strict we are (ie, the smaller the number),
the less tolerant we are. If left unspecified, we'll allow any ammount
of drift. It's specified in seconds.

Challenge()

Issues a new challenge. The object creating this challenge stores
information about it that allows for later recognition and validation.
The Challenge() is a typical function of the server, though a double
Challenge/Response scenario allows mutual authentication such as
in the following scheme:

		Client				Server
(1)		  C1	---------------------->   
(2)			<----------------------  R(C1), C2
(3)		R(C2)   ---------------------->

On stage (2), the Client is authenticated to the server. On stage (3),
both are mutually authenticated.

This method is usually invoked like

	$challenge = $server->Challenge;

$challenge will contain a printable string that must be passed to the
peer in order for a response to be received.

Response()

This method takes a challenge and generates the required response. This
is usually invoked as

	$response = $client->Response($challenge);

Where $challenge contains a Challenge generated by a call to Challenge().
$response will contain a printable string that must be returned to the
peer in order for it to be validated.

Validate()

This method verifies the correctness of the Challenge/Response session by
insuring that:

	(a) The challenge was indeed generated from this instance 
	and is pending validation
	(b) The time interval for the validation is acceptable
	(c) The shared secret was used by the peer to create the 
	response

Usually this method is invoked like this

	$r = $server->Validate($challenge, $response);

It returns a true value to indicate a correct session where (a), (b) and
(c) hold true or false otherwise.

=head1 CAVEATS

Note that this module helps insure that the peers were who they said they
where provided that the shared secret is not known to any third parties.
If this is not true, then anything could happen.

Also, after the initial authentication, a network connection can be
stolen or hijacked, rendering all of the tests useless.

=head1 AUTHOR

Luis E. Munoz <lem@cantv.net>

=cut

use MD5;

sub new {
    my ($class, @opt) = @_;
    my ($r_param) = canon_params(@opt);
    $self = {'p' => $r_param,
	     'Error' => undef,
	     'Stamp' => '',
	     'Count' => 0,
	     'Alive' => {}
	     };
    bless $self, $class;
    return $self;
}

sub Error {
    my ($self) = @_;
    $self->{'Error'};
}

sub Challenge {
    my ($self) = @_;
    my ($ctx) = new MD5;
    my ($Count) = sprintf("%05d", $self->{'Count'}++);
    my ($Stamp) = time;
    # Add the shared secret...
    $ctx->add($self->{'p'}->{'secret'});
    # ... and a transaction counter...
    $ctx->add($Count);
    # ... and a perturbed timestamp, keeping it safe.
    $ctx->add($self->{'Stamp'} = $Stamp + rand(16384));
    my ($result) = $ctx->hexdigest;
    $self->{'Alive'}->{$result} = $Stamp;
    $self->{'Error'} = undef;
    $result;
}

sub Response {
    my ($self, $challenge) = @_;
    my ($ctx) = new MD5;
    my ($Stamp) = time;
    $self->{'Error'} = undef;
    $ctx->add($challenge);
    $ctx->add($self->{'p'}->{'secret'});
    $ctx->add($Stamp);
    $ctx->hexdigest . "/" . sprintf("%012d", $Stamp);
}

sub Validate {
    my ($self, $c, $r) = @_;
    my ($Stamp) = time;
    my ($mysignature, $this);

    # Insure the response is in a valid format...
    my ($signature, $tstamp) = split(/\//, $r, 2);

    if (!$tstamp or !$signature) {
	$self->{'Error'} = "Wrong response format";
	return undef;
    }

    # Insure this challenge was issued by this instance...
    if (!($this = $self->{'Alive'}->{$c})) {
	$self->{'Error'} = "This challenge doesn't belong to this instance";
	return undef;
    }

    undef($self->{'Alive'}->{$c});

    # Insure that this response has come on time...
    if ($self->{'p'}->{'timeout'}
	&& $self->{'p'}->{'timeout'} + $this < $Stamp)
    {
	$self->{'Error'} = "Response came too late";
	return undef;
    }

    # Insure time sync between client and server...
    if ($self->{'p'}->{'sync'}
	&& $self->{'p'}->{'sync'} + $this < $tstamp)
    {
	$self->{'Error'} = "Endpoints out of time-sync";
	return undef;
    }

    # Insure a correct signature...
    $mysignature = new MD5;
    $mysignature->add($c);
    $mysignature->add($self->{'p'}->{'secret'});
    $mysignature->add(int($tstamp));
    if ($mysignature->hexdigest ne $signature) {
	$self->{'Error'} = "Wrong signature for this response";
	return undef;
    }
    $c;
}

sub canon_params {
    my(@param) = @_;
    my(%param) = @param;
    my ($k, $n);

    foreach $k (keys %param) {
	$n = $k;
	$n =~ tr/A-Z/a-z/;
	$param{$n} = $param{$k};
	undef($param{$k}) unless $n eq $k;
    }

    \%param;

}

1;




