Authen-SCRAM

 view release on metacpan or  search on metacpan

lib/Authen/SCRAM/Client.pm  view on Meta::CPAN


#pod =attr username (required)
#pod
#pod Authentication identity.  This will be normalized with the SASLprep algorithm
#pod before being transmitted to the server.
#pod
#pod =cut

has username => (
    is       => 'ro',
    isa      => Str,
    required => 1,
);

#pod =attr password (required)
#pod
#pod Authentication password.  This will be normalized with the SASLprep algorithm
#pod before being transmitted to the server.
#pod
#pod =cut

has password => (
    is       => 'ro',
    isa      => Str,
    required => 1,
);

#pod =attr authorization_id
#pod
#pod If the authentication identity (C<username>) will act as a different,
#pod authorization identity, this attribute provides the authorization identity.  It
#pod is optional.  If not provided, the authentication identity is considered by the
#pod server to be the same as the authorization identity.
#pod
#pod =cut

has authorization_id => (
    is      => 'ro',
    isa     => Str,
    default => '',
);

#pod =attr minimum_iteration_count
#pod
#pod If the server requests an iteration count less than this value, the client
#pod throws an error.  This protects against downgrade attacks.  The default is
#pod 4096, consistent with recommendations in the RFC.
#pod
#pod =cut

has minimum_iteration_count => (
    is      => 'ro',
    isa     => Num,
    default => 4096,
);

# The derived PBKDF2 password can be reused if the salt and iteration count
# is the same as a previous authentication conversation.
has _cached_credentials => (
    is      => 'rw',
    default => sub { [ "", 0, "" ] }, # salt, iterations, derived password
);

#--------------------------------------------------------------------------#
# provided by Authen::SCRAM::Role::Common
#--------------------------------------------------------------------------#

with 'Authen::SCRAM::Role::Common';

#pod =attr digest
#pod
#pod Name of a digest function available via L<PBKDF2::Tiny>.  Valid values are
#pod SHA-1, SHA-224, SHA-256, SHA-384, or SHA-512.  Defaults to SHA-1.
#pod
#pod =attr nonce_size
#pod
#pod Size of the client-generated nonce, in bits.  Defaults to 192.
#pod The server-nonce will be appended, so the final nonce size will
#pod be substantially larger.
#pod
#pod =attr skip_saslprep
#pod
#pod A boolean that defaults to false.  If set to true, usernames and passwords will
#pod not be normalized through SASLprep.  This is a deviation from the RFC5802 spec
#pod and is not recommended.
#pod
#pod =cut

#--------------------------------------------------------------------------#
# private attributes
#--------------------------------------------------------------------------#

has _prepped_user => (
    is  => 'lazy',
    isa => Str,
);

sub _build__prepped_user {
    my ($self) = @_;
    return $self->_saslprep( $self->username );
}

has _prepped_pass => (
    is  => 'lazy',
    isa => Str,
);

sub _build__prepped_pass {
    my ($self) = @_;
    return $self->_saslprep( $self->password );
}

has _prepped_authz => (
    is  => 'lazy',
    isa => Str,
);

sub _build__prepped_authz {
    my ($self) = @_;
    return $self->_saslprep( $self->authorization_id );
}

lib/Authen/SCRAM/Client.pm  view on Meta::CPAN

#pod =cut

sub first_msg {
    my ($self) = @_;

    $self->_clear_session;
    $self->_set_session(
        n => $self->_prepped_user,
        r => $self->_get_session('_nonce'),
    );
    my $c_1_bare = $self->_join_reply(qw/n r/);
    $self->_set_session( _c1b => $c_1_bare );
    my $msg = $self->_gs2_header . $c_1_bare;
    utf8::upgrade($msg); # ensure UTF-8 encoding internally
    return $msg;
}

#pod =method final_msg
#pod
#pod     $client_final_msg = $client->final_msg( $server_first_msg );
#pod
#pod This takes the C<server-first-message> character string received from the
#pod server and returns the C<client-final-message> character string containing the
#pod authentication proof to be sent to the server.  This will throw an exception
#pod should an error occur.
#pod
#pod =cut

sub final_msg {
    my ( $self, $s_first_msg ) = @_;

    my ( $mext, @params ) = $s_first_msg =~ $self->_server_first_re;

    if ( defined $mext ) {
        croak
          "SCRAM server-first-message required mandatory extension '$mext', but we do not support it";
    }
    if ( !@params ) {
        croak "SCRAM server-first-message could not be parsed";
    }

    my $original_nonce = $self->_get_session("r");
    $self->_parse_to_session(@params);

    my $joint_nonce = $self->_get_session("r");
    unless ( $joint_nonce =~ m{^\Q$original_nonce\E.} ) {
        croak "SCRAM server-first-message nonce invalid";
    }

    # assemble client-final-wo-proof
    $self->_set_session(
        _s1 => $s_first_msg,
        c   => $self->_base64( encode_utf8( $self->_gs2_header ) ),
    );
    $self->_set_session( '_c2wop' => $self->_join_reply(qw/c r/) );

    # assemble proof
    my $salt  = decode_base64( $self->_get_session("s") );
    my $iters = $self->_get_session("i");
    if ( $iters < $self->minimum_iteration_count ) {
        croak sprintf( "SCRAM server requested %d iterations, less than the minimum of %d",
            $iters, $self->minimum_iteration_count );
    }

    my ( $stored_key, $client_key, $server_key ) = $self->computed_keys( $salt, $iters );

    $self->_set_session(
        _stored_key => $stored_key,
        _server_key => $server_key,
    );

    my $client_sig = $self->_client_sig;

    $self->_set_session( p => $self->_base64( $client_key ^ $client_sig ) );

    return $self->_join_reply(qw/c r p/);
}

#pod =method validate
#pod
#pod     $client->validate( $server_final_msg );
#pod
#pod This takes the C<server-final-message> character string received from the
#pod server and verifies that the server actually has a copy of the client
#pod credentials.  It will return true if valid and throw an exception, otherwise.
#pod
#pod =cut

sub validate {
    my ( $self, $s_final_msg ) = @_;

    my (@params) = $s_final_msg =~ $self->_server_final_re;
    $self->_parse_to_session(@params);

    if ( my $err = $self->_get_session("e") ) {
        croak "SCRAM server-final-message was error '$err'";
    }

    my $server_sig =
      $self->_hmac_fcn->( $self->_get_session("_server_key"), $self->_auth_msg );

    if ( $self->_base64($server_sig) ne $self->_get_session("v") ) {
        croak "SCRAM server-final-message failed validation";
    }

    return 1;
}

#pod =method computed_keys
#pod
#pod This method returns the opaque keys used in the SCRAM protocol.  It returns
#pod the 'stored key', the 'client key' and the 'server key'.  The server must
#pod have a copy of the stored key and server key for a given user in order to
#pod authenticate.
#pod
#pod This method caches the computed values -- it generates them fresh only if
#pod the supplied salt and iteration count don't match the cached salt and
#pod iteration count.
#pod
#pod =cut



( run in 0.682 second using v1.01-cache-2.11-cpan-96521ef73a4 )