# Copyright (c) 2004 Anthony D. Urso. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
use strict;
our $VERSION = "0.88";
sub new {
my $type = shift;
my %prms = @_;
my $self = {};
$self->{'ALGO'} = $prms{'Algorithm'};
$self->{'DATA'} = $prms{'Signature'};
$self->{'METH'} = $prms{'Method'};
$self->{'DOMN'} = $prms{'Domain'};
$self->{'HDRS'} = $prms{'Headers'};
$self->{'PROT'} = $prms{'Query'};
$self->{'SLCT'} = $prms{'Selector'};
$self->{'SHDR'} = $prms{'SignHeaders'};
$self->{'SIGN'} = $prms{'Signing'};
$self->{'CFWS'} = $prms{'FWS'};
bless $self, $type;
}
sub parse {
my $type = shift;
my %prms = @_;
my $self = {};
foreach my $tag (split /;/, $prms{"String"}) {
$tag =~ s/^\s*|\s*$//g;
foreach ($tag) {
/^a=(rsa-sha1)$/i and
$self->{'ALGO'} = lc $1;
/^b=([A-Za-z0-9\+\/\=\s]+)$/ and
$self->{'DATA'} = $1;
/^c=(nofws|simple)$/i and
$self->{'METH'} = lc $1;
/^d=([A-Za-z0-9\-\.]+)$/ and
$self->{'DOMN'} = lc $1;
/^h=(.*)$/s and
$self->{'HDRS'} = lc $1;
/^q=(dns)$/i and
$self->{'PROT'} = lc $1;
/^s=(\S+)$/ and
$self->{'SLCT'} = $1;
}
}
bless $self, $type;
}
sub wantheader {
my $self = shift;
my $attr = shift;
# we are signing, and a list of headers to sign was specified
if ($self->signheaderlist) {
foreach my $key ($self->signheaderlist) {
lc $attr eq lc $key and
return 1;
}
return;
}
# we are verifying
if ($self->headerlist) {
foreach my $key ($self->headerlist) {
lc $attr eq lc $key and
return 1;
}
return;
}
# we are signing and a list of headers to sign was not specified,
# or we are verifying and the DomainKeys-Signature header does not
# have a h= term
return 1;
}
sub as_string {
my $self = shift;
my $text;
$self->algorithm and
$text .= "a=" . $self->algorithm . "; ";
$self->headerlist and
$text .= "h=" . $self->headerlist . "; ";
$text .= "q=" . $self->protocol . "; ";
$text .= "c=" . $self->method . "; ";
$text .= "s=" . $self->selector . "; ";
$text .= "d=" . $self->domain . "; ";
$text .= "b=" . $self->signature;
if (defined (my $cfws = $self->fws)) {
require Text::Wrap;
local $Text::Wrap::columns = 78;
$text = Text::Wrap::wrap("", $cfws, $text);
$text .= "\n";
}
return $text;
}
sub sign {
my $self = shift;
my %prms = @_;
$self->method($prms{'Method'}) if $prms{'Method'};
$self->selector($prms{'Selector'}) if $prms{'Selector'};
$self->private($prms{'Private'}) if $prms{'Private'};
my $text = $prms{'Text'} or
$self->errorstr("no text given"),
return;
$self->method or
$self->errorstr("no method specified"),
return;
$self->private or
$self->errorstr("no private key specified"),
return;
$self->selector or
$self->errorstr("no selector specified"),
return;
$self->domain or
$self->errorstr("no domain specified"),
return;
$self->protocol or $self->protocol("dns");
$self->algorithm or $self->algorithm("rsa-sha1");
# d=... The value in this tag MUST match the domain of the sending
# email address or MUST be one of the parent domains of the sending
# email address. Domain name comparison is case insensitive.
my $signing_domain = $self->domain;
$prms{'Sender'}->host =~ /(^|\.)\Q$signing_domain\E\z/i or
$self->errorstr("domain does not match address"),
return;
my $sign = $self->private->sign($text);
my $signb64 = encode_base64($sign, "");
$self->signature($signb64);
$self->status("good");
return 1;
}
sub verify {
my $self = shift;
my %prms = @_;
$self->status("bad format"),
$self->selector or
$self->errorstr("no selector specified"),
return;
$self->domain or
$self->errorstr("no domain specified"),
return;
unless ($self->public) {
my $pubk = fetch Mail::DomainKeys::Key::Public(
Protocol => $self->protocol,
Selector => $self->selector,
Domain => $self->domain) or
$self->status("no key"),
$self->errorstr("no public key available"),
return;
$pubk->revoked and
$self->status("revoked"),
$self->errorstr("public key has been revoked"),
return;
$self->public($pubk);
}
$self->status("bad");
# d=... The value in this tag MUST match the domain of the sending
# email address or MUST be one of the parent domains of the sending
# email address. Domain name comparison is case insensitive.
my $signing_domain = $self->domain;
$prms{'Sender'}->host =~ /(^|\.)\Q$signing_domain\E\z/i or
$self->errorstr("domain does not match address"),
return;
$prms{'Sender'}->host eq $self->domain or
$self->errorstr("domain does not match address"),
return;
$self->public->granularity and
$prms{'Sender'}->user ne $self->public->granularity and
$self->errorstr("granularity does not match address"),
return;
$self->public->verify(Text => $prms{'Text'},
Signature => decode_base64($self->signature)) and
$self->errorstr(undef),
$self->status("good"),
return 1;
$self->errorstr("signature invalid");
return;
}
sub algorithm {
my $self = shift;
@_ and
$self->{'ALGO'} = shift;
$self->{'ALGO'};
}
sub domain {
my $self = shift;
@_ and
$self->{'DOMN'} = shift;
$self->{'DOMN'};
}
sub errorstr {
my $self = shift;
@_ and
$self->{'ESTR'} = shift;
$self->{'ESTR'};
}
sub fws {
my $self = shift;
@_ and
$self->{'CFWS'} = shift;
return $self->{'CFWS'};
}
sub headerlist {
my $self = shift;
@_ and
$self->{'HDRS'} = shift;
if (wantarray and $self->{'HDRS'}) {
my @list = split /[ \t]*:[ \t]*/, $self->{'HDRS'};
return @list;
}
$self->{'HDRS'};
}
sub method {
my $self = shift;
@_ and
$self->{'METH'} = shift;
$self->{'METH'};
}
sub public {
my $self = shift;
@_ and
$self->{'PBLC'} = shift;
$self->{'PBLC'};
}
sub private {
my $self = shift;
@_ and
$self->{'PRIV'} = shift;
$self->{'PRIV'};
}
sub protocol {
my $self = shift;
@_ and
$self->{'PROT'} = shift;
$self->{'PROT'};
}
sub selector {
my $self = shift;
@_ and
$self->{'SLCT'} = shift;
$self->{'SLCT'};
}
sub signature {
my $self = shift;
@_ and
$self->{'DATA'} = shift;
$self->{'DATA'};
}
sub signheaderlist {
my $self = shift;
@_ and
$self->{'SHDR'} = shift;
if (wantarray and $self->{'SHDR'}) {
my @list = split /:/, $self->{'SHDR'};
return @list;
}
$self->{'SHDR'};
}
sub signing {
my $self = shift;
@_ and
$self->{'SIGN'} = shift;
$self->{'SIGN'};
}
sub status {
my $self = shift;
@_ and
$self->{'STAT'} = shift;
$self->{'STAT'};
}
sub testing {
my $self = shift;
$self->public and $self->public->testing and
return 1;
return;
}
1;