Authen-U2F-Tester

 view release on metacpan or  search on metacpan

lib/Authen/U2F/Tester.pm  view on Meta::CPAN

my $COUNTER = 0;


has key => (
    is       => 'ro',
    isa      => 'Crypt::PK::ECC',
    required => 1);


has keystore => (
    is       => 'ro',
    does     => 'Authen::U2F::Tester::Role::Keystore',
    required => 1);


has certificate => (
    is       => 'ro',
    isa      => 'Crypt::OpenSSL::X509',
    required => 1);

around BUILDARGS => sub {
    my ($orig, $self) = splice @_, 0, 2;

    if (@_ > 1) {
        my %args = @_;

        if (my $keyfile = delete $args{key_file}) {
            $args{key} = Crypt::PK::ECC->new($keyfile);
        }

        if (my $certfile = delete $args{cert_file}) {
            $args{certificate} = Crypt::OpenSSL::X509->new_from_file($certfile);
        }

        # if no keystore was given, use the wrapped keystore
        unless (defined $args{keystore}) {
            require Authen::U2F::Tester::Keystore::Wrapped;
            $args{keystore} = Authen::U2F::Tester::Keystore::Wrapped->new(key => $args{key});
        }

        return $self->$orig(%args);
    }
    else {
        return $self->$orig(@_);
    }
};


sub register {
    my ($self, $app_id, $challenge, @registered_handles) = @_;

    # check if this device has already been registered
    for my $registered (@registered_handles) {
        if ($self->keystore->exists($registered)) {
            return Authen::U2F::Tester::Error->new(DEVICE_INELIGIBLE);
        }
    }

    # generate a new keypair for this application
    my $keypair = Authen::U2F::Tester::Keypair->new;
    my $handle  = $self->keystore->put($keypair->private_key);
    my $cert    = $self->certificate->as_string(Crypt::OpenSSL::X509::FORMAT_ASN1);

    my %client_data = (
        typ        => 'navigator.id.finishEnrollment',
        challenge  => $challenge,
        origin     => $app_id,
        cid_pubkey => 'unused');

    my $client_data = encode_json(\%client_data);

    my $sign_data = pack 'x a32 a32 a* a65',
        sha256($app_id),
        sha256($client_data),
        $handle,
        $keypair->public_key;

    my $signature = $self->key->sign_hash(sha256($sign_data));

    my $response = pack 'a a65 C/a* a* a*',
        chr(0x05), $keypair->public_key, $handle, $cert, $signature;

    return Authen::U2F::Tester::RegisterResponse->new(
        error_code  => OK,
        response    => $response,
        client_data => encode_base64url($client_data));
}


sub sign {
    my ($self, $app_id, $challenge, @handles) = @_;

    my $handle = first { $self->keystore->exists($_) } @handles;

    unless (defined $handle) {
        return Authen::U2F::Tester::Error->new(DEVICE_INELIGIBLE);
    }

    my %client_data = (
        typ        => 'navigator.id.getAssertion',
        challenge  => $challenge,
        origin     => $app_id,
        cid_pubkey => 'unused');

    my $client_data = encode_json(\%client_data);

    my $pkec = $self->keystore->get($handle);

    my $counter = ++$COUNTER;

    # generate the signature
    my $sign_data = pack 'a32 a N a32',
        sha256($app_id),            # 32 byte SHA256 application parameter
        chr(0x01),                  # 1 byte user presence
        $counter,                   # 4 byte counter
        sha256($client_data);       # 32 byte SHA256 of client data JSON

    my $signature = $pkec->sign_hash(sha256($sign_data));

    my $response = pack 'a N a*',
        chr(0x01),



( run in 1.044 second using v1.01-cache-2.11-cpan-13bb782fe5a )