Firefox-Marionette

 view release on metacpan or  search on metacpan

README  view on Meta::CPAN

      Credential ID <https://www.w3.org/TR/webauthn-2/#credential-id>. If
      this is not supplied, one will be generated.

      * is_resident - contains a boolean that if set to true, a client-side
      discoverable credential
      <https://w3c.github.io/webauthn/#client-side-discoverable-credential>
      is created. If set to false, a server-side credential
      <https://w3c.github.io/webauthn/#server-side-credential> is created
      instead.

      * private_key - either a RFC5958
      <https://www.rfc-editor.org/rfc/rfc5958> encoded private key encoded
      using encode_base64url or a hash containing the following keys;

	* name - contains the name of the private key algorithm, such as
	"RSA-PSS" (the default), "RSASSA-PKCS1-v1_5", "ECDSA" or "ECDH".

	* size - contains the modulus length of the private key. This is
	only valid for "RSA-PSS" or "RSASSA-PKCS1-v1_5" private keys.

	* hash - contains the name of the hash algorithm, such as "SHA-512"

README  view on Meta::CPAN

        $firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });
        foreach my $credential ($firefox->webauthn_credentials()) {
            $firefox->delete_webauthn_credential($credential);

    # ... time passes ...

            $firefox->add_webauthn_credential(
                      id            => $credential->id(),
                      host          => $credential->host(),
                      user          => $credential->user(),
                      private_key   => $credential->private_key(),
                      is_resident   => $credential->is_resident(),
                      sign_count    => $credential->sign_count(),
                                  );
        }
        $firefox->go('about:blank');
        $firefox->clear_cache(Firefox::Marionette::Cache::CLEAR_COOKIES());
        $firefox->go('https://webauthn.io');
        $firefox->find_id('input-email')->type($user_name);
        $firefox->find_id('login-button')->click();
        $firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });

README.md  view on Meta::CPAN

    $firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });
    foreach my $credential ($firefox->webauthn_credentials()) {
        $firefox->delete_webauthn_credential($credential);

\# ... time passes ...

        $firefox->add_webauthn_credential(
                  id            => $credential->id(),
                  host          => $credential->host(),
                  user          => $credential->user(),
                  private_key   => $credential->private_key(),
                  is_resident   => $credential->is_resident(),
                  sign_count    => $credential->sign_count(),
                              );
    }
    $firefox->go('about:blank');
    $firefox->clear_cache(Firefox::Marionette::Cache::CLEAR_COOKIES());
    $firefox->go('https://webauthn.io');
    $firefox->find_id('input-email')->type($user_name);
    $firefox->find_id('login-button')->click();
    $firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });

lib/Firefox/Marionette.pm  view on Meta::CPAN

    }
    if ( !defined $parameters{id} ) {
        my $credential_id = MIME::Base64::encode_base64url(
            Crypt::URandom::urandom( _CREDENTIAL_ID_LENGTH() ) );
        $parameters{id} = $credential_id;
    }
    if ( defined $parameters{user} ) {
        $parameters{user} =
          MIME::Base64::encode_base64url( $parameters{user} );
    }
    if ( !defined $parameters{private_key} ) {
        $parameters{private_key} = {};
    }
    if ( ref $parameters{private_key} eq 'HASH' ) {
        my $script = <<'_JS_';
let privateKeyArguments = {};
if (arguments[0]["name"]) {
  privateKeyArguments["name"] = arguments[0]["name"];
} else {
  privateKeyArguments["name"] = "RSA-PSS";
}
if ((privateKeyArguments["name"] == "RSA-PSS") || (privateKeyArguments["name"] == "RSASSA-PKCS1-v1_5")) {
  privateKeyArguments["modulusLength"] = arguments[0]["size"] || 8192;
  privateKeyArguments["publicExponent"] = new Uint8Array([1, 0, 1]);

lib/Firefox/Marionette.pm  view on Meta::CPAN

  for (let i = 0; i < array.length; i += CHUNK_SZ) {
    c.push(String.fromCharCode.apply(null, array.subarray(i, i + CHUNK_SZ)));
  }
  let b64Key = window.btoa(c.join(""));
  let urlSafeKey = b64Key.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
  return urlSafeKey;
})();
return privateKey;
_JS_
        my $old = $self->_context('chrome');
        $parameters{private_key} = $self->script(
            $self->_compress_script($script),
            args => [ $parameters{private_key} ]
        );
        $self->_context($old);
    }
    if ( !defined $parameters{sign_count} ) {
        $parameters{sign_count} = 0;
    }
    my $authenticator         = $self->_get_webauthn_authenticator(%parameters);
    my %credential_parameters = (
        authenticatorId      => $authenticator->id(),
        credentialId         => $parameters{id},
        isResidentCredential => $parameters{is_resident},
        rpId                 => $parameters{host},
        privateKey           => $parameters{private_key},
        signCount            => $parameters{sign_count},
        userHandle           => $parameters{user},
    );
    my $message_id = $self->_new_message_id();
    $self->_send_request(
        [
            _COMMAND(), $message_id, $self->_command('WebAuthn:AddCredential'),
            \%credential_parameters,
        ]
    );

