117 lines
4.3 KiB
Perl
117 lines
4.3 KiB
Perl
|
# Before `make install' is performed this script should be runnable with
|
||
|
# `make test'. After `make install' it should work as `perl Lemonldap-NG-Common.t'
|
||
|
#########################
|
||
|
use Time::Fake;
|
||
|
|
||
|
# Must subclass TOTP because it uses $self->logger etc.
|
||
|
package TestableTotp;
|
||
|
use Moose;
|
||
|
use Test::More;
|
||
|
use Lemonldap::NG::Common::TOTP;
|
||
|
use Lemonldap::NG::Common::Logger::Null;
|
||
|
extends 'Lemonldap::NG::Common::TOTP';
|
||
|
has logger => ( is => "ro", lazy => 1, builder => '_null_logger' );
|
||
|
has userLogger => ( is => "ro", lazy => 1, builder => '_null_logger' );
|
||
|
|
||
|
sub _null_logger {
|
||
|
return Lemonldap::NG::Common::Logger::Null->new;
|
||
|
}
|
||
|
|
||
|
package main;
|
||
|
use Test::More tests => 16;
|
||
|
|
||
|
BEGIN {
|
||
|
use_ok('Lemonldap::NG::Common::TOTP');
|
||
|
}
|
||
|
use strict;
|
||
|
|
||
|
### WARNING FOR DEVELOPPERS ###
|
||
|
# These constants are not to be messed with. If this unit test breaks, do NOT
|
||
|
# modify them, fix the code instead.
|
||
|
#
|
||
|
# In particular, if the $stored_secret no longer decrypts to $cleartext_secret,
|
||
|
# it means that users will lose their encrypted TOTP secrets on the next
|
||
|
# upgrade.
|
||
|
# If you need to change the cryptographic algorithm, make sure you remain
|
||
|
# compatible with existing stored values
|
||
|
|
||
|
my $timestamp = 1633009395;
|
||
|
my $totp_for_timestamp = 766039;
|
||
|
my $cleartext_secret = "ggtoch5x6naorymli6nh72ku4khwd4jr";
|
||
|
my $key = "azert";
|
||
|
my $encrypted_secret =
|
||
|
"{llngcrypt}TdEcd2vkmn4j0D8+str3v2D8zt0Dbm3sZ8TwlzdOKcang+qUmLraTQBztSrESRHDpAh+pQCKvDozuz9va7GxhHIkaKI3EZxOCWJ0rQCun/I=";
|
||
|
|
||
|
#########################
|
||
|
|
||
|
# Insert your test code below, the Test::More module is used here so read
|
||
|
# its man page ( perldoc Test::More ) for help writing this test script.
|
||
|
|
||
|
my $t = TestableTotp->new( key => $key, encryptSecret => 0 );
|
||
|
|
||
|
# Verification with no offset
|
||
|
Time::Fake->offset($timestamp);
|
||
|
is( $t->verifyCode( 30, 0, 6, $cleartext_secret, $totp_for_timestamp ),
|
||
|
1, "TOTP code is valid" );
|
||
|
|
||
|
Time::Fake->offset( $timestamp + 30 );
|
||
|
is( $t->verifyCode( 30, 0, 6, $cleartext_secret, $totp_for_timestamp ),
|
||
|
0, "TOTP code is no longer valid" );
|
||
|
|
||
|
Time::Fake->offset( $timestamp - 30 );
|
||
|
is( $t->verifyCode( 30, 0, 6, $cleartext_secret, $totp_for_timestamp ),
|
||
|
0, "TOTP code is not valid yet" );
|
||
|
|
||
|
# Verification with offset 2 allows +1m and -1m
|
||
|
Time::Fake->offset( $timestamp + 45 );
|
||
|
is( $t->verifyCode( 30, 2, 6, $cleartext_secret, $totp_for_timestamp ),
|
||
|
1, "TOTP code is valid" );
|
||
|
|
||
|
Time::Fake->offset( $timestamp - 45 );
|
||
|
is( $t->verifyCode( 30, 2, 6, $cleartext_secret, $totp_for_timestamp ),
|
||
|
1, "TOTP code is valid" );
|
||
|
|
||
|
Time::Fake->offset( $timestamp + 95 );
|
||
|
is( $t->verifyCode( 30, 2, 6, $cleartext_secret, $totp_for_timestamp ),
|
||
|
0, "TOTP code is no longer valid" );
|
||
|
|
||
|
Time::Fake->offset( $timestamp - 95 );
|
||
|
is( $t->verifyCode( 30, 2, 6, $cleartext_secret, $totp_for_timestamp ),
|
||
|
0, "TOTP code is not valid yet" );
|
||
|
|
||
|
# TOTP encryption tests
|
||
|
|
||
|
$t = TestableTotp->new( key => $key, encryptSecret => 0 );
|
||
|
Time::Fake->offset($timestamp);
|
||
|
is( $t->verifyCode( 30, 0, 6, $encrypted_secret, $totp_for_timestamp ),
|
||
|
1, "TOTP is valid with encrypted secret and encryption disabled" );
|
||
|
|
||
|
$t = TestableTotp->new( key => $key, encryptSecret => 1 );
|
||
|
Time::Fake->offset($timestamp);
|
||
|
is( $t->verifyCode( 30, 0, 6, $encrypted_secret, $totp_for_timestamp ),
|
||
|
1, "TOTP is valid with encrypted secret and encryption enabled" );
|
||
|
Time::Fake->offset($timestamp);
|
||
|
is( $t->verifyCode( 30, 0, 6, $cleartext_secret, $totp_for_timestamp ),
|
||
|
1, "TOTP is valid with cleartext secret and encryption enabled" );
|
||
|
|
||
|
# Encryption of TOTP secret, wrong key
|
||
|
$t = TestableTotp->new( key => "idunno", encryptSecret => 0 );
|
||
|
is( $t->verifyCode( 30, 0, 6, $encrypted_secret, $totp_for_timestamp ),
|
||
|
-1, "TOTP code fails to verify" );
|
||
|
|
||
|
# Do not encrypt new secrets unless we configured it
|
||
|
$t = TestableTotp->new( key => $key, encryptSecret => 0 );
|
||
|
is( $t->get_storable_secret($cleartext_secret),
|
||
|
$cleartext_secret,
|
||
|
"TOTP secret is stored as-is when encryption is disabled" );
|
||
|
|
||
|
# Encrypt new secrets if we configured it
|
||
|
$t = TestableTotp->new( key => $key, encryptSecret => 1 );
|
||
|
my $new = $t->get_storable_secret($cleartext_secret);
|
||
|
like( $new, qr/^{llngcrypt}/, "Secret looks encrypted" );
|
||
|
unlike( $new, qr/$cleartext_secret/, "Secret looks encrypted" );
|
||
|
|
||
|
Time::Fake->offset($timestamp);
|
||
|
is( $t->verifyCode( 30, 0, 6, $new, $totp_for_timestamp ),
|
||
|
1, "get_storable_secret produces working secret" );
|