Add U2F to WebAuthn migration script (#1411)

This commit is contained in:
Maxime Besson 2022-01-21 18:08:55 +01:00
parent 2cc2a5804b
commit f7852b3302
2 changed files with 143 additions and 19 deletions

View File

@ -84,7 +84,6 @@ sub _search {
}
return $res;
}
sub search {
@ -264,12 +263,86 @@ sub _del_psession_special {
if ($deleted) {
$data->{$specialKeyName} = to_json( [@new] );
}
# TODO should this be in the if???
$psession->update($data);
}
sub _migrateu2f_device {
my ( $self, $device ) = @_;
my $credential_id = $device->{_keyHandle};
my $_userKey = $device->{_userKey};
eval { require Authen::WebAuthn };
if ($@) {
die "Missing Authen::WebAuthn dependency: $@";
}
my $credential_pubkey =
Authen::WebAuthn::convert_raw_ecc_to_cose($_userKey);
return {
type => "WebAuthn",
name => "$device->{name}",
_credentialId => "$credential_id",
_credentialPublicKey => "$credential_pubkey",
_signCount => 0,
epoch => "$device->{epoch}",
};
}
sub _migrateu2f {
my $self = shift;
my $target = shift;
my $psession = $self->_get_psession($target);
my $data = $psession->data;
my $migrated = 0;
my $_2fDevices = $data->{_2fDevices} || "[]";
$_2fDevices = from_json($_2fDevices);
die "Expecting JSON array in _2fDevices"
unless ref($_2fDevices) eq "ARRAY";
my @new_2fDevices = @{$_2fDevices};
my @u2f_devices = grep { $_->{type} eq "U2F" } @{$_2fDevices};
my %migrated_devices;
for my $u2f_device (@u2f_devices) {
my $migrated_device = $self->_migrateu2f_device($u2f_device);
$migrated_devices{ $migrated_device->{_credentialId} } =
$migrated_device;
}
for my $migrated_device ( keys %migrated_devices ) {
# If credentialId is not already present
unless (
grep {
$_->{type} eq "WebAuthn"
and $_->{_credentialId} eq $migrated_device
} @new_2fDevices
)
{
push @new_2fDevices, $migrated_devices{$migrated_device};
$migrated = 1;
}
}
if ($migrated) {
$data->{_2fDevices} = to_json( [@new_2fDevices] );
$psession->update($data);
}
}
sub consents_get {
my $self = shift;
my $target = shift;
my $self = shift;
my $target = shift;
return 0 unless $target;
my $o = $self->stdout;
my $consents = $self->_get_psession_special( $target, '_oidcConsents',
sub { $_[0]->{rp} } );
@ -278,8 +351,10 @@ sub consents_get {
}
sub secondfactors_get {
my $self = shift;
my $target = shift;
my $self = shift;
my $target = shift;
return 0 unless $target;
my $o = $self->stdout;
my $consents = $self->_get_psession_special( $target, '_2fDevices',
sub { genId2F( $_[0] ) } );
@ -290,8 +365,11 @@ sub secondfactors_get {
sub consents_delete {
my $self = shift;
my $target = shift;
my @ids = @_;
return unless @ids;
return 0 unless $target;
my @ids = @_;
return 0 unless @ids;
$self->_del_psession_special( $target, '_oidcConsents',
sub { $_[0]->{rp} }, @ids );
return 0;
@ -300,23 +378,66 @@ sub consents_delete {
sub secondfactors_delete {
my $self = shift;
my $target = shift;
my @ids = @_;
return unless @ids;
return 0 unless $target;
my @ids = @_;
return 0 unless @ids;
$self->_del_psession_special( $target, '_2fDevices',
sub { genId2F( $_[0] ) }, @ids );
return 0;
}
sub _get_psession_targets {
my ( $self, @args ) = @_;
if ( $self->opts->{where} or $self->opts->{all} ) {
$self->opts->{persistent} = 1;
if ( $self->opts->{all} ) {
delete $self->opts->{where};
}
my $res = $self->_search();
return ( map { $res->{$_}->{_session_uid} } keys %{$res} );
}
else {
return @args;
}
}
sub secondfactors_delType {
my $self = shift;
my $target = shift;
my @types = @_;
return unless @types;
$self->_del_psession_special( $target, '_2fDevices', sub { $_[0]->{type} },
@types );
my $self = shift;
my $target;
unless ( $self->opts->{where} or $self->opts->{all} ) {
$target = shift;
}
my @types = @_;
return 0 unless @types;
my @targets = $self->_get_psession_targets($target);
for my $target (@targets) {
$self->_del_psession_special( $target, '_2fDevices',
sub { $_[0]->{type} }, @types );
}
return 0;
}
sub secondfactors_migrateu2f {
my ( $self, @ids ) = @_;
my $result = 0;
my @sessions = $self->_get_psession_targets(@ids);
for my $id (@sessions) {
if ( !$self->_migrateu2f($id) ) {
$result = 1;
}
}
return $result;
}
sub setKey {
my $self = shift;
my $id = shift;
@ -380,8 +501,8 @@ sub run {
# Subcommands and target
elsif ( $action =~ /^(?:secondfactors|consents)$/ ) {
my $subcommand = shift;
unless ( $subcommand and @_ ) {
die "Missing subcommand and target for $action";
unless ($subcommand) {
die "Missing subcommand $action";
}
my $func = "${action}_${subcommand}";
if ( $self->can($func) ) {

View File

@ -22,6 +22,7 @@ GetOptions(
'help|h' => \$help,
'select|s=s@' => \$opts->{select},
'where|w=s' => \$opts->{where},
'all|a' => \$opts->{all},
'backend|b=s' => \$opts->{backend},
'persistent|p' => \$opts->{persistent},
'id-only|i' => \$opts->{idonly},
@ -80,7 +81,7 @@ if ( $action eq "setKey" ) {
}
if ( $action eq "secondfactors" ) {
unless ( @ARGV >= 2 ) {
unless ( @ARGV >= 1 ) {
pod2usage(
-exitval => 1,
-verbose => 99,
@ -238,8 +239,10 @@ Commands:
delete <user> <id> [<id> ...]
delete second factors for a user. The ID must match one of the
IDs returned by the "show" command.
delType <user> <type> [<type> ...]
delType [<user>|--all] <type> [<type> ...]
delete all second factors of a given type for a user
migrateu2f [<user>|--all]
migrate U2F device registrations to WebAuthn device registrations
=head2 Consents