lib/Firefox/Marionette.pm  view on Meta::CPAN

=over 4

=item * authenticator - contains the L<authenticator|Firefox::Marionette::WebAuthn::Authenticator> that the credential will be added to.  If this parameter is not supplied, the credential will be added to the default authenticator, if one exists.

=item * host - contains the domain that this credential is to be used for.  In the language of L<WebAuthn|https://www.w3.org/TR/webauthn-2>, this field is referred to as the L<relying party identifier|https://www.w3.org/TR/webauthn-2/#relying-party-i...

=item * id - contains the unique id for this credential, also known as the L<Credential ID|https://www.w3.org/TR/webauthn-2/#credential-id>.  If this is not supplied, one will be generated.

=item * is_resident - contains a boolean that if set to true, a L<client-side discoverable credential|https://w3c.github.io/webauthn/#client-side-discoverable-credential> is created. If set to false, a L<server-side credential|https://w3c.github.io/w...

=item * private_key - either a L<RFC5958|https://www.rfc-editor.org/rfc/rfc5958> encoded private key encoded using L<encode_base64url|MIME::Base64::encode_base64url> or a hash containing the following keys;

=over 8

=item * name - contains the name of the private key algorithm, such as "RSA-PSS" (the default), "RSASSA-PKCS1-v1_5", "ECDSA" or "ECDH".

=item * size - contains the modulus length of the private key.  This is only valid for "RSA-PSS" or "RSASSA-PKCS1-v1_5" private keys.

=item * hash - contains the name of the hash algorithm, such as "SHA-512" (the default).  This is only valid for "RSA-PSS" or "RSASSA-PKCS1-v1_5" private keys.

=item * curve - contains the name of the curve for the private key, such as "P-384" (the default).  This is only valid for "ECDSA" or "ECDH" private keys.

lib/Firefox/Marionette.pm  view on Meta::CPAN

    $firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });
    foreach my $credential ($firefox->webauthn_credentials()) {
        $firefox->delete_webauthn_credential($credential);

# ... time passes ...

        $firefox->add_webauthn_credential(
                  id            => $credential->id(),
                  host          => $credential->host(),
                  user          => $credential->user(),
                  private_key   => $credential->private_key(),
                  is_resident   => $credential->is_resident(),
                  sign_count    => $credential->sign_count(),
                              );
    }
    $firefox->go('about:blank');
    $firefox->clear_cache(Firefox::Marionette::Cache::CLEAR_COOKIES());
    $firefox->go('https://webauthn.io');
    $firefox->find_id('input-email')->type($user_name);
    $firefox->find_id('login-button')->click();
    $firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });

lib/Firefox/Marionette/WebAuthn/Credential.pm  view on Meta::CPAN

use strict;
use warnings;

our $VERSION = '1.67';

my %key_mapping = (
    isResidentCredential => 'is_resident',
    rpId                 => 'host',
    signCount            => 'sign_count',
    userHandle           => 'user',
    privateKey           => 'private_key',
    credentialId         => 'id',
);

