#!/usr/bin/perl
use strict;
use File::Copy;

# File under Perl Artistic Licence 2.0
# http://www.opensource.org/licenses/artistic-license-2.0.php

# Copyright holder: TBS INTERNET SAS, France
# Author: JP Donnio
# Author: PALLAVIDINO Luc
# Contact: tag-nss-restrict_ca@tbs-internet.com
# http://www.tbs-certificats.com/
#
# Doc at: http://www.tbs-certificats.com/ssl/nss_tools_crl_ca_control.html
#---------------------------------------------------------------------
# Version: 1.4
# Date: 2009-07-22
#---------------------------------------------------------------------
# Version: 1.3
# Date: 2009-05-13
#---------------------------------------------------------------------
# Version: 1.2
# Date: 2009-05-05
#---------------------------------------------------------------------
# Version: 1.1
# Date: 2009-03-06
#---------------------------------------------------------------------
# Version: 1.0
# Date: 2008-08-20

# USAGE: nss_restrict_ca [--builtin] <cert8.db directory location> <ca policy file location>

my $DEBUG=0;
my $VERBOSE=1;

# process the default or the builtin store?
# use --builtin for the builtin store
my $BUILTINONLY;
my $hwdev;
if ($ARGV[0] =~ /-{1,2}builtin/) 
{
	$BUILTINONLY="Builtin Object Token:";
	$hwdev="-h \"Builtin Object Token\"";
	shift @ARGV;
}

# set default DB dir
my $DBDIR;

if ( -d "$ARGV[0]" ) 
{
	$DBDIR=$ARGV[0];
} 
else 
{	
	print "Can't access to DB dir $ARGV[0]";
	exit;
}

# set default Policy dir
my $CERTLIST;
if ( -e "$ARGV[1]" ) 
{
	$CERTLIST=$ARGV[1];
	if ( ! -f $CERTLIST )
	{
	     print "You're certificate list i s not a valid file";
	     exit 
	}	
} 
else 
{
	# SET HERE your ca policy file location
	print "Can't access to certificate list $ARGV[1]";
	exit
}

my $CERTUTIL="/usr/bin/certutil";

my %trusted;

# lets load the trusted list
open FHCERTLIST, $CERTLIST or die "Could not open $CERTLIST";
while (<FHCERTLIST>) 
{
	chomp;
	next if /^\s*#/;
	next if /^\s*$/;
	my ($thecn,$theflag) = split /\t/;
	$thecn =~ s/\s+$//;
	$trusted{"$thecn"}=$theflag;
}
close FHCERTLIST;

# Check certificate(s) in NSS DB ?
my $CERLISTLOCATION;
my $TEMPDB="";
my @CERTTOCHECK;
if ( -e "$DBDIR/nss_verification_certificate.list" )
{
     $CERLISTLOCATION="$DBDIR/nss_verification_certificate.list";
     $TEMPDB="$DBDIR/cert8.db.".rand();
     # On ne garde que les certificats de la liste qui sont contenus dans la base NSS
     open FHCERTLIST, $CERLISTLOCATION or die "Could not open $CERLISTLOCATION";
     while (<FHCERTLIST>) 
     {
	chomp;
	next if /^\s*#/;
	next if /^\s*$/;	
	if ( `$CERTUTIL -L -d \"$DBDIR\" | grep \"$_\"` )
	{
	          # Si le certificat est contenu dans la base, on vérifie qu'il est actuellement valide, sinon on ne réalise pas les traitements
	          if ( `$CERTUTIL -L -d \"$DBDIR\" -n \"$_\" 2>&1 | grep \"bad database\"` )
	          {
		# On met un # devant le libellé de l'erreur pour indiquer que l'on va renvoyer cette erreur sur la sortie std dans nss_root_util
		print "#Failed : $_ invalid, I can't perform restrictions on CA in ths NSS database ( $DBDIR )\n";
		exit (2);
	          }
	          unshift (@CERTTOCHECK, $_);
	}
     }
     close FHCERTLIST;
}
     
