319 lines
9.3 KiB
Perl
Executable File
319 lines
9.3 KiB
Perl
Executable File
#!/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));
|
|
}
|