sub new {
    my ( $class, %parameters ) = @_;
    my $self = bless {}, $class;
    foreach my $key_name ( sort { $a cmp $b } keys %key_mapping ) {
        if ( exists $parameters{ $key_mapping{$key_name} } ) {
            $self->{ $key_mapping{$key_name} } =
              $parameters{ $key_mapping{$key_name} };

lib/Firefox/Marionette/WebAuthn/Credential.pm  view on Meta::CPAN

sub host {
    my ($self) = @_;
    return $self->{host};
}

sub is_resident {
    my ($self) = @_;
    return $self->{is_resident};
}

sub private_key {
    my ($self) = @_;
    return $self->{private_key};
}

sub sign_count {
    my ($self) = @_;
    return $self->{sign_count};
}

sub user {
    my ($self) = @_;
    return $self->{user};

lib/Firefox/Marionette/WebAuthn/Credential.pm  view on Meta::CPAN

    $firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });
    foreach my $credential ($firefox->webauthn_credentials()) {
        $firefox->delete_webauthn_credential($credential);

# ... time passes ...

        $firefox->add_webauthn_credential(
                  id            => $credential->id(),
                  host          => $credential->host(),
                  user          => $credential->user(),
                  private_key   => $credential->private_key(),
                  is_resident   => $credential->is_resident(),
                  sign_count    => $credential->sign_count(),
                              );
    }
    $firefox->go('about:blank');
    $firefox->clear_cache(Firefox::Marionette::Cache::CLEAR_COOKIES());
    $firefox->go('https://webauthn.io');
    $firefox->find_id('input-email')->type($user_name);
    $firefox->find_id('login-button')->click();
    $firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); });

lib/Firefox/Marionette/WebAuthn/Credential.pm  view on Meta::CPAN

accepts a hash as a parameter.  Allowed keys are below;

=over 4

=item * host - contains the domain that this credential is to be used for.  In the language of L<WebAuthn|https://www.w3.org/TR/webauthn-2>, this field is referred to as the L<relying party identifier|https://www.w3.org/TR/webauthn-2/#relying-party-i...

=item * id - contains the unique id for this credential, also known as the L<Credential ID|https://www.w3.org/TR/webauthn-2/#credential-id>.

=item * is_resident - contains a boolean that if set to true, a L<client-side discoverable credential|https://w3c.github.io/webauthn/#client-side-discoverable-credential> is to be created. If set to false, a L<server-side credential|https://w3c.githu...

=item * private_key - either a L<RFC5958|https://www.rfc-editor.org/rfc/rfc5958> encoded private key encoded using L<encode_base64url|MIME::Base64#encode_base64url(-$bytes-)> or a hash containing the following keys;

=over 8

=item * name - contains the name of the private key algorithm, such as "RSA-PSS" (the default), "RSASSA-PKCS1-v1_5", "ECDSA" or "ECDH".

=item * size - contains the modulus length of the private key.  This is only valid for "RSA-PSS" or "RSASSA-PKCS1-v1_5" private keys.

=item * hash - contains the name of the hash algorithm, such as "SHA-512" (the default).  This is only valid for "RSA-PSS" or "RSASSA-PKCS1-v1_5" private keys.

=item * curve - contains the name of the curve for the private key, such as "P-384" (the default).  This is only valid for "ECDSA" or "ECDH" private keys.

lib/Firefox/Marionette/WebAuthn/Credential.pm  view on Meta::CPAN

This method returns a new L<Firefox::Marionette::WebAuthn::Credential|Firefox::Marionette::WebAuthn::Credential> object.

=head2 host

returns the domain that this credential is to be used for.  In the language of L<WebAuthn|https://www.w3.org/TR/webauthn-2>, this field is referred to as the L<relying party identifier|https://www.w3.org/TR/webauthn-2/#relying-party-identifier> or L<...

=head2 is_resident

returns a boolean that if true, a L<client-side discoverable credential|https://w3c.github.io/webauthn/#client-side-discoverable-credential> is to be created. If false, a L<server-side credential|https://w3c.github.io/webauthn/#server-side-credential...

=head2 private_key

returns a L<RFC5958|https://www.rfc-editor.org/rfc/rfc5958> encoded private key encoded using L<encode_base64url|MIME::Base64#encode_base64url(-$bytes-)>.