# read the database
if (open NSSLIST, "$CERTUTIL -L $hwdev -d \"$DBDIR\" |") 
{
	while (<NSSLIST>) 
	{	
		# On crée une copie temporaire de la base
		if( "$CERLISTLOCATION" )
		{
		     copy("$DBDIR/cert8.db","$TEMPDB") or die "Copy failed: $!";     
		}
		
		chomp;
		next if (/\s+[uUpP]*,[uUpP]*,[uUpP]*\s*$/); # dont care about peer or user certs
		if (/$BUILTINONLY(.*)\s+([cTCG]*,[cTC]*,[cTC]*)\s*$/) # found, save the CN and the flags
		{
			my $nsscn=$1;
			my $nssflag=$2;
			$nsscn =~ s/\s+$//;

			if (! $trusted{"$nsscn"}) # we do not trust this item, make sure it is inactive
			{
				my $newflag=lc($nssflag);
				$newflag =~ s/t//g;	# lower case t does not exist

 				# we need to ignore the G (stepup)
 				my $cmp1 = $newflag; $cmp1 =~ s/g//ig;
 				my $cmp2 = $nssflag; $cmp2 =~ s/g//ig;

 				if ($cmp1 ne $cmp2) 
				{          
					my $rc = 0xffff & system("$CERTUTIL -M $hwdev -d \"$DBDIR\" -n \"$BUILTINONLY$nsscn\" -t \"$newflag\" ");
					if ($rc != 0) 
					{
						print "$DBDIR: Failed to change flags of $nsscn to $newflag with error $rc\n";
					} 
					else 
					{
						print "$DBDIR: Info: disabled $nsscn (was $nssflag now $newflag)\n" if ($VERBOSE);
					}
 				}
			}
			else 
			{
				# WE TRUST IT - make sure the flags are the same
				if ($trusted{"$nsscn"} ne $nssflag) # hmm, lets correct the flags
				{

					# we need to ignore the G
					my $cmp1 = $trusted{"$nsscn"}; $cmp1 =~ s/g//ig;
					my $cmp2 = $nssflag; $cmp2 =~ s/g//ig;

					if ($cmp1 ne $cmp2) 
					{	
						my $rc = 0xffff & system("$CERTUTIL -M $hwdev -d \"$DBDIR\" -n \"$BUILTINONLY$nsscn\" -t \"$trusted{$nsscn}\" ");
						if ($rc != 0) 
						{
							print "$DBDIR: Failed to change flags of trusted $nsscn to $trusted{$nsscn} (was $nssflag) with error $rc\n";
						} 
						else 
						{
							print "$DBDIR: Warning: flags of trusted $nsscn was reset to $trusted{$nsscn} (was $nssflag)\n";
						}
					}
				}
			}
		} 
		else 
		{
			print "$DBDIR: MISMATCH:$_\n" if ($DEBUG);
		}
		
		# On vérifie la validité des certificats présent dans la liste aprés chaque modification
		foreach my $CERT (@CERTTOCHECK) 
		{ 		     
		     # Si le certificat contenu dans la base NSS est invalide on passe à la version antérieure de la base 
		     if ( `$CERTUTIL -L -d \"$DBDIR\" -n \"$CERT\" 2>&1 | grep \"bad database\"` )
		     {
		          # On met un # devant le libellé de l'erreur pour indiquer que l'on va renvoyer cette erreur sur la sortie std dans nss_root_util
		          print "#Failed : $_ invalid, go back to the previous database version ( $DBDIR )\n";
		          copy("$TEMPDB","$DBDIR/cert8.db") or die "Copy failed: $!";                                      
		     }
		}

		
	}
	#On supprime le fichier temporaire si il existe
	if ( -e "$TEMPDB" )
	{
	     unlink ($TEMPDB);
	}
} 
else 
{
	die "Failed to call $CERTUTIL\n";
}
close NSSLIST;