#!/usr/bin/perl -w
#
# $Id: ipfcsign.pm,v 1.10 2002/03/14 10:14:11 fpmip Exp $
#
# Implements XML signature generation and verification
#
# Author : Tycho Fruru <tycho@fruru.com>
#
# Copyright (C) 2001 Tycho Fruru <tycho@fruru.com>
# Copyright (C) 2001 Conostix S.A. Luxembourg - Tycho Fruru <tycho@conostix.com>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
## This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
package IPFC::Sign;

use Digest::HMAC_SHA1 (hmac_sha1);
use MIME::Base64;
#use Data::Dumper;

my $config;

sub set_config($) { 
# takes a hashref or a hash containing the key data and sets the configuration
	my $r_config = shift;
	if (ref($r_config)) {
		$config = $r_config;
	} else {
		$config = \$r_config;
	}
}

sub sign_message($$) {
	my $message = shift;
	my $keyid = shift;

	chomp $message;
	
	if (!defined $config->{'key'}->{$keyid}) {
		warn "I could not find key id $keyid !";
	}
	
	my $type = $config->{'key'}->{$keyid}->{'type'};
	my $format;
	my $signature;

	if ($type eq "none") {
		$format = "ascii";
		$signature = "";
	} elsif ($type eq "HMAC-SHA1") {
		$format = "base64";
		my $key = $config->{'key'}->{$keyid}->{'key'};
		my $hmac = Digest::HMAC_SHA1->new($key);
		$hmac->add($message);
		$signature = encode_base64($hmac->digest);
		chomp $signature;
	} else {
		warn "Signature type $type not supported !";
		return undef;
	}

	$result = $message."\n".
	         "<ipfc-signature type=\"$type\" ".
		 		 "keyid=\"$keyid\" ".
				 "format=\"$format\">".
		  $signature.
		 "</ipfc-signature>\n";
	return $result;
}

sub check_signature($) {
	my $complete_message = shift;
	
	my $message;
	my $agent_id;
	my $signature;
	
	if ($complete_message =~ /^(.*<\/ipfc>)/s) {
		$message = $1;
	} else {
		warn "This doesn't seem to be an IPFC message !";
		return undef;
	}
	
	# extract agent id without doing full XML parsing (which might not be available on agents anyway)
	if ($message =~ /<agent\s[^>]*\bid=["']([0-9]+)["']/s) {
		$agent_id = $1;
	} else {
		warn "There is no agent id !";
		return undef;
	}

	my $signature_type;
	my $signature_keyid;
	my $signature_format;
	my $signature_signature;
	
	if ($complete_message =~ /(<ipfc-signature[\s>].*<\/ipfc-signature>)/s) {
		$signature = $1;
		$signature =~ /\btype=["']([^"']+)["']/;
		$signature_type = $1;
		$signature =~ /\bkeyid=["']([^"']+)["']/;
		$signature_keyid = $1;
		$signature =~ /\bformat=["']([^"']+)["']/;
		$signature_format = $1;
		$signature =~ /<ipfc-signature[^>]+>(.*)<\/ipfc-signature>/s;
		$signature_signature = $1;
	} else {
		$signature_type = "none";
		$signature_keyid = "none";
		$signature_format = "none";		
	}
	
	# comma separated list of agents which can use this key
	my $authorized_agents = $config->{'key'}->{$signature_keyid}->{'agent'};
	my @authorized_agents = split /,/,$authorized_agents;
	
	# if our agent_id did not occur within the "agent" attribute of this key,
	# the signature is not acceptable !
	if (! grep (($_==$agent_id),@authorized_agents)) {
		warn "wrong signature ... accepted are $authorized_agents, but the agent id is $agent_id";
		return undef;
	}
	
	my $signature_validated = 0;
	
	if ($signature_type eq "none") {
		$signature_validated = 1;
	} elsif (($signature_type eq "HMAC-SHA1") && ($signature_format eq "base64")) {
		my $key = $config->{'key'}->{$signature_keyid}->{'key'};
		my $hmac = Digest::HMAC_SHA1->new($key);
		$hmac->add($message);
		my $check_signature = encode_base64($hmac->digest);
		chomp $check_signature;
		if ($signature_signature eq $check_signature) {
			$signature_validated = 1;
		}
	} else {
		warn "Unknown signature algorithm used ...";
	}

	if ($signature_validated == 1) {
		return $message;
	} else {
		return undef;
	}

}


return 1;