=head2 sign_count

returns the L<signature counter|https://w3c.github.io/webauthn/#signature-counter> associated to the L<public key credential source|https://w3c.github.io/webauthn/#public-key-credential-source>.

=head2 user

returns the L<userHandle|https://w3c.github.io/webauthn/#public-key-credential-source-userhandle> associated to the credential encoded using L<encode_base64url|MIME::Base64#encode_base64url(-$bytes-)>.

t/01-marionette.t  view on Meta::CPAN

		return;
	} elsif (time - $^T > $test_time_limit) {
		return 1;
	} else {
		return;
	}
}

my $launches = 0;
my $ca_cert_handle;
my $ca_private_key_handle;
my $metacpan_ca_cert_handle;
my $guid_regex = qr/[a-f\d]{8}\-[a-f\d]{4}\-[a-f\d]{4}\-[a-f\d]{4}\-[a-f\d]{12}/smx;
my @old_binary_keys = (qw(firefox_binary firefox marionette));;

my ($major_version, $minor_version, $patch_version); 
sub start_firefox {
	my ($require_visible, %parameters) = @_;
	if ($terminated) {
		die "Caught a signal";
	}

t/01-marionette.t  view on Meta::CPAN

		$firefox = undef;
		ok(!process_alive($firefox_pid), "Cannot contact firefox process ($firefox_pid)");
	}
}

if ($^O eq 'MSWin32') {
} elsif ($ENV{RELEASE_TESTING}) {
	eval {
		$ca_cert_handle = File::Temp->new( TEMPLATE => File::Spec->catfile( File::Spec->tmpdir(), 'firefox_test_ca_cert_XXXXXXXXXXX')) or Firefox::Marionette::Exception->throw( "Failed to open temporary file for writing:$!");
		fcntl $ca_cert_handle, Fcntl::F_SETFD(), 0 or Carp::croak("Can't clear close-on-exec flag on temporary file:$!");
		$ca_private_key_handle = File::Temp->new( TEMPLATE => File::Spec->catfile( File::Spec->tmpdir(), 'firefox_test_ca_private_XXXXXXXXXXX')) or Firefox::Marionette::Exception->throw( "Failed to open temporary file for writing:$!");
		system {'openssl'} 'openssl', 'genrsa', '-out' => $ca_private_key_handle->filename(), 4096 and Carp::croak("Failed to generate a private key:$!");
		my $ca_config_handle = File::Temp->new( TEMPLATE => File::Spec->catfile( File::Spec->tmpdir(), 'firefox_test_ca_config_XXXXXXXXXXX')) or Firefox::Marionette::Exception->throw( "Failed to open temporary file for writing:$!");
		$ca_config_handle->print(<<"_CONFIG_");
[ req ]
distinguished_name     = req_distinguished_name
attributes             = req_attributes
prompt                 = no

[ req_distinguished_name ]
C                      = AU
ST                     = Victoria

t/01-marionette.t  view on Meta::CPAN

emailAddress           = ddick\@cpan.org

[ req_attributes ]
_CONFIG_
		seek $ca_config_handle, 0, 0 or Carp::croak("Failed to seek to start of temporary file:$!");
		fcntl $ca_config_handle, Fcntl::F_SETFD(), 0 or Carp::croak("Can't clear close-on-exec flag on temporary file:$!");
		system {'openssl'} 'openssl', 'req', '-new', '-x509',
			'-set_serial' => '1',
			'-config'     => $ca_config_handle->filename(),
			'-days'       => 10,
			'-key'        => $ca_private_key_handle->filename(),
			'-out'        => $ca_cert_handle->filename()
			and Carp::croak("Failed to generate a CA root certificate:$!");
		1;
	} or do {
		chomp $@;
		diag("Did not generate a CA root certificate:$@");
	};
}

