ljr/wcmtools/openid/perl/Net-OpenID-Server/t/01-newproto.t

319 lines
9.3 KiB
Perl
Raw Normal View History

2019-02-05 21:49:12 +00:00
#!/usr/bin/perl
use strict;
use Test::More 'no_plan';
use Data::Dumper;
use Net::OpenID::Server;
use Crypt::DH;
use Digest::SHA1 qw(sha1 sha1_hex);
for (my $num=1; $num <= 2000; $num += 20) {
my $bi = Math::BigInt->new("$num");
my $bytes = Net::OpenID::Server::_bi2bytes($bi);
my $bi2 = Net::OpenID::Server::_bytes2bi($bytes);
is($bi,$bi2);
}
my ($query_string, %get, %post, $ctype, $content);
my $parse = sub {
%get = map { durl($_) } split(/[&=]/, $query_string);
};
my %res;
my $nos = Net::OpenID::Server->new(
get_args => \%get,
post_args => \%post,
server_secret => "o3kjn3nf9832hf32nfo32nfdo32nro32n29332",
setup_url => "http://server.com/setup.app",
);
ok($nos);
my ($secret, $ahandle);
assoc_clear();
login_success();
assoc_dh();
login_success();
login_im_fail();
login_setup_fail();
login_setup_fail2();
login_bogus_handle();
sub assoc_clear {
%get = ();
# regular associate
%post = (
"openid.mode" => "associate",
"openid.assoc_type" => "HMAC-SHA1",
);
($ctype, $content) = $nos->handle_page;
is($ctype, "text/plain");
%res = parse_reply($content);
ok($res{assoc_handle});
$ahandle = $res{'assoc_handle'};
ok($ahandle !~ /\bSTLS\./);
is($res{assoc_type}, "HMAC-SHA1");
ok(good_date($res{expiry}));
ok(good_date($res{issued}));
ok($res{mac_key});
$secret = $res{'mac_key'};
}
# DH associate
sub assoc_dh {
my $dh = Crypt::DH->new;
$dh->p("155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443");
$dh->g("2");
$dh->generate_keys;
%get = ();
%post = (
"openid.mode" => "associate",
"openid.assoc_type" => "HMAC-SHA1",
"openid.session_type" => "DH-SHA1",
"openid.dh_consumer_public" => _bi2arg($dh->pub_key),
);
($ctype, $content) = $nos->handle_page;
is($ctype, "text/plain");
%res = parse_reply($content);
ok($res{assoc_handle});
ok($res{dh_server_public});
is($res{assoc_type}, "HMAC-SHA1");
is($res{session_type}, "DH-SHA1");
ok(good_date($res{expiry}));
ok(good_date($res{issued}));
ok($res{enc_mac_key});
ok(! $res{mac_key});
my $server_pub = _arg2bi($res{'dh_server_public'});
my $dh_sec = $dh->compute_secret($server_pub);
$ahandle = $res{'assoc_handle'};
ok($ahandle !~ /\bSTLS\./);
is(length(_d64($res{'enc_mac_key'})), 20);
is(length(sha1(_bi2bytes($dh_sec))), 20);
$secret = _d64($res{'enc_mac_key'}) ^ sha1(_bi2bytes($dh_sec));
is(length($secret), 20);
}
# try to login, with success
sub login_success {
$nos->is_identity(sub { 1; });
$nos->is_trusted(sub { 1; });
$nos->get_user(sub { "brad"; });
%post = ();
%get = (
"openid.mode" => "checkid_immediate",
"openid.identity" => "http://bradfitz.com/",
"openid.return_to" => "http://trust.root/return/",
"openid.trust_root" => "http://trust.root/",
"openid.assoc_handle" => $ahandle,
);
($ctype, $content) = $nos->handle_page;
is($ctype, "redirect");
ok($content =~ s!^http://trust.root/return/\?!!);
my %rarg = map { durl($_) } split(/[\&\=]/, $content);
my $token = "";
foreach my $p (split(/,/, $rarg{'openid.signed'})) {
$token .= "$p:" . $rarg{"openid.$p"} . "\n";
}
my $good_sig = _b64(hmac_sha1($token, $secret));
ok($rarg{'openid.sig'}, $good_sig);
# and verify that check_authentication never lets this succeed
%get = ();
%post = (
"openid.mode" => "check_authentication",
);
foreach my $p ("assoc_handle", "sig", "signed", "invalidate_handle",
split(/,/, $rarg{"openid.signed"}))
{
$post{"openid.$p"} ||= $rarg{"openid.$p"};
}
($ctype, $content) = $nos->handle_page;
is($ctype, "text/plain");
%rarg = parse_reply($content);
ok($rarg{"error"} =~ /bad_handle/);
}
# try to login, with success
sub login_bogus_handle {
$nos->is_identity(sub { 1; });
$nos->is_trusted(sub { 1; });
$nos->get_user(sub { "brad"; });
%post = ();
%get = (
"openid.mode" => "checkid_immediate",
"openid.identity" => "http://bradfitz.com/",
"openid.return_to" => "http://trust.root/return/",
"openid.trust_root" => "http://trust.root/",
"openid.assoc_handle" => "GIBBERISH",
);
($ctype, $content) = $nos->handle_page;
is($ctype, "redirect");
ok($content =~ s!^http://trust.root/return/\?!!);
my %rarg = map { durl($_) } split(/[\&\=]/, $content);
is($rarg{'openid.invalidate_handle'}, "GIBBERISH");
ok($rarg{'openid.assoc_handle'} =~ /\bSTLS\./);
# try to verify it with check_authentication
%get = ();
%post = (
"openid.mode" => "check_authentication",
);
foreach my $p ("assoc_handle", "sig", "signed", "invalidate_handle",
split(/,/, $rarg{"openid.signed"}))
{
$post{"openid.$p"} ||= $rarg{"openid.$p"};
}
($ctype, $content) = $nos->handle_page;
is($ctype, "text/plain");
%rarg = parse_reply($content);
ok($rarg{"lifetime"} > 0);
is($rarg{"invalidate_handle"}, "GIBBERISH");
}
# try to login, but fail (immediately)
sub login_im_fail {
$nos->is_identity(sub { 0; });
$nos->is_trusted(sub { 1; });
$nos->get_user(sub { "brad"; });
%post = ();
%get = (
"openid.mode" => "checkid_immediate",
"openid.identity" => "http://bradfitz.com/",
"openid.return_to" => "http://trust.root/return/",
"openid.trust_root" => "http://trust.root/",
"openid.assoc_handle" => $ahandle,
);
($ctype, $content) = $nos->handle_page;
is($ctype, "redirect");
ok($content =~ s!^http://trust.root/return/\?!!);
my %rarg = map { durl($_) } split(/[\&\=]/, $content);
is($rarg{'openid.mode'}, "id_res");
ok($rarg{'openid.user_setup_url'} =~ m!setup\.app.+bradfitz!);
}
# try to login, but fail (w/ setup)
sub login_setup_fail {
$nos->is_identity(sub { 0; });
$nos->is_trusted(sub { 1; });
$nos->get_user(sub { "brad"; });
%post = ();
%get = (
"openid.mode" => "checkid_setup",
"openid.identity" => "http://bradfitz.com/",
"openid.return_to" => "http://trust.root/return/",
"openid.trust_root" => "http://trust.root/",
"openid.assoc_handle" => $ahandle,
);
($ctype, $content) = $nos->handle_page;
is($ctype, "setup");
ok(ref $content eq "HASH");
}
# try to login, but fail (w/ setup redirect)
sub login_setup_fail2 {
$nos->is_identity(sub { 0; });
$nos->is_trusted(sub { 1; });
$nos->get_user(sub { "brad"; });
%post = ();
%get = (
"openid.mode" => "checkid_setup",
"openid.identity" => "http://bradfitz.com/",
"openid.return_to" => "http://trust.root/return/",
"openid.trust_root" => "http://trust.root/",
"openid.assoc_handle" => $ahandle,
);
($ctype, $content) = $nos->handle_page(redirect_for_setup => 1);
is($ctype, "redirect");
ok($content =~ m!^http://.+setup\.app\?!);
}
sub good_date {
return $_[0] =~ /^(\d{4,4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/;
}
sub parse_reply {
my $reply = shift;
my %ret;
foreach (split /\n/, $reply) {
next unless /^(\S+?):(.+)/;
$ret{$1} = $2;
}
return %ret;
}
sub durl
{
my ($a) = @_;
$a =~ tr/+/ /;
$a =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
return $a;
}
sub _bi2bytes {
my $bigint = shift;
die "Can't deal with negative numbers" if $bigint->is_negative;
my $bits = $bigint->as_bin;
die unless $bits =~ s/^0b//;
# prepend zeros to round to byte boundary, or to unset high bit
my $prepend = (8 - length($bits) % 8) || ($bits =~ /^1/ ? 8 : 0);
$bits = ("0" x $prepend) . $bits if $prepend;
return pack("B*", $bits);
}
sub _bi2arg {
my $b64 = MIME::Base64::encode_base64(_bi2bytes($_[0]));
$b64 =~ s/\s+//g;
return $b64;
}
sub _b64 {
my $val = MIME::Base64::encode_base64($_[0]);
$val =~ s/\s+//g;
return $val;
}
sub _d64 {
return MIME::Base64::decode_base64($_[0]);
}
sub _bytes2bi {
return Math::BigInt->new("0b" . unpack("B*", $_[0]));
}
sub _arg2bi {
return undef unless defined $_[0] and $_[0] ne "";
# don't acccept base-64 encoded numbers over 700 bytes. which means
# those over 4200 bits.
return Math::BigInt->new("0") if length($_[0]) > 700;
return _bytes2bi(MIME::Base64::decode_base64($_[0]));
}
# From Digest::HMAC
sub hmac_sha1_hex {
unpack("H*", &hmac_sha1);
}
sub hmac_sha1 {
hmac($_[0], $_[1], \&sha1, 64);
}
sub hmac {
my($data, $key, $hash_func, $block_size) = @_;
$block_size ||= 64;
$key = &$hash_func($key) if length($key) > $block_size;
my $k_ipad = $key ^ (chr(0x36) x $block_size);
my $k_opad = $key ^ (chr(0x5c) x $block_size);
&$hash_func($k_opad, &$hash_func($k_ipad, $data));
}