This commit is contained in:
2019-02-06 00:49:12 +03:00
commit 8dbb1bb605
4796 changed files with 506072 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
0.09:
* version 1.1 of the protocol, with 1.0 as a "compat" option
(where both 1.0 and 1.1 response keys are sent) compat is either
on, off, or unspecified, in which case it's on by default for
one month
0.08:
* security fix, as pointed out by meepbear: check_authentication
shouldn't honor signature verification requests using
assoc_handles that were given out in associate requests. that
means that we must be able to distinguish (internally) handles
that were given out to "dumb" consumbers (stateless) vs. ones we
gave out in associate requests.
for more information, see:
http://lists.danga.com/pipermail/yadis/2005-July/001144.html
0.07:
* openid.mode=cancel support
* invalidate_handle support
* fix a call to error_page that should've been _error_page
* _secret_of_handle now only takes an assoc_handle, not
also an assoc_type, as an assoc_handle should always
self-imply its type
0.06:
* make rand_chars public
* remove old DSA-based code
* test suite for new DH/HMAC-based code
0.05:
* start implementing the new DH + HMAC-SHA1 spec, instead
of being DSA-based. The DSA code is still working for now,
and it'll do either protocol, but it'll be removed in time.
0.04:
* add "signed_return" method and docs
* require Convert::PEM 0.07, which was always required,
but I forgot its version number before
* add "redirect_for_setup" option on handle_page and docs
0.03:
* stupid push_url_arg bugfix
* more tests
0.02:
* checkid_immediate vs checkid_setup mode (handle_page can return
$type of "setup")
0.01:
* initial release. test suite works. no example app yet.
* requires Crypt::DSA or Crypt::OpenSSL::DSA

View File

@@ -0,0 +1,7 @@
Makefile.PL
ChangeLog
MANIFEST
lib/Net/OpenID/Server.pm
t/00-use.t
t/01-newproto.t
META.yml Module meta-data (added by MakeMaker)

View File

@@ -0,0 +1,13 @@
use ExtUtils::MakeMaker;
WriteMakefile( 'NAME' => 'Net::OpenID::Server',
'VERSION_FROM' => 'lib/Net/OpenID/Server.pm',
'PREREQ_PM' => {
'URI' => 0,
'MIME::Base64' => 0,
'Digest::SHA1' => 0,
'Crypt::DH' => 0.05,
},
($] >= 5.005 ?
(ABSTRACT_FROM => 'lib/Net/OpenID/Server.pm',
AUTHOR => 'Brad Fitzpatrick <brad@danga.com>') : ()),
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
#!/usr/bin/perl
use strict;
use Test::More 'no_plan';
use Data::Dumper;
use Net::OpenID::Server;
use Crypt::OpenSSL::DSA;
use Math::BigInt;
for my $num (1..1080) {
my $bi = Math::BigInt->new("$num");
my $bytes = Net::OpenID::Server::_bi2bytes($bi);
my $bi2 = Net::OpenID::Server::_bytes2bi($bytes);
is($bi,$bi2);
printf "$bi = $bi2\n";
}
exit 0;
my ($query_string, %get_vars, $ctype, $content);
my $parse = sub {
%get_vars = map { durl($_) } split(/[&=]/, $query_string);
};
my $pub_key_file = "test.openid_public.key";
my $priv_key_file = "test.openid_private.key";
my $nos = Net::OpenID::Server->new(
args => \%get_vars,
public_key => $pub_key_file,
private_key => $priv_key_file,
);
ok($nos);
# generate a key
my $dsa = Crypt::OpenSSL::DSA->generate_parameters( 512 );
$dsa->generate_key;
print "done.\n";
$dsa->write_pub_key($pub_key_file);
$dsa->write_priv_key($priv_key_file);
my $read_pub_key = sub {
open (F, $pub_key_file);
my $content = do { local $/; <F>; };
close F;
return $content;
};
my $read_priv_key = sub {
open (F, $priv_key_file);
my $content = do { local $/; <F>; };
close F;
return $content;
};
# see if we get our public key back
$query_string = "openid.mode=getpubkey";
$parse->();
$nos->private_key("BOGUS");
for (1..3) {
$nos->public_key($pub_key_file) if $_ == 1;
$nos->public_key($read_pub_key) if $_ == 2;
$nos->public_key($read_pub_key->()) if $_ == 3;
($ctype, $content) = $nos->handle_page;
ok($ctype eq "text/plain");
ok($content =~ /\-\-\-BEGIN/ && $content =~ /\-\-\-END/);
}
# see if we get a user_setup_url vs. signature
$query_string = "openid.is_identity=http://bradfitz.com/&openid.return_to=http://return.example.com/%3Ffoo%3Dbar";
$parse->();
$nos->get_user(sub { return "brad"; });
$nos->is_identity(sub {
my ($u, $url) = @_;
return $u eq "brad" && $url eq "http://bradfitz.com/";
});
# first an untrusted case:
$nos->is_trusted(sub { 0; });
$nos->setup_url("http://setup.example.com/?set1=set2");
($ctype, $content) = $nos->handle_page or die $nos->err;
ok($ctype eq "redirect");
ok($content =~ m!user_setup_url=http://setup\.example\.com!);
ok($content =~ m!return\.example\.com/\?foo=bar\&open!);
# now a trusted case, but with bogus private key:
$nos->is_trusted(sub { 1; });
$nos->private_key("BOGUS");
($ctype, $content) = $nos->handle_page;
ok(! $ctype);
$nos->private_key($priv_key_file);
($ctype, $content) = $nos->handle_page;
ok($ctype eq "redirect");
ok($content =~ m!return\.example\.com/\?foo=bar\&open!);
ok($content =~ m!\&openid\.sig=M!);
$nos->private_key($read_priv_key);
($ctype, $content) = $nos->handle_page;
ok($ctype eq "redirect");
ok($content =~ m!return\.example\.com/\?foo=bar\&open!);
ok($content =~ m!\&openid\.sig=M!);
# checking two types of failure cases
$nos->setup_url("http://setup.example.com/");
$nos->is_trusted(sub { 0; });
# immediate mode:
$query_string = "openid.mode=checkid_immediate&openid.is_identity=http://bradfitz.com/&openid.return_to=http://return.example.com/%3Ffoo%3Dbar";
$parse->();
($ctype, $content) = $nos->handle_page;
ok($ctype eq "redirect");
# setup mode:
$query_string = "openid.mode=checkid_setup&openid.is_identity=http://bradfitz.com/&openid.return_to=http://return.example.com/%3Ffoo%3Dbar";
$parse->();
($ctype, $content) = $nos->handle_page;
ok($ctype eq "setup");
ok($content->{return_to} eq "http://return.example.com/?foo=bar");
sub durl
{
my ($a) = @_;
$a =~ tr/+/ /;
$a =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
return $a;
}

View File

@@ -0,0 +1,318 @@
#!/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));
}