SKIP: {

t/01-marionette.t  view on Meta::CPAN

			diag("insecure cert test is not supported for remote hosts");
		} elsif (($ENV{FIREFOX_HOST}) && ($ENV{FIREFOX_HOST} eq 'localhost') && ($ENV{FIREFOX_PORT})) {
			diag("insecure cert test is not supported for remote hosts");
		} elsif ((exists $Config::Config{'d_fork'}) && (defined $Config::Config{'d_fork'}) && ($Config::Config{'d_fork'} eq 'define')) {
			my $ip_address = '127.0.0.1';
			my $daemon = IO::Socket::SSL->new(
				LocalAddr => $ip_address,
				LocalPort => 0,
				Listen => 20,
				SSL_cert_file => $ca_cert_handle->filename(),
				SSL_key_file => $ca_private_key_handle->filename(),
			);
			my $url = "https://$ip_address:" . $daemon->sockport();
			if (my $pid = fork) {
				wait_for_server_on($daemon, $url, $pid);
				eval { $firefox->go(URI->new($url)) };
				my $exception = "$@";
				chomp $exception;
				ok(ref $@ eq 'Firefox::Marionette::Exception::InsecureCertificate', $url . " threw an exception:$exception");
				while(kill 0, $pid) {
					kill $signals_by_name{TERM}, $pid;

t/04-webauthn.t  view on Meta::CPAN

			ok($credential->id(), "Credential id is " . $credential->id());
			ok($credential->host() eq $host_name, "Hostname is $host_name:" . $credential->host());
			ok($credential->user(), "Username is " . $credential->user());
			$sign_count_after_one_login = $credential->sign_count();
			ok($credential->sign_count() >= 1, "Sign Count is >= 1:" . $credential->sign_count());
			$firefox->delete_webauthn_credential($credential);
			$firefox->add_webauthn_credential(
					id => $credential->id(), 
					host => $credential->host(),
					user => $credential->user(),
					private_key => $credential->private_key(),
					is_resident => $credential->is_resident(),
					sign_count => $credential->sign_count(),
							);
		}
		ok($firefox->go('about:blank'), "Loading about:blank");
		ok($firefox->clear_cache(Firefox::Marionette::Cache::CLEAR_COOKIES()), "Deleting all cookies");
		ok($firefox->go('https://' . $host_name), "Loading https://$host_name");
		ok($firefox->find_id('input-email')->type($user_name), "Entering $user_name for username");
		ok($firefox->find_id('login-button')->click(), "Clicking login button for $host_name");
		ok($firefox->await(sub { sleep 1; $firefox->find_class('hero confetti'); }), "Successfully authenticated to $host_name");

t/04-webauthn.t  view on Meta::CPAN

		};
		foreach my $credential ($firefox->webauthn_credentials($authenticator)) {
			ok($credential->id() eq $cred_id, "Credential id is '$cred_id':" . $credential->id());
			ok($credential->host() eq $host_name, "Hostname is '$host_name':" . $credential->host());
			ok(!$credential->user(), "Username is empty");
			ok($credential->is_resident() == 0, "is_resident is 0:" . $credential->is_resident());
			ok($credential->sign_count() == 0, "Sign Count is 0:" . $credential->sign_count());
			$firefox->delete_webauthn_credential($credential, $authenticator);
		}
		eval {
			$credential = $firefox->add_webauthn_credential( private_key => { name => 'RSA-PSS', size => 1024 }, host => $host_name, user => $user_name);
			ok($credential->id(), "Credential id is " . $credential->id());
			ok($credential->host() eq $host_name, "Hostname is '$host_name':" . $credential->host());
			ok($credential->user() eq $user_name, "Username is '$user_name':" . $credential->user());
		};
		ok($firefox->delete_webauthn_authenticator($authenticator), "Deleted virtual authenticator");
		my $default_authenticator = $firefox->webauthn_authenticator();
		ok($default_authenticator, "Default virtual authenticator still exists");
		ok($firefox->delete_webauthn_authenticator(), "Deleted default virtual authenticator");
		ok(!$firefox->webauthn_authenticator(), "Default virtual authenticator no longer exists");
		$authenticator = $firefox->add_webauthn_authenticator( transport => Firefox::Marionette::WebAuthn::Authenticator::BLE(), protocol => Firefox::Marionette::WebAuthn::Authenticator::CTAP1_U2F() );

t/test_daemons.pm  view on Meta::CPAN

    $self->{ca_directory} = $class->tmp_directory('ca');
    $self->{ca_cert_path} =
      File::Spec->catfile( $self->{ca_directory}->dirname(), 'ca.crt' );
    $self->{ca_cert_handle} = FileHandle->new(
        $self->{ca_cert_path},
        Fcntl::O_EXCL() | Fcntl::O_RDWR() | Fcntl::O_CREAT(),
        Fcntl::S_IRUSR() | Fcntl::S_IWUSR()
      )
      or
      Carp::croak("Failed to create $self->{ca_cert_path}:$EXTENDED_OS_ERROR");
    $self->{ca_private_key_path} =
      File::Spec->catfile( $self->{ca_directory}->dirname(), 'ca.key' );
    $self->{ca_private_key_handle} =
      $class->new_key( $key_size, $self->{ca_private_key_path} );
    $self->{ca_serial_path} =
      File::Spec->catfile( $self->{ca_directory}->dirname(), 'ca.serial' );
    $self->{ca_serial_handle} = FileHandle->new(
        $self->{ca_serial_path},
        Fcntl::O_EXCL() | Fcntl::O_RDWR() | Fcntl::O_CREAT(),
        Fcntl::S_IRUSR() | Fcntl::S_IWUSR()
      )
      or Carp::croak(
        "Failed to create $self->{ca_serial_path}:$EXTENDED_OS_ERROR");
    print { $self->{ca_serial_handle} } '01'

t/test_daemons.pm  view on Meta::CPAN

basicConstraints=critical,CA:TRUE,pathlen:1
extendedKeyUsage=serverAuth

_CONFIG_
    seek $self->{ca_config_handle}, 0, 0
      or Carp::croak(
        "Failed to seek to start of temporary file:$EXTENDED_OS_ERROR");
    system {$openssl_binary} $openssl_binary, 'req', '-new', '-x509',
      '-config' => $self->{ca_config_path},
      '-days'   => 10,
      '-key'    => $self->{ca_private_key_path},
      '-out'    => $self->{ca_cert_path}
      and Carp::croak(
        "Failed to generate a CA root certificate:$EXTENDED_OS_ERROR");
    return $self;
}

