2691 lines
77 KiB
Perl
Executable File
2691 lines
77 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use 5.8.0;
|
|
|
|
use Getopt::Long qw(:config gnu_getopt no_ignore_case pass_through);
|
|
use Cwd qw(getcwd realpath);
|
|
use File::Basename;
|
|
use File::Copy;
|
|
use File::Path;
|
|
use Sys::Hostname;
|
|
use Carp;
|
|
|
|
our @CC = (
|
|
["AF","AFGHANISTAN"],
|
|
["AX","ÅLAND ISLANDS"],
|
|
["AL","ALBANIA"],
|
|
["DZ","ALGERIA"],
|
|
["AS","AMERICAN SAMOA"],
|
|
["AD","ANDORRA"],
|
|
["AO","ANGOLA"],
|
|
["AI","ANGUILLA"],
|
|
["AQ","ANTARCTICA"],
|
|
["AG","ANTIGUA AND BARBUDA"],
|
|
["AR","ARGENTINA"],
|
|
["AM","ARMENIA"],
|
|
["AW","ARUBA"],
|
|
["AU","AUSTRALIA"],
|
|
["AT","AUSTRIA"],
|
|
["AZ","AZERBAIJAN"],
|
|
["BS","BAHAMAS"],
|
|
["BH","BAHRAIN"],
|
|
["BD","BANGLADESH"],
|
|
["BB","BARBADOS"],
|
|
["BY","BELARUS"],
|
|
["BE","BELGIUM"],
|
|
["BZ","BELIZE"],
|
|
["BJ","BENIN"],
|
|
["BM","BERMUDA"],
|
|
["BT","BHUTAN"],
|
|
["BO","BOLIVIA, PLURINATIONAL STATE OF"],
|
|
["BA","BOSNIA AND HERZEGOVINA"],
|
|
["BW","BOTSWANA"],
|
|
["BV","BOUVET ISLAND"],
|
|
["BR","BRAZIL"],
|
|
["IO","BRITISH INDIAN OCEAN TERRITORY"],
|
|
["BN","BRUNEI DARUSSALAM"],
|
|
["BG","BULGARIA"],
|
|
["BF","BURKINA FASO"],
|
|
["BI","BURUNDI"],
|
|
["KH","CAMBODIA"],
|
|
["CM","CAMEROON"],
|
|
["CA","CANADA"],
|
|
["CV","CAPE VERDE"],
|
|
["KY","CAYMAN ISLANDS"],
|
|
["CF","CENTRAL AFRICAN REPUBLIC"],
|
|
["TD","CHAD"],
|
|
["CL","CHILE"],
|
|
["CN","CHINA"],
|
|
["CX","CHRISTMAS ISLAND"],
|
|
["CC","COCOS (KEELING) ISLANDS"],
|
|
["CO","COLOMBIA"],
|
|
["KM","COMOROS"],
|
|
["CG","CONGO"],
|
|
["CD","CONGO, THE DEMOCRATIC REPUBLIC OF THE"],
|
|
["CK","COOK ISLANDS"],
|
|
["CR","COSTA RICA"],
|
|
["CI","CÔTE D'IVOIRE"],
|
|
["HR","CROATIA"],
|
|
["CU","CUBA"],
|
|
["CY","CYPRUS"],
|
|
["CZ","CZECH REPUBLIC"],
|
|
["DK","DENMARK"],
|
|
["DJ","DJIBOUTI"],
|
|
["DM","DOMINICA"],
|
|
["DO","DOMINICAN REPUBLIC"],
|
|
["EC","ECUADOR"],
|
|
["EG","EGYPT"],
|
|
["SV","EL SALVADOR"],
|
|
["GQ","EQUATORIAL GUINEA"],
|
|
["ER","ERITREA"],
|
|
["EE","ESTONIA"],
|
|
["ET","ETHIOPIA"],
|
|
["FK","FALKLAND ISLANDS (MALVINAS)"],
|
|
["FO","FAROE ISLANDS"],
|
|
["FJ","FIJI"],
|
|
["FI","FINLAND"],
|
|
["FR","FRANCE"],
|
|
["GF","FRENCH GUIANA"],
|
|
["PF","FRENCH POLYNESIA"],
|
|
["TF","FRENCH SOUTHERN TERRITORIES"],
|
|
["GA","GABON"],
|
|
["GM","GAMBIA"],
|
|
["GE","GEORGIA"],
|
|
["DE","GERMANY"],
|
|
["GH","GHANA"],
|
|
["GI","GIBRALTAR"],
|
|
["GR","GREECE"],
|
|
["GL","GREENLAND"],
|
|
["GD","GRENADA"],
|
|
["GP","GUADELOUPE"],
|
|
["GU","GUAM"],
|
|
["GT","GUATEMALA"],
|
|
["GG","GUERNSEY"],
|
|
["GN","GUINEA"],
|
|
["GW","GUINEA-BISSAU"],
|
|
["GY","GUYANA"],
|
|
["HT","HAITI"],
|
|
["HM","HEARD ISLAND AND MCDONALD ISLANDS"],
|
|
["VA","HOLY SEE (VATICAN CITY STATE)"],
|
|
["HN","HONDURAS"],
|
|
["HK","HONG KONG"],
|
|
["HU","HUNGARY"],
|
|
["IS","ICELAND"],
|
|
["IN","INDIA"],
|
|
["ID","INDONESIA"],
|
|
["IR","IRAN, ISLAMIC REPUBLIC OF"],
|
|
["IQ","IRAQ"],
|
|
["IE","IRELAND"],
|
|
["IM","ISLE OF MAN"],
|
|
["IL","ISRAEL"],
|
|
["IT","ITALY"],
|
|
["JM","JAMAICA"],
|
|
["JP","JAPAN"],
|
|
["JE","JERSEY"],
|
|
["JO","JORDAN"],
|
|
["KZ","KAZAKHSTAN"],
|
|
["KE","KENYA"],
|
|
["KI","KIRIBATI"],
|
|
["KP","KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF"],
|
|
["KR","KOREA, REPUBLIC OF"],
|
|
["KW","KUWAIT"],
|
|
["KG","KYRGYZSTAN"],
|
|
["LA","LAO PEOPLE'S DEMOCRATIC REPUBLIC"],
|
|
["LV","LATVIA"],
|
|
["LB","LEBANON"],
|
|
["LS","LESOTHO"],
|
|
["LR","LIBERIA"],
|
|
["LY","LIBYAN ARAB JAMAHIRIYA"],
|
|
["LI","LIECHTENSTEIN"],
|
|
["LT","LITHUANIA"],
|
|
["LU","LUXEMBOURG"],
|
|
["MO","MACAO"],
|
|
["MK","MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF"],
|
|
["MG","MADAGASCAR"],
|
|
["MW","MALAWI"],
|
|
["MY","MALAYSIA"],
|
|
["MV","MALDIVES"],
|
|
["ML","MALI"],
|
|
["MT","MALTA"],
|
|
["MH","MARSHALL ISLANDS"],
|
|
["MQ","MARTINIQUE"],
|
|
["MR","MAURITANIA"],
|
|
["MU","MAURITIUS"],
|
|
["YT","MAYOTTE"],
|
|
["MX","MEXICO"],
|
|
["FM","MICRONESIA, FEDERATED STATES OF"],
|
|
["MD","MOLDOVA, REPUBLIC OF"],
|
|
["MC","MONACO"],
|
|
["MN","MONGOLIA"],
|
|
["ME","MONTENEGRO"],
|
|
["MS","MONTSERRAT"],
|
|
["MA","MOROCCO"],
|
|
["MZ","MOZAMBIQUE"],
|
|
["MM","MYANMAR"],
|
|
["NA","NAMIBIA"],
|
|
["NR","NAURU"],
|
|
["NP","NEPAL"],
|
|
["NL","NETHERLANDS"],
|
|
["AN","NETHERLANDS ANTILLES"],
|
|
["NC","NEW CALEDONIA"],
|
|
["NZ","NEW ZEALAND"],
|
|
["NI","NICARAGUA"],
|
|
["NE","NIGER"],
|
|
["NG","NIGERIA"],
|
|
["NU","NIUE"],
|
|
["NF","NORFOLK ISLAND"],
|
|
["MP","NORTHERN MARIANA ISLANDS"],
|
|
["NO","NORWAY"],
|
|
["OM","OMAN"],
|
|
["PK","PAKISTAN"],
|
|
["PW","PALAU"],
|
|
["PS","PALESTINIAN TERRITORY, OCCUPIED"],
|
|
["PA","PANAMA"],
|
|
["PG","PAPUA NEW GUINEA"],
|
|
["PY","PARAGUAY"],
|
|
["PE","PERU"],
|
|
["PH","PHILIPPINES"],
|
|
["PN","PITCAIRN"],
|
|
["PL","POLAND"],
|
|
["PT","PORTUGAL"],
|
|
["PR","PUERTO RICO"],
|
|
["QA","QATAR"],
|
|
["RE","RÉUNION"],
|
|
["RO","ROMANIA"],
|
|
["RU","RUSSIAN FEDERATION"],
|
|
["RW","RWANDA"],
|
|
["BL","SAINT BARTHÉLEMY"],
|
|
["SH","SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA"],
|
|
["KN","SAINT KITTS AND NEVIS"],
|
|
["LC","SAINT LUCIA"],
|
|
["MF","SAINT MARTIN"],
|
|
["PM","SAINT PIERRE AND MIQUELON"],
|
|
["VC","SAINT VINCENT AND THE GRENADINES"],
|
|
["WS","SAMOA"],
|
|
["SM","SAN MARINO"],
|
|
["ST","SAO TOME AND PRINCIPE"],
|
|
["SA","SAUDI ARABIA"],
|
|
["SN","SENEGAL"],
|
|
["RS","SERBIA"],
|
|
["SC","SEYCHELLES"],
|
|
["SL","SIERRA LEONE"],
|
|
["SG","SINGAPORE"],
|
|
["SK","SLOVAKIA"],
|
|
["SI","SLOVENIA"],
|
|
["SB","SOLOMON ISLANDS"],
|
|
["SO","SOMALIA"],
|
|
["ZA","SOUTH AFRICA"],
|
|
["GS","SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS"],
|
|
["ES","SPAIN"],
|
|
["LK","SRI LANKA"],
|
|
["SD","SUDAN"],
|
|
["SR","SURINAME"],
|
|
["SJ","SVALBARD AND JAN MAYEN"],
|
|
["SZ","SWAZILAND"],
|
|
["SE","SWEDEN"],
|
|
["CH","SWITZERLAND"],
|
|
["SY","SYRIAN ARAB REPUBLIC"],
|
|
["TW","TAIWAN, PROVINCE OF CHINA"],
|
|
["TJ","TAJIKISTAN"],
|
|
["TZ","TANZANIA, UNITED REPUBLIC OF"],
|
|
["TH","THAILAND"],
|
|
["TL","TIMOR-LESTE"],
|
|
["TG","TOGO"],
|
|
["TK","TOKELAU"],
|
|
["TO","TONGA"],
|
|
["TT","TRINIDAD AND TOBAGO"],
|
|
["TN","TUNISIA"],
|
|
["TR","TURKEY"],
|
|
["TM","TURKMENISTAN"],
|
|
["TC","TURKS AND CAICOS ISLANDS"],
|
|
["TV","TUVALU"],
|
|
["UG","UGANDA"],
|
|
["UA","UKRAINE"],
|
|
["AE","UNITED ARAB EMIRATES"],
|
|
["GB","UNITED KINGDOM"],
|
|
["US","UNITED STATES"],
|
|
["UM","UNITED STATES MINOR OUTLYING ISLANDS"],
|
|
["UY","URUGUAY"],
|
|
["UZ","UZBEKISTAN"],
|
|
["VU","VANUATU"],
|
|
["VE","VENEZUELA, BOLIVARIAN REPUBLIC OF"],
|
|
["VN","VIET NAM"],
|
|
["VG","VIRGIN ISLANDS, BRITISH"],
|
|
["VI","VIRGIN ISLANDS, U.S."],
|
|
["WF","WALLIS AND FUTUNA"],
|
|
["EH","WESTERN SAHARA"],
|
|
["YE","YEMEN"],
|
|
["ZM","ZAMBIA"],
|
|
["ZW","ZIMBABWE"],
|
|
);
|
|
|
|
package NetSNMP::Term;
|
|
# gratefully taken from Wayne Thompson's Term::Complete
|
|
# if newer CORE modules could be used this could be removed
|
|
our($complete, $exit, $done, $kill, $erase1, $erase2, $tty_raw_noecho, $tty_restore, $stty, $tty_safe_restore);
|
|
our($tty_saved_state) = '';
|
|
CONFIG: {
|
|
$exit = "\003";
|
|
$done = "\004";
|
|
$kill = "\025";
|
|
$erase1 = "\177";
|
|
$erase2 = "\010";
|
|
foreach my $s (qw(/bin/stty /usr/bin/stty)) {
|
|
if (-x $s) {
|
|
$tty_raw_noecho = "$s raw -echo";
|
|
$tty_restore = "$s -raw echo";
|
|
$tty_safe_restore = $tty_restore;
|
|
$stty = $s;
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub Complete {
|
|
my($prompt, $dflt, $help, $match, @cmp_lst);
|
|
my (%help, $cmp, $test, $l, @match);
|
|
my ($return, $r, $exitting) = ("", 0, 0);
|
|
my $tab;
|
|
|
|
$prompt = shift;
|
|
$dflt = shift;
|
|
$help = shift;
|
|
$match = shift;
|
|
|
|
if (ref $_[0] and ref($_[0][0])) {
|
|
@cmp_lst = @{$_[0]};
|
|
} else {
|
|
@cmp_lst = @_;
|
|
}
|
|
@cmp_lst = map {if (ref($_)) { $help{$_->[0]}=$_->[1]; $_->[0]} else {$_;}} @cmp_lst;
|
|
|
|
# Attempt to save the current stty state, to be restored later
|
|
if (defined $stty && defined $tty_saved_state && $tty_saved_state eq '') {
|
|
$tty_saved_state = qx($stty -g 2>/dev/null);
|
|
if ($?) {
|
|
# stty -g not supported
|
|
$tty_saved_state = undef;
|
|
} else {
|
|
$tty_saved_state =~ s/\s+$//g;
|
|
$tty_restore = qq($stty "$tty_saved_state" 2>/dev/null);
|
|
}
|
|
}
|
|
system $tty_raw_noecho if defined $tty_raw_noecho;
|
|
LOOP: {
|
|
local $_;
|
|
print($prompt, $return);
|
|
while (($_ = getc(STDIN)) ne "\r") {
|
|
CASE: {
|
|
# (TAB) attempt completion
|
|
$_ eq "\t" && do {
|
|
if ($tab) {
|
|
print(join("\r\n", '', map {exists $help{$_} ? sprintf("\t%-10.10s - %s", $_, $help{$_}) :
|
|
"\t$_"} grep(/^\Q$return/, @cmp_lst)), "\r\n");
|
|
$tab--;
|
|
redo LOOP;
|
|
}
|
|
@match = grep(/^\Q$return/, @cmp_lst);
|
|
unless ($#match < 0) {
|
|
$l = length($test = shift(@match));
|
|
foreach $cmp (@match) {
|
|
until (substr($cmp, 0, $l) eq substr($test, 0, $l)) {
|
|
$l--;
|
|
}
|
|
}
|
|
print("\a");
|
|
print($test = substr($test, $r, $l - $r));
|
|
$r = length($return .= $test);
|
|
}
|
|
$tab++;
|
|
last CASE;
|
|
};
|
|
|
|
# (^C) exit
|
|
$_ eq $exit && do {
|
|
print("\r\naborting application...\r\n");
|
|
$exitting++;
|
|
undef $return;
|
|
last LOOP;
|
|
};
|
|
|
|
# (^D) done
|
|
$_ eq $done && do {
|
|
undef $return;
|
|
last LOOP;
|
|
};
|
|
|
|
# (?) show help if available
|
|
$_ eq '?' && do {
|
|
print("\r\n$help\r\n");
|
|
if (exists $help{$return}) {
|
|
print("\t$return - $help{$return}\r\n");
|
|
} else {
|
|
print(join("\r\n", map {exists $help{$_} ? "\t$_ - $help{$_}" :
|
|
"\t$_"} grep(/^\Q$return/, @cmp_lst)), "\r\n");
|
|
}
|
|
redo LOOP;
|
|
};
|
|
|
|
# (^U) kill
|
|
$_ eq $kill && do {
|
|
if ($r) {
|
|
$r = 0;
|
|
$return = "";
|
|
print("\r\n");
|
|
redo LOOP;
|
|
}
|
|
last CASE;
|
|
};
|
|
|
|
# (DEL) || (BS) erase
|
|
($_ eq $erase1 || $_ eq $erase2) && do {
|
|
if ($r) {
|
|
print("\b \b");
|
|
chop($return);
|
|
$r--;
|
|
}
|
|
last CASE;
|
|
};
|
|
|
|
# printable char
|
|
ord >= 32 && do {
|
|
$return .= $_;
|
|
$r++;
|
|
print;
|
|
last CASE;
|
|
};
|
|
}
|
|
}
|
|
|
|
if (defined $return) {
|
|
if (length($return) == 0 or $return eq $dflt) {
|
|
$return = $dflt;
|
|
} elsif ($match == $NetSNMP::Cert::MATCH and scalar(grep {/^$return$/} @cmp_lst) != 1 or
|
|
$match == $NetSNMP::Cert::PREMATCH and scalar(grep {$return=~/$_/} @cmp_lst) != 1) {
|
|
$r = 0;
|
|
$return = "";
|
|
print("\r\nChoose a valid option, or ^D to exit\r\n");
|
|
redo LOOP;
|
|
}
|
|
}
|
|
}
|
|
DONE:
|
|
# system $tty_restore if defined $tty_restore;
|
|
if (defined $tty_saved_state && defined $tty_restore && defined $tty_safe_restore) {
|
|
system $tty_restore;
|
|
if ($?) {
|
|
# tty_restore caused error
|
|
system $tty_safe_restore;
|
|
}
|
|
}
|
|
|
|
exit(1) if $exitting;
|
|
print("\n");
|
|
return $return;
|
|
}
|
|
|
|
package NetSNMP::Cert;
|
|
|
|
our $VERSION = '0.2.9';
|
|
|
|
our $PROG = ::basename($0);
|
|
our $DEBUG = 0;
|
|
our $OK = 0;
|
|
our $ERR = -1;
|
|
|
|
# user input param
|
|
our $MATCH = 1;
|
|
our $PREMATCH = 2;
|
|
|
|
# Load LWP if possible to import cert from URL
|
|
eval('use LWP::UserAgent;');
|
|
our $haveUserAgent = ($@ ? 0 : 1);
|
|
|
|
# required executables
|
|
our $OPENSSL = $ENV{NET_SNMP_CRT_OPENSSL} || 'openssl';
|
|
our $CFGTOOL = $ENV{NET_SNMP_CRT_CFGTOOL} || 'net-snmp-config';
|
|
|
|
# default app config file
|
|
our $CFGFILE = $ENV{NET_SNMP_CRT_CFGFILE} || 'net-snmp-cert.conf';
|
|
|
|
# default OpenSSL config files
|
|
our $SSLCFGIN = $ENV{NET_SNMP_CRT_SSLCFGIN} || 'openssl.in';
|
|
our $SSLCFGOUT = $ENV{NET_SNMP_CRT_SSLCFGIN} || '.openssl.conf';
|
|
our $SSLCFG = $ENV{NET_SNMP_CRT_SSLCFG};
|
|
|
|
# default cmd logs
|
|
our $OUTLOG = $ENV{NET_SNMP_CRT_OUTLOG} || '.cmd.out.log';
|
|
our $ERRLOG = $ENV{NET_SNMP_CRT_ERRLOG} || '.cmd.err.log';
|
|
|
|
# default cert dirs
|
|
our $TLSDIR = $ENV{NET_SNMP_CRT_TLSDIR} || 'tls';
|
|
our $CRTDIR = $ENV{NET_SNMP_CRT_CRTDIR} || 'certs';
|
|
our $NEWCRTDIR = $ENV{NET_SNMP_CRT_NEWCRTDIR}|| 'newcerts';
|
|
our $CADIR = $ENV{NET_SNMP_CRT_CADIR} || 'ca-certs';
|
|
our $PRIVDIR = $ENV{NET_SNMP_CRT_PRIVDIR} || 'private';
|
|
|
|
our $SERIAL = $ENV{NET_SNMP_CRT_SERIAL} || '.serial';
|
|
our $INDEX = $ENV{NET_SNMP_CRT_INDEX} || '.index';
|
|
|
|
our $DEFCADAYS = $ENV{NET_SNMP_CRT_DEFCADAYS} || 1825;
|
|
our $DEFCRTDAYS = $ENV{NET_SNMP_CRT_DEFCRTDAYS}|| 365;
|
|
|
|
our @TLSDIRS = ($CRTDIR, $NEWCRTDIR, $CADIR, $PRIVDIR);
|
|
|
|
our @CRTSUFFIXES = qw(.pem .csr .der .crt);
|
|
|
|
our @X509FMTS = qw(text modulus serial subject_hash issuer_hash hash subject
|
|
purpose issuer startdate enddate dates fingerprint C);
|
|
|
|
sub dprint {
|
|
my $str = shift;
|
|
my $opts = shift;
|
|
my $dlevel = shift || 1;
|
|
my $flag = (defined $opts ? int($opts->{'debug'} >= $dlevel) : 1);
|
|
my ($pkg, $file, $line, $sub) = caller(1);
|
|
my ($pkg0, $file0, $line0) = caller(0);
|
|
print("${sub}():$line0: $str") if $flag;
|
|
}
|
|
|
|
sub dwarn {
|
|
my $str = shift;
|
|
my $opts = shift;
|
|
my $dlevel = shift || 1;
|
|
my $flag = (defined $opts ? $opts->{'debug'} >= $dlevel : 1);
|
|
my ($pkg, $file, $line, $sub) = caller(1);
|
|
my ($pkg0, $file0, $line0) = caller(0);
|
|
warn("${sub}():$line0: $str") if $flag;
|
|
}
|
|
|
|
sub vprint {
|
|
my $str = shift;
|
|
my $opts = shift;
|
|
my $flag = (defined $opts ? $opts->{'verbose'} : 1);
|
|
my $debug = (defined $opts ? $opts->{'debug'} : 1);
|
|
my ($pkg, $file, $line, $sub) = caller(1);
|
|
my ($pkg0, $file0, $line0) = caller(0);
|
|
$str = ($debug ? "${sub}():$line0: $str" : "$str");
|
|
print("$str") if $flag;
|
|
}
|
|
|
|
sub vwarn {
|
|
my $str = shift;
|
|
my $opts = shift;
|
|
my $flag = (defined $opts ? $opts->{'verbose'} : 1);
|
|
my ($pkg, $file, $line, $sub) = caller(1);
|
|
my ($pkg0, $file0, $line0) = caller(0);
|
|
warn("${sub}():$line0: $str") if $flag;
|
|
}
|
|
|
|
sub rsystem {
|
|
my $cmd = shift;
|
|
my $flag = shift;
|
|
|
|
# if not running as root try to use sudo
|
|
if ($>) {
|
|
$cmd = "sudo $flag $cmd";
|
|
} else {
|
|
if ($flag =~ /-b/) {
|
|
$cmd = "$cmd \&";
|
|
}
|
|
}
|
|
die("cmd failed($!): $cmd\n") if system("$cmd");
|
|
}
|
|
|
|
sub usystem {
|
|
my $cmd = shift;
|
|
my $opts = shift;
|
|
my $ret;
|
|
|
|
unlink $NetSNMP::Cert::OUTLOG if -e $NetSNMP::Cert::OUTLOG;
|
|
unlink $NetSNMP::Cert::ERRLOG if -e $NetSNMP::Cert::ERRLOG;
|
|
|
|
$ret = system("$cmd");
|
|
|
|
if ($ret) {
|
|
if ($opts->{'verbose'}) {
|
|
system("cat $NetSNMP::Cert::OUTLOG") if -r $NetSNMP::Cert::OUTLOG;
|
|
system("cat $NetSNMP::Cert::ERRLOG") if -r $NetSNMP::Cert::ERRLOG;
|
|
}
|
|
die("cmd failed($!): $cmd\n");
|
|
}
|
|
}
|
|
|
|
sub home_dir {
|
|
my $cdir = ::getcwd();
|
|
|
|
chdir("~");
|
|
my $dir = ::getcwd();
|
|
chdir($cdir);
|
|
|
|
return $dir;
|
|
}
|
|
|
|
sub find_bin_dir {
|
|
# This code finds the path to the currently invoked program and
|
|
my $dir;
|
|
|
|
$0 =~ m%^(([^/]*)(.*)/)([^/]+)$%;
|
|
if ($1) {
|
|
if ($2) {
|
|
# Invoked using a relative path. CD there and use pwd.
|
|
$dir = `cd $1 && pwd`;
|
|
chomp $dir;
|
|
} else {
|
|
# Invoked using an absolute path; that's it!
|
|
$dir = $3;
|
|
}
|
|
} else {
|
|
# No path. Look in PATH for the first instance.
|
|
foreach my $p (split /:/, $ENV{PATH}) {
|
|
$p ||= '.';
|
|
-x "$p/$4" or next;
|
|
$dir = $p;
|
|
}
|
|
}
|
|
$dir or die "Cannot locate program '$0'!";
|
|
}
|
|
|
|
sub fq_rel_path {
|
|
my $path = shift;
|
|
my $cwd = ::getcwd();
|
|
my $rdir = shift || $cwd;
|
|
|
|
chdir($rdir) or die("can't change directory: $rdir");
|
|
# get fully qualified path
|
|
if ($path !~ m|^/|) {
|
|
my $pwd = `pwd`; chomp $pwd;
|
|
$path = "$pwd/$path";
|
|
$path = ::realpath($path) if -e $path;
|
|
}
|
|
chdir($cwd) or die("can't change directory: $cwd");
|
|
|
|
return $path;
|
|
}
|
|
|
|
sub dir_empty
|
|
{
|
|
my $path = shift;
|
|
opendir(DIR, $path);
|
|
foreach (readdir(DIR)) {
|
|
next if /^\.\.?$/;
|
|
closedir(DIR);
|
|
return 0;
|
|
}
|
|
closedir(DIR);
|
|
return 1;
|
|
}
|
|
|
|
sub make_dirs {
|
|
my $opts = shift;
|
|
my $dir = shift;
|
|
my $mode = shift;
|
|
my @dirs = @_;
|
|
|
|
my $wd = ::getcwd();
|
|
|
|
NetSNMP::Cert::dprint("make dirs [$dir:@dirs] from $wd\n", $opts);
|
|
|
|
::mkpath($dir, $opts->{'debug'}, $mode) or die("error - can't make $dir")
|
|
if defined $dir and not -d $dir;
|
|
|
|
foreach my $subdir (@dirs) {
|
|
my $d = "$subdir";
|
|
$d = "$dir/$d" if defined $dir;
|
|
NetSNMP::Cert::dprint("making directory: $d\n", $opts) unless -d $d;
|
|
mkdir($d, $mode) or die("error - can't make $d") unless -d $d;
|
|
}
|
|
}
|
|
|
|
sub is_url {
|
|
my $url = shift;
|
|
|
|
return $url =~ /^(?#Protocol)(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?#Username:Password)(?:\w+:\w+@)?(?#Subdomains)(?:(?:[-\w]+\.)+(?#TopLevel Domains)(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?#Port)(?::[\d]{1,5})?(?#Directories)(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?#Query)(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?#Anchor)(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?$/;
|
|
}
|
|
|
|
sub in_set {
|
|
my $elem = shift;
|
|
my $set = shift;
|
|
for my $e (eval($set)) {
|
|
return 1 if $e == $elem;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub in_arr {
|
|
my $elem = shift;
|
|
my $arr = shift;
|
|
for my $e (@{$arr}) {
|
|
return 1 if $e eq $elem;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub map_bool {
|
|
my $val = shift;
|
|
|
|
return 1 if $val =~ /^true$/i;
|
|
return 0 if $val =~ /^false$/i;
|
|
return $val;
|
|
}
|
|
|
|
sub version {
|
|
my $ret = (@_ ? shift : 1);
|
|
print "$NetSNMP::Cert::PROG: $NetSNMP::Cert::VERSION\n";
|
|
exit($ret);
|
|
}
|
|
|
|
sub GetOptsFromArray {
|
|
my $args = shift;
|
|
local *ARGV;
|
|
@ARGV = @$args;
|
|
my $ret = ::GetOptions(@_);
|
|
@$args = @ARGV; # GetOptions strips out the ones it handles and leaves others
|
|
return $ret;
|
|
}
|
|
sub pull_cmd {
|
|
my $args = shift;
|
|
my $cmd;
|
|
|
|
foreach (@{$args}) {
|
|
if (/^(gence?rt|genca|gencsr|signcsr|showcas?|showce?rts?|import)$/) {
|
|
$cmd = $1;
|
|
}
|
|
}
|
|
|
|
@{$args} = grep {!/^$cmd$/} @{$args};
|
|
|
|
return $cmd;
|
|
}
|
|
|
|
|
|
sub usage {
|
|
my $ret = (@_ ? shift : 1);
|
|
my $arg = shift;
|
|
|
|
print "Command not implmeneted yet: $arg\n" if $ret == 2;
|
|
print "Unknown: $arg\n" if $ret == 3;
|
|
print "\n NAME:\n";
|
|
print " $NetSNMP::Cert::PROG: [$NetSNMP::Cert::VERSION] - ";
|
|
print "Net-SNMP Certificate Management Tool\n";
|
|
print "\n DESCRIPTION:\n";
|
|
print " net-snmp-cert creates, signs, installs and displays X.509\n";
|
|
print " certificates used in the operation of Net-SNMP/(D)TLS.\n";
|
|
print "\n SYNOPSIS:\n";
|
|
print " net-snmp-cert [--help|-?]\n";
|
|
print " net-snmp-cert [--version|-V]\n";
|
|
print " net-snmp-cert genca [<flags>] [<dn-opts>] [--with-ca <ca>]\n";
|
|
print " net-snmp-cert gencert [<flags>] [<dn-opts>] [--with-ca <ca>]\n";
|
|
print " net-snmp-cert gencsr [<flags>] [<dn-opts>] [--from-crt <crt>]\n";
|
|
print " net-snmp-cert signcsr [<flags>] [--install] --csr <csr> --with-ca <ca>\n";
|
|
print " net-snmp-cert showca [<flags>] [<format-opts>] [<file>|<search-tag>]\n";
|
|
print " net-snmp-cert showcert [<flags>] [<format-opts>] [<file>|<search-tag>]\n";
|
|
print " net-snmp-cert import [<flags>] <file|url> [<key>]\n";
|
|
print "\n COMMANDS:\n";
|
|
print " genca -- generate a signed CA certificate suitable for signing other\n";
|
|
print " certificates. default: self-signed unless --with-ca <ca> supplied\n\n";
|
|
print " gencert -- generate a signed certificate suitable for identification, or\n";
|
|
print " validation. default: self-signed unless --with-ca <ca> supplied\n\n";
|
|
print " gencsr -- generate a certificate signing request. will create a new\n";
|
|
print " key and certificate unless --from-crt <crt> supplied\n\n";
|
|
print " signcsr -- sign a certificate signing request specified by --csr <csr>\n";
|
|
print " with the CA certificate specified by --with-ca <ca>\n";
|
|
print " import -- import an identity or CA certificate, optionally import <key>\n";
|
|
print " if an URL is passed, will attempt to import certificate from site\n";
|
|
print " showca,\n";
|
|
print " showcert -- show CA or identity certificate(s). may pass fully qualified\n";
|
|
print " file or directory name, or a search-tag which prefix matches\n";
|
|
print " installed CA or identity certificate file name(s)\n";
|
|
print " see FORMAT OPTIONS to specify output format\n\n";
|
|
print "\n FLAGS:\n";
|
|
print " -?, --help -- show this text and exit\n";
|
|
print " -V, --version -- show version string and exit\n";
|
|
print " -D, --debug -- raise debug level (-D -D for higher level)\n";
|
|
print " -F, --force -- force overwrite of existing output files\n";
|
|
print " -I, --nointeractive -- non-interactive run (default interactive)\n";
|
|
print " -Q, --noverbose -- non-verbose output (default verbose)\n";
|
|
print " -C, --cfgdir <dir> -- configuration dir (see man(5) snmp_config)\n";
|
|
print " -T, --tlsdir <dir> -- root for cert storage (default <cfgdir>/tls)\n";
|
|
print " -f, --cfgfile <file> -- config (default <cfgdir>/net-snmp-cert.conf)\n";
|
|
print " -i, --identity <id> -- identity to use from config\n";
|
|
print " -t, --tag <tag> -- application tag (default 'snmp')\n";
|
|
print " --<cfg-param>[=<val>] -- additional config params\n";
|
|
print "\n CERTIFICATE OPTIONS (<cert-opts>):\n";
|
|
print " -a, --with-ca <ca> -- CA certificate used to sign request\n";
|
|
print " -A, --ca <ca> -- explicit output CA certificate\n";
|
|
print " -r, --csr <csr> -- certificate signing request\n";
|
|
print " -x, --from-crt <crt> -- input certificate for current operation\n";
|
|
print " -X, --crt <crt> -- explicit output certificate\n";
|
|
print " -y, --install -- install result in local repository\n";
|
|
print "\n DISTINGUISHED NAME OPTIONS (<dn-opts>):\n";
|
|
print " -e, --email <email> -- email name\n";
|
|
print " -h, --host <host> -- DNS host name, or IP address\n";
|
|
print " -d, --days <days> -- number of days certificate is valid\n";
|
|
print " -n, --cn <cn> -- common name (CN)\n";
|
|
print " -o, --org <org> -- organiztion name\n";
|
|
print " -u, --unit <unit> -- organiztion unit name\n";
|
|
print " -c, --country <country> -- country code (e.g., US)\n";
|
|
print " -p, --province <province> -- province name (synomynous w/ state)\n";
|
|
print " -p, --state <state> -- state name (synomynous w/ province)\n";
|
|
print " -l, --locality <locality> -- locality name (e.g, town)\n";
|
|
print " -s, --san <san> -- subjectAltName, repeat for each <san>\n";
|
|
print " -- <san> value format (<FMT>:<VAL>):\n";
|
|
print " -- dirName:/usr/share/snmp/tls\n";
|
|
print " -- DNS:net-snmp.org\n";
|
|
print " -- email:admin\@net-snmp.org\n";
|
|
print " -- IP:192.168.1.1\n";
|
|
print " -- RID:1.1.3.6\n";
|
|
print " -- URI:http://net-snmp.org\n";
|
|
print "\n FORMAT OPTIONS (<format-opts>): \n";
|
|
print " --brief -- minimized output (values only where applicable)\n";
|
|
print " --text -- full text description\n";
|
|
print " --subject -- subject description\n";
|
|
print " --subject_hash -- hash of subject for indexing\n";
|
|
print " --issuer -- issuer description\n";
|
|
print " --issuer_hash -- hash of issuer for indexing\n";
|
|
print " --fingerprint -- SHA1 digest of DER\n";
|
|
print " --serial -- serial number\n";
|
|
print " --modulus -- modulus of the public key\n";
|
|
print " --dates -- start and end dates\n";
|
|
print " --purpose -- displays allowed uses\n";
|
|
print " --C -- C code description\n";
|
|
print "\n EXAMPLES: \n";
|
|
print " net-snmp-cert genca --cn ca-net-snmp.org --days 1000\n";
|
|
print " net-snmp-cert genca -f .snmp/net-snmp-cert.conf -i nocadm -I\n";
|
|
print " net-snmp-cert gencert -t snmpd --cn host.net-snmp.org\n";
|
|
print " net-snmp-cert gencsr -t snmpapp\n";
|
|
print " net-snmp-cert signcsr --csr snmpapp --with-ca ca-net-snmp.org\n";
|
|
print " net-snmp-cert showcerts --subject --issuer --dates snmpapp\n";
|
|
print " net-snmp-cert showcas --fingerprint ca-net-snmp.org --brief\n";
|
|
print " net-snmp-cert import ca-third-party.crt\n";
|
|
print " net-snmp-cert import signed-request.crt signed-request.key\n\n";
|
|
|
|
exit $ret;
|
|
}
|
|
|
|
sub set_default {
|
|
my $opts = shift;
|
|
my $config = shift;
|
|
my $field = shift;
|
|
my $val = shift;
|
|
|
|
if (not defined $opts->{$field}) {
|
|
my $cval = $config->inherit($field);
|
|
$opts->{$field} = (defined $cval ? $cval : $val);
|
|
}
|
|
}
|
|
|
|
sub cfg_path {
|
|
my $path;
|
|
|
|
$path = `$NetSNMP::Cert::CFGTOOL --snmpconfpath`;
|
|
chomp $path;
|
|
return (wantarray ? split(':', $path) : $path);
|
|
}
|
|
|
|
sub find_cfgfile {
|
|
my $dir = shift;
|
|
my $file = shift;
|
|
|
|
if (defined $dir and -d $dir and
|
|
defined $file and $file !~ /^[\.\/]/) {
|
|
return fq_rel_path("$dir/$file") if -f "$dir/$file";
|
|
}
|
|
|
|
if (defined $file) {
|
|
if (-f $file) {
|
|
return fq_rel_path($file);
|
|
} else {
|
|
return $file; # file is not found, complain later
|
|
}
|
|
}
|
|
|
|
my @path = cfg_path();
|
|
unshift(@path, $dir) if defined $dir;
|
|
while (@path) {
|
|
my $p = pop(@path);
|
|
next if $p eq '/var/lib/snmp';
|
|
if (-r "$p/$NetSNMP::Cert::CFGFILE") {
|
|
return fq_rel_path("$p/$NetSNMP::Cert::CFGFILE");
|
|
}
|
|
}
|
|
|
|
return $file;
|
|
}
|
|
|
|
sub find_cfgdir {
|
|
my $dir = shift;
|
|
my $file = shift;
|
|
|
|
if (defined $dir) {
|
|
$dir = NetSNMP::Cert::fq_rel_path($dir);
|
|
return $dir;
|
|
}
|
|
|
|
if (defined $file and -f $file) {
|
|
$dir = ::dirname($file);
|
|
return NetSNMP::Cert::fq_rel_path($dir);
|
|
} else {
|
|
my @path = cfg_path();
|
|
# search first for writeable tls dir
|
|
# for root search top down, for user bottom up
|
|
while (@path) {
|
|
$dir = ($> ? pop(@path): shift(@path));
|
|
next if $dir eq '/var/lib/snmp';
|
|
return $dir if -d "$dir/$NetSNMP::Cert::TLSDIR" and -w "$dir/$NetSNMP::Cert::TLSDIR";
|
|
}
|
|
@path = cfg_path();
|
|
# if no tls dir found, pick first writable config dir
|
|
# for root search top down, for user bottom up
|
|
while (@path) {
|
|
$dir = ($> ? pop(@path): shift(@path));
|
|
next if $dir eq '/var/lib/snmp';
|
|
return $dir if -d $dir and -w $dir;
|
|
}
|
|
my $home = $ENV{HOME} || die "Unable to determine home directory: set \$HOME\n";
|
|
return ($> ? "$home/.snmp" : "/usr/share/snmp"); # XXX hack - no dirs existed or were writable
|
|
}
|
|
# this should never happen
|
|
return undef;
|
|
}
|
|
|
|
sub find_certs {
|
|
my $opts = shift;
|
|
my $dir = shift || 'certs';
|
|
my $targ = shift;
|
|
my $suffix_regex;
|
|
my $cwd = ::getcwd();
|
|
|
|
if ($dir eq 'csrs') {
|
|
$dir = $NetSNMP::Cert::NEWCRTDIR;
|
|
$suffix_regex = '\.csr|\.pem';
|
|
} else {
|
|
$suffix_regex = '\.crt|\.pem';
|
|
}
|
|
|
|
NetSNMP::Cert::dprint("find_certs(1:$cwd):$dir:$targ:$suffix_regex\n", $opts);
|
|
|
|
if ($targ =~ /\.?\//) {
|
|
# see if targ could be file - calc orig. rel. path
|
|
my $arg = NetSNMP::Cert::fq_rel_path($targ, $opts->{'rdir'});
|
|
NetSNMP::Cert::dprint("find_certs(2):$dir:$targ:$arg\n", $opts);
|
|
# targ is a file name - use it
|
|
return (wantarray ? ($arg) : $arg) if -f $arg;
|
|
# else mark as dir if it is
|
|
$targ = "$arg" if -d $arg;
|
|
}
|
|
|
|
$targ =~ s/\/*$/\// if -d $targ;
|
|
|
|
NetSNMP::Cert::dprint("find_certs(3):$dir:$targ\n", $opts);
|
|
|
|
# find certs in targ if a dir (in tlsdir, or relative)
|
|
$dir = $1 if $targ =~ s/^(\S+)\/([^\/\s]*)$/$2/ and -d $1;
|
|
|
|
NetSNMP::Cert::dprint("find_certs(4):${dir}:$targ:$cwd\n", $opts);
|
|
|
|
my @certs;
|
|
my $glob = "$dir/$targ";
|
|
foreach (<$dir/*>) {
|
|
NetSNMP::Cert::dprint("checking($dir:$targ): $_\n", $opts);
|
|
next unless /^$dir\/$targ(.*$suffix_regex)?$/;
|
|
# return exact match if not wantarray()
|
|
return $_ if (not wantarray()) and /^$dir\/$targ($suffix_regex)?$/;
|
|
NetSNMP::Cert::dprint("pushing: $_\n", $opts);
|
|
push(@certs, $_);
|
|
}
|
|
|
|
return (wantarray ? @certs : $certs[0]);
|
|
}
|
|
|
|
sub check_output_file {
|
|
my $opts = shift;
|
|
my $file = shift;
|
|
my $interactive = shift;
|
|
my $force = shift;
|
|
my $continue = 1;
|
|
|
|
if (-w $file) {
|
|
if ($interactive and not $force) {
|
|
print "Output file ($file) exists; will be overwritten\nContinue [y/N]: ";
|
|
$continue = <STDIN>; chomp $continue;
|
|
$continue = 0 if $continue =~ /^\s*$|^no?\s*$/i;
|
|
} else {
|
|
if ($force) {
|
|
NetSNMP::Cert::vprint("Output file ($file) exists; overwriting...\n", $opts);
|
|
} else {
|
|
NetSNMP::Cert::vprint("Output file ($file) exists; exiting...\n", $opts);
|
|
$continue = 0;
|
|
}
|
|
}
|
|
} elsif (-e $file) {
|
|
NetSNMP::Cert::vprint("Output file ($file) not writable; exiting...\n", $opts);
|
|
$continue = 0;
|
|
}
|
|
exit(1) unless $continue;
|
|
}
|
|
|
|
sub is_server {
|
|
my $tag = shift;
|
|
return 1 if $tag eq 'snmpd' or $tag eq 'snmptrapd';
|
|
return 0;
|
|
}
|
|
|
|
sub is_ca_cert {
|
|
my $crt = shift;
|
|
my $output = `openssl x509 -in '$crt' -text -noout`;
|
|
return ($output =~ /^\s*CA:TRUE\s*$/m ? 1 : 0);
|
|
}
|
|
|
|
sub x509_format {
|
|
my $opts = shift;
|
|
my $fmt;
|
|
|
|
foreach my $f (@NetSNMP::Cert::X509FMTS) {
|
|
$fmt .= " -$f" if defined $opts->{$f};
|
|
}
|
|
|
|
return $fmt;
|
|
}
|
|
|
|
sub make_openssl_conf {
|
|
my $file = shift;
|
|
return if -r $file;
|
|
|
|
open(F, ">$file") or die("could not open $file");
|
|
|
|
print F <<'END';
|
|
#
|
|
# Net-SNMP (D)TLS OpenSSL configuration file.
|
|
#
|
|
|
|
rdir = .
|
|
dir = $ENV::DIR
|
|
RANDFILE = $rdir/.rand
|
|
MD = sha1
|
|
KSIZE = 2048
|
|
CN = net-snmp.org
|
|
EMAIL = admin@net-snmp.org
|
|
COUNTRY = US
|
|
STATE = CA
|
|
LOCALITY = Davis
|
|
ORG = Net-SNMP
|
|
ORG_UNIT = Development
|
|
SAN = email:copy
|
|
DAYS = 365
|
|
CRLDAYS = 30
|
|
|
|
default_days = $ENV::DAYS # how long to certify for
|
|
default_crl_days= $ENV::CRLDAYS # how long before next CRL
|
|
default_md = $ENV::MD # which md to use.
|
|
|
|
database = $dir/.index # database index file.
|
|
serial = $dir/.serial # The current serial number
|
|
certs = $rdir/certs # identity certs
|
|
new_certs_dir = $dir/newcerts # default place for new certs.
|
|
ca_certs_dir = $rdir/ca-certs # default place for new certs.
|
|
key_dir = $rdir/private
|
|
|
|
crl_dir = $dir/crl # crl's
|
|
crlnumber = $dir/.crlnumber # the current crl number
|
|
# must be commented out to leave V1 CRL
|
|
crl = $crl_dir/crl.pem # The current CRL
|
|
|
|
preserve = no # keep passed DN ordering
|
|
unique_subject = yes # Set to 'no' to allow creation of
|
|
# certificates with same subject.
|
|
# Extra OBJECT IDENTIFIER info:
|
|
oid_section = new_oids
|
|
|
|
[ new_oids ]
|
|
|
|
# Add new OIDs in here for use by 'ca' and 'req'.
|
|
# Add a simple OID like this:
|
|
# testoid1=1.2.3.4
|
|
# Use config file substitution like this:
|
|
# testoid2=${testoid1}.5.6
|
|
|
|
####################################################################
|
|
[ ca ]
|
|
default_ca = CA_default # The default ca section
|
|
|
|
####################################################################
|
|
[ CA_default ]
|
|
# certificate = $ca_certs_dir/$ENV::TAG.crt # CA certificate so sign with
|
|
name_opt = ca_default # Subject Name options
|
|
cert_opt = ca_default # Certificate field options
|
|
policy = policy_match
|
|
copy_extensions = copy # copy v3 extensions (subjectAltName)
|
|
subjectAltName = copy
|
|
|
|
# For the CA policy
|
|
[ policy_match ]
|
|
countryName = match
|
|
stateOrProvinceName = match
|
|
organizationName = match
|
|
organizationalUnitName = optional
|
|
commonName = supplied
|
|
emailAddress = optional
|
|
|
|
# For the 'anything' policy
|
|
# At this point in time, you must list all acceptable 'object'
|
|
# types.
|
|
[ policy_anything ]
|
|
countryName = optional
|
|
stateOrProvinceName = optional
|
|
localityName = optional
|
|
organizationName = optional
|
|
organizationalUnitName = optional
|
|
commonName = supplied
|
|
emailAddress = optional
|
|
|
|
####################################################################
|
|
[ req ]
|
|
default_bits = $ENV::KSIZE
|
|
default_md = $ENV::MD
|
|
distinguished_name = req_distinguished_name
|
|
string_mask = MASK:0x2002
|
|
req_extensions = v3_req
|
|
x509_extensions = v3_user_create
|
|
|
|
[ req_distinguished_name ]
|
|
countryName = Country Name (2 letter code)
|
|
countryName_default = $ENV::COUNTRY
|
|
countryName_min = 2
|
|
countryName_max = 2
|
|
|
|
stateOrProvinceName = State or Province Name (full name)
|
|
stateOrProvinceName_default = $ENV::STATE
|
|
|
|
localityName = Locality Name (eg, city)
|
|
localityName_default = $ENV::LOCALITY
|
|
|
|
0.organizationName = Organization Name (eg, company)
|
|
0.organizationName_default = $ENV::ORG
|
|
|
|
organizationalUnitName = Organizational Unit Name (eg, section)
|
|
organizationalUnitName_default = $ENV::ORG_UNIT
|
|
|
|
commonName = Common Name (eg, your name or your server\'s hostname)
|
|
commonName_max = 64
|
|
commonName_default = $ENV::CN
|
|
|
|
emailAddress = Email Address
|
|
emailAddress_max = 64
|
|
emailAddress_default = $ENV::EMAIL
|
|
|
|
[ v3_req ]
|
|
|
|
# Extensions to add to a certificate request
|
|
basicConstraints = CA:FALSE
|
|
|
|
# Import the email address and/or specified SANs.
|
|
%[subjectAltName = $ENV::SAN]
|
|
|
|
[ v3_user_create ]
|
|
|
|
# Extensions to add to a certificate request
|
|
basicConstraints = CA:FALSE
|
|
#keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
|
|
|
# PKIX recommendation.
|
|
subjectKeyIdentifier = hash
|
|
authorityKeyIdentifier = keyid:always,issuer:always
|
|
|
|
# Import the email address and/or specified SANs.
|
|
%[subjectAltName = $ENV::SAN]
|
|
|
|
[ v3_ca_create ]
|
|
# Extensions to add to a CA certificate
|
|
basicConstraints = CA:TRUE
|
|
# This will be displayed in Netscape's comment listbox.
|
|
nsComment = "OpenSSL Generated Certificate (net-snmp)"
|
|
|
|
# PKIX recommendation.
|
|
subjectKeyIdentifier = hash
|
|
authorityKeyIdentifier = keyid:always,issuer:always
|
|
|
|
# Import the email address and/or specified SANs.
|
|
%[subjectAltName = $ENV::SAN]
|
|
|
|
[ v3_ca_sign ]
|
|
# Extensions to add when 'ca' signs a request.
|
|
basicConstraints = CA:FALSE
|
|
# This will be displayed in Netscape's comment listbox.
|
|
nsComment = "OpenSSL Generated Certificate (net-snmp)"
|
|
|
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
|
|
|
# PKIX recommendations harmless if included in all certificates.
|
|
subjectKeyIdentifier = hash
|
|
authorityKeyIdentifier = keyid:always,issuer:always
|
|
|
|
[ v3_ca_sign_ca ]
|
|
# Extensions to add when 'ca' signs a ca request.
|
|
basicConstraints = CA:TRUE
|
|
|
|
# This will be displayed in Netscape's comment listbox.
|
|
nsComment = "OpenSSL Generated Certificate (net-snmp)"
|
|
|
|
# PKIX recommendations harmless if included in all certificates.
|
|
subjectKeyIdentifier = hash
|
|
authorityKeyIdentifier = keyid:always,issuer:always
|
|
|
|
END
|
|
|
|
}
|
|
|
|
|
|
package NetSNMP::Cert::Obj;
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $cfield = shift;
|
|
my $parent = shift;
|
|
my $ind = shift;
|
|
my $this = {};
|
|
bless($this, $class);
|
|
|
|
# initialize hash of keys which are dynamically created or internal
|
|
$this->{'AUTOKEYS'}{'AUTOKEYS'}++;
|
|
|
|
# store a reference to ourselves so our children can find us
|
|
$this->autoSet('CFIELD', $cfield);
|
|
$this->autoSet($cfield , $this);
|
|
$this->autoSet('CHILDREN', []);
|
|
$this->autoSet('INDEX', $ind) if defined $ind;
|
|
|
|
if (defined $parent) {
|
|
# cache 'APP' in all objs for easy reference
|
|
$this->autoSet('APP', $parent->inherit('APP'));
|
|
my $app = $this->{'APP'};
|
|
|
|
die("net-snmp-cert: error: no NetSNMP::Cert::App context provided")
|
|
if not defined $app or not ref $app eq 'NetSNMP::Cert::App';
|
|
# save children for list traversal
|
|
push(@{$parent->{'CHILDREN'}}, $this) unless $parent eq $this;
|
|
} else {
|
|
# we are the top of the list
|
|
$parent = $this;
|
|
}
|
|
$this->autoSet('PARENT' , $parent);
|
|
|
|
return $this;
|
|
}
|
|
|
|
sub autoSet {
|
|
my $this = shift;
|
|
my $key = shift;
|
|
if (@_) {
|
|
my $val = shift;
|
|
$this->{'AUTOKEYS'}{$key}++;
|
|
$this->{$key} = $val;
|
|
}
|
|
return exists $this->{'AUTOKEYS'}{$key};
|
|
}
|
|
|
|
sub inherit {
|
|
my $this = shift;
|
|
my $field = shift;
|
|
my $id;
|
|
|
|
# cmd opts override config settings
|
|
if (exists $this->{'APP'} and exists $this->{'APP'}{'OPTS'}) {
|
|
my $opts = $this->{'APP'}{'OPTS'};
|
|
$id = $opts->{'identity'};
|
|
return $opts->{$field} if defined $opts->{$field};
|
|
}
|
|
|
|
if (defined $id and exists $this->{'identity'} and
|
|
exists $this->{'identity'}{$id} and
|
|
exists $this->{'identity'}{$id}{$field}) {
|
|
return $this->{'identity'}{$id}{$field};
|
|
}
|
|
|
|
# return our field if we have it
|
|
return $this->{$field} if defined $this->{$field};
|
|
|
|
# terminate recursion at top and return undef if not found
|
|
return undef if not defined $this->{'PARENT'} or $this->{'PARENT'} eq $this;
|
|
|
|
# recurse to parent
|
|
$this->{'PARENT'}->inherit($field);
|
|
}
|
|
|
|
sub resolve {
|
|
my $this = shift;
|
|
my $opts = $this->inherit('OPTS');
|
|
my $val = shift;
|
|
|
|
NetSNMP::Cert::dprint("resolving: $val\n", $opts);
|
|
|
|
$val =~ s/(\$(\w+))/$this->inherit($2) or die("unresolved reference in config: $1")/ge;
|
|
$val =~ s/(\&\{(.*?)\})/$2/gee;
|
|
|
|
NetSNMP::Cert::dprint("resolved: $val\n", $opts);
|
|
|
|
return $val
|
|
}
|
|
|
|
sub delete {
|
|
my $this = shift;
|
|
my $opts = $this->inherit('OPTS');
|
|
|
|
NetSNMP::Cert::dprint("Obj::delete: ($this) [$this->{CFIELD}]\n", $opts);
|
|
|
|
my $parent = $this->{'PARENT'};
|
|
my @children = @{$this->{'CHILDREN'}};
|
|
|
|
foreach my $child (@children) {
|
|
$child->delete();
|
|
}
|
|
|
|
NetSNMP::Cert::dwarn("Obj: children not freed\n", $opts) if @{$this->{'CHILDREN'}};
|
|
|
|
# delete all our self-references
|
|
delete($this->{$this->{'CFIELD'}}) if exists $this->{'CFIELD'};
|
|
delete($this->{'PARENT'});
|
|
delete($this->{'CHILDREN'});
|
|
|
|
$parent->disown($this) if defined $parent and ref($parent) =~ /NetSNMP::Cert/;
|
|
}
|
|
|
|
sub disown {
|
|
my $this = shift;
|
|
my $thechild = shift;
|
|
my $opts = $this->inherit('OPTS');
|
|
my $ind = 0;
|
|
|
|
NetSNMP::Cert::dprint("Obj::disown: ($this) [$this->{CFIELD}] disowning ($thechild) [$thechild->{CFIELD}]\n", $opts);
|
|
|
|
foreach my $child (@{$this->{'CHILDREN'}}) {
|
|
last if $child eq $thechild;
|
|
$ind++;
|
|
}
|
|
if ($ind < @{$this->{'CHILDREN'}}) {
|
|
splice(@{$this->{'CHILDREN'}}, $ind, 1);
|
|
} else {
|
|
NetSNMP::Cert::dwarn("Child ($thechild) not found in object ($this)\n", $opts);
|
|
}
|
|
}
|
|
|
|
sub disabled {
|
|
my $this = shift;
|
|
return (exists $this->{'disable'} ? $this->{'disable'} : 0);
|
|
}
|
|
|
|
my %cfg = (
|
|
'name' => 1,
|
|
'type' => 2,
|
|
'id' => 6,
|
|
);
|
|
|
|
sub cfgsort {
|
|
my $self = shift;
|
|
my $a = shift;
|
|
my $b = shift;
|
|
return -1 if exists $cfg{$a} and not exists $cfg{$b};
|
|
return 1 if exists $cfg{$b} and not exists $cfg{$a};
|
|
return $cfg{$a} <=> $cfg{$b} if exists $cfg{$a} and exists $cfg{$b};
|
|
return -1 if !ref($self->{$a}) and ref($self->{$b});
|
|
return 1 if !ref($self->{$b}) and ref($self->{$a});
|
|
return -1 if ref($self->{$a}) =~ /NetSNMP::Cert/ and ref($self->{$b}) !~ /NetSNMP::Cert/;
|
|
return 1 if ref($self->{$b}) =~ /NetSNMP::Cert/ and ref($self->{$a}) !~ /NetSNMP::Cert/;
|
|
return 0;
|
|
}
|
|
|
|
sub dump {
|
|
my $self = shift;
|
|
my $indent = shift;
|
|
my $self_str = $self->{'CFIELD'};
|
|
my @data_keys = grep {!$self->autoSet($_)} keys %$self;
|
|
my @lines;
|
|
|
|
push(@lines, "\n" . ' ' x $indent . "$self_str = {\n") if defined $indent;
|
|
|
|
{
|
|
my $indent = (defined $indent ? $indent + 3 : 0);
|
|
foreach my $key (sort {cfgsort($self,$NetSNMP::Cert::Obj::a,
|
|
$NetSNMP::Cert::Obj::b)} (sort @data_keys)) {
|
|
if (ref($self->{$key}) =~ /NetSNMP::Cert/) {
|
|
push(@lines, $self->{$key}->dump($indent));
|
|
} elsif (ref($self->{$key}) =~ /ARRAY/) {
|
|
push(@lines, "\n") if ref(${$self->{$key}}[0]) !~ /NetSNMP::Cert/;
|
|
foreach my $elem (@{$self->{$key}}) {
|
|
if (ref($elem) =~ /NetSNMP::Cert/) {
|
|
push(@lines, $elem->dump($indent));
|
|
} else {
|
|
push(@lines, ' ' x $indent . "$key = $elem\n");
|
|
}
|
|
}
|
|
} else {
|
|
my $str = $key . (defined $self->{$key} ? " = $self->{$key}\n" : "\n");
|
|
push(@lines, ' ' x $indent . $str);
|
|
}
|
|
}
|
|
}
|
|
|
|
push(@lines, ' ' x $indent . "}; # end $self_str\n") if defined $indent;
|
|
return @lines;
|
|
}
|
|
|
|
sub DESTROY {
|
|
my $this = shift;
|
|
|
|
print("Obj::DESTROY $this [", ref $this, "]\n") if $NetSNMP::Cert::DEBUG >= 3;
|
|
}
|
|
|
|
package NetSNMP::Cert::App;
|
|
use vars qw(@ISA);
|
|
|
|
@ISA = qw(NetSNMP::Cert::Obj);
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
|
|
# the app is god, it is its own parent
|
|
my $this = $class->SUPER::new('APP');
|
|
|
|
bless($this, $class);
|
|
|
|
# verify required tools or die
|
|
$this->checkReqs();
|
|
|
|
# internal intitialization
|
|
$this->initOpts();
|
|
|
|
# make a new empty config and init (not parsed)
|
|
$this->{'config'} = new NetSNMP::Cert::Config($this);
|
|
|
|
return $this;
|
|
}
|
|
|
|
sub checkReqs {
|
|
my $app = shift;
|
|
|
|
die("$NetSNMP::Cert::OPENSSL does not exist or is not executable")
|
|
if system("$NetSNMP::Cert::OPENSSL version > /dev/null 2>&1");
|
|
|
|
my $ossl_ver = `$NetSNMP::Cert::OPENSSL version`; chomp $ossl_ver;
|
|
$ossl_ver =~ s/^OpenSSL\s+([\d\.]+).*$/$1/;
|
|
my $ossl_min_ver = $NetSNMP::Cert::OPENSSL_MIN_VER;
|
|
|
|
die("$NetSNMP::Cert::OPENSSL (v$ossl_ver): must be $ossl_min_ver or later")
|
|
if ($ossl_ver cmp $ossl_min_ver) < 0;
|
|
|
|
die("$NetSNMP::Cert::CFGTOOL not found: please install")
|
|
if system("$NetSNMP::Cert::CFGTOOL > /dev/null 2>&1");
|
|
}
|
|
|
|
sub initOpts {
|
|
my $app = shift;
|
|
my $opts = {};
|
|
$app->autoSet('OPTS', $opts);
|
|
|
|
# Define directories we need.
|
|
$opts->{'bindir'} = NetSNMP::Cert::find_bin_dir();
|
|
|
|
$opts->{'out'} = "> $NetSNMP::Cert::OUTLOG";
|
|
$opts->{'err'} = "2> $NetSNMP::Cert::ERRLOG";
|
|
|
|
# set up paths for app install and runtime env
|
|
$ENV{PATH} = "/sbin:/usr/sbin:$ENV{PATH}";
|
|
$ENV{PATH} = "$opts->{'bindir'}:$ENV{PATH}";
|
|
|
|
# default all privs to -rw-------
|
|
umask(077);
|
|
}
|
|
|
|
sub init {
|
|
my $app = shift;
|
|
my $opts = $app->{'OPTS'};
|
|
my $config = $app->{'config'};
|
|
my @args = @_; # pass external args (typically ARGV)
|
|
|
|
# parse command line
|
|
$app->parseArgs(@args);
|
|
|
|
# lazy config parsing postponed until here, will not reparse
|
|
$config->parse();
|
|
|
|
# set defaults here if not already set in cmdline or config
|
|
# guided interactive mode by default
|
|
NetSNMP::Cert::set_default($opts, $config, 'interactive', 1);
|
|
# verbose output
|
|
NetSNMP::Cert::set_default($opts, $config, 'verbose', 1);
|
|
|
|
# find tlsdir/subdirs or make it
|
|
$opts->{'tlsdir'} = $app->createTlsDir();
|
|
}
|
|
|
|
sub parseArgs {
|
|
my $app = shift;
|
|
my $opts = $app->{'OPTS'};
|
|
my @args = @_;
|
|
|
|
NetSNMP::Cert::GetOptsFromArray(\@args, $opts, 'help|?', 'version|V',
|
|
'interactive!', 'I', 'verbose!', 'Q', 'force|F',
|
|
'cfgfile|f=s', 'cfgdir|C=s', 'tlsdir|tlsDir|T=s',
|
|
'tag|t=s', 'identity|i=s', 'debug|D+',);
|
|
|
|
NetSNMP::Cert::version(0) if $opts->{'version'};
|
|
|
|
NetSNMP::Cert::usage(0) if $opts->{'help'};
|
|
|
|
# pull out the cmd - getOpts should leave it untouched for us
|
|
$opts->{'cmd'} = NetSNMP::Cert::pull_cmd(\@args);
|
|
|
|
# save extra args for command specific processing
|
|
$opts->{'cmdargs'} = [@args];
|
|
|
|
# Check debug option first
|
|
$NetSNMP::Cert::DEBUG = $opts->{'debug'};
|
|
$opts->{'err'} = $opts->{'out'} = "" if $opts->{'debug'};
|
|
$opts->{'interactive'} = not $opts->{'I'} if defined $opts->{'I'};
|
|
$opts->{'verbose'} = not $opts->{'Q'} if defined $opts->{'Q'};
|
|
|
|
# search for cfgdir and cfgfile based on opts and confpath
|
|
$opts->{'cfgdir'} = NetSNMP::Cert::find_cfgdir($opts->{'cfgdir'},
|
|
$opts->{'cfgfile'});
|
|
$opts->{'cfgfile'} = NetSNMP::Cert::find_cfgfile($opts->{'cfgdir'},
|
|
$opts->{'cfgfile'});
|
|
}
|
|
|
|
sub createTlsDir {
|
|
my $app = shift;
|
|
my $opts = $app->{'OPTS'};
|
|
my $config = $app->{'config'};
|
|
my $dir;
|
|
my $file;
|
|
my $cmd;
|
|
|
|
$dir = $config->inherit('tlsDir');
|
|
|
|
if (not defined $dir) {
|
|
my $cfgdir = $opts->{'cfgdir'};
|
|
die("undefined cfgdir: unable to creat tlsdir: exiting...\n") unless defined $cfgdir;
|
|
$dir = "$cfgdir/$NetSNMP::Cert::TLSDIR";
|
|
}
|
|
|
|
NetSNMP::Cert::dprint("tlsDir is: $dir\n", $opts);
|
|
$dir = NetSNMP::Cert::fq_rel_path($dir);
|
|
NetSNMP::Cert::dprint("tlsDir is: $dir\n", $opts);
|
|
|
|
NetSNMP::Cert::dprint("tlsDir not found, creating\n", $opts) unless -d $dir;
|
|
NetSNMP::Cert::make_dirs($opts, $dir, 0700, @NetSNMP::Cert::TLSDIRS);
|
|
|
|
my $ssl_cfg_in = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGIN,$dir);
|
|
|
|
# Existing openssl.conf tmpl will be preserved
|
|
if (-f $ssl_cfg_in) {
|
|
NetSNMP::Cert::dwarn("OpenSSL template exists ($ssl_cfg_in): preserving...", $opts);
|
|
}
|
|
|
|
if (not -f $ssl_cfg_in) {
|
|
NetSNMP::Cert::dprint("make_openssl_conf($ssl_cfg_in)", $opts);
|
|
NetSNMP::Cert::make_openssl_conf($ssl_cfg_in);
|
|
chmod(0600, $ssl_cfg_in);
|
|
}
|
|
|
|
NetSNMP::Cert::dprint("createTlsDir: done\n", $opts);
|
|
return $dir;
|
|
}
|
|
|
|
my @interactive_ops = (['gencert', "Generate a signed certificate"],
|
|
['genca', "Generate a CA certificate"],
|
|
['gencsr', "Generate a Certificate Signing Request"],
|
|
['signcsr', "Sign a Certificate Signing Request"],
|
|
);
|
|
|
|
sub run {
|
|
my $app = shift;
|
|
my $opts = $app->{'OPTS'};
|
|
my $cmd = $opts->{'cmd'};
|
|
|
|
# must have a command in non-Interactive mode
|
|
NetSNMP::Cert::usage(3) if !$opts->{'interactive'} and !$opts->{'cmd'};
|
|
|
|
# change dir tls dir - the cwd for all commands - save cwd first
|
|
$opts->{'rdir'} = ::getcwd();
|
|
chdir($opts->{'tlsdir'}) or die("could'nt change directory: $opts->{tlsdir}");
|
|
# display context
|
|
NetSNMP::Cert::dprint("PATH: $ENV{PATH}\n\n", $opts);
|
|
NetSNMP::Cert::dprint("config file: $opts->{cfgfile}\n", $opts);
|
|
NetSNMP::Cert::dprint("config dir: $opts->{cfgdir}\n", $opts);
|
|
NetSNMP::Cert::dprint("tls dir: $opts->{tlsdir}\n", $opts);
|
|
|
|
my $cmdstr = join(' ', $cmd, @{$opts->{'cmdargs'}});
|
|
NetSNMP::Cert::dprint("command: $cmdstr\n", $opts);
|
|
|
|
NetSNMP::Cert::GetOptsFromArray(\@{$opts->{'cmdargs'}}, $opts,
|
|
'with-ca|a=s', 'ca|A=s','csr|r=s',
|
|
'from-crt|x=s', 'crt|X=s', 'days|d=s',
|
|
'cn|n=s', 'email|e=s', 'host|h=s',
|
|
'san|s=s@', 'org|o=s', 'unit|u=s',
|
|
'country|c=s', 'state|province|p=s',
|
|
'locality|l=s', 'brief|b',
|
|
@NetSNMP::Cert::X509FMTS);
|
|
|
|
# process extra args; --<cfg-name>[=<val>]
|
|
$app->processExtraArgs();
|
|
|
|
# If in interactive mode - fill missing info by interviewing user
|
|
if (not defined $cmd or grep {/$cmd/} map {$_->[0]} @interactive_ops) {
|
|
$app->userInput() if $opts->{interactive};
|
|
$cmd = $opts->{'cmd'}; # may have changed
|
|
}
|
|
|
|
# resolve input args to filenames
|
|
$app->resolveCrtArgs();
|
|
|
|
# use env. or merge template for OpenSSL config
|
|
$NetSNMP::Cert::SSLCFG ||= $app->opensslCfg();
|
|
|
|
if ($cmd =~ /^genca$/) {
|
|
NetSNMP::Cert::usage(1) if defined $opts->{'from-crt'} or defined $opts->{'csr'};
|
|
$app->genCa($opts->{'with-ca'});
|
|
} elsif ($cmd =~ /^gence?rt$/) {
|
|
NetSNMP::Cert::usage(1) if defined $opts->{'from-crt'} or defined $opts->{'csr'};
|
|
$app->genCert($opts->{'with-ca'});
|
|
} elsif ($cmd =~ /^gencsr$/) {
|
|
NetSNMP::Cert::usage(1) if defined $opts->{'with-ca'} or defined $opts->{'crt'};
|
|
$app->genCsr();
|
|
} elsif ($cmd =~ /^signcsr$/) {
|
|
NetSNMP::Cert::usage(1) unless defined $opts->{'with-ca'} and defined $opts->{'csr'};
|
|
$app->signCsr();
|
|
} elsif ($cmd =~ /^show(ce?rts?)?$/) {
|
|
$app->show('certs');
|
|
} elsif ($cmd =~ /^showcas?$/) {
|
|
$app->show('ca-certs');
|
|
} elsif ($cmd =~ /^import$/) {
|
|
$app->import();
|
|
} else {
|
|
NetSNMP::Cert::usage();
|
|
}
|
|
}
|
|
|
|
sub processExtraArgs {
|
|
my $app = shift;
|
|
my $opts = $app->{'OPTS'};
|
|
my @args;
|
|
|
|
NetSNMP::Cert::dprint("processing extra args...\n", $opts);
|
|
|
|
while (@{$opts->{'cmdargs'}}) {
|
|
my $arg = shift(@{$opts->{'cmdargs'}});
|
|
NetSNMP::Cert::dprint("found: arg --> $arg\n", $opts);
|
|
if ($arg =~ /^-+(\w+)(?:=(.*?))?\s*$/) {
|
|
NetSNMP::Cert::dprint("found: arg($1) val($2)\n", $opts);
|
|
if (exists $opts->{$1}) {
|
|
NetSNMP::Cert::vwarn("option ($1) already set: overwriting\n", $opts);
|
|
}
|
|
$opts->{$1} = (defined $2 ? $2 : 1);
|
|
} else {
|
|
push(@args, $arg);
|
|
}
|
|
}
|
|
@{$opts->{'cmdargs'}} = @args;
|
|
}
|
|
|
|
sub resolveCrtArgs {
|
|
my $app = shift;
|
|
my $opts = $app->{'OPTS'};
|
|
|
|
# find ca, crt, csr args if present and return fully qualified path
|
|
if (defined $opts->{'with-ca'}) {
|
|
my $ca;
|
|
$ca = NetSNMP::Cert::find_certs($opts, 'ca-certs', $opts->{'with-ca'});
|
|
die("unable to locate CA certificate ($opts->{'with-ca'})\n") unless -e $ca;
|
|
die("unable read CA certificate ($opts->{'with-ca'})\n") unless -r $ca;
|
|
$opts->{'with-ca'} = $ca;
|
|
}
|
|
|
|
if (defined $opts->{'from-crt'}) {
|
|
my $crt;
|
|
$crt = NetSNMP::Cert::find_certs($opts, 'certs', $opts->{'from-crt'});
|
|
die("unable to locate certificate ($opts->{'from-crt'})\n") unless -e $crt;
|
|
die("unable read certificate ($opts->{'from-crt'})\n") unless -r $crt;
|
|
$opts->{'from-crt'} = $crt;
|
|
}
|
|
|
|
if (defined $opts->{'csr'}) {
|
|
my $csr;
|
|
$csr = NetSNMP::Cert::find_certs($opts, 'csrs', $opts->{'csr'});
|
|
die("unable to locate CSR certificate ($opts->{csr})\n") unless -e $csr;
|
|
die("unable read CSR certificate ($opts->{csr})\n") unless -r $csr;
|
|
$opts->{'csr'} = $csr;
|
|
}
|
|
}
|
|
|
|
sub opensslCfg {
|
|
my $app = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $san = $config->inherit('san') || $config->inherit('subjectAltName');
|
|
my $ssl_cfg_in = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGIN);
|
|
my $ssl_cfg_out = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGOUT);
|
|
|
|
if (not -f $ssl_cfg_in) {
|
|
NetSNMP::Cert::vwarn("OpenSSL template not found: $ssl_cfg_in\n", $opts);
|
|
die("no OpenSSL template");
|
|
}
|
|
|
|
open(IN, $ssl_cfg_in) or die("unable to open OpenSSL template: $ssl_cfg_in\n");
|
|
open(OUT, ">$ssl_cfg_out") or die("unable to open OpenSSL config: $ssl_cfg_out\n");
|
|
|
|
print OUT "#######################################################\n";
|
|
print OUT "##### Warning: Do Not Edit - Generated File #####\n";
|
|
print OUT "#######################################################\n";
|
|
while (<IN>) {
|
|
if ($san) {
|
|
s/\%\[([^\]]*?)\]/$1/;
|
|
} else {
|
|
s/\%\[([^\]]*?)\]//;
|
|
}
|
|
print OUT $_;
|
|
}
|
|
close(IN);
|
|
close(OUT);
|
|
|
|
return $ssl_cfg_out;
|
|
}
|
|
|
|
sub opensslEnv {
|
|
my $app = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
|
|
my $cn = shift;
|
|
my $days = shift;
|
|
my $dir = shift || ".";
|
|
# XXX - need to handle config'd identity here
|
|
my $name = $config->inherit("name");
|
|
my $host = $config->inherit("host");
|
|
my $email = $config->inherit("email");
|
|
my $country = $config->inherit("country");
|
|
my $state = $config->inherit("state");
|
|
my $locality = $config->inherit("locality");
|
|
my $org = $config->inherit("org");
|
|
my $org_unit = $config->inherit("unit") || $config->inherit("orgUnit");
|
|
my $san;
|
|
my $san_arr_ref;
|
|
my $md = $config->inherit("msgDigest");
|
|
my $ksize = $config->inherit("keySize");
|
|
|
|
my $env;
|
|
|
|
$env .= " KSIZE=$ksize" if defined $ksize;
|
|
$env .= " DAYS=$days" if defined $days;
|
|
$env .= " MD=$md" if defined $md;
|
|
|
|
$env .= " DIR='$dir'" if defined $dir;
|
|
|
|
$env .= " EMAIL=$email" if defined $email;
|
|
$env .= " CN='$cn'" if defined $cn;
|
|
$env .= " ORG='$org'" if defined $org;
|
|
$env .= " ORG_UNIT='$org_unit'" if defined $org_unit;
|
|
$env .= " COUNTRY=$country" if defined $country;
|
|
$env .= " STATE=$state" if defined $state;
|
|
$env .= " LOCALITY=$locality" if defined $locality;
|
|
|
|
$san_arr_ref = $config->inherit('subjectAltName');
|
|
$san = join('\,\ ', @{$san_arr_ref}) if ref $san_arr_ref;
|
|
$san_arr_ref = $config->inherit('san');
|
|
$san .= join('\,\ ', @{$san_arr_ref}) if ref $san_arr_ref;
|
|
$san =~ s/EMAIL:/email:/g;
|
|
$env .= " SAN=$san" if defined $san;
|
|
|
|
NetSNMP::Cert::dprint("opensslEnv: $env\n", $opts);
|
|
|
|
return $env;
|
|
}
|
|
our @san_prefix = (['dirName:', "e.g., dirName:/usr/share/snmp/tls"],
|
|
['DNS:', "e.g., DNS:test.net-snmp.org)"],
|
|
['email:', "e.g., email:admin\@net-snmp.org"],
|
|
['IP:', "e.g., IP:192.168.1.1"],
|
|
['RID:', "e.g., RID:1.1.3.6.20"],
|
|
['URI:', "e.g., URI:http://www.net-snmp.org"]);
|
|
|
|
sub userInputDN {
|
|
my $app = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $prompt;
|
|
my $ret;
|
|
my $host = $config->inherit("host") || ::hostname();
|
|
my $email = $config->inherit('email') || getlogin() . "\@$host";
|
|
|
|
# get EMAIL
|
|
$prompt = "Enter Email";
|
|
$ret = $email;
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret,
|
|
"\tEmail Address - (e.g., <name>@<domain>)");
|
|
$email = $opts->{'email'} = $ret if defined $ret;
|
|
# get CN
|
|
$prompt = "Enter Common Name";
|
|
$ret = ($opts->{'cmd'} eq 'genca' ? "ca-$host" : $email);
|
|
$ret = $config->inherit('cn') || $config->inherit('commonName') || $ret;
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret,
|
|
"\tCommon Name - (e.g., net-snmp.org)");
|
|
$opts->{'cn'} = $ret if defined $ret;
|
|
# get ORG
|
|
$prompt = "Enter Organization";
|
|
$ret = $config->inherit('org') || 'Net-SNMP';
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret,
|
|
"\tOrganization - (e.g., Net-SNMP)");
|
|
$opts->{'org'} = $ret if defined $ret;
|
|
# get ORG_UNIT
|
|
$prompt = "Enter Organizational Unit";
|
|
$ret = $config->inherit('unit') || 'Development';
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret,
|
|
"\tOrganizational Unit - (e.g., Development)");
|
|
$opts->{'unit'} = $ret if defined $ret;
|
|
# get COUNTRY
|
|
$prompt = "Enter Country Code";
|
|
$ret = $config->inherit('country') || 'US';
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret,
|
|
"Country Code, 2 letters (<tab> for options)",
|
|
$NetSNMP::Cert::MATCH, \@CC);
|
|
$opts->{'country'} = $ret if defined $ret;
|
|
# get STATE(Province)
|
|
$prompt = "Enter State or Province";
|
|
$ret = $config->inherit('state') || 'CA';
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret,
|
|
"\tState or Province - (e.g., CA)");
|
|
$opts->{'state'} = $ret if defined $ret;
|
|
# get LOCALITY
|
|
$prompt = "Enter Locality";
|
|
$ret = $config->inherit('locality') || 'Davis';
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret, "\tLocality - (e.g., Davis)");
|
|
$opts->{'locality'} = $ret if defined $ret;
|
|
# get SAN (loop)
|
|
if (!$config->{'brief'}) {
|
|
print "Enter Subject Alt Names. Examples:\n";
|
|
foreach my $pair (@san_prefix) {
|
|
printf("\t%-10.10s %s\n", $pair->[0], $pair->[1]);
|
|
}
|
|
}
|
|
do {
|
|
$ret = 'done';
|
|
$prompt = "Enter Subject Alt Name (enter 'done' when finished) [$ret]: ";
|
|
$ret = NetSNMP::Term::Complete ($prompt, $ret,
|
|
"\tSubject Alt Name - (<type>:<val>)",
|
|
$NetSNMP::Cert::PREMATCH, \@san_prefix);
|
|
push(@{$opts->{'san'}}, $ret) if defined $ret and $ret ne 'done';
|
|
} while (defined $ret and $ret ne 'done');
|
|
}
|
|
|
|
our @snmp_apps = (['snmp', 'Generic Certificate'],
|
|
['snmpapp','Client Certificate'],
|
|
['snmpd','Agent Certificate'],
|
|
['snmptrapd','Trap-agent Certificate']);
|
|
|
|
sub userInputTag {
|
|
my $app = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $ret;
|
|
my $prompt;
|
|
|
|
print "Application Tag:\n\tused to name the certificate and dictate its filename\n";
|
|
print "\tIt may also associate it with a particular application (eg \"snmpd\")\n";
|
|
print "\tif 'none', a name will be generated from other parameters\n";
|
|
print "\tenter <tab><tab> for typical options, or enter new name\n";
|
|
$prompt = "Enter Application Tag";
|
|
$ret = $config->inherit('tag') || 'none';
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret,
|
|
"Application Tag assocaiated with certificate",
|
|
(not $NetSNMP::Cert::MATCH), \@snmp_apps);
|
|
$opts->{'tag'} = $ret if defined $ret and $ret ne 'none';
|
|
}
|
|
|
|
sub userInput {
|
|
my $app = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $prompt;
|
|
my $ret;
|
|
|
|
print "Choose an operation:\n";
|
|
foreach my $op (@interactive_ops) {
|
|
print "\t$op->[0]\t- $op->[1]\n";
|
|
}
|
|
|
|
$prompt = "Operation";
|
|
$ret = $config->inherit('cmd') || $interactive_ops[0][0];
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret,
|
|
"Certifciate Operation to perform",
|
|
$NetSNMP::Cert::MATCH, \@interactive_ops);
|
|
|
|
$opts->{'cmd'} = $ret;
|
|
|
|
if ($ret =~ /^gencert$/) {
|
|
# get tag
|
|
$app->userInputTag();
|
|
# get DN
|
|
$app->userInputDN();
|
|
# self-signed/CA-signed(ca-cert)
|
|
} elsif ($ret =~ /^genca$/) {
|
|
# get DN
|
|
$app->userInputDN();
|
|
} elsif ($ret =~ /^gencsr$/) {
|
|
# get tag
|
|
$app->userInputTag();
|
|
# get DN
|
|
$app->userInputDN();
|
|
} elsif ($ret =~ /^signcsr$/) {
|
|
# get csr
|
|
$prompt = "Choose Certificate Signing Request";
|
|
$ret = $config->inherit('csr');
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret);
|
|
$opts->{'csr'} = $ret if defined $ret;
|
|
# get ca
|
|
$prompt = "Choose CA Certificate";
|
|
$ret = $config->inherit('with-ca');
|
|
$prompt .= (defined $ret ? " [$ret]: " : ": ");
|
|
$ret = NetSNMP::Term::Complete($prompt, $ret);
|
|
$opts->{'with-ca'} = $ret if defined $ret;
|
|
} else {
|
|
NetSNMP::Cert::vwarn("aborting operation: exiting...\n", $opts);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
sub createCaDir {
|
|
my $app = shift;
|
|
my $dir = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $file;
|
|
my $cmd;
|
|
|
|
NetSNMP::Cert::make_dirs($opts, $dir, 0700,'newcerts','private');
|
|
|
|
$file = "$dir/$NetSNMP::Cert::SERIAL";
|
|
if (not -f $file) {
|
|
$cmd = "echo '01' > '$file'";
|
|
NetSNMP::Cert::dprint("$cmd\n", $opts);
|
|
NetSNMP::Cert::usystem($cmd, $opts);
|
|
chmod(0600, $file);
|
|
}
|
|
|
|
$file = "$dir/$NetSNMP::Cert::INDEX";
|
|
if (not -f $file) {
|
|
$cmd = "touch '$file'";
|
|
NetSNMP::Cert::dprint("$cmd\n", $opts);
|
|
NetSNMP::Cert::usystem($cmd, $opts);
|
|
chmod(0600, $file);
|
|
}
|
|
}
|
|
|
|
sub genCa {
|
|
my $app = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $host = $config->inherit('host') || ::hostname();
|
|
my $days = $config->inherit('days') || $config->inherit('caDays') ||
|
|
$NetSNMP::Cert::DEFCADAYS;
|
|
my $cn = $config->inherit('cn') || $config->inherit('commonName') ||
|
|
"ca-$host";
|
|
my $ca = $config->inherit('with-ca');
|
|
my $tag = $config->inherit('tag') || $cn;
|
|
|
|
# create CA dir
|
|
my $dir = ".ca/$tag";
|
|
$app->createCaDir($dir);
|
|
|
|
my $outCrt = "$NetSNMP::Cert::CADIR/$tag.crt";
|
|
my $outKey = "$NetSNMP::Cert::PRIVDIR/$tag.key";
|
|
|
|
# set command env
|
|
my $env = $app->opensslEnv($cn, $days);
|
|
|
|
NetSNMP::Cert::check_output_file($opts, $outCrt,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
NetSNMP::Cert::check_output_file($opts, $outKey,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
|
|
my $cmd = "$env openssl req -extensions v3_ca_create -new -days $days -batch -config $NetSNMP::Cert::SSLCFG -keyout '$outKey'";
|
|
$cmd .= " -nodes";
|
|
|
|
if (defined $ca) {
|
|
# we have to gen a csr and then sign it, must preserve CA:TRUE
|
|
my $outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr";
|
|
NetSNMP::Cert::check_output_file($opts, $outCsr,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
$cmd .= " -out '$outCsr'";
|
|
|
|
NetSNMP::Cert::dprint("genCa (gencsr): $cmd\n", $opts);
|
|
NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
|
|
|
|
my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES);
|
|
NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts);
|
|
|
|
# set command env
|
|
$env = $app->opensslEnv($cn, $days, ".ca/$ca_base");
|
|
|
|
$cmd = "$env openssl ca -extensions v3_ca_sign_ca -days $days -cert '$ca' -keyfile '$NetSNMP::Cert::PRIVDIR/$ca_base.key' -in '$outCsr' -batch -config $NetSNMP::Cert::SSLCFG -out '$outCrt'";
|
|
|
|
NetSNMP::Cert::dprint("genCa (signcsr): $cmd\n", $opts);
|
|
NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
|
|
} else {
|
|
$cmd .= " -x509 -out '$outCrt'";
|
|
|
|
NetSNMP::Cert::dprint("genCa: $cmd\n", $opts);
|
|
NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
|
|
}
|
|
|
|
NetSNMP::Cert::vprint("CA Generated:\n", $opts);
|
|
NetSNMP::Cert::vprint(" $outCrt\n", $opts);
|
|
NetSNMP::Cert::vprint(" $outKey\n", $opts);
|
|
}
|
|
|
|
sub genCert {
|
|
my $app = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $host = $config->inherit("host") || ::hostname();
|
|
my $email = $config->inherit("email") || getlogin() . "\@$host";
|
|
my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS;
|
|
my $cn = $config->inherit('cn') || $config->inherit('commonName');
|
|
my $ca = $config->inherit('with-ca');
|
|
my $cmd;
|
|
|
|
my $tag = $opts->{'tag'} || 'snmp';
|
|
$cn ||= (NetSNMP::Cert::is_server($tag) ? $host : $email);
|
|
|
|
my $env = $app->opensslEnv($cn, $days);
|
|
|
|
my $outCrt = "$NetSNMP::Cert::CRTDIR/$tag.crt";
|
|
my $outKey = "$NetSNMP::Cert::PRIVDIR/$tag.key";
|
|
|
|
NetSNMP::Cert::check_output_file($opts, $outCrt,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
NetSNMP::Cert::check_output_file($opts, $outKey,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
|
|
$cmd = "$env openssl req -extensions v3_user_create -new -days $days -keyout '$outKey' -batch -config $NetSNMP::Cert::SSLCFG";
|
|
$cmd .= " -nodes";
|
|
|
|
if (defined $ca) {
|
|
my $outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr";
|
|
NetSNMP::Cert::check_output_file($opts, $outCsr,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
$cmd .= " -out '$outCsr'";
|
|
|
|
NetSNMP::Cert::dprint("genCert (gencsr): $cmd\n", $opts);
|
|
NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
|
|
|
|
# XXX cleanup this temp CSR
|
|
my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES);
|
|
NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts);
|
|
|
|
# set command env
|
|
$env = $app->opensslEnv($cn, $days, ".ca/$ca_base");
|
|
|
|
$cmd = "$env openssl ca -extensions v3_ca_sign -days $days -cert '$ca' -keyfile '$NetSNMP::Cert::PRIVDIR/$ca_base.key' -in '$outCsr' -batch -config $NetSNMP::Cert::SSLCFG -out '$outCrt'";
|
|
|
|
NetSNMP::Cert::dprint("gencert (signcsr): $cmd\n", $opts);
|
|
NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
|
|
} else {
|
|
$cmd .= " -x509 -out '$outCrt'";
|
|
|
|
NetSNMP::Cert::dprint("genCert: $cmd\n", $opts);
|
|
NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
|
|
}
|
|
|
|
NetSNMP::Cert::vprint("Certificate Generated:\n", $opts);
|
|
NetSNMP::Cert::vprint(" $outCrt\n", $opts);
|
|
NetSNMP::Cert::vprint(" $outKey\n", $opts);
|
|
}
|
|
|
|
sub genCsr {
|
|
my $app = shift;
|
|
my $isCa = shift; # XXX - not implemented yet
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $host = $config->inherit("host") || ::hostname();
|
|
my $email = $config->inherit("email") || getlogin() . "\@$host";
|
|
my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS;
|
|
my $cn = $config->inherit('cn') || $config->inherit('commonName');
|
|
my $tag = $config->inherit('tag');
|
|
my $inCrt = $config->inherit('from-crt') || $config->inherit('fromCert');
|
|
my $outCsr;
|
|
my $csrKey;
|
|
|
|
if (defined $inCrt) {
|
|
$inCrt = NetSNMP::Cert::find_certs($opts, 'certs', $inCrt);
|
|
my $crt = ::basename($inCrt, @NetSNMP::Cert::CRTSUFFIXES);
|
|
$csrKey = "$NetSNMP::Cert::PRIVDIR/$crt.key";
|
|
$tag ||= $crt;
|
|
} else {
|
|
$tag ||= 'snmp';
|
|
$csrKey ||= "$NetSNMP::Cert::PRIVDIR/$tag.key";
|
|
}
|
|
|
|
$outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr";
|
|
|
|
$cn ||= (NetSNMP::Cert::is_server($tag) ? $host : $email);
|
|
|
|
my $env = $app->opensslEnv($cn, $days);
|
|
|
|
NetSNMP::Cert::check_output_file($opts, $outCsr,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
NetSNMP::Cert::check_output_file($opts, $csrKey,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
|
|
my $cmd = (defined $inCrt ?
|
|
"$env openssl x509 -x509toreq -in $inCrt -out '$outCsr' -signkey '$csrKey' -nodes -days $days -batch -config $NetSNMP::Cert::SSLCFG" :
|
|
"$env openssl req -new -nodes -days $days -batch -keyout '$csrKey' -out '$outCsr' -config $NetSNMP::Cert::SSLCFG");
|
|
|
|
$cmd .= ($isCa ? " -extensions v3_ca_create" : " -extensions v3_user_create");
|
|
|
|
NetSNMP::Cert::dprint("genCsr: $cmd\n", $opts);
|
|
|
|
NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
|
|
|
|
NetSNMP::Cert::vprint("Certificate Signing Request Generated:\n", $opts);
|
|
NetSNMP::Cert::vprint(" $outCsr\n", $opts);
|
|
NetSNMP::Cert::vprint(" $csrKey\n", $opts);
|
|
}
|
|
|
|
sub signCsr {
|
|
my $app = shift;
|
|
my $isCa = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $host = $config->inherit("host") || ::hostname();
|
|
my $email = $config->inherit("email") || getlogin() . "\@$host";
|
|
my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS;
|
|
my $cn = $config->inherit('cn') || $config->inherit('commonName');
|
|
my $tag = $config->inherit('tag') || 'snmp';
|
|
my $install = $config->inherit('install');
|
|
|
|
$cn = (NetSNMP::Cert::is_server($tag) ? $host : $email);
|
|
|
|
my $ca = $opts->{'with-ca'};
|
|
NetSNMP::Cert::dprint("ca: $ca\n", $opts);
|
|
my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES);
|
|
NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts);
|
|
my $ca_key = "$NetSNMP::Cert::PRIVDIR/$ca_base.key";
|
|
|
|
my $csr = $opts->{'csr'};
|
|
NetSNMP::Cert::dprint("csr: $csr\n", $opts);
|
|
my $csr_base = ::basename($csr, @NetSNMP::Cert::CRTSUFFIXES);
|
|
NetSNMP::Cert::dprint("csr_base: $csr_base\n", $opts);
|
|
my $outdir = ($install ? $NetSNMP::Cert::CRTDIR : $NetSNMP::Cert::NEWCRTDIR);
|
|
my $outCrt = "$outdir/$csr_base.crt";
|
|
|
|
my $env = $app->opensslEnv($cn, $days, ".ca/$ca_base");
|
|
|
|
NetSNMP::Cert::check_output_file($opts, $outCrt,
|
|
$config->inherit('interactive'),
|
|
$config->inherit('force'));
|
|
|
|
# XXX - handle keyfile search??
|
|
my $cmd = "$env openssl ca -batch -days $days -extensions v3_ca_sign -cert '$ca' -keyfile '$ca_key' -in '$csr' -out '$outCrt' -config $NetSNMP::Cert::SSLCFG";
|
|
|
|
# $cmd .= ($isCa ? " -extensions v3_ca_sign_ca" : " -extensions v3_ca_sign");
|
|
|
|
NetSNMP::Cert::dprint("signCsr: $cmd\n", $opts);
|
|
|
|
NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts);
|
|
|
|
NetSNMP::Cert::vprint("Signed Certificate Signing Request:\n", $opts);
|
|
NetSNMP::Cert::vprint(" $csr\n", $opts);
|
|
NetSNMP::Cert::vprint("with CA:\n", $opts);
|
|
NetSNMP::Cert::vprint(" $ca\n", $opts);
|
|
NetSNMP::Cert::vprint(" $ca_key\n", $opts);
|
|
NetSNMP::Cert::vprint("Generated Certificate:\n", $opts);
|
|
NetSNMP::Cert::vprint(" $outCrt\n", $opts);
|
|
}
|
|
|
|
sub show {
|
|
my $app = shift;
|
|
my $type = shift || 'certs';
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $stag = shift(@{$opts->{'cmdargs'}});
|
|
my $fmt = NetSNMP::Cert::x509_format($opts) || '-subject';
|
|
my $brief = $config->inherit('brief');
|
|
my $output;
|
|
my $cmd;
|
|
|
|
my $cwd = ::getcwd();
|
|
NetSNMP::Cert::dprint("show ($cwd):$type:$stag:$fmt\n", $opts);
|
|
NetSNMP::Cert::vprint("$opts->{'tlsdir'}:\n", $opts) unless $brief;
|
|
|
|
foreach my $c (NetSNMP::Cert::find_certs($opts, $type, $stag)) {
|
|
print "\n$c:\n" unless $brief;
|
|
$cmd = "openssl x509 -in '$c' -noout $fmt";
|
|
NetSNMP::Cert::dprint("show: $cmd\n", $opts);
|
|
|
|
$output = `$cmd`; chomp $output;
|
|
NetSNMP::Cert::vwarn("show-$type failed ($?): $output\n", $opts) if $?;
|
|
|
|
$output =~ s/^[^\n=]+=// if $brief;
|
|
|
|
print "$output\n";
|
|
print "\n" unless $brief;
|
|
}
|
|
}
|
|
|
|
sub import_file {
|
|
my ($file, $suffix, $targ, $rdir, $tag) = @_;
|
|
|
|
if (NetSNMP::Cert::is_url($file)) {
|
|
if ($NetSNMP::Cert::haveUserAgent) {
|
|
|
|
require File::Temp;
|
|
import File::Temp qw(tempfile);
|
|
|
|
my ($fh, $newfilename) = tempfile(SUFFIX => $suffix);
|
|
return if (!$fh || !$newfilename);
|
|
my $ua = LWP::UserAgent->new;
|
|
my $response = $ua->get($file);
|
|
if ($response->is_success) {
|
|
print $fh $response->decoded_content();
|
|
} else {
|
|
NetSNMP::Cert::vwarn("failed to download a certificate from $file");
|
|
return;
|
|
}
|
|
$fh->close;
|
|
$file = $newfilename;
|
|
} else {
|
|
NetSNMP::Cert::vwarn("LWP::UserAgent not installed: unable to import certificate");
|
|
return;
|
|
}
|
|
}
|
|
|
|
$file = NetSNMP::Cert::fq_rel_path($file, $rdir);
|
|
die("file unreadable: $file\n") unless -r $file;
|
|
|
|
|
|
if (! $targ) {
|
|
$targ = (NetSNMP::Cert::is_ca_cert($file) ? $NetSNMP::Cert::CADIR : $NetSNMP::Cert::CRTDIR);
|
|
}
|
|
|
|
$targ .= "/" . $tag . $suffix if ($tag);
|
|
::copy($file, $targ);
|
|
}
|
|
|
|
|
|
sub import {
|
|
my $app = shift;
|
|
my $config = $app->{'config'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $carg = shift(@{$opts->{'cmdargs'}});
|
|
my $karg = shift(@{$opts->{'cmdargs'}});
|
|
my $targ;
|
|
|
|
if (not defined $carg) {
|
|
NetSNMP::Cert::vwarn("import: no certificate supplied\n", $opts);
|
|
NetSNMP::Cert::usage(1);
|
|
}
|
|
|
|
import_file($carg, '.crt', '',,
|
|
$opts->{'rdir'}, $opts->{'tag'});
|
|
|
|
return unless defined $karg;
|
|
|
|
import_file($karg, '.key', 'private',,
|
|
$opts->{'rdir'}, $opts->{'tag'});
|
|
}
|
|
|
|
|
|
package NetSNMP::Cert::Config;
|
|
use vars qw(@ISA);
|
|
@ISA = qw(NetSNMP::Cert::Obj);
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $parent = shift;
|
|
my $this = $class->SUPER::new('config', $parent);
|
|
|
|
bless($this, $class);
|
|
}
|
|
|
|
sub parse {
|
|
my $config = shift;
|
|
my $app = $config->{'APP'};
|
|
my $opts = $app->{'OPTS'};
|
|
my $cfgfile = shift;
|
|
$cfgfile ||= $opts->{'cfgfile'};
|
|
|
|
return '0 but true' if $config->{'PARSED'};
|
|
return '0 but true' unless defined $cfgfile;
|
|
|
|
open( CONFIG, "<$cfgfile" )
|
|
or die "error - could not open configuration file: $cfgfile";
|
|
|
|
while (<CONFIG>) {
|
|
next if /^\s*#/ or /^\s*$/;
|
|
|
|
if (/^\s*(\w+)(?:\(([\)\(\d\,\.]+)\))?\s*=\s*{/) {
|
|
my $obj = $1;
|
|
|
|
NetSNMP::Cert::dprint("object: $obj ($2) = {\n", $opts);
|
|
die "error - identity: indices not supported: $2" if defined $2;
|
|
|
|
# Found an object.
|
|
if ( $obj eq 'identity' ) {
|
|
my $identity = NetSNMP::Cert::Identity::parse(*CONFIG, $config);
|
|
my $id = $identity->{'id'};
|
|
die "error - identity: 'id' not defined" unless defined $id;
|
|
die "error - identity: duplicate '$id'" if exists $config->{$obj}{$id};
|
|
$config->{$obj}{$id} = $identity;
|
|
} else {
|
|
die "error - unrecognized object ($1) at scope (config)";
|
|
}
|
|
} elsif (/^\s*(\w+)\s*=?\s*(.*?)\s*$/) {
|
|
my $key = $1;
|
|
my $val = $2;
|
|
|
|
$val = $config->resolve($val) if $val =~ /\$\w+|\&\{.*?\}/;
|
|
# Found a symbol.
|
|
NetSNMP::Cert::dprint(" $key = $val\n", $opts);
|
|
if ($key eq 'subjectAltName' or $key eq 'san') {
|
|
push(@{$config->{$key}}, $val);
|
|
} elsif ( defined $config->{$key} ) {
|
|
die "error - duplicate symbol $key";
|
|
} else {
|
|
$config->{$key} = (defined $val ? NetSNMP::Cert::map_bool($val) : 1 );
|
|
}
|
|
} elsif (/^\s*env\s*(\w+=\S+)\s*$/) {
|
|
# Found an environment variable.
|
|
NetSNMP::Cert::dprint("$&\n", $opts);
|
|
push(@{$config->{'env'}}, $1);
|
|
} else {
|
|
die("error in config file [$cfgfile:line $.]");
|
|
}
|
|
}
|
|
|
|
NetSNMP::Cert::dprint("end parse config\n", $opts);
|
|
close(CONFIG);
|
|
|
|
# augment with any config directives supplied in opts
|
|
foreach my $cfg (@{$opts->{'config'}}) {
|
|
NetSNMP::Cert::dprint("augmenting config: $cfg\n", $opts);
|
|
|
|
$config->autoSet($1, (defined($2) ? $2 : 1 ))
|
|
if $cfg =~ /^\s*(\w+)\s*=?\s*(.*?)\s*$/;
|
|
}
|
|
$config->autoSet('PARSED', 1);
|
|
return $config->{'PARSED'};
|
|
}
|
|
|
|
package NetSNMP::Cert::Identity;
|
|
use vars qw(@ISA);
|
|
@ISA = qw(NetSNMP::Cert::Obj);
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $this = shift || $class->SUPER::new('identity', @_);
|
|
my $ind = $this->{'INDEX'} || 1;
|
|
|
|
$this->autoSet('name', "$this->{type}.$ind") unless exists $this->{'name'};
|
|
|
|
$this->autoSet('LOG','') unless exists $this->{'LOG'};
|
|
$this->autoSet('TTY_LOG','') unless exists $this->{TTY_LOG};
|
|
|
|
bless($this, $class);
|
|
}
|
|
|
|
|
|
sub parse {
|
|
my $FILE = shift;
|
|
my $parent = shift;
|
|
my $opts = $parent->inherit('OPTS');
|
|
my $identity = new NetSNMP::Cert::Obj('identity', $parent);
|
|
|
|
NetSNMP::Cert::dprint("parse identity\n", $opts);
|
|
|
|
while (<$FILE>) {
|
|
next if /^\s*#/ or /^\s*$/;
|
|
|
|
if (/^\s*(\w+)\s*=\s*{/) {
|
|
# Found an object.
|
|
die "error - can't have nested $1";
|
|
} elsif (/^\s*(\w+)\s*=?\s*(.*?)\s*$/) {
|
|
my $key = $1;
|
|
my $val = $2;
|
|
|
|
# Found a symbol.
|
|
NetSNMP::Cert::dprint(" $key = $val\n", $opts);
|
|
|
|
$val = $identity->resolve($val) if $val =~ /\$\w+|\&\{.*?\}/;
|
|
|
|
if ( $key eq 'subjectAltName' or $key eq 'san') {
|
|
push(@{$identity->{$key}}, $val);
|
|
} elsif ( defined $identity->{$key} ) {
|
|
die "error - duplicate symbol $key";
|
|
} else {
|
|
$identity->{$key} = (defined $val ? NetSNMP::Cert::map_bool($val) : 1 );
|
|
}
|
|
} elsif (/\s*\}\s*\;/) {
|
|
# End of definition.
|
|
NetSNMP::Cert::dprint("end parse identity\n", $opts);
|
|
return new NetSNMP::Cert::Identity($identity);
|
|
} else {
|
|
die("error in config file [$opts->{cfgfile}:line $.]");
|
|
}
|
|
}
|
|
die "error - unexpected end of conf file";
|
|
}
|
|
|
|
package main;
|
|
|
|
my $app = new NetSNMP::Cert::App();
|
|
$app->init(@ARGV);
|
|
$app->run();
|
|
|
|
__END__
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
net-snmp-cert - Net-SNMP Certificate Management Tool
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
=over
|
|
|
|
=item $ net-snmp-cert genca -I --cn ca-Net-SNMP
|
|
|
|
=item $ net-snmp-cert gencert -I -t snmpapp --with-ca ca-Net-SNMP
|
|
|
|
=item $ net-snmp-cert gencert -I -t snmpd --cn net-snmp.org
|
|
|
|
=item $ net-snmp-cert showcas
|
|
|
|
=item $ net-snmp-cert showcerts
|
|
|
|
=back
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
net-snmp-cert creates, signs, installs and displays X.509
|
|
certificates used in the operation of Net-SNMP/(D)TLS.
|
|
|
|
=head1 SYNTAX
|
|
|
|
=over
|
|
|
|
=item net-snmp-cert [--help|-?]
|
|
|
|
=item net-snmp-cert [--version|-V]
|
|
|
|
=item net-snmp-cert genca [<flags>] [<dn-opts>] [--with-ca <ca>]
|
|
|
|
=item net-snmp-cert gencert [<flags>] [<dn-opts>] [--with-ca <ca>]
|
|
|
|
=item net-snmp-cert gencsr [<flags>] [<dn-opts>] [--from-crt <crt>]
|
|
|
|
=item net-snmp-cert signcsr [<flags>] [--install] --csr <csr> --with-ca <ca>
|
|
|
|
=item net-snmp-cert showca [<flags>] [<format-opts>] [<file>|<search-tag>]
|
|
|
|
=item net-snmp-cert showcert [<flags>] [<format-opts>] [<file>|<search-tag>]
|
|
|
|
=item net-snmp-cert import [<flags>] <file|url> [<key>]
|
|
|
|
=back
|
|
|
|
=head1 COMMANDS
|
|
|
|
=over
|
|
|
|
=item genca
|
|
|
|
generate a signed CA certificate suitable for signing other
|
|
certificates. default: self-signed unless --with-ca <ca> supplied
|
|
|
|
=item gencert
|
|
|
|
generate a signed certificate suitable for identification, or
|
|
validation. default: self-signed unless --with-ca <ca> supplied
|
|
|
|
=item gencsr
|
|
|
|
generate a certificate signing request. will create a new
|
|
key and certificate unless --from-crt <crt> supplied
|
|
|
|
=item signcsr
|
|
|
|
sign a certificate signing request specified by --csr <csr>
|
|
with the CA certificate specified by --with-ca <ca>
|
|
|
|
=item import
|
|
|
|
import an identity or CA certificate, optionally import <key>
|
|
if an URL is passed, will attempt to import certificate from site
|
|
|
|
=item showca, showcert
|
|
|
|
show CA or identity certificate(s). may pass fully qualified
|
|
file or directory name, or a search-tag which prefix matches
|
|
installed CA or identity certificate file name(s)
|
|
see FORMAT OPTIONS to specify output format
|
|
|
|
|
|
=back
|
|
|
|
=head1 FLAGS
|
|
|
|
=over
|
|
|
|
=item -?, --help -- show this text and exit
|
|
|
|
=item -V, --version -- show version string and exit
|
|
|
|
=item -D, --debug -- raise debug level (-D -D for higher level)
|
|
|
|
=item -F, --force -- force overwrite of existing output files
|
|
|
|
=item -I, --nointeractive -- non-interactive run (default interactive)
|
|
|
|
=item -Q, --noverbose -- non-verbose output (default verbose)
|
|
|
|
=item -C, --cfgdir <dir> -- configuration dir (see man(5) snmp_config)
|
|
|
|
=item -T, --tlsdir <dir> -- root for cert storage (default <cfgdir>/tls)
|
|
|
|
=item -f, --cfgfile <file> -- config (default <cfgdir>/net-snmp-cert.conf)
|
|
|
|
=item -i, --identity <id> -- identity to use from config
|
|
|
|
=item -t, --tag <tag> -- application tag (default 'snmp')
|
|
|
|
=item --<cfg-param>[=<val>] -- additional config params
|
|
|
|
=back
|
|
|
|
=head1 CERTIFICATE OPTIONS (<cert-opts>)
|
|
|
|
=over
|
|
|
|
=item -a, --with-ca <ca> -- CA certificate used to sign request
|
|
|
|
=item -A, --ca <ca> -- explicit output CA certificate
|
|
|
|
=item -r, --csr <csr> -- certificate signing request
|
|
|
|
=item -x, --from-crt <crt> -- input certificate for current operation
|
|
|
|
=item -X, --crt <crt> -- explicit output certificate
|
|
|
|
=item -y, --install -- install result in local repository
|
|
|
|
=back
|
|
|
|
=head1 DISTINGUISHED NAME OPTIONS (<dn-opts>)
|
|
|
|
=over
|
|
|
|
=item -e, --email <email> -- email name
|
|
|
|
=item -h, --host <host> -- DNS host name, or IP address
|
|
|
|
=item -d, --days <days> -- number of days certificate is valid
|
|
|
|
=item -n, --cn <cn> -- common name (CN)
|
|
|
|
=item -o, --org <org> -- organiztion name
|
|
|
|
=item -u, --unit <unit> -- organiztion unit name
|
|
|
|
=item -c, --country <country> -- country code (e.g., US)
|
|
|
|
=item -p, --province <province> -- province name (synomynous w/ state)
|
|
|
|
=item -p, --state <state> -- state name (synomynous w/ province)
|
|
|
|
=item -l, --locality <locality> -- locality name (e.g, town)
|
|
|
|
=item -s, --san <san> -- subjectAltName, repeat for each <san>
|
|
|
|
=over 2
|
|
|
|
=item <san> value format (<FMT>:<VAL>):
|
|
|
|
=over 3
|
|
|
|
=item dirName:/usr/share/snmp/tls
|
|
|
|
=item DNS:net-snmp.org
|
|
|
|
=item email:admin@net-snmp.org
|
|
|
|
=item IP:192.168.1.1
|
|
|
|
=item RID:1.1.3.6
|
|
|
|
=item URI:http://net-snmp.org
|
|
|
|
=back
|
|
|
|
=back
|
|
|
|
=back
|
|
|
|
=head1 FORMAT OPTIONS (<format-opts>)
|
|
|
|
=over
|
|
|
|
=item --brief -- minimized output (values only where applicable)
|
|
|
|
=item --text -- full text description
|
|
|
|
=item --subject -- subject description
|
|
|
|
=item --subject_hash -- hash of subject for indexing
|
|
|
|
=item --issuer -- issuer description
|
|
|
|
=item --issuer_hash -- hash of issuer for indexing
|
|
|
|
=item --fingerprint -- SHA1 digest of DER
|
|
|
|
=item --serial -- serial number
|
|
|
|
=item --modulus -- modulus of the public key
|
|
|
|
=item --dates -- start and end dates
|
|
|
|
=item --purpose -- displays allowed uses
|
|
|
|
=item --C -- C code description
|
|
|
|
=back
|
|
|
|
=head1 OPERATIONAL NOTES
|
|
|
|
|
|
=head2 Interactive Mode
|
|
|
|
The application operates in interactive mode by default. In this mode
|
|
basic operations of offered and required input is queried through the
|
|
command line.
|
|
|
|
Typical <tab> completion is provided when possible and field specific
|
|
help may be obtained by entering '?'.
|
|
|
|
To turn off interactive mode, supply '--nointeractive' or '-I' on the
|
|
initial command line. Equivalantly, 'interactive = false' maybe placed
|
|
in the configuration file (see below).
|
|
|
|
=head2 Configuration
|
|
|
|
A configuration file may be supplied on the command line or found in a
|
|
default location (<snmpconfpath>/net-snmp-cert.conf). This file may
|
|
contain configuration parameters equivalent to those supplied on the
|
|
command line and effectively provides default values for these
|
|
values. If a command line parameter is supplied it will override the
|
|
value in the config file. If neither is present then an application
|
|
value will be used.
|
|
|
|
=head2 Certificate Naming
|
|
|
|
Filenames of created certificates, as stored in the configuration
|
|
directory, are chosen in the following manner. If and application tag
|
|
is supplied, it will be used as the basename for the certificate and
|
|
key generated. Otherwise, for CA certificates, the basename will be
|
|
derived from the Common Name. For non-CA certificates the application
|
|
tag defaults to 'snmp' which will then be used as the basename of the
|
|
certificate and key.
|
|
|
|
=head1 EXAMPLES
|
|
|
|
=over
|
|
|
|
=item net-snmp-cert genca --cn ca-net-snmp.org --days 1000
|
|
|
|
=item net-snmp-cert genca -f .snmp/net-snmp-cert.conf -i nocadm
|
|
|
|
=item net-snmp-cert gencert -t snmpd --cn host.net-snmp.org
|
|
|
|
=item net-snmp-cert gencsr -t snmpapp
|
|
|
|
=item net-snmp-cert signcsr --csr snmpapp --with-ca ca-net-snmp.org
|
|
|
|
=item net-snmp-cert showcerts --subject --issuer --dates snmpapp
|
|
|
|
=item net-snmp-cert showcas --fingerprint ca-net-snmp.org --brief
|
|
|
|
=item net-snmp-cert import ca-third-party.crt
|
|
|
|
=item net-snmp-cert import signed-request.crt signed-request.key
|
|
|
|
=back
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright (c) 2010 Cobham Analytic Solutions - All rights reserved.
|
|
Copyright (c) 2010 G. S. Marzot - All rights reserved.
|
|
|
|
=head1 AUTHOR
|
|
|
|
G. S. Marzot (marz@users.sourceforge.net)
|
|
|
|
=cut
|
|
|