| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # LICENSE: You're free to distribute this under the same terms as Perl itself. | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 161574 | use strict; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 111 |  | 
| 4 | 1 |  |  | 1 |  | 7 | use Carp (); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 23 |  | 
| 5 | 1 |  |  | 1 |  | 870 | use Net::OpenID::Common; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | use Net::OpenID::IndirectMessage; | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | ############################################################################ | 
| 9 |  |  |  |  |  |  | package Net::OpenID::Server; | 
| 10 |  |  |  |  |  |  | BEGIN { | 
| 11 |  |  |  |  |  |  | $Net::OpenID::Server::VERSION = '1.09'; | 
| 12 |  |  |  |  |  |  | } | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | use fields ( | 
| 15 |  |  |  |  |  |  | 'last_errcode',   # last error code we got | 
| 16 |  |  |  |  |  |  | 'last_errtext',   # last error code we got | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | 'get_user',        # subref returning a defined value representing the logged in user, or undef if no user. | 
| 19 |  |  |  |  |  |  | # this return value ($u) is passed to the other subrefs | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | 'get_identity',    # subref given a ( $u, $identity_url). | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | 'is_identity',     # subref given a ($u, $identity_url).  should return true if $u owns the URL | 
| 24 |  |  |  |  |  |  | # tree given by $identity_url.  not that $u may be undef, if get_user returned undef. | 
| 25 |  |  |  |  |  |  | # it's up to you if you immediately return 0 on $u or do some work to make the | 
| 26 |  |  |  |  |  |  | # timing be approximately equal, so you don't reveal if somebody's logged in or not | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | 'is_trusted',      # subref given a ($u, $trust_root, $is_identity).  should return true if $u wants $trust_root | 
| 29 |  |  |  |  |  |  | # to know about their identity.  if you don't care about timing attacks, you can | 
| 30 |  |  |  |  |  |  | # immediately return 0 if ! $is_identity, as the entire case can't succeed | 
| 31 |  |  |  |  |  |  | # unless both is_identity and is_trusted pass, and is_identity is called first. | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | 'handle_request',  # callback to handle a request. If present, get_user, get_identity, is_identity and is_trusted | 
| 34 |  |  |  |  |  |  | # are all ignored and this single callback is used to replace all of them. | 
| 35 |  |  |  |  |  |  | 'endpoint_url', | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | 'setup_url',       # setup URL base (optionally with query parameters) where users should go | 
| 38 |  |  |  |  |  |  | # to login/setup trust/etc. | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | 'setup_map',       # optional hashref mapping some/all standard keys that would be added to | 
| 41 |  |  |  |  |  |  | # setup_url to your preferred names. | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | 'args',            # thing to get args | 
| 44 |  |  |  |  |  |  | 'message',         # current IndirectMessage object | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | 'server_secret',    # subref returning secret given $time | 
| 47 |  |  |  |  |  |  | 'secret_gen_interval', | 
| 48 |  |  |  |  |  |  | 'secret_expire_age', | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | 'compat',          # version 1.0 compatibility flag (otherwise only sends 1.1 parameters) | 
| 51 |  |  |  |  |  |  | ); | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | use Carp; | 
| 54 |  |  |  |  |  |  | use URI; | 
| 55 |  |  |  |  |  |  | use MIME::Base64 (); | 
| 56 |  |  |  |  |  |  | use Digest::SHA qw(sha1 sha1_hex sha256 sha256_hex hmac_sha1 hmac_sha1_hex hmac_sha256 hmac_sha256_hex); | 
| 57 |  |  |  |  |  |  | use Time::Local qw(timegm); | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | my $OPENID2_NS = qq!http://specs.openid.net/auth/2.0!; | 
| 60 |  |  |  |  |  |  | my $OPENID2_ID_SELECT = qq!http://specs.openid.net/auth/2.0/identifier_select!; | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | sub new { | 
| 63 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 64 |  |  |  |  |  |  | $self = fields::new( $self ) unless ref $self; | 
| 65 |  |  |  |  |  |  | my %opts = @_; | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | $self->{last_errcode} = undef; | 
| 68 |  |  |  |  |  |  | $self->{last_errtext} = undef; | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | if (exists $opts{get_args}) { | 
| 71 |  |  |  |  |  |  | carp "Option 'get_args' is deprecated, use 'args' instead"; | 
| 72 |  |  |  |  |  |  | $self->args(delete $opts{get_args}); | 
| 73 |  |  |  |  |  |  | } | 
| 74 |  |  |  |  |  |  | if (exists $opts{post_args}) { | 
| 75 |  |  |  |  |  |  | carp "Option 'post_args' is deprecated, use 'args' instead"; | 
| 76 |  |  |  |  |  |  | $self->args(delete $opts{post_args}); | 
| 77 |  |  |  |  |  |  | } | 
| 78 |  |  |  |  |  |  | $self->args(delete $opts{args}); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | $opts{'secret_gen_interval'} ||= 86400; | 
| 81 |  |  |  |  |  |  | $opts{'secret_expire_age'}   ||= 86400 * 14; | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | $opts{'get_identity'} ||= sub { $_[1] }; | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | # use compatibility mode until 30 days from July 10, 2005 | 
| 86 |  |  |  |  |  |  | unless (defined $opts{'compat'}) { | 
| 87 |  |  |  |  |  |  | $opts{'compat'} = time() < 1121052339 + 86400*30 ? 1 : 0; | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | $self->$_(delete $opts{$_}) | 
| 91 |  |  |  |  |  |  | foreach (qw( | 
| 92 |  |  |  |  |  |  | get_user get_identity is_identity is_trusted handle_request | 
| 93 |  |  |  |  |  |  | endpoint_url setup_url setup_map server_secret | 
| 94 |  |  |  |  |  |  | secret_gen_interval secret_expire_age | 
| 95 |  |  |  |  |  |  | compat | 
| 96 |  |  |  |  |  |  | )); | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; | 
| 99 |  |  |  |  |  |  | return $self; | 
| 100 |  |  |  |  |  |  | } | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | sub get_user     { &_getsetcode; } | 
| 103 |  |  |  |  |  |  | sub get_identity { &_getsetcode; } | 
| 104 |  |  |  |  |  |  | sub is_identity  { &_getsetcode; } | 
| 105 |  |  |  |  |  |  | sub is_trusted   { &_getsetcode; } | 
| 106 |  |  |  |  |  |  | sub handle_request { &_getsetcode; } | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | sub endpoint_url { &_getset; } | 
| 109 |  |  |  |  |  |  | sub setup_url    { &_getset; } | 
| 110 |  |  |  |  |  |  | sub setup_map    { &_getset; } | 
| 111 |  |  |  |  |  |  | sub compat       { &_getset; } | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | sub server_secret       { &_getset; } | 
| 114 |  |  |  |  |  |  | sub secret_gen_interval { &_getset; } | 
| 115 |  |  |  |  |  |  | sub secret_expire_age   { &_getset; } | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  | # returns ($content_type, $page), where $content_type can be "redirect" | 
| 119 |  |  |  |  |  |  | # in which case a temporary redirect should be done to the URL in $page | 
| 120 |  |  |  |  |  |  | # $content_type can also be "setup", in which case the setup_map variables | 
| 121 |  |  |  |  |  |  | # are in $page as a hashref, and caller has full control from there. | 
| 122 |  |  |  |  |  |  | # | 
| 123 |  |  |  |  |  |  | # returns undef on error, in which case caller should generate an error | 
| 124 |  |  |  |  |  |  | # page using info in $nos->err. | 
| 125 |  |  |  |  |  |  | sub handle_page { | 
| 126 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 127 |  |  |  |  |  |  | my %opts = @_; | 
| 128 |  |  |  |  |  |  | my $redirect_for_setup = delete $opts{'redirect_for_setup'}; | 
| 129 |  |  |  |  |  |  | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; | 
| 130 |  |  |  |  |  |  | Carp::croak("handle_page must be called in list context") unless wantarray; | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | my $mode = $self->_message_mode; | 
| 133 |  |  |  |  |  |  |  | 
| 134 |  |  |  |  |  |  | return $self->_mode_associate | 
| 135 |  |  |  |  |  |  | if $self->_message_mode eq "associate"; | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | return $self->_mode_check_authentication | 
| 138 |  |  |  |  |  |  | if $self->_message_mode eq "check_authentication"; | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | unless ($mode) { | 
| 141 |  |  |  |  |  |  | return ("text/html", | 
| 142 |  |  |  |  |  |  | "OpenID EndpointThis is an OpenID server endpoint, not a human-readable resource.  For more information, see http://openid.net/."); | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | return $self->_error_page("Unknown mode") | 
| 146 |  |  |  |  |  |  | unless $mode =~ /^checkid_(?:immediate|setup)/; | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | return $self->_mode_checkid($mode, $redirect_for_setup); | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | # given something that can have GET arguments, returns a subref to get them: | 
| 152 |  |  |  |  |  |  | #   Apache | 
| 153 |  |  |  |  |  |  | #   Apache::Request | 
| 154 |  |  |  |  |  |  | #   CGI | 
| 155 |  |  |  |  |  |  | #   HASH of get args | 
| 156 |  |  |  |  |  |  | #   CODE returning get arg, given key | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | #   ... | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | sub args { | 
| 161 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | if (my $what = shift) { | 
| 164 |  |  |  |  |  |  | unless (ref $what) { | 
| 165 |  |  |  |  |  |  | return $self->{args} ? $self->{args}->($what) : Carp::croak("No args defined"); | 
| 166 |  |  |  |  |  |  | } | 
| 167 |  |  |  |  |  |  | else { | 
| 168 |  |  |  |  |  |  | Carp::croak("Too many parameters") if @_; | 
| 169 |  |  |  |  |  |  | my $message = Net::OpenID::IndirectMessage->new($what, ( | 
| 170 |  |  |  |  |  |  | minimum_version => $self->minimum_version, | 
| 171 |  |  |  |  |  |  | )); | 
| 172 |  |  |  |  |  |  | $self->{message} = $message; | 
| 173 |  |  |  |  |  |  | $self->{args} = $message ? $message->getter : sub { undef }; | 
| 174 |  |  |  |  |  |  | } | 
| 175 |  |  |  |  |  |  | } | 
| 176 |  |  |  |  |  |  | $self->{args}; | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | sub message { | 
| 180 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 181 |  |  |  |  |  |  | if (my $key = shift) { | 
| 182 |  |  |  |  |  |  | return $self->{message} ? $self->{message}->get($key) : undef; | 
| 183 |  |  |  |  |  |  | } | 
| 184 |  |  |  |  |  |  | else { | 
| 185 |  |  |  |  |  |  | return $self->{message}; | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | sub minimum_version { | 
| 190 |  |  |  |  |  |  | # TODO: Make this configurable | 
| 191 |  |  |  |  |  |  | 1; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | sub _message_mode { | 
| 195 |  |  |  |  |  |  | my $message = $_[0]->message; | 
| 196 |  |  |  |  |  |  | return $message ? $message->mode : undef; | 
| 197 |  |  |  |  |  |  | } | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | sub _message_version { | 
| 200 |  |  |  |  |  |  | my $message = $_[0]->message; | 
| 201 |  |  |  |  |  |  | return $message ? $message->protocol_version : undef; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | sub cancel_return_url { | 
| 205 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | my %opts = @_; | 
| 208 |  |  |  |  |  |  | my $return_to = delete $opts{'return_to'}; | 
| 209 |  |  |  |  |  |  | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | my $ret_url = $return_to; | 
| 212 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$ret_url, "openid.mode" => "cancel"); | 
| 213 |  |  |  |  |  |  | return $ret_url; | 
| 214 |  |  |  |  |  |  | } | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | sub signed_return_url { | 
| 217 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 218 |  |  |  |  |  |  | my %opts = @_; | 
| 219 |  |  |  |  |  |  | my $identity     = delete $opts{'identity'}; | 
| 220 |  |  |  |  |  |  | my $claimed_id   = delete $opts{'claimed_id'}; | 
| 221 |  |  |  |  |  |  | my $return_to    = delete $opts{'return_to'}; | 
| 222 |  |  |  |  |  |  | my $assoc_handle = delete $opts{'assoc_handle'}; | 
| 223 |  |  |  |  |  |  | my $assoc_type   = delete $opts{'assoc_type'} || 'HMAC-SHA1'; | 
| 224 |  |  |  |  |  |  | my $ns           = delete $opts{'ns'}; | 
| 225 |  |  |  |  |  |  | my $extra_fields = delete $opts{'additional_fields'} || {}; | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | # verify the trust_root and realm, if provided | 
| 228 |  |  |  |  |  |  | if (my $realm = delete $opts{'realm'}) { | 
| 229 |  |  |  |  |  |  | return undef unless _url_is_under($realm, $return_to); | 
| 230 |  |  |  |  |  |  | delete $opts{'trust_root'}; | 
| 231 |  |  |  |  |  |  | } elsif (my $trust_root = delete $opts{'trust_root'}) { | 
| 232 |  |  |  |  |  |  | return undef unless _url_is_under($trust_root, $return_to); | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | my $ret_url = $return_to; | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | my $c_sec; | 
| 239 |  |  |  |  |  |  | my $invalid_handle; | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | if ($assoc_handle) { | 
| 242 |  |  |  |  |  |  | $c_sec = $self->_secret_of_handle($assoc_handle, type=>$assoc_type); | 
| 243 |  |  |  |  |  |  |  | 
| 244 |  |  |  |  |  |  | # tell the consumer that their provided handle is bogus | 
| 245 |  |  |  |  |  |  | # (or we forgot it) and that they should stop using it | 
| 246 |  |  |  |  |  |  | $invalid_handle = $assoc_handle unless $c_sec; | 
| 247 |  |  |  |  |  |  | } | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | unless ($c_sec) { | 
| 250 |  |  |  |  |  |  | # dumb consumer mode | 
| 251 |  |  |  |  |  |  | ($assoc_handle, $c_sec, undef) = $self->_generate_association(type => $assoc_type, | 
| 252 |  |  |  |  |  |  | dumb => 1); | 
| 253 |  |  |  |  |  |  | } | 
| 254 |  |  |  |  |  |  |  | 
| 255 |  |  |  |  |  |  | $claimed_id ||= $identity; | 
| 256 |  |  |  |  |  |  | $claimed_id = $identity if $claimed_id eq $OPENID2_ID_SELECT; | 
| 257 |  |  |  |  |  |  | my @sign = qw(mode claimed_id identity op_endpoint return_to response_nonce assoc_handle assoc_type); | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | my $now = time(); | 
| 260 |  |  |  |  |  |  | my %arg = ( | 
| 261 |  |  |  |  |  |  | mode           => "id_res", | 
| 262 |  |  |  |  |  |  | identity       => $identity, | 
| 263 |  |  |  |  |  |  | claimed_id     => $claimed_id, | 
| 264 |  |  |  |  |  |  | return_to      => $return_to, | 
| 265 |  |  |  |  |  |  | assoc_handle   => $assoc_handle, | 
| 266 |  |  |  |  |  |  | assoc_type     => $assoc_type, | 
| 267 |  |  |  |  |  |  | response_nonce => OpenID::util::time_to_w3c($now) . _rand_chars(6), | 
| 268 |  |  |  |  |  |  | ); | 
| 269 |  |  |  |  |  |  | $arg{'op_endpoint'} = $self->endpoint_url if $self->endpoint_url && $ns eq $OPENID2_NS; | 
| 270 |  |  |  |  |  |  | $arg{'ns'} = $ns if $ns; | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | # compatibility mode with version 1.0 of the protocol which still | 
| 273 |  |  |  |  |  |  | # had absolute dates | 
| 274 |  |  |  |  |  |  | if ($self->{compat}) { | 
| 275 |  |  |  |  |  |  | $arg{issued}   = OpenID::util::time_to_w3c($now); | 
| 276 |  |  |  |  |  |  | $arg{valid_to} = OpenID::util::time_to_w3c($now + 3600); | 
| 277 |  |  |  |  |  |  | push @sign, "issued", "valid_to"; | 
| 278 |  |  |  |  |  |  | } | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | # add in the additional fields | 
| 281 |  |  |  |  |  |  | foreach my $k (keys %{ $extra_fields }) { | 
| 282 |  |  |  |  |  |  | die "Invalid extra field: $k" unless | 
| 283 |  |  |  |  |  |  | $k =~ /^\w+\./; | 
| 284 |  |  |  |  |  |  | $arg{$k} = $extra_fields->{$k}; | 
| 285 |  |  |  |  |  |  | push @sign, $k; | 
| 286 |  |  |  |  |  |  | } | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | # since signing of empty fields is not well defined, | 
| 289 |  |  |  |  |  |  | # remove such fields from the list of fields to be signed | 
| 290 |  |  |  |  |  |  | @sign = grep { defined $arg{$_} && $arg{$_} ne '' } @sign; | 
| 291 |  |  |  |  |  |  | $arg{signed} = join(",", @sign); | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  | my @arg; # arguments we'll append to the URL | 
| 294 |  |  |  |  |  |  | my $token_contents = ""; | 
| 295 |  |  |  |  |  |  | foreach my $f (@sign) { | 
| 296 |  |  |  |  |  |  | $token_contents .= "$f:$arg{$f}\n"; | 
| 297 |  |  |  |  |  |  | push @arg, "openid.$f" => $arg{$f}; | 
| 298 |  |  |  |  |  |  | delete $arg{$f}; | 
| 299 |  |  |  |  |  |  | } | 
| 300 |  |  |  |  |  |  |  | 
| 301 |  |  |  |  |  |  | # include the arguments we didn't sign in the URL | 
| 302 |  |  |  |  |  |  | push @arg, map { ( "openid.$_" => $arg{$_} ) } sort keys %arg; | 
| 303 |  |  |  |  |  |  |  | 
| 304 |  |  |  |  |  |  | # include (unsigned) the handle we're telling the consumer to invalidate | 
| 305 |  |  |  |  |  |  | if ($invalid_handle) { | 
| 306 |  |  |  |  |  |  | push @arg, "openid.invalidate_handle" => $invalid_handle; | 
| 307 |  |  |  |  |  |  | } | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | # finally include the signature | 
| 310 |  |  |  |  |  |  | if ($assoc_type eq 'HMAC-SHA1') { | 
| 311 |  |  |  |  |  |  | push @arg, "openid.sig" => OpenID::util::b64(hmac_sha1($token_contents, $c_sec)); | 
| 312 |  |  |  |  |  |  | } | 
| 313 |  |  |  |  |  |  | elsif ($assoc_type eq 'HMAC-SHA256') { | 
| 314 |  |  |  |  |  |  | push @arg, "openid.sig" => OpenID::util::b64(hmac_sha256($token_contents, $c_sec)); | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  | else { | 
| 317 |  |  |  |  |  |  | die "Unknown assoc_type $assoc_type"; | 
| 318 |  |  |  |  |  |  | } | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$ret_url, @arg); | 
| 321 |  |  |  |  |  |  | return $ret_url; | 
| 322 |  |  |  |  |  |  | } | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | sub _mode_checkid { | 
| 325 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 326 |  |  |  |  |  |  | my ($mode, $redirect_for_setup) = @_; | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | my $return_to = $self->args("openid.return_to"); | 
| 329 |  |  |  |  |  |  | return $self->_fail("no_return_to") unless $return_to =~ m!^https?://!; | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | my $trust_root = $self->args("openid.trust_root") || $return_to; | 
| 332 |  |  |  |  |  |  | $trust_root = $self->args("openid.realm") if $self->args('openid.ns') eq $OPENID2_NS; | 
| 333 |  |  |  |  |  |  | return $self->_fail("invalid_trust_root") unless _url_is_under($trust_root, $return_to); | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | my $identity = $self->args("openid.identity"); | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | # chop off the query string, in case our trust_root came from the return_to URL | 
| 338 |  |  |  |  |  |  | $trust_root =~ s/\?.*//; | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | my $is_identity = 0; | 
| 341 |  |  |  |  |  |  | my $is_trusted = 0; | 
| 342 |  |  |  |  |  |  | if (0 && $self->{handle_request}) { | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  | else { | 
| 347 |  |  |  |  |  |  | my $u = $self->_proxy("get_user"); | 
| 348 |  |  |  |  |  |  | if ( $self->args('openid.ns') eq $OPENID2_NS && $identity eq $OPENID2_ID_SELECT ) { | 
| 349 |  |  |  |  |  |  | $identity = $self->_proxy("get_identity",  $u, $identity ); | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  | $is_identity = $self->_proxy("is_identity", $u, $identity); | 
| 352 |  |  |  |  |  |  | $is_trusted  = $self->_proxy("is_trusted",  $u, $trust_root, $is_identity); | 
| 353 |  |  |  |  |  |  | } | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | # assertion path: | 
| 356 |  |  |  |  |  |  | if ($is_identity && $is_trusted) { | 
| 357 |  |  |  |  |  |  | my $ret_url = $self->signed_return_url( | 
| 358 |  |  |  |  |  |  | identity => $identity, | 
| 359 |  |  |  |  |  |  | claimed_id => $self->args('openid.claimed_id'), | 
| 360 |  |  |  |  |  |  | return_to => $return_to, | 
| 361 |  |  |  |  |  |  | assoc_handle => $self->args("openid.assoc_handle"), | 
| 362 |  |  |  |  |  |  | assoc_type => $self->args("openid.assoc_type"), | 
| 363 |  |  |  |  |  |  | ns => $self->args('openid.ns'), | 
| 364 |  |  |  |  |  |  | ); | 
| 365 |  |  |  |  |  |  | return ("redirect", $ret_url); | 
| 366 |  |  |  |  |  |  | } | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | # assertion could not be made, so user requires setup (login/trust.. something) | 
| 369 |  |  |  |  |  |  | # two ways that can happen:  caller might have asked us for an immediate return | 
| 370 |  |  |  |  |  |  | # with a setup URL (the default), or explictly said that we're in control of | 
| 371 |  |  |  |  |  |  | # the user-agent's full window, and we can do whatever we want with them now. | 
| 372 |  |  |  |  |  |  | my %setup_args = ( | 
| 373 |  |  |  |  |  |  | $self->_setup_map("trust_root"),   $trust_root, | 
| 374 |  |  |  |  |  |  | $self->_setup_map("realm"),        $trust_root, | 
| 375 |  |  |  |  |  |  | $self->_setup_map("return_to"),    $return_to, | 
| 376 |  |  |  |  |  |  | $self->_setup_map("identity"),     $identity, | 
| 377 |  |  |  |  |  |  | ); | 
| 378 |  |  |  |  |  |  | $setup_args{$self->_setup_map('ns')} = $self->args('openid.ns') if $self->args('openid.ns'); | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | if ( $self->args("openid.assoc_handle") ) { | 
| 381 |  |  |  |  |  |  | $setup_args{ $self->_setup_map("assoc_handle") } = | 
| 382 |  |  |  |  |  |  | $self->args("openid.assoc_handle"); | 
| 383 |  |  |  |  |  |  | $setup_args{ $self->_setup_map("assoc_type") } = | 
| 384 |  |  |  |  |  |  | $self->_determine_assoc_type_from_assoc_handle( | 
| 385 |  |  |  |  |  |  | $self->args("openid.assoc_handle") ); | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | my $setup_url = $self->{setup_url} or Carp::croak("No setup_url defined."); | 
| 389 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$setup_url, %setup_args); | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | if ($mode eq "checkid_immediate") { | 
| 392 |  |  |  |  |  |  | my $ret_url = $return_to; | 
| 393 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$setup_url, 'openid.mode'=>'checkid_setup'); | 
| 394 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$setup_url, 'openid.claimed_id'=>$identity); | 
| 395 |  |  |  |  |  |  | if ($self->args('openid.ns') eq $OPENID2_NS) { | 
| 396 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$ret_url, "openid.ns",             $self->args('openid.ns')); | 
| 397 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$ret_url, "openid.mode",           "setup_needed"); | 
| 398 |  |  |  |  |  |  | } else { | 
| 399 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$ret_url, "openid.mode",           "id_res"); | 
| 400 |  |  |  |  |  |  | } | 
| 401 |  |  |  |  |  |  | # We send this even in the 2.0 case -- despite what the spec says -- | 
| 402 |  |  |  |  |  |  | # because several consumer implementations, including Net::OpenID::Consumer | 
| 403 |  |  |  |  |  |  | # at this time, depend on it. | 
| 404 |  |  |  |  |  |  | OpenID::util::push_url_arg(\$ret_url, "openid.user_setup_url", $setup_url); | 
| 405 |  |  |  |  |  |  | return ("redirect", $ret_url); | 
| 406 |  |  |  |  |  |  | } else { | 
| 407 |  |  |  |  |  |  | # the "checkid_setup" mode, where we take control of the user-agent | 
| 408 |  |  |  |  |  |  | # and return to their return_to URL later. | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | if ($redirect_for_setup) { | 
| 411 |  |  |  |  |  |  | return ("redirect", $setup_url); | 
| 412 |  |  |  |  |  |  | } else { | 
| 413 |  |  |  |  |  |  | return ("setup", \%setup_args); | 
| 414 |  |  |  |  |  |  | } | 
| 415 |  |  |  |  |  |  | } | 
| 416 |  |  |  |  |  |  | } | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | sub _determine_assoc_type_from_assoc_handle { | 
| 419 |  |  |  |  |  |  | my ($self, $assoc_handle)=@_; | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | my $assoc_type=$self->args("openid.assoc_type"); | 
| 422 |  |  |  |  |  |  | return $assoc_type if ($assoc_type); # set? Just return it. | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | if ($assoc_handle) { | 
| 425 |  |  |  |  |  |  | my (undef, undef, $hmac_part)=split /:/, $assoc_handle, 3; | 
| 426 |  |  |  |  |  |  | my $len=length($hmac_part); # see _generate_association | 
| 427 |  |  |  |  |  |  | if ($len==16) { | 
| 428 |  |  |  |  |  |  | $assoc_type='HMAC-SHA256'; | 
| 429 |  |  |  |  |  |  | } | 
| 430 |  |  |  |  |  |  | elsif ($len==10) { | 
| 431 |  |  |  |  |  |  | $assoc_type='HMAC-SHA1'; | 
| 432 |  |  |  |  |  |  | } | 
| 433 |  |  |  |  |  |  | } | 
| 434 |  |  |  |  |  |  |  | 
| 435 |  |  |  |  |  |  | return $assoc_type; | 
| 436 |  |  |  |  |  |  | } | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  | sub _setup_map { | 
| 439 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 440 |  |  |  |  |  |  | my $key = shift; | 
| 441 |  |  |  |  |  |  | Carp::croak("Too many parameters") if @_; | 
| 442 |  |  |  |  |  |  | return $key unless ref $self->{setup_map} eq "HASH" && $self->{setup_map}{$key}; | 
| 443 |  |  |  |  |  |  | return $self->{setup_map}{$key}; | 
| 444 |  |  |  |  |  |  | } | 
| 445 |  |  |  |  |  |  |  | 
| 446 |  |  |  |  |  |  | sub _proxy { | 
| 447 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 448 |  |  |  |  |  |  | my $meth = shift; | 
| 449 |  |  |  |  |  |  |  | 
| 450 |  |  |  |  |  |  | my $getter = $self->{$meth}; | 
| 451 |  |  |  |  |  |  | Carp::croak("You haven't defined a subref for '$meth'") | 
| 452 |  |  |  |  |  |  | unless ref $getter eq "CODE"; | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | return $getter->(@_); | 
| 455 |  |  |  |  |  |  | } | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | sub _get_server_secret { | 
| 458 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 459 |  |  |  |  |  |  | my $time = shift; | 
| 460 |  |  |  |  |  |  |  | 
| 461 |  |  |  |  |  |  | my $ss; | 
| 462 |  |  |  |  |  |  | if (ref $self->{server_secret} eq "CODE") { | 
| 463 |  |  |  |  |  |  | $ss = $self->{server_secret}; | 
| 464 |  |  |  |  |  |  | } elsif ($self->{server_secret}) { | 
| 465 |  |  |  |  |  |  | $ss = sub { return $self->{server_secret}; }; | 
| 466 |  |  |  |  |  |  | } else { | 
| 467 |  |  |  |  |  |  | Carp::croak("You haven't defined a server_secret value or subref defined.\n"); | 
| 468 |  |  |  |  |  |  | } | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | my $sec = $ss->($time); | 
| 471 |  |  |  |  |  |  | Carp::croak("Server secret too long") if length($sec) > 255; | 
| 472 |  |  |  |  |  |  | return $sec; | 
| 473 |  |  |  |  |  |  | } | 
| 474 |  |  |  |  |  |  |  | 
| 475 |  |  |  |  |  |  | # returns ($assoc_handle, $secret, $expires) | 
| 476 |  |  |  |  |  |  | sub _generate_association { | 
| 477 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 478 |  |  |  |  |  |  | my %opts = @_; | 
| 479 |  |  |  |  |  |  | my $type = delete $opts{type}; | 
| 480 |  |  |  |  |  |  | my $dumb = delete $opts{dumb} || 0; | 
| 481 |  |  |  |  |  |  | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; | 
| 482 |  |  |  |  |  |  | die unless $type =~ /^HMAC-SHA(1|256)$/; | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | my $now = time(); | 
| 485 |  |  |  |  |  |  | my $sec_time = $now - ($now % $self->secret_gen_interval); | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | my $s_sec = $self->_get_server_secret($sec_time) | 
| 488 |  |  |  |  |  |  | or Carp::croak("server_secret didn't return a secret given what should've been a valid time ($sec_time)\n"); | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | my $nonce = _rand_chars(20); | 
| 491 |  |  |  |  |  |  | $nonce = "STLS.$nonce" if $dumb;  # flag nonce as stateless | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | my $handle = "$now:$nonce"; | 
| 494 |  |  |  |  |  |  | if ($type eq 'HMAC-SHA1') { | 
| 495 |  |  |  |  |  |  | $handle .= ":" . substr(hmac_sha1_hex($handle, $s_sec), 0, 10); | 
| 496 |  |  |  |  |  |  | } | 
| 497 |  |  |  |  |  |  | elsif ($type eq 'HMAC-SHA256') { | 
| 498 |  |  |  |  |  |  | $handle .= ":" . substr(hmac_sha256_hex($handle, $s_sec), 0, 16); | 
| 499 |  |  |  |  |  |  | } | 
| 500 |  |  |  |  |  |  |  | 
| 501 |  |  |  |  |  |  | my $c_sec = $self->_secret_of_handle($handle, dumb => $dumb, type=>$type) | 
| 502 |  |  |  |  |  |  | or return (); | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | my $expires = $sec_time + $self->secret_expire_age; | 
| 505 |  |  |  |  |  |  | return ($handle, $c_sec, $expires); | 
| 506 |  |  |  |  |  |  | } | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  | sub _secret_of_handle { | 
| 509 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 510 |  |  |  |  |  |  | my ($handle, %opts) = @_; | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | my $dumb_mode = delete $opts{'dumb'}      || 0; | 
| 513 |  |  |  |  |  |  | my $no_verify = delete $opts{'no_verify'} || 0; | 
| 514 |  |  |  |  |  |  | my $type = delete $opts{'type'} || 'HMAC-SHA1'; | 
| 515 |  |  |  |  |  |  | my %hmac_functions_hex=( | 
| 516 |  |  |  |  |  |  | 'HMAC-SHA1'  =>\&hmac_sha1_hex, | 
| 517 |  |  |  |  |  |  | 'HMAC-SHA256'=>\&hmac_sha256_hex, | 
| 518 |  |  |  |  |  |  | ); | 
| 519 |  |  |  |  |  |  | my %hmac_functions=( | 
| 520 |  |  |  |  |  |  | 'HMAC-SHA1'  =>\&hmac_sha1, | 
| 521 |  |  |  |  |  |  | 'HMAC-SHA256'=>\&hmac_sha256, | 
| 522 |  |  |  |  |  |  | ); | 
| 523 |  |  |  |  |  |  | my %nonce_80_lengths=( | 
| 524 |  |  |  |  |  |  | 'HMAC-SHA1'=>10, | 
| 525 |  |  |  |  |  |  | 'HMAC-SHA256'=>16, | 
| 526 |  |  |  |  |  |  | ); | 
| 527 |  |  |  |  |  |  | my $nonce_80_len=$nonce_80_lengths{$type}; | 
| 528 |  |  |  |  |  |  | my $hmac_function_hex=$hmac_functions_hex{$type} || Carp::croak "No function for $type"; | 
| 529 |  |  |  |  |  |  | my $hmac_function=$hmac_functions{$type} || Carp::croak "No function for $type"; | 
| 530 |  |  |  |  |  |  | Carp::croak("Unknown options: " . join(", ", keys %opts)) if %opts; | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | my ($time, $nonce, $nonce_sig80) = split(/:/, $handle); | 
| 533 |  |  |  |  |  |  | return unless $time =~ /^\d+$/ && $nonce && $nonce_sig80; | 
| 534 |  |  |  |  |  |  |  | 
| 535 |  |  |  |  |  |  | # check_authentication mode only verifies signatures made with | 
| 536 |  |  |  |  |  |  | # dumb (stateless == STLS) handles, so if that caller requests it, | 
| 537 |  |  |  |  |  |  | # don't return the secrets here of non-stateless handles | 
| 538 |  |  |  |  |  |  | return if $dumb_mode && $nonce !~ /^STLS\./; | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | my $sec_time = $time - ($time % $self->secret_gen_interval); | 
| 541 |  |  |  |  |  |  | my $s_sec = $self->_get_server_secret($sec_time)  or return; | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | length($nonce)       == ($dumb_mode ? 25 : 20) or return; | 
| 544 |  |  |  |  |  |  | length($nonce_sig80) == $nonce_80_len          or return; | 
| 545 |  |  |  |  |  |  |  | 
| 546 |  |  |  |  |  |  | return unless $no_verify || $nonce_sig80 eq substr($hmac_function_hex->("$time:$nonce", $s_sec), 0, $nonce_80_len); | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | return $hmac_function->($handle, $s_sec); | 
| 549 |  |  |  |  |  |  | } | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | sub _mode_associate { | 
| 552 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 553 |  |  |  |  |  |  |  | 
| 554 |  |  |  |  |  |  | my $now = time(); | 
| 555 |  |  |  |  |  |  | my %prop; | 
| 556 |  |  |  |  |  |  |  | 
| 557 |  |  |  |  |  |  | my $assoc_type = $self->message('assoc_type') || "HMAC-SHA1"; | 
| 558 |  |  |  |  |  |  |  | 
| 559 |  |  |  |  |  |  | if ($self->message('ns') eq $OPENID2_NS && | 
| 560 |  |  |  |  |  |  | ($self->message('assoc_type') ne $assoc_type || | 
| 561 |  |  |  |  |  |  | $self->message('session_type') ne 'DH-SHA1')) { | 
| 562 |  |  |  |  |  |  |  | 
| 563 |  |  |  |  |  |  | $prop{'ns'}         = $self->message('ns') if $self->message('ns'); | 
| 564 |  |  |  |  |  |  | $prop{'error_code'} = "unsupported-type"; | 
| 565 |  |  |  |  |  |  | $prop{'error'}      = "This server support $assoc_type only."; | 
| 566 |  |  |  |  |  |  | $prop{'assoc_type'} = $assoc_type; | 
| 567 |  |  |  |  |  |  | $prop{'session_type'} = "DH-SHA1"; | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | return $self->_serialized_props(\%prop); | 
| 570 |  |  |  |  |  |  | } | 
| 571 |  |  |  |  |  |  |  | 
| 572 |  |  |  |  |  |  | my ($assoc_handle, $secret, $expires) = | 
| 573 |  |  |  |  |  |  | $self->_generate_association(type => $assoc_type); | 
| 574 |  |  |  |  |  |  |  | 
| 575 |  |  |  |  |  |  | # make absolute form of expires | 
| 576 |  |  |  |  |  |  | my $exp_abs = $expires > 1000000000 ? $expires : $expires + $now; | 
| 577 |  |  |  |  |  |  |  | 
| 578 |  |  |  |  |  |  | # make relative form of expires | 
| 579 |  |  |  |  |  |  | my $exp_rel = $exp_abs - $now; | 
| 580 |  |  |  |  |  |  |  | 
| 581 |  |  |  |  |  |  | $prop{'ns'}   = $self->args('openid.ns') if $self->args('openid.ns'); | 
| 582 |  |  |  |  |  |  | $prop{'assoc_type'}   = $assoc_type; | 
| 583 |  |  |  |  |  |  | $prop{'assoc_handle'} = $assoc_handle; | 
| 584 |  |  |  |  |  |  | $prop{'assoc_type'}   = $assoc_type; | 
| 585 |  |  |  |  |  |  | $prop{'expires_in'}   = $exp_rel; | 
| 586 |  |  |  |  |  |  |  | 
| 587 |  |  |  |  |  |  | if ($self->{compat}) { | 
| 588 |  |  |  |  |  |  | $prop{'expiry'}   = OpenID::util::time_to_w3c($exp_abs); | 
| 589 |  |  |  |  |  |  | $prop{'issued'}   = OpenID::util::time_to_w3c($now); | 
| 590 |  |  |  |  |  |  | } | 
| 591 |  |  |  |  |  |  |  | 
| 592 |  |  |  |  |  |  | if ($self->args("openid.session_type") =~ /^DH-SHA(1|256)$/) { | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | my $p    = OpenID::util::arg2int($self->args("openid.dh_modulus")); | 
| 595 |  |  |  |  |  |  | my $g    = OpenID::util::arg2int($self->args("openid.dh_gen")); | 
| 596 |  |  |  |  |  |  | my $cpub = OpenID::util::arg2int($self->args("openid.dh_consumer_public")); | 
| 597 |  |  |  |  |  |  |  | 
| 598 |  |  |  |  |  |  | my $dh = OpenID::util::get_dh($p, $g); | 
| 599 |  |  |  |  |  |  | return $self->_error_page("invalid dh params p=$p, g=$g, cpub=$cpub") | 
| 600 |  |  |  |  |  |  | unless $dh and $cpub; | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | my $dh_sec = $dh->compute_secret($cpub); | 
| 603 |  |  |  |  |  |  |  | 
| 604 |  |  |  |  |  |  | $prop{'dh_server_public'} = OpenID::util::int2arg($dh->pub_key); | 
| 605 |  |  |  |  |  |  | $prop{'session_type'}     = $self->message("session_type"); | 
| 606 |  |  |  |  |  |  | if ($self->args("openid.session_type") eq 'DH-SHA1') { | 
| 607 |  |  |  |  |  |  | $prop{'enc_mac_key'}      = OpenID::util::b64($secret ^ sha1(OpenID::util::int2bytes($dh_sec))); | 
| 608 |  |  |  |  |  |  | } | 
| 609 |  |  |  |  |  |  | elsif ($self->args("openid.session_type") eq 'DH-SHA256') { | 
| 610 |  |  |  |  |  |  | $prop{'enc_mac_key'}      = OpenID::util::b64($secret ^ sha256(OpenID::util::int2bytes($dh_sec))); | 
| 611 |  |  |  |  |  |  | } | 
| 612 |  |  |  |  |  |  |  | 
| 613 |  |  |  |  |  |  | } else { | 
| 614 |  |  |  |  |  |  | $prop{'mac_key'} = OpenID::util::b64($secret); | 
| 615 |  |  |  |  |  |  | } | 
| 616 |  |  |  |  |  |  |  | 
| 617 |  |  |  |  |  |  | return $self->_serialized_props(\%prop); | 
| 618 |  |  |  |  |  |  | } | 
| 619 |  |  |  |  |  |  |  | 
| 620 |  |  |  |  |  |  | sub _mode_check_authentication { | 
| 621 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 622 |  |  |  |  |  |  |  | 
| 623 |  |  |  |  |  |  | my $signed = $self->args("openid.signed") || ""; | 
| 624 |  |  |  |  |  |  | my $token = ""; | 
| 625 |  |  |  |  |  |  | foreach my $param (split(/,/, $signed)) { | 
| 626 |  |  |  |  |  |  | next unless $param =~ /^[\w\.]+$/; | 
| 627 |  |  |  |  |  |  | my $val = $param eq "mode" ? "id_res" : $self->args("openid.$param"); | 
| 628 |  |  |  |  |  |  | next unless defined $val; | 
| 629 |  |  |  |  |  |  | next if $val =~ /\n/; | 
| 630 |  |  |  |  |  |  | $token .= "$param:$val\n"; | 
| 631 |  |  |  |  |  |  | } | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | my $sig = $self->args("openid.sig"); | 
| 634 |  |  |  |  |  |  | my $ahandle = $self->args("openid.assoc_handle") | 
| 635 |  |  |  |  |  |  | or return $self->_error_page("no_assoc_handle"); | 
| 636 |  |  |  |  |  |  |  | 
| 637 |  |  |  |  |  |  | my $c_sec = $self->_secret_of_handle($ahandle, dumb => 1) | 
| 638 |  |  |  |  |  |  | or return $self->_error_page("bad_handle"); | 
| 639 |  |  |  |  |  |  |  | 
| 640 |  |  |  |  |  |  | my $assoc_type = $self->args('openid.assoc_type') || 'HMAC-SHA1'; | 
| 641 |  |  |  |  |  |  |  | 
| 642 |  |  |  |  |  |  | my $good_sig; | 
| 643 |  |  |  |  |  |  | if ($assoc_type eq 'HMAC-SHA1') { | 
| 644 |  |  |  |  |  |  | $good_sig = OpenID::util::b64(hmac_sha1($token, $c_sec)); | 
| 645 |  |  |  |  |  |  | } | 
| 646 |  |  |  |  |  |  | elsif ($assoc_type eq 'HMAC-SHA256') { | 
| 647 |  |  |  |  |  |  | $good_sig = OpenID::util::b64(hmac_sha256($token, $c_sec)); | 
| 648 |  |  |  |  |  |  | } | 
| 649 |  |  |  |  |  |  | else { | 
| 650 |  |  |  |  |  |  | die "Unknown assoc_type $assoc_type"; | 
| 651 |  |  |  |  |  |  | } | 
| 652 |  |  |  |  |  |  |  | 
| 653 |  |  |  |  |  |  | my $is_valid = OpenID::util::timing_indep_eq($sig, $good_sig); | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | my $ret = { | 
| 656 |  |  |  |  |  |  | is_valid => $is_valid ? "true" : "false", | 
| 657 |  |  |  |  |  |  | }; | 
| 658 |  |  |  |  |  |  | $ret->{'ns'}   = $self->args('openid.ns') if $self->args('openid.ns'); | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | if ($self->{compat}) { | 
| 661 |  |  |  |  |  |  | $ret->{lifetime} = 3600; | 
| 662 |  |  |  |  |  |  | $ret->{WARNING} = | 
| 663 |  |  |  |  |  |  | "The lifetime parameter is deprecated and will " . | 
| 664 |  |  |  |  |  |  | "soon be removed.  Use is_valid instead.  " . | 
| 665 |  |  |  |  |  |  | "See openid.net/specs.bml."; | 
| 666 |  |  |  |  |  |  | } | 
| 667 |  |  |  |  |  |  |  | 
| 668 |  |  |  |  |  |  | # tell them if a handle they asked about is invalid, too | 
| 669 |  |  |  |  |  |  | if (my $ih = $self->args("openid.invalidate_handle")) { | 
| 670 |  |  |  |  |  |  | $c_sec = $self->_secret_of_handle($ih); | 
| 671 |  |  |  |  |  |  | $ret->{"invalidate_handle"} = $ih unless $c_sec; | 
| 672 |  |  |  |  |  |  | } | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  | return $self->_serialized_props($ret); | 
| 675 |  |  |  |  |  |  | } | 
| 676 |  |  |  |  |  |  |  | 
| 677 |  |  |  |  |  |  | sub _error_page { | 
| 678 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 679 |  |  |  |  |  |  | return $self->_serialized_props({ 'error' => $_[0] }); | 
| 680 |  |  |  |  |  |  | } | 
| 681 |  |  |  |  |  |  |  | 
| 682 |  |  |  |  |  |  | sub _serialized_props { | 
| 683 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 684 |  |  |  |  |  |  | my $props = shift; | 
| 685 |  |  |  |  |  |  |  | 
| 686 |  |  |  |  |  |  | my $body = ""; | 
| 687 |  |  |  |  |  |  | foreach (sort keys %$props) { | 
| 688 |  |  |  |  |  |  | $body .= "$_:$props->{$_}\n"; | 
| 689 |  |  |  |  |  |  | } | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | return ("text/plain", $body); | 
| 692 |  |  |  |  |  |  | } | 
| 693 |  |  |  |  |  |  |  | 
| 694 |  |  |  |  |  |  | sub _get_key_contents { | 
| 695 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 696 |  |  |  |  |  |  | my $key = shift; | 
| 697 |  |  |  |  |  |  | Carp::croak("Too many parameters") if @_; | 
| 698 |  |  |  |  |  |  | Carp::croak("Unknown key type") unless $key =~ /^public|private$/; | 
| 699 |  |  |  |  |  |  |  | 
| 700 |  |  |  |  |  |  | my $mval = $self->{"${key}_key"}; | 
| 701 |  |  |  |  |  |  | my $contents; | 
| 702 |  |  |  |  |  |  |  | 
| 703 |  |  |  |  |  |  | if (ref $mval eq "CODE") { | 
| 704 |  |  |  |  |  |  | $contents = $mval->(); | 
| 705 |  |  |  |  |  |  | } elsif ($mval !~ /\n/ && -f $mval) { | 
| 706 |  |  |  |  |  |  | local *KF; | 
| 707 |  |  |  |  |  |  | return $self->_fail("key_open_failure", "Couldn't open key file for reading") | 
| 708 |  |  |  |  |  |  | unless open(KF, $mval); | 
| 709 |  |  |  |  |  |  | $contents = do { local $/; ; }; | 
| 710 |  |  |  |  |  |  | close KF; | 
| 711 |  |  |  |  |  |  | } else { | 
| 712 |  |  |  |  |  |  | $contents = $mval; | 
| 713 |  |  |  |  |  |  | } | 
| 714 |  |  |  |  |  |  |  | 
| 715 |  |  |  |  |  |  | return $self->_fail("invalid_key", "$key file not in correct format") | 
| 716 |  |  |  |  |  |  | unless $contents =~ /\-\-\-\-BEGIN/ && $contents =~ /\-\-\-\-END/; | 
| 717 |  |  |  |  |  |  | return $contents; | 
| 718 |  |  |  |  |  |  | } | 
| 719 |  |  |  |  |  |  |  | 
| 720 |  |  |  |  |  |  |  | 
| 721 |  |  |  |  |  |  | sub _getset { | 
| 722 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 723 |  |  |  |  |  |  | my $param = (caller(1))[3]; | 
| 724 |  |  |  |  |  |  | $param =~ s/.+:://; | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | if (@_) { | 
| 727 |  |  |  |  |  |  | my $val = shift; | 
| 728 |  |  |  |  |  |  | Carp::croak("Too many parameters") if @_; | 
| 729 |  |  |  |  |  |  | $self->{$param} = $val; | 
| 730 |  |  |  |  |  |  | } | 
| 731 |  |  |  |  |  |  | return $self->{$param}; | 
| 732 |  |  |  |  |  |  | } | 
| 733 |  |  |  |  |  |  |  | 
| 734 |  |  |  |  |  |  | sub _getsetcode { | 
| 735 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 736 |  |  |  |  |  |  | my $param = (caller(1))[3]; | 
| 737 |  |  |  |  |  |  | $param =~ s/.+:://; | 
| 738 |  |  |  |  |  |  |  | 
| 739 |  |  |  |  |  |  | if (my $code = shift) { | 
| 740 |  |  |  |  |  |  | Carp::croak("Too many parameters") if @_; | 
| 741 |  |  |  |  |  |  | Carp::croak("Expected CODE reference") unless ref $code eq "CODE"; | 
| 742 |  |  |  |  |  |  | $self->{$param} = $code; | 
| 743 |  |  |  |  |  |  | } | 
| 744 |  |  |  |  |  |  | return $self->{$param}; | 
| 745 |  |  |  |  |  |  | } | 
| 746 |  |  |  |  |  |  |  | 
| 747 |  |  |  |  |  |  | sub _fail { | 
| 748 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 749 |  |  |  |  |  |  | $self->{last_errcode} = shift; | 
| 750 |  |  |  |  |  |  | $self->{last_errtext} = shift; | 
| 751 |  |  |  |  |  |  | wantarray ? () : undef; | 
| 752 |  |  |  |  |  |  | } | 
| 753 |  |  |  |  |  |  |  | 
| 754 |  |  |  |  |  |  | sub err { | 
| 755 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 756 |  |  |  |  |  |  | return undef unless $self->{last_errcode}; | 
| 757 |  |  |  |  |  |  | $self->{last_errcode} . ": " . $self->{last_errtext}; | 
| 758 |  |  |  |  |  |  | } | 
| 759 |  |  |  |  |  |  |  | 
| 760 |  |  |  |  |  |  | sub errcode { | 
| 761 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 762 |  |  |  |  |  |  | $self->{last_errcode}; | 
| 763 |  |  |  |  |  |  | } | 
| 764 |  |  |  |  |  |  |  | 
| 765 |  |  |  |  |  |  | sub errtext { | 
| 766 |  |  |  |  |  |  | my Net::OpenID::Server $self = shift; | 
| 767 |  |  |  |  |  |  | $self->{last_errtext}; | 
| 768 |  |  |  |  |  |  | } | 
| 769 |  |  |  |  |  |  |  | 
| 770 |  |  |  |  |  |  | # FIXME: duplicated in Net::OpenID::Consumer's VerifiedIdentity | 
| 771 |  |  |  |  |  |  | sub _url_is_under { | 
| 772 |  |  |  |  |  |  | my ($root, $test, $err_ref) = @_; | 
| 773 |  |  |  |  |  |  |  | 
| 774 |  |  |  |  |  |  | my $err = sub { | 
| 775 |  |  |  |  |  |  | $$err_ref = shift if $err_ref; | 
| 776 |  |  |  |  |  |  | return undef; | 
| 777 |  |  |  |  |  |  | }; | 
| 778 |  |  |  |  |  |  |  | 
| 779 |  |  |  |  |  |  | my $ru = URI->new($root); | 
| 780 |  |  |  |  |  |  | return $err->("invalid root scheme") unless $ru->scheme =~ /^https?$/; | 
| 781 |  |  |  |  |  |  | my $tu = URI->new($test); | 
| 782 |  |  |  |  |  |  | return $err->("invalid test scheme") unless $tu->scheme =~ /^https?$/; | 
| 783 |  |  |  |  |  |  | return $err->("schemes don't match") unless $ru->scheme eq $tu->scheme; | 
| 784 |  |  |  |  |  |  | return $err->("ports don't match") unless $ru->port == $tu->port; | 
| 785 |  |  |  |  |  |  |  | 
| 786 |  |  |  |  |  |  | # check hostnames | 
| 787 |  |  |  |  |  |  | my $ru_host = $ru->host; | 
| 788 |  |  |  |  |  |  | my $tu_host = $tu->host; | 
| 789 |  |  |  |  |  |  | my $wildcard_host = 0; | 
| 790 |  |  |  |  |  |  | if ($ru_host =~ s!^\*\.!!) { | 
| 791 |  |  |  |  |  |  | $wildcard_host = 1; | 
| 792 |  |  |  |  |  |  | } | 
| 793 |  |  |  |  |  |  | unless ($ru_host eq $tu_host) { | 
| 794 |  |  |  |  |  |  | if ($wildcard_host) { | 
| 795 |  |  |  |  |  |  | return $err->("host names don't match") unless | 
| 796 |  |  |  |  |  |  | $tu_host =~ /\.\Q$ru_host\E$/; | 
| 797 |  |  |  |  |  |  | } else { | 
| 798 |  |  |  |  |  |  | return $err->("host names don't match"); | 
| 799 |  |  |  |  |  |  | } | 
| 800 |  |  |  |  |  |  | } | 
| 801 |  |  |  |  |  |  |  | 
| 802 |  |  |  |  |  |  | # check paths | 
| 803 |  |  |  |  |  |  | my $ru_path = $ru->path || "/"; | 
| 804 |  |  |  |  |  |  | my $tu_path = $tu->path || "/"; | 
| 805 |  |  |  |  |  |  | $ru_path .= "/" unless $ru_path =~ m!/$!; | 
| 806 |  |  |  |  |  |  | $tu_path .= "/" unless $tu_path =~ m!/$!; | 
| 807 |  |  |  |  |  |  | return $err->("path not a subpath") unless $tu_path =~ m!^\Q$ru_path\E!; | 
| 808 |  |  |  |  |  |  |  | 
| 809 |  |  |  |  |  |  | return 1; | 
| 810 |  |  |  |  |  |  | } | 
| 811 |  |  |  |  |  |  |  | 
| 812 |  |  |  |  |  |  | sub _rand_chars | 
| 813 |  |  |  |  |  |  | { | 
| 814 |  |  |  |  |  |  | shift if @_ == 2;  # shift off classname/obj, if called as method | 
| 815 |  |  |  |  |  |  | my $length = shift; | 
| 816 |  |  |  |  |  |  |  | 
| 817 |  |  |  |  |  |  | my $chal = ""; | 
| 818 |  |  |  |  |  |  | my $digits = "abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0123456789"; | 
| 819 |  |  |  |  |  |  | for (1..$length) { | 
| 820 |  |  |  |  |  |  | $chal .= substr($digits, int(rand(62)), 1); | 
| 821 |  |  |  |  |  |  | } | 
| 822 |  |  |  |  |  |  | return $chal; | 
| 823 |  |  |  |  |  |  | } | 
| 824 |  |  |  |  |  |  |  | 
| 825 |  |  |  |  |  |  | # also a public interface: | 
| 826 |  |  |  |  |  |  | *rand_chars = \&_rand_chars; | 
| 827 |  |  |  |  |  |  |  | 
| 828 |  |  |  |  |  |  | __END__ |