sub config {
    my ($self) = @_;
    return $self->{ca_config_path};
}

t/test_daemons.pm  view on Meta::CPAN

    return $self->{ca_serial_path};
}

sub cert {
    my ($self) = @_;
    return $self->{ca_cert_path};
}

sub key {
    my ($self) = @_;
    return $self->{ca_private_key_path};
}

sub new_cert {
    my ( $self, $key_path, $host_name, $path ) = @_;
    my $csr = $self->tmp_handle('csr');
    my $cert_handle;
    my $cert_path;
    if ($path) {
        $cert_handle = FileHandle->new(
            $path,

t/test_daemons.pm  view on Meta::CPAN

          Carp::croak("Failed to write to temporary file:$EXTENDED_OS_ERROR");
    }
    seek $cert_handle, 0, Fcntl::SEEK_SET()
      or Carp::croak(
        "Failed to seek to start of temporary file:$EXTENDED_OS_ERROR");
    return $cert_handle;
}

sub new_key {
    my ( $class, $size, $path ) = @_;
    my $private_key_handle;
    my $private_key_path;
    if ($path) {
        $private_key_handle = FileHandle->new(
            $path,
            Fcntl::O_EXCL() | Fcntl::O_RDWR() | Fcntl::O_CREAT(),
            Fcntl::S_IRUSR() | Fcntl::S_IWUSR()
        ) or Carp::croak("Failed to create $path:$EXTENDED_OS_ERROR");
        $private_key_path = $path;
    }
    else {
        $private_key_handle = $class->tmp_handle('private_key');
        $private_key_path   = $private_key_handle->filename();
    }
    system {$openssl_binary} $openssl_binary, 'genrsa',
      '-out' => $private_key_path,
      $size
      and Carp::croak("Failed to generate a private key:$EXTENDED_OS_ERROR");
    return $private_key_handle;
}

package Test::Daemon;

use strict;
use warnings;
use Carp();
use Config;
use Socket();
use English qw( -no_match_vars );



( run in 0.436 second using v1.01-cache-2.11-cpan-4d50c553e7e )