1#!/usr/bin/perl 2 3use strict; 4use 5.8.0; 5 6use Getopt::Long qw(:config gnu_getopt no_ignore_case pass_through); 7use Cwd qw(getcwd realpath); 8use File::Basename; 9use File::Copy; 10use File::Path; 11use Sys::Hostname; 12use Carp; 13 14our @CC = ( 15["AF","AFGHANISTAN"], 16["AX","ÅLAND ISLANDS"], 17["AL","ALBANIA"], 18["DZ","ALGERIA"], 19["AS","AMERICAN SAMOA"], 20["AD","ANDORRA"], 21["AO","ANGOLA"], 22["AI","ANGUILLA"], 23["AQ","ANTARCTICA"], 24["AG","ANTIGUA AND BARBUDA"], 25["AR","ARGENTINA"], 26["AM","ARMENIA"], 27["AW","ARUBA"], 28["AU","AUSTRALIA"], 29["AT","AUSTRIA"], 30["AZ","AZERBAIJAN"], 31["BS","BAHAMAS"], 32["BH","BAHRAIN"], 33["BD","BANGLADESH"], 34["BB","BARBADOS"], 35["BY","BELARUS"], 36["BE","BELGIUM"], 37["BZ","BELIZE"], 38["BJ","BENIN"], 39["BM","BERMUDA"], 40["BT","BHUTAN"], 41["BO","BOLIVIA, PLURINATIONAL STATE OF"], 42["BA","BOSNIA AND HERZEGOVINA"], 43["BW","BOTSWANA"], 44["BV","BOUVET ISLAND"], 45["BR","BRAZIL"], 46["IO","BRITISH INDIAN OCEAN TERRITORY"], 47["BN","BRUNEI DARUSSALAM"], 48["BG","BULGARIA"], 49["BF","BURKINA FASO"], 50["BI","BURUNDI"], 51["KH","CAMBODIA"], 52["CM","CAMEROON"], 53["CA","CANADA"], 54["CV","CAPE VERDE"], 55["KY","CAYMAN ISLANDS"], 56["CF","CENTRAL AFRICAN REPUBLIC"], 57["TD","CHAD"], 58["CL","CHILE"], 59["CN","CHINA"], 60["CX","CHRISTMAS ISLAND"], 61["CC","COCOS (KEELING) ISLANDS"], 62["CO","COLOMBIA"], 63["KM","COMOROS"], 64["CG","CONGO"], 65["CD","CONGO, THE DEMOCRATIC REPUBLIC OF THE"], 66["CK","COOK ISLANDS"], 67["CR","COSTA RICA"], 68["CI","CÔTE D'IVOIRE"], 69["HR","CROATIA"], 70["CU","CUBA"], 71["CY","CYPRUS"], 72["CZ","CZECH REPUBLIC"], 73["DK","DENMARK"], 74["DJ","DJIBOUTI"], 75["DM","DOMINICA"], 76["DO","DOMINICAN REPUBLIC"], 77["EC","ECUADOR"], 78["EG","EGYPT"], 79["SV","EL SALVADOR"], 80["GQ","EQUATORIAL GUINEA"], 81["ER","ERITREA"], 82["EE","ESTONIA"], 83["ET","ETHIOPIA"], 84["FK","FALKLAND ISLANDS (MALVINAS)"], 85["FO","FAROE ISLANDS"], 86["FJ","FIJI"], 87["FI","FINLAND"], 88["FR","FRANCE"], 89["GF","FRENCH GUIANA"], 90["PF","FRENCH POLYNESIA"], 91["TF","FRENCH SOUTHERN TERRITORIES"], 92["GA","GABON"], 93["GM","GAMBIA"], 94["GE","GEORGIA"], 95["DE","GERMANY"], 96["GH","GHANA"], 97["GI","GIBRALTAR"], 98["GR","GREECE"], 99["GL","GREENLAND"], 100["GD","GRENADA"], 101["GP","GUADELOUPE"], 102["GU","GUAM"], 103["GT","GUATEMALA"], 104["GG","GUERNSEY"], 105["GN","GUINEA"], 106["GW","GUINEA-BISSAU"], 107["GY","GUYANA"], 108["HT","HAITI"], 109["HM","HEARD ISLAND AND MCDONALD ISLANDS"], 110["VA","HOLY SEE (VATICAN CITY STATE)"], 111["HN","HONDURAS"], 112["HK","HONG KONG"], 113["HU","HUNGARY"], 114["IS","ICELAND"], 115["IN","INDIA"], 116["ID","INDONESIA"], 117["IR","IRAN, ISLAMIC REPUBLIC OF"], 118["IQ","IRAQ"], 119["IE","IRELAND"], 120["IM","ISLE OF MAN"], 121["IL","ISRAEL"], 122["IT","ITALY"], 123["JM","JAMAICA"], 124["JP","JAPAN"], 125["JE","JERSEY"], 126["JO","JORDAN"], 127["KZ","KAZAKHSTAN"], 128["KE","KENYA"], 129["KI","KIRIBATI"], 130["KP","KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF"], 131["KR","KOREA, REPUBLIC OF"], 132["KW","KUWAIT"], 133["KG","KYRGYZSTAN"], 134["LA","LAO PEOPLE'S DEMOCRATIC REPUBLIC"], 135["LV","LATVIA"], 136["LB","LEBANON"], 137["LS","LESOTHO"], 138["LR","LIBERIA"], 139["LY","LIBYAN ARAB JAMAHIRIYA"], 140["LI","LIECHTENSTEIN"], 141["LT","LITHUANIA"], 142["LU","LUXEMBOURG"], 143["MO","MACAO"], 144["MK","MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF"], 145["MG","MADAGASCAR"], 146["MW","MALAWI"], 147["MY","MALAYSIA"], 148["MV","MALDIVES"], 149["ML","MALI"], 150["MT","MALTA"], 151["MH","MARSHALL ISLANDS"], 152["MQ","MARTINIQUE"], 153["MR","MAURITANIA"], 154["MU","MAURITIUS"], 155["YT","MAYOTTE"], 156["MX","MEXICO"], 157["FM","MICRONESIA, FEDERATED STATES OF"], 158["MD","MOLDOVA, REPUBLIC OF"], 159["MC","MONACO"], 160["MN","MONGOLIA"], 161["ME","MONTENEGRO"], 162["MS","MONTSERRAT"], 163["MA","MOROCCO"], 164["MZ","MOZAMBIQUE"], 165["MM","MYANMAR"], 166["NA","NAMIBIA"], 167["NR","NAURU"], 168["NP","NEPAL"], 169["NL","NETHERLANDS"], 170["AN","NETHERLANDS ANTILLES"], 171["NC","NEW CALEDONIA"], 172["NZ","NEW ZEALAND"], 173["NI","NICARAGUA"], 174["NE","NIGER"], 175["NG","NIGERIA"], 176["NU","NIUE"], 177["NF","NORFOLK ISLAND"], 178["MP","NORTHERN MARIANA ISLANDS"], 179["NO","NORWAY"], 180["OM","OMAN"], 181["PK","PAKISTAN"], 182["PW","PALAU"], 183["PS","PALESTINIAN TERRITORY, OCCUPIED"], 184["PA","PANAMA"], 185["PG","PAPUA NEW GUINEA"], 186["PY","PARAGUAY"], 187["PE","PERU"], 188["PH","PHILIPPINES"], 189["PN","PITCAIRN"], 190["PL","POLAND"], 191["PT","PORTUGAL"], 192["PR","PUERTO RICO"], 193["QA","QATAR"], 194["RE","RÉUNION"], 195["RO","ROMANIA"], 196["RU","RUSSIAN FEDERATION"], 197["RW","RWANDA"], 198["BL","SAINT BARTHÉLEMY"], 199["SH","SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA"], 200["KN","SAINT KITTS AND NEVIS"], 201["LC","SAINT LUCIA"], 202["MF","SAINT MARTIN"], 203["PM","SAINT PIERRE AND MIQUELON"], 204["VC","SAINT VINCENT AND THE GRENADINES"], 205["WS","SAMOA"], 206["SM","SAN MARINO"], 207["ST","SAO TOME AND PRINCIPE"], 208["SA","SAUDI ARABIA"], 209["SN","SENEGAL"], 210["RS","SERBIA"], 211["SC","SEYCHELLES"], 212["SL","SIERRA LEONE"], 213["SG","SINGAPORE"], 214["SK","SLOVAKIA"], 215["SI","SLOVENIA"], 216["SB","SOLOMON ISLANDS"], 217["SO","SOMALIA"], 218["ZA","SOUTH AFRICA"], 219["GS","SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS"], 220["ES","SPAIN"], 221["LK","SRI LANKA"], 222["SD","SUDAN"], 223["SR","SURINAME"], 224["SJ","SVALBARD AND JAN MAYEN"], 225["SZ","SWAZILAND"], 226["SE","SWEDEN"], 227["CH","SWITZERLAND"], 228["SY","SYRIAN ARAB REPUBLIC"], 229["TW","TAIWAN, PROVINCE OF CHINA"], 230["TJ","TAJIKISTAN"], 231["TZ","TANZANIA, UNITED REPUBLIC OF"], 232["TH","THAILAND"], 233["TL","TIMOR-LESTE"], 234["TG","TOGO"], 235["TK","TOKELAU"], 236["TO","TONGA"], 237["TT","TRINIDAD AND TOBAGO"], 238["TN","TUNISIA"], 239["TR","TURKEY"], 240["TM","TURKMENISTAN"], 241["TC","TURKS AND CAICOS ISLANDS"], 242["TV","TUVALU"], 243["UG","UGANDA"], 244["UA","UKRAINE"], 245["AE","UNITED ARAB EMIRATES"], 246["GB","UNITED KINGDOM"], 247["US","UNITED STATES"], 248["UM","UNITED STATES MINOR OUTLYING ISLANDS"], 249["UY","URUGUAY"], 250["UZ","UZBEKISTAN"], 251["VU","VANUATU"], 252["VE","VENEZUELA, BOLIVARIAN REPUBLIC OF"], 253["VN","VIET NAM"], 254["VG","VIRGIN ISLANDS, BRITISH"], 255["VI","VIRGIN ISLANDS, U.S."], 256["WF","WALLIS AND FUTUNA"], 257["EH","WESTERN SAHARA"], 258["YE","YEMEN"], 259["ZM","ZAMBIA"], 260["ZW","ZIMBABWE"], 261); 262 263package NetSNMP::Term; 264# gratefully taken from Wayne Thompson's Term::Complete 265# if newer CORE modules could be used this could be removed 266our($complete, $exit, $done, $kill, $erase1, $erase2, $tty_raw_noecho, $tty_restore, $stty, $tty_safe_restore); 267our($tty_saved_state) = ''; 268CONFIG: { 269 $exit = "\003"; 270 $done = "\004"; 271 $kill = "\025"; 272 $erase1 = "\177"; 273 $erase2 = "\010"; 274 foreach my $s (qw(/bin/stty /usr/bin/stty)) { 275 if (-x $s) { 276 $tty_raw_noecho = "$s raw -echo"; 277 $tty_restore = "$s -raw echo"; 278 $tty_safe_restore = $tty_restore; 279 $stty = $s; 280 last; 281 } 282 } 283} 284 285sub Complete { 286 my($prompt, $dflt, $help, $match, @cmp_lst); 287 my (%help, $cmp, $test, $l, @match); 288 my ($return, $r, $exitting) = ("", 0, 0); 289 my $tab; 290 291 $prompt = shift; 292 $dflt = shift; 293 $help = shift; 294 $match = shift; 295 296 if (ref $_[0] and ref($_[0][0])) { 297 @cmp_lst = @{$_[0]}; 298 } else { 299 @cmp_lst = @_; 300 } 301 @cmp_lst = map {if (ref($_)) { $help{$_->[0]}=$_->[1]; $_->[0]} else {$_;}} @cmp_lst; 302 303 # Attempt to save the current stty state, to be restored later 304 if (defined $stty && defined $tty_saved_state && $tty_saved_state eq '') { 305 $tty_saved_state = qx($stty -g 2>/dev/null); 306 if ($?) { 307 # stty -g not supported 308 $tty_saved_state = undef; 309 } else { 310 $tty_saved_state =~ s/\s+$//g; 311 $tty_restore = qq($stty "$tty_saved_state" 2>/dev/null); 312 } 313 } 314 system $tty_raw_noecho if defined $tty_raw_noecho; 315 LOOP: { 316 local $_; 317 print($prompt, $return); 318 while (($_ = getc(STDIN)) ne "\r") { 319 CASE: { 320 # (TAB) attempt completion 321 $_ eq "\t" && do { 322 if ($tab) { 323 print(join("\r\n", '', map {exists $help{$_} ? sprintf("\t%-10.10s - %s", $_, $help{$_}) : 324 "\t$_"} grep(/^\Q$return/, @cmp_lst)), "\r\n"); 325 $tab--; 326 redo LOOP; 327 } 328 @match = grep(/^\Q$return/, @cmp_lst); 329 unless ($#match < 0) { 330 $l = length($test = shift(@match)); 331 foreach $cmp (@match) { 332 until (substr($cmp, 0, $l) eq substr($test, 0, $l)) { 333 $l--; 334 } 335 } 336 print("\a"); 337 print($test = substr($test, $r, $l - $r)); 338 $r = length($return .= $test); 339 } 340 $tab++; 341 last CASE; 342 }; 343 344 # (^C) exit 345 $_ eq $exit && do { 346 print("\r\naborting application...\r\n"); 347 $exitting++; 348 undef $return; 349 last LOOP; 350 }; 351 352 # (^D) done 353 $_ eq $done && do { 354 undef $return; 355 last LOOP; 356 }; 357 358 # (?) show help if available 359 $_ eq '?' && do { 360 print("\r\n$help\r\n"); 361 if (exists $help{$return}) { 362 print("\t$return - $help{$return}\r\n"); 363 } else { 364 print(join("\r\n", map {exists $help{$_} ? "\t$_ - $help{$_}" : 365 "\t$_"} grep(/^\Q$return/, @cmp_lst)), "\r\n"); 366 } 367 redo LOOP; 368 }; 369 370 # (^U) kill 371 $_ eq $kill && do { 372 if ($r) { 373 $r = 0; 374 $return = ""; 375 print("\r\n"); 376 redo LOOP; 377 } 378 last CASE; 379 }; 380 381 # (DEL) || (BS) erase 382 ($_ eq $erase1 || $_ eq $erase2) && do { 383 if ($r) { 384 print("\b \b"); 385 chop($return); 386 $r--; 387 } 388 last CASE; 389 }; 390 391 # printable char 392 ord >= 32 && do { 393 $return .= $_; 394 $r++; 395 print; 396 last CASE; 397 }; 398 } 399 } 400 401 if (defined $return) { 402 if (length($return) == 0 or $return eq $dflt) { 403 $return = $dflt; 404 } elsif ($match == $NetSNMP::Cert::MATCH and scalar(grep {/^$return$/} @cmp_lst) != 1 or 405 $match == $NetSNMP::Cert::PREMATCH and scalar(grep {$return=~/$_/} @cmp_lst) != 1) { 406 $r = 0; 407 $return = ""; 408 print("\r\nChoose a valid option, or ^D to exit\r\n"); 409 redo LOOP; 410 } 411 } 412 } 413 DONE: 414 # system $tty_restore if defined $tty_restore; 415 if (defined $tty_saved_state && defined $tty_restore && defined $tty_safe_restore) { 416 system $tty_restore; 417 if ($?) { 418 # tty_restore caused error 419 system $tty_safe_restore; 420 } 421 } 422 423 exit(1) if $exitting; 424 print("\n"); 425 return $return; 426} 427 428package NetSNMP::Cert; 429 430our $VERSION = '0.2.9'; 431 432our $PROG = ::basename($0); 433our $DEBUG = 0; 434our $OK = 0; 435our $ERR = -1; 436 437# user input param 438our $MATCH = 1; 439our $PREMATCH = 2; 440 441# Load LWP if possible to import cert from URL 442eval('use LWP::UserAgent;'); 443our $haveUserAgent = ($@ ? 0 : 1); 444 445# required executables 446our $OPENSSL = $ENV{NET_SNMP_CRT_OPENSSL} || 'openssl'; 447our $CFGTOOL = $ENV{NET_SNMP_CRT_CFGTOOL} || 'net-snmp-config'; 448 449# default app config file 450our $CFGFILE = $ENV{NET_SNMP_CRT_CFGFILE} || 'net-snmp-cert.conf'; 451 452# default OpenSSL config files 453our $SSLCFGIN = $ENV{NET_SNMP_CRT_SSLCFGIN} || 'openssl.in'; 454our $SSLCFGOUT = $ENV{NET_SNMP_CRT_SSLCFGIN} || '.openssl.conf'; 455our $SSLCFG = $ENV{NET_SNMP_CRT_SSLCFG}; 456 457# default cmd logs 458our $OUTLOG = $ENV{NET_SNMP_CRT_OUTLOG} || '.cmd.out.log'; 459our $ERRLOG = $ENV{NET_SNMP_CRT_ERRLOG} || '.cmd.err.log'; 460 461# default cert dirs 462our $TLSDIR = $ENV{NET_SNMP_CRT_TLSDIR} || 'tls'; 463our $CRTDIR = $ENV{NET_SNMP_CRT_CRTDIR} || 'certs'; 464our $NEWCRTDIR = $ENV{NET_SNMP_CRT_NEWCRTDIR}|| 'newcerts'; 465our $CADIR = $ENV{NET_SNMP_CRT_CADIR} || 'ca-certs'; 466our $PRIVDIR = $ENV{NET_SNMP_CRT_PRIVDIR} || 'private'; 467 468our $SERIAL = $ENV{NET_SNMP_CRT_SERIAL} || '.serial'; 469our $INDEX = $ENV{NET_SNMP_CRT_INDEX} || '.index'; 470 471our $DEFCADAYS = $ENV{NET_SNMP_CRT_DEFCADAYS} || 1825; 472our $DEFCRTDAYS = $ENV{NET_SNMP_CRT_DEFCRTDAYS}|| 365; 473 474our @TLSDIRS = ($CRTDIR, $NEWCRTDIR, $CADIR, $PRIVDIR); 475 476our @CRTSUFFIXES = qw(.pem .csr .der .crt); 477 478our @X509FMTS = qw(text modulus serial subject_hash issuer_hash hash subject 479 purpose issuer startdate enddate dates fingerprint C); 480 481sub dprint { 482 my $str = shift; 483 my $opts = shift; 484 my $dlevel = shift || 1; 485 my $flag = (defined $opts ? int($opts->{'debug'} >= $dlevel) : 1); 486 my ($pkg, $file, $line, $sub) = caller(1); 487 my ($pkg0, $file0, $line0) = caller(0); 488 print("${sub}():$line0: $str") if $flag; 489} 490 491sub dwarn { 492 my $str = shift; 493 my $opts = shift; 494 my $dlevel = shift || 1; 495 my $flag = (defined $opts ? $opts->{'debug'} >= $dlevel : 1); 496 my ($pkg, $file, $line, $sub) = caller(1); 497 my ($pkg0, $file0, $line0) = caller(0); 498 warn("${sub}():$line0: $str") if $flag; 499} 500 501sub vprint { 502 my $str = shift; 503 my $opts = shift; 504 my $flag = (defined $opts ? $opts->{'verbose'} : 1); 505 my $debug = (defined $opts ? $opts->{'debug'} : 1); 506 my ($pkg, $file, $line, $sub) = caller(1); 507 my ($pkg0, $file0, $line0) = caller(0); 508 $str = ($debug ? "${sub}():$line0: $str" : "$str"); 509 print("$str") if $flag; 510} 511 512sub vwarn { 513 my $str = shift; 514 my $opts = shift; 515 my $flag = (defined $opts ? $opts->{'verbose'} : 1); 516 my ($pkg, $file, $line, $sub) = caller(1); 517 my ($pkg0, $file0, $line0) = caller(0); 518 warn("${sub}():$line0: $str") if $flag; 519} 520 521sub rsystem { 522 my $cmd = shift; 523 my $flag = shift; 524 525 # if not running as root try to use sudo 526 if ($>) { 527 $cmd = "sudo $flag $cmd"; 528 } else { 529 if ($flag =~ /-b/) { 530 $cmd = "$cmd \&"; 531 } 532 } 533 die("cmd failed($!): $cmd\n") if system("$cmd"); 534} 535 536sub usystem { 537 my $cmd = shift; 538 my $opts = shift; 539 my $ret; 540 541 unlink $NetSNMP::Cert::OUTLOG if -e $NetSNMP::Cert::OUTLOG; 542 unlink $NetSNMP::Cert::ERRLOG if -e $NetSNMP::Cert::ERRLOG; 543 544 $ret = system("$cmd"); 545 546 if ($ret) { 547 if ($opts->{'verbose'}) { 548 system("cat $NetSNMP::Cert::OUTLOG") if -r $NetSNMP::Cert::OUTLOG; 549 system("cat $NetSNMP::Cert::ERRLOG") if -r $NetSNMP::Cert::ERRLOG; 550 } 551 die("cmd failed($!): $cmd\n"); 552 } 553} 554 555sub home_dir { 556 my $cdir = ::getcwd(); 557 558 chdir("~"); 559 my $dir = ::getcwd(); 560 chdir($cdir); 561 562 return $dir; 563} 564 565sub find_bin_dir { 566 # This code finds the path to the currently invoked program and 567 my $dir; 568 569 $0 =~ m%^(([^/]*)(.*)/)([^/]+)$%; 570 if ($1) { 571 if ($2) { 572 # Invoked using a relative path. CD there and use pwd. 573 $dir = `cd $1 && pwd`; 574 chomp $dir; 575 } else { 576 # Invoked using an absolute path; that's it! 577 $dir = $3; 578 } 579 } else { 580 # No path. Look in PATH for the first instance. 581 foreach my $p (split /:/, $ENV{PATH}) { 582 $p ||= '.'; 583 -x "$p/$4" or next; 584 $dir = $p; 585 } 586 } 587 $dir or die "Cannot locate program '$0'!"; 588} 589 590sub fq_rel_path { 591 my $path = shift; 592 my $cwd = ::getcwd(); 593 my $rdir = shift || $cwd; 594 595 chdir($rdir) or die("can't change directory: $rdir"); 596 # get fully qualified path 597 if ($path !~ m|^/|) { 598 my $pwd = `pwd`; chomp $pwd; 599 $path = "$pwd/$path"; 600 $path = ::realpath($path) if -e $path; 601 } 602 chdir($cwd) or die("can't change directory: $cwd"); 603 604 return $path; 605} 606 607sub dir_empty 608 { 609 my $path = shift; 610 opendir(DIR, $path); 611 foreach (readdir(DIR)) { 612 next if /^\.\.?$/; 613 closedir(DIR); 614 return 0; 615 } 616 closedir(DIR); 617 return 1; 618 } 619 620sub make_dirs { 621 my $opts = shift; 622 my $dir = shift; 623 my $mode = shift; 624 my @dirs = @_; 625 626 my $wd = ::getcwd(); 627 628 NetSNMP::Cert::dprint("make dirs [$dir:@dirs] from $wd\n", $opts); 629 630 ::mkpath($dir, $opts->{'debug'}, $mode) or die("error - can't make $dir") 631 if defined $dir and not -d $dir; 632 633 foreach my $subdir (@dirs) { 634 my $d = "$subdir"; 635 $d = "$dir/$d" if defined $dir; 636 NetSNMP::Cert::dprint("making directory: $d\n", $opts) unless -d $d; 637 mkdir($d, $mode) or die("error - can't make $d") unless -d $d; 638 } 639} 640 641sub is_url { 642 my $url = shift; 643 644 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})*)?$/; 645} 646 647sub in_set { 648 my $elem = shift; 649 my $set = shift; 650 for my $e (eval($set)) { 651 return 1 if $e == $elem; 652 } 653 return 0; 654} 655 656sub in_arr { 657 my $elem = shift; 658 my $arr = shift; 659 for my $e (@{$arr}) { 660 return 1 if $e eq $elem; 661 } 662 return 0; 663} 664 665sub map_bool { 666 my $val = shift; 667 668 return 1 if $val =~ /^true$/i; 669 return 0 if $val =~ /^false$/i; 670 return $val; 671} 672 673sub version { 674 my $ret = (@_ ? shift : 1); 675 print "$NetSNMP::Cert::PROG: $NetSNMP::Cert::VERSION\n"; 676 exit($ret); 677} 678 679sub GetOptsFromArray { 680 my $args = shift; 681 local *ARGV; 682 @ARGV = @$args; 683 my $ret = ::GetOptions(@_); 684 @$args = @ARGV; # GetOptions strips out the ones it handles and leaves others 685 return $ret; 686} 687sub pull_cmd { 688 my $args = shift; 689 my $cmd; 690 691 foreach (@{$args}) { 692 if (/^(gence?rt|genca|gencsr|signcsr|showcas?|showce?rts?|import)$/) { 693 $cmd = $1; 694 } 695 } 696 697 @{$args} = grep {!/^$cmd$/} @{$args}; 698 699 return $cmd; 700} 701 702 703sub usage { 704 my $ret = (@_ ? shift : 1); 705 my $arg = shift; 706 707 print "Command not implmeneted yet: $arg\n" if $ret == 2; 708 print "Unknown: $arg\n" if $ret == 3; 709 print "\n NAME:\n"; 710 print " $NetSNMP::Cert::PROG: [$NetSNMP::Cert::VERSION] - "; 711 print "Net-SNMP Certificate Management Tool\n"; 712 print "\n DESCRIPTION:\n"; 713 print " net-snmp-cert creates, signs, installs and displays X.509\n"; 714 print " certificates used in the operation of Net-SNMP/(D)TLS.\n"; 715 print "\n SYNOPSIS:\n"; 716 print " net-snmp-cert [--help|-?]\n"; 717 print " net-snmp-cert [--version|-V]\n"; 718 print " net-snmp-cert genca [<flags>] [<dn-opts>] [--with-ca <ca>]\n"; 719 print " net-snmp-cert gencert [<flags>] [<dn-opts>] [--with-ca <ca>]\n"; 720 print " net-snmp-cert gencsr [<flags>] [<dn-opts>] [--from-crt <crt>]\n"; 721 print " net-snmp-cert signcsr [<flags>] [--install] --csr <csr> --with-ca <ca>\n"; 722 print " net-snmp-cert showca [<flags>] [<format-opts>] [<file>|<search-tag>]\n"; 723 print " net-snmp-cert showcert [<flags>] [<format-opts>] [<file>|<search-tag>]\n"; 724 print " net-snmp-cert import [<flags>] <file|url> [<key>]\n"; 725 print "\n COMMANDS:\n"; 726 print " genca -- generate a signed CA certificate suitable for signing other\n"; 727 print " certificates. default: self-signed unless --with-ca <ca> supplied\n\n"; 728 print " gencert -- generate a signed certificate suitable for identification, or\n"; 729 print " validation. default: self-signed unless --with-ca <ca> supplied\n\n"; 730 print " gencsr -- generate a certificate signing request. will create a new\n"; 731 print " key and certificate unless --from-crt <crt> supplied\n\n"; 732 print " signcsr -- sign a certificate signing request specified by --csr <csr>\n"; 733 print " with the CA certificate specified by --with-ca <ca>\n"; 734 print " import -- import an identity or CA certificate, optionally import <key>\n"; 735 print " if an URL is passed, will attempt to import certificate from site\n"; 736 print " showca,\n"; 737 print " showcert -- show CA or identity certificate(s). may pass fully qualified\n"; 738 print " file or directory name, or a search-tag which prefix matches\n"; 739 print " installed CA or identity certificate file name(s)\n"; 740 print " see FORMAT OPTIONS to specify output format\n\n"; 741 print "\n FLAGS:\n"; 742 print " -?, --help -- show this text and exit\n"; 743 print " -V, --version -- show version string and exit\n"; 744 print " -D, --debug -- raise debug level (-D -D for higher level)\n"; 745 print " -F, --force -- force overwrite of existing output files\n"; 746 print " -I, --nointeractive -- non-interactive run (default interactive)\n"; 747 print " -Q, --noverbose -- non-verbose output (default verbose)\n"; 748 print " -C, --cfgdir <dir> -- configuration dir (see man(5) snmp_config)\n"; 749 print " -T, --tlsdir <dir> -- root for cert storage (default <cfgdir>/tls)\n"; 750 print " -f, --cfgfile <file> -- config (default <cfgdir>/net-snmp-cert.conf)\n"; 751 print " -i, --identity <id> -- identity to use from config\n"; 752 print " -t, --tag <tag> -- application tag (default 'snmp')\n"; 753 print " --<cfg-param>[=<val>] -- additional config params\n"; 754 print "\n CERTIFICATE OPTIONS (<cert-opts>):\n"; 755 print " -a, --with-ca <ca> -- CA certificate used to sign request\n"; 756 print " -A, --ca <ca> -- explicit output CA certificate\n"; 757 print " -r, --csr <csr> -- certificate signing request\n"; 758 print " -x, --from-crt <crt> -- input certificate for current operation\n"; 759 print " -X, --crt <crt> -- explicit output certificate\n"; 760 print " -y, --install -- install result in local repository\n"; 761 print "\n DISTINGUISHED NAME OPTIONS (<dn-opts>):\n"; 762 print " -e, --email <email> -- email name\n"; 763 print " -h, --host <host> -- DNS host name, or IP address\n"; 764 print " -d, --days <days> -- number of days certificate is valid\n"; 765 print " -n, --cn <cn> -- common name (CN)\n"; 766 print " -o, --org <org> -- organiztion name\n"; 767 print " -u, --unit <unit> -- organiztion unit name\n"; 768 print " -c, --country <country> -- country code (e.g., US)\n"; 769 print " -p, --province <province> -- province name (synomynous w/ state)\n"; 770 print " -p, --state <state> -- state name (synomynous w/ province)\n"; 771 print " -l, --locality <locality> -- locality name (e.g, town)\n"; 772 print " -s, --san <san> -- subjectAltName, repeat for each <san>\n"; 773 print " -- <san> value format (<FMT>:<VAL>):\n"; 774 print " -- dirName:/usr/share/snmp/tls\n"; 775 print " -- DNS:net-snmp.org\n"; 776 print " -- email:admin\@net-snmp.org\n"; 777 print " -- IP:192.168.1.1\n"; 778 print " -- RID:1.1.3.6\n"; 779 print " -- URI:http://net-snmp.org\n"; 780 print "\n FORMAT OPTIONS (<format-opts>): \n"; 781 print " --brief -- minimized output (values only where applicable)\n"; 782 print " --text -- full text description\n"; 783 print " --subject -- subject description\n"; 784 print " --subject_hash -- hash of subject for indexing\n"; 785 print " --issuer -- issuer description\n"; 786 print " --issuer_hash -- hash of issuer for indexing\n"; 787 print " --fingerprint -- SHA1 digest of DER\n"; 788 print " --serial -- serial number\n"; 789 print " --modulus -- modulus of the public key\n"; 790 print " --dates -- start and end dates\n"; 791 print " --purpose -- displays allowed uses\n"; 792 print " --C -- C code description\n"; 793 print "\n EXAMPLES: \n"; 794 print " net-snmp-cert genca --cn ca-net-snmp.org --days 1000\n"; 795 print " net-snmp-cert genca -f .snmp/net-snmp-cert.conf -i nocadm -I\n"; 796 print " net-snmp-cert gencert -t snmpd --cn host.net-snmp.org\n"; 797 print " net-snmp-cert gencsr -t snmpapp\n"; 798 print " net-snmp-cert signcsr --csr snmpapp --with-ca ca-net-snmp.org\n"; 799 print " net-snmp-cert showcerts --subject --issuer --dates snmpapp\n"; 800 print " net-snmp-cert showcas --fingerprint ca-net-snmp.org --brief\n"; 801 print " net-snmp-cert import ca-third-party.crt\n"; 802 print " net-snmp-cert import signed-request.crt signed-request.key\n\n"; 803 804 exit $ret; 805} 806 807sub set_default { 808 my $opts = shift; 809 my $config = shift; 810 my $field = shift; 811 my $val = shift; 812 813 if (not defined $opts->{$field}) { 814 my $cval = $config->inherit($field); 815 $opts->{$field} = (defined $cval ? $cval : $val); 816 } 817} 818 819sub cfg_path { 820 my $path; 821 822 $path = `$NetSNMP::Cert::CFGTOOL --snmpconfpath`; 823 chomp $path; 824 return (wantarray ? split(':', $path) : $path); 825} 826 827sub find_cfgfile { 828 my $dir = shift; 829 my $file = shift; 830 831 if (defined $dir and -d $dir and 832 defined $file and $file !~ /^[\.\/]/) { 833 return fq_rel_path("$dir/$file") if -f "$dir/$file"; 834 } 835 836 if (defined $file) { 837 if (-f $file) { 838 return fq_rel_path($file); 839 } else { 840 return $file; # file is not found, complain later 841 } 842 } 843 844 my @path = cfg_path(); 845 unshift(@path, $dir) if defined $dir; 846 while (@path) { 847 my $p = pop(@path); 848 next if $p eq '/var/lib/snmp'; 849 if (-r "$p/$NetSNMP::Cert::CFGFILE") { 850 return fq_rel_path("$p/$NetSNMP::Cert::CFGFILE"); 851 } 852 } 853 854 return $file; 855} 856 857sub find_cfgdir { 858 my $dir = shift; 859 my $file = shift; 860 861 if (defined $dir) { 862 $dir = NetSNMP::Cert::fq_rel_path($dir); 863 return $dir; 864 } 865 866 if (defined $file and -f $file) { 867 $dir = ::dirname($file); 868 return NetSNMP::Cert::fq_rel_path($dir); 869 } else { 870 my @path = cfg_path(); 871 # search first for writeable tls dir 872 # for root search top down, for user bottom up 873 while (@path) { 874 $dir = ($> ? pop(@path): shift(@path)); 875 next if $dir eq '/var/lib/snmp'; 876 return $dir if -d "$dir/$NetSNMP::Cert::TLSDIR" and -w "$dir/$NetSNMP::Cert::TLSDIR"; 877 } 878 @path = cfg_path(); 879 # if no tls dir found, pick first writable config dir 880 # for root search top down, for user bottom up 881 while (@path) { 882 $dir = ($> ? pop(@path): shift(@path)); 883 next if $dir eq '/var/lib/snmp'; 884 return $dir if -d $dir and -w $dir; 885 } 886 my $home = $ENV{HOME} || die "Unable to determine home directory: set \$HOME\n"; 887 return ($> ? "$home/.snmp" : "/usr/share/snmp"); # XXX hack - no dirs existed or were writable 888 } 889 # this should never happen 890 return undef; 891} 892 893sub find_certs { 894 my $opts = shift; 895 my $dir = shift || 'certs'; 896 my $targ = shift; 897 my $suffix_regex; 898 my $cwd = ::getcwd(); 899 900 if ($dir eq 'csrs') { 901 $dir = $NetSNMP::Cert::NEWCRTDIR; 902 $suffix_regex = '\.csr|\.pem'; 903 } else { 904 $suffix_regex = '\.crt|\.pem'; 905 } 906 907 NetSNMP::Cert::dprint("find_certs(1:$cwd):$dir:$targ:$suffix_regex\n", $opts); 908 909 if ($targ =~ /\.?\//) { 910 # see if targ could be file - calc orig. rel. path 911 my $arg = NetSNMP::Cert::fq_rel_path($targ, $opts->{'rdir'}); 912 NetSNMP::Cert::dprint("find_certs(2):$dir:$targ:$arg\n", $opts); 913 # targ is a file name - use it 914 return (wantarray ? ($arg) : $arg) if -f $arg; 915 # else mark as dir if it is 916 $targ = "$arg" if -d $arg; 917 } 918 919 $targ =~ s/\/*$/\// if -d $targ; 920 921 NetSNMP::Cert::dprint("find_certs(3):$dir:$targ\n", $opts); 922 923 # find certs in targ if a dir (in tlsdir, or relative) 924 $dir = $1 if $targ =~ s/^(\S+)\/([^\/\s]*)$/$2/ and -d $1; 925 926 NetSNMP::Cert::dprint("find_certs(4):${dir}:$targ:$cwd\n", $opts); 927 928 my @certs; 929 my $glob = "$dir/$targ"; 930 foreach (<$dir/*>) { 931 NetSNMP::Cert::dprint("checking($dir:$targ): $_\n", $opts); 932 next unless /^$dir\/$targ(.*$suffix_regex)?$/; 933 # return exact match if not wantarray() 934 return $_ if (not wantarray()) and /^$dir\/$targ($suffix_regex)?$/; 935 NetSNMP::Cert::dprint("pushing: $_\n", $opts); 936 push(@certs, $_); 937 } 938 939 return (wantarray ? @certs : $certs[0]); 940} 941 942sub check_output_file { 943 my $opts = shift; 944 my $file = shift; 945 my $interactive = shift; 946 my $force = shift; 947 my $continue = 1; 948 949 if (-w $file) { 950 if ($interactive and not $force) { 951 print "Output file ($file) exists; will be overwritten\nContinue [y/N]: "; 952 $continue = <STDIN>; chomp $continue; 953 $continue = 0 if $continue =~ /^\s*$|^no?\s*$/i; 954 } else { 955 if ($force) { 956 NetSNMP::Cert::vprint("Output file ($file) exists; overwriting...\n", $opts); 957 } else { 958 NetSNMP::Cert::vprint("Output file ($file) exists; exiting...\n", $opts); 959 $continue = 0; 960 } 961 } 962 } elsif (-e $file) { 963 NetSNMP::Cert::vprint("Output file ($file) not writable; exiting...\n", $opts); 964 $continue = 0; 965 } 966 exit(1) unless $continue; 967} 968 969sub is_server { 970 my $tag = shift; 971 return 1 if $tag eq 'snmpd' or $tag eq 'snmptrapd'; 972 return 0; 973} 974 975sub is_ca_cert { 976 my $crt = shift; 977 my $output = `openssl x509 -in '$crt' -text -noout`; 978 return ($output =~ /^\s*CA:TRUE\s*$/m ? 1 : 0); 979} 980 981sub x509_format { 982 my $opts = shift; 983 my $fmt; 984 985 foreach my $f (@NetSNMP::Cert::X509FMTS) { 986 $fmt .= " -$f" if defined $opts->{$f}; 987 } 988 989 return $fmt; 990} 991 992sub make_openssl_conf { 993 my $file = shift; 994 return if -r $file; 995 996 open(F, ">$file") or die("could not open $file"); 997 998 print F <<'END'; 999# 1000# Net-SNMP (D)TLS OpenSSL configuration file. 1001# 1002 1003rdir = . 1004dir = $ENV::DIR 1005RANDFILE = $rdir/.rand 1006MD = sha1 1007KSIZE = 2048 1008CN = net-snmp.org 1009EMAIL = admin@net-snmp.org 1010COUNTRY = US 1011STATE = CA 1012LOCALITY = Davis 1013ORG = Net-SNMP 1014ORG_UNIT = Development 1015SAN = email:copy 1016DAYS = 365 1017CRLDAYS = 30 1018 1019default_days = $ENV::DAYS # how long to certify for 1020default_crl_days= $ENV::CRLDAYS # how long before next CRL 1021default_md = $ENV::MD # which md to use. 1022 1023database = $dir/.index # database index file. 1024serial = $dir/.serial # The current serial number 1025certs = $rdir/certs # identity certs 1026new_certs_dir = $dir/newcerts # default place for new certs. 1027ca_certs_dir = $rdir/ca-certs # default place for new certs. 1028key_dir = $rdir/private 1029 1030crl_dir = $dir/crl # crl's 1031crlnumber = $dir/.crlnumber # the current crl number 1032 # must be commented out to leave V1 CRL 1033crl = $crl_dir/crl.pem # The current CRL 1034 1035preserve = no # keep passed DN ordering 1036unique_subject = yes # Set to 'no' to allow creation of 1037 # certificates with same subject. 1038# Extra OBJECT IDENTIFIER info: 1039oid_section = new_oids 1040 1041[ new_oids ] 1042 1043# Add new OIDs in here for use by 'ca' and 'req'. 1044# Add a simple OID like this: 1045# testoid1=1.2.3.4 1046# Use config file substitution like this: 1047# testoid2=${testoid1}.5.6 1048 1049#################################################################### 1050[ ca ] 1051default_ca = CA_default # The default ca section 1052 1053#################################################################### 1054[ CA_default ] 1055# certificate = $ca_certs_dir/$ENV::TAG.crt # CA certificate so sign with 1056name_opt = ca_default # Subject Name options 1057cert_opt = ca_default # Certificate field options 1058policy = policy_match 1059copy_extensions = copy # copy v3 extensions (subjectAltName) 1060subjectAltName = copy 1061 1062# For the CA policy 1063[ policy_match ] 1064countryName = match 1065stateOrProvinceName = match 1066organizationName = match 1067organizationalUnitName = optional 1068commonName = supplied 1069emailAddress = optional 1070 1071# For the 'anything' policy 1072# At this point in time, you must list all acceptable 'object' 1073# types. 1074[ policy_anything ] 1075countryName = optional 1076stateOrProvinceName = optional 1077localityName = optional 1078organizationName = optional 1079organizationalUnitName = optional 1080commonName = supplied 1081emailAddress = optional 1082 1083#################################################################### 1084[ req ] 1085default_bits = $ENV::KSIZE 1086default_md = $ENV::MD 1087distinguished_name = req_distinguished_name 1088string_mask = MASK:0x2002 1089req_extensions = v3_req 1090x509_extensions = v3_user_create 1091 1092[ req_distinguished_name ] 1093countryName = Country Name (2 letter code) 1094countryName_default = $ENV::COUNTRY 1095countryName_min = 2 1096countryName_max = 2 1097 1098stateOrProvinceName = State or Province Name (full name) 1099stateOrProvinceName_default = $ENV::STATE 1100 1101localityName = Locality Name (eg, city) 1102localityName_default = $ENV::LOCALITY 1103 11040.organizationName = Organization Name (eg, company) 11050.organizationName_default = $ENV::ORG 1106 1107organizationalUnitName = Organizational Unit Name (eg, section) 1108organizationalUnitName_default = $ENV::ORG_UNIT 1109 1110commonName = Common Name (eg, your name or your server\'s hostname) 1111commonName_max = 64 1112commonName_default = $ENV::CN 1113 1114emailAddress = Email Address 1115emailAddress_max = 64 1116emailAddress_default = $ENV::EMAIL 1117 1118[ v3_req ] 1119 1120# Extensions to add to a certificate request 1121basicConstraints = CA:FALSE 1122 1123# Import the email address and/or specified SANs. 1124%[subjectAltName = $ENV::SAN] 1125 1126[ v3_user_create ] 1127 1128# Extensions to add to a certificate request 1129basicConstraints = CA:FALSE 1130#keyUsage = nonRepudiation, digitalSignature, keyEncipherment 1131 1132# PKIX recommendation. 1133subjectKeyIdentifier = hash 1134authorityKeyIdentifier = keyid:always,issuer:always 1135 1136# Import the email address and/or specified SANs. 1137%[subjectAltName = $ENV::SAN] 1138 1139[ v3_ca_create ] 1140# Extensions to add to a CA certificate 1141basicConstraints = CA:TRUE 1142# This will be displayed in Netscape's comment listbox. 1143nsComment = "OpenSSL Generated Certificate (net-snmp)" 1144 1145# PKIX recommendation. 1146subjectKeyIdentifier = hash 1147authorityKeyIdentifier = keyid:always,issuer:always 1148 1149# Import the email address and/or specified SANs. 1150%[subjectAltName = $ENV::SAN] 1151 1152[ v3_ca_sign ] 1153# Extensions to add when 'ca' signs a request. 1154basicConstraints = CA:FALSE 1155# This will be displayed in Netscape's comment listbox. 1156nsComment = "OpenSSL Generated Certificate (net-snmp)" 1157 1158keyUsage = nonRepudiation, digitalSignature, keyEncipherment 1159 1160# PKIX recommendations harmless if included in all certificates. 1161subjectKeyIdentifier = hash 1162authorityKeyIdentifier = keyid:always,issuer:always 1163 1164[ v3_ca_sign_ca ] 1165# Extensions to add when 'ca' signs a ca request. 1166basicConstraints = CA:TRUE 1167 1168# This will be displayed in Netscape's comment listbox. 1169nsComment = "OpenSSL Generated Certificate (net-snmp)" 1170 1171# PKIX recommendations harmless if included in all certificates. 1172subjectKeyIdentifier = hash 1173authorityKeyIdentifier = keyid:always,issuer:always 1174 1175END 1176 1177} 1178 1179 1180package NetSNMP::Cert::Obj; 1181 1182sub new { 1183 my $class = shift; 1184 my $cfield = shift; 1185 my $parent = shift; 1186 my $ind = shift; 1187 my $this = {}; 1188 bless($this, $class); 1189 1190 # initialize hash of keys which are dynamically created or internal 1191 $this->{'AUTOKEYS'}{'AUTOKEYS'}++; 1192 1193 # store a reference to ourselves so our children can find us 1194 $this->autoSet('CFIELD', $cfield); 1195 $this->autoSet($cfield , $this); 1196 $this->autoSet('CHILDREN', []); 1197 $this->autoSet('INDEX', $ind) if defined $ind; 1198 1199 if (defined $parent) { 1200 # cache 'APP' in all objs for easy reference 1201 $this->autoSet('APP', $parent->inherit('APP')); 1202 my $app = $this->{'APP'}; 1203 1204 die("net-snmp-cert: error: no NetSNMP::Cert::App context provided") 1205 if not defined $app or not ref $app eq 'NetSNMP::Cert::App'; 1206 # save children for list traversal 1207 push(@{$parent->{'CHILDREN'}}, $this) unless $parent eq $this; 1208 } else { 1209 # we are the top of the list 1210 $parent = $this; 1211 } 1212 $this->autoSet('PARENT' , $parent); 1213 1214 return $this; 1215} 1216 1217sub autoSet { 1218 my $this = shift; 1219 my $key = shift; 1220 if (@_) { 1221 my $val = shift; 1222 $this->{'AUTOKEYS'}{$key}++; 1223 $this->{$key} = $val; 1224 } 1225 return exists $this->{'AUTOKEYS'}{$key}; 1226} 1227 1228sub inherit { 1229 my $this = shift; 1230 my $field = shift; 1231 my $id; 1232 1233 # cmd opts override config settings 1234 if (exists $this->{'APP'} and exists $this->{'APP'}{'OPTS'}) { 1235 my $opts = $this->{'APP'}{'OPTS'}; 1236 $id = $opts->{'identity'}; 1237 return $opts->{$field} if defined $opts->{$field}; 1238 } 1239 1240 if (defined $id and exists $this->{'identity'} and 1241 exists $this->{'identity'}{$id} and 1242 exists $this->{'identity'}{$id}{$field}) { 1243 return $this->{'identity'}{$id}{$field}; 1244 } 1245 1246 # return our field if we have it 1247 return $this->{$field} if defined $this->{$field}; 1248 1249 # terminate recursion at top and return undef if not found 1250 return undef if not defined $this->{'PARENT'} or $this->{'PARENT'} eq $this; 1251 1252 # recurse to parent 1253 $this->{'PARENT'}->inherit($field); 1254} 1255 1256sub resolve { 1257 my $this = shift; 1258 my $opts = $this->inherit('OPTS'); 1259 my $val = shift; 1260 1261 NetSNMP::Cert::dprint("resolving: $val\n", $opts); 1262 1263 $val =~ s/(\$(\w+))/$this->inherit($2) or die("unresolved reference in config: $1")/ge; 1264 $val =~ s/(\&\{(.*?)\})/$2/gee; 1265 1266 NetSNMP::Cert::dprint("resolved: $val\n", $opts); 1267 1268 return $val 1269} 1270 1271sub delete { 1272 my $this = shift; 1273 my $opts = $this->inherit('OPTS'); 1274 1275 NetSNMP::Cert::dprint("Obj::delete: ($this) [$this->{CFIELD}]\n", $opts); 1276 1277 my $parent = $this->{'PARENT'}; 1278 my @children = @{$this->{'CHILDREN'}}; 1279 1280 foreach my $child (@children) { 1281 $child->delete(); 1282 } 1283 1284 NetSNMP::Cert::dwarn("Obj: children not freed\n", $opts) if @{$this->{'CHILDREN'}}; 1285 1286 # delete all our self-references 1287 delete($this->{$this->{'CFIELD'}}) if exists $this->{'CFIELD'}; 1288 delete($this->{'PARENT'}); 1289 delete($this->{'CHILDREN'}); 1290 1291 $parent->disown($this) if defined $parent and ref($parent) =~ /NetSNMP::Cert/; 1292} 1293 1294sub disown { 1295 my $this = shift; 1296 my $thechild = shift; 1297 my $opts = $this->inherit('OPTS'); 1298 my $ind = 0; 1299 1300 NetSNMP::Cert::dprint("Obj::disown: ($this) [$this->{CFIELD}] disowning ($thechild) [$thechild->{CFIELD}]\n", $opts); 1301 1302 foreach my $child (@{$this->{'CHILDREN'}}) { 1303 last if $child eq $thechild; 1304 $ind++; 1305 } 1306 if ($ind < @{$this->{'CHILDREN'}}) { 1307 splice(@{$this->{'CHILDREN'}}, $ind, 1); 1308 } else { 1309 NetSNMP::Cert::dwarn("Child ($thechild) not found in object ($this)\n", $opts); 1310 } 1311} 1312 1313sub disabled { 1314 my $this = shift; 1315 return (exists $this->{'disable'} ? $this->{'disable'} : 0); 1316} 1317 1318my %cfg = ( 1319 'name' => 1, 1320 'type' => 2, 1321 'id' => 6, 1322 ); 1323 1324sub cfgsort { 1325 my $self = shift; 1326 my $a = shift; 1327 my $b = shift; 1328 return -1 if exists $cfg{$a} and not exists $cfg{$b}; 1329 return 1 if exists $cfg{$b} and not exists $cfg{$a}; 1330 return $cfg{$a} <=> $cfg{$b} if exists $cfg{$a} and exists $cfg{$b}; 1331 return -1 if !ref($self->{$a}) and ref($self->{$b}); 1332 return 1 if !ref($self->{$b}) and ref($self->{$a}); 1333 return -1 if ref($self->{$a}) =~ /NetSNMP::Cert/ and ref($self->{$b}) !~ /NetSNMP::Cert/; 1334 return 1 if ref($self->{$b}) =~ /NetSNMP::Cert/ and ref($self->{$a}) !~ /NetSNMP::Cert/; 1335 return 0; 1336} 1337 1338sub dump { 1339 my $self = shift; 1340 my $indent = shift; 1341 my $self_str = $self->{'CFIELD'}; 1342 my @data_keys = grep {!$self->autoSet($_)} keys %$self; 1343 my @lines; 1344 1345 push(@lines, "\n" . ' ' x $indent . "$self_str = {\n") if defined $indent; 1346 1347 { 1348 my $indent = (defined $indent ? $indent + 3 : 0); 1349 foreach my $key (sort {cfgsort($self,$NetSNMP::Cert::Obj::a, 1350 $NetSNMP::Cert::Obj::b)} (sort @data_keys)) { 1351 if (ref($self->{$key}) =~ /NetSNMP::Cert/) { 1352 push(@lines, $self->{$key}->dump($indent)); 1353 } elsif (ref($self->{$key}) =~ /ARRAY/) { 1354 push(@lines, "\n") if ref(${$self->{$key}}[0]) !~ /NetSNMP::Cert/; 1355 foreach my $elem (@{$self->{$key}}) { 1356 if (ref($elem) =~ /NetSNMP::Cert/) { 1357 push(@lines, $elem->dump($indent)); 1358 } else { 1359 push(@lines, ' ' x $indent . "$key = $elem\n"); 1360 } 1361 } 1362 } else { 1363 my $str = $key . (defined $self->{$key} ? " = $self->{$key}\n" : "\n"); 1364 push(@lines, ' ' x $indent . $str); 1365 } 1366 } 1367 } 1368 1369 push(@lines, ' ' x $indent . "}; # end $self_str\n") if defined $indent; 1370 return @lines; 1371} 1372 1373sub DESTROY { 1374 my $this = shift; 1375 1376 print("Obj::DESTROY $this [", ref $this, "]\n") if $NetSNMP::Cert::DEBUG >= 3; 1377} 1378 1379package NetSNMP::Cert::App; 1380use vars qw(@ISA); 1381 1382@ISA = qw(NetSNMP::Cert::Obj); 1383 1384sub new { 1385 my $class = shift; 1386 1387 # the app is god, it is its own parent 1388 my $this = $class->SUPER::new('APP'); 1389 1390 bless($this, $class); 1391 1392 # verify required tools or die 1393 $this->checkReqs(); 1394 1395 # internal intitialization 1396 $this->initOpts(); 1397 1398 # make a new empty config and init (not parsed) 1399 $this->{'config'} = new NetSNMP::Cert::Config($this); 1400 1401 return $this; 1402} 1403 1404sub checkReqs { 1405 my $app = shift; 1406 1407 die("$NetSNMP::Cert::OPENSSL does not exist or is not executable") 1408 if system("$NetSNMP::Cert::OPENSSL version > /dev/null 2>&1"); 1409 1410 my $ossl_ver = `$NetSNMP::Cert::OPENSSL version`; chomp $ossl_ver; 1411 $ossl_ver =~ s/^OpenSSL\s+([\d\.]+).*$/$1/; 1412 my $ossl_min_ver = $NetSNMP::Cert::OPENSSL_MIN_VER; 1413 1414 die("$NetSNMP::Cert::OPENSSL (v$ossl_ver): must be $ossl_min_ver or later") 1415 if ($ossl_ver cmp $ossl_min_ver) < 0; 1416 1417 die("$NetSNMP::Cert::CFGTOOL not found: please install") 1418 if system("$NetSNMP::Cert::CFGTOOL > /dev/null 2>&1"); 1419} 1420 1421sub initOpts { 1422 my $app = shift; 1423 my $opts = {}; 1424 $app->autoSet('OPTS', $opts); 1425 1426 # Define directories we need. 1427 $opts->{'bindir'} = NetSNMP::Cert::find_bin_dir(); 1428 1429 $opts->{'out'} = "> $NetSNMP::Cert::OUTLOG"; 1430 $opts->{'err'} = "2> $NetSNMP::Cert::ERRLOG"; 1431 1432 # set up paths for app install and runtime env 1433 $ENV{PATH} = "/sbin:/usr/sbin:$ENV{PATH}"; 1434 $ENV{PATH} = "$opts->{'bindir'}:$ENV{PATH}"; 1435 1436 # default all privs to -rw------- 1437 umask(077); 1438} 1439 1440sub init { 1441 my $app = shift; 1442 my $opts = $app->{'OPTS'}; 1443 my $config = $app->{'config'}; 1444 my @args = @_; # pass external args (typically ARGV) 1445 1446 # parse command line 1447 $app->parseArgs(@args); 1448 1449 # lazy config parsing postponed until here, will not reparse 1450 $config->parse(); 1451 1452 # set defaults here if not already set in cmdline or config 1453 # guided interactive mode by default 1454 NetSNMP::Cert::set_default($opts, $config, 'interactive', 1); 1455 # verbose output 1456 NetSNMP::Cert::set_default($opts, $config, 'verbose', 1); 1457 1458 # find tlsdir/subdirs or make it 1459 $opts->{'tlsdir'} = $app->createTlsDir(); 1460} 1461 1462sub parseArgs { 1463 my $app = shift; 1464 my $opts = $app->{'OPTS'}; 1465 my @args = @_; 1466 1467 NetSNMP::Cert::GetOptsFromArray(\@args, $opts, 'help|?', 'version|V', 1468 'interactive!', 'I', 'verbose!', 'Q', 'force|F', 1469 'cfgfile|f=s', 'cfgdir|C=s', 'tlsdir|tlsDir|T=s', 1470 'tag|t=s', 'identity|i=s', 'debug|D+',); 1471 1472 NetSNMP::Cert::version(0) if $opts->{'version'}; 1473 1474 NetSNMP::Cert::usage(0) if $opts->{'help'}; 1475 1476 # pull out the cmd - getOpts should leave it untouched for us 1477 $opts->{'cmd'} = NetSNMP::Cert::pull_cmd(\@args); 1478 1479 # save extra args for command specific processing 1480 $opts->{'cmdargs'} = [@args]; 1481 1482 # Check debug option first 1483 $NetSNMP::Cert::DEBUG = $opts->{'debug'}; 1484 $opts->{'err'} = $opts->{'out'} = "" if $opts->{'debug'}; 1485 $opts->{'interactive'} = not $opts->{'I'} if defined $opts->{'I'}; 1486 $opts->{'verbose'} = not $opts->{'Q'} if defined $opts->{'Q'}; 1487 1488 # search for cfgdir and cfgfile based on opts and confpath 1489 $opts->{'cfgdir'} = NetSNMP::Cert::find_cfgdir($opts->{'cfgdir'}, 1490 $opts->{'cfgfile'}); 1491 $opts->{'cfgfile'} = NetSNMP::Cert::find_cfgfile($opts->{'cfgdir'}, 1492 $opts->{'cfgfile'}); 1493} 1494 1495sub createTlsDir { 1496 my $app = shift; 1497 my $opts = $app->{'OPTS'}; 1498 my $config = $app->{'config'}; 1499 my $dir; 1500 my $file; 1501 my $cmd; 1502 1503 $dir = $config->inherit('tlsDir'); 1504 1505 if (not defined $dir) { 1506 my $cfgdir = $opts->{'cfgdir'}; 1507 die("undefined cfgdir: unable to creat tlsdir: exiting...\n") unless defined $cfgdir; 1508 $dir = "$cfgdir/$NetSNMP::Cert::TLSDIR"; 1509 } 1510 1511 NetSNMP::Cert::dprint("tlsDir is: $dir\n", $opts); 1512 $dir = NetSNMP::Cert::fq_rel_path($dir); 1513 NetSNMP::Cert::dprint("tlsDir is: $dir\n", $opts); 1514 1515 NetSNMP::Cert::dprint("tlsDir not found, creating\n", $opts) unless -d $dir; 1516 NetSNMP::Cert::make_dirs($opts, $dir, 0700, @NetSNMP::Cert::TLSDIRS); 1517 1518 my $ssl_cfg_in = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGIN,$dir); 1519 1520 # Existing openssl.conf tmpl will be preserved 1521 if (-f $ssl_cfg_in) { 1522 NetSNMP::Cert::dwarn("OpenSSL template exists ($ssl_cfg_in): preserving...", $opts); 1523 } 1524 1525 if (not -f $ssl_cfg_in) { 1526 NetSNMP::Cert::dprint("make_openssl_conf($ssl_cfg_in)", $opts); 1527 NetSNMP::Cert::make_openssl_conf($ssl_cfg_in); 1528 chmod(0600, $ssl_cfg_in); 1529 } 1530 1531 NetSNMP::Cert::dprint("createTlsDir: done\n", $opts); 1532 return $dir; 1533} 1534 1535my @interactive_ops = (['gencert', "Generate a signed certificate"], 1536 ['genca', "Generate a CA certificate"], 1537 ['gencsr', "Generate a Certificate Signing Request"], 1538 ['signcsr', "Sign a Certificate Signing Request"], 1539 ); 1540 1541sub run { 1542 my $app = shift; 1543 my $opts = $app->{'OPTS'}; 1544 my $cmd = $opts->{'cmd'}; 1545 1546 # must have a command in non-Interactive mode 1547 NetSNMP::Cert::usage(3) if !$opts->{'interactive'} and !$opts->{'cmd'}; 1548 1549 # change dir tls dir - the cwd for all commands - save cwd first 1550 $opts->{'rdir'} = ::getcwd(); 1551 chdir($opts->{'tlsdir'}) or die("could'nt change directory: $opts->{tlsdir}"); 1552 # display context 1553 NetSNMP::Cert::dprint("PATH: $ENV{PATH}\n\n", $opts); 1554 NetSNMP::Cert::dprint("config file: $opts->{cfgfile}\n", $opts); 1555 NetSNMP::Cert::dprint("config dir: $opts->{cfgdir}\n", $opts); 1556 NetSNMP::Cert::dprint("tls dir: $opts->{tlsdir}\n", $opts); 1557 1558 my $cmdstr = join(' ', $cmd, @{$opts->{'cmdargs'}}); 1559 NetSNMP::Cert::dprint("command: $cmdstr\n", $opts); 1560 1561 NetSNMP::Cert::GetOptsFromArray(\@{$opts->{'cmdargs'}}, $opts, 1562 'with-ca|a=s', 'ca|A=s','csr|r=s', 1563 'from-crt|x=s', 'crt|X=s', 'days|d=s', 1564 'cn|n=s', 'email|e=s', 'host|h=s', 1565 'san|s=s@', 'org|o=s', 'unit|u=s', 1566 'country|c=s', 'state|province|p=s', 1567 'locality|l=s', 'brief|b', 1568 @NetSNMP::Cert::X509FMTS); 1569 1570 # process extra args; --<cfg-name>[=<val>] 1571 $app->processExtraArgs(); 1572 1573 # If in interactive mode - fill missing info by interviewing user 1574 if (not defined $cmd or grep {/$cmd/} map {$_->[0]} @interactive_ops) { 1575 $app->userInput() if $opts->{interactive}; 1576 $cmd = $opts->{'cmd'}; # may have changed 1577 } 1578 1579 # resolve input args to filenames 1580 $app->resolveCrtArgs(); 1581 1582 # use env. or merge template for OpenSSL config 1583 $NetSNMP::Cert::SSLCFG ||= $app->opensslCfg(); 1584 1585 if ($cmd =~ /^genca$/) { 1586 NetSNMP::Cert::usage(1) if defined $opts->{'from-crt'} or defined $opts->{'csr'}; 1587 $app->genCa($opts->{'with-ca'}); 1588 } elsif ($cmd =~ /^gence?rt$/) { 1589 NetSNMP::Cert::usage(1) if defined $opts->{'from-crt'} or defined $opts->{'csr'}; 1590 $app->genCert($opts->{'with-ca'}); 1591 } elsif ($cmd =~ /^gencsr$/) { 1592 NetSNMP::Cert::usage(1) if defined $opts->{'with-ca'} or defined $opts->{'crt'}; 1593 $app->genCsr(); 1594 } elsif ($cmd =~ /^signcsr$/) { 1595 NetSNMP::Cert::usage(1) unless defined $opts->{'with-ca'} and defined $opts->{'csr'}; 1596 $app->signCsr(); 1597 } elsif ($cmd =~ /^show(ce?rts?)?$/) { 1598 $app->show('certs'); 1599 } elsif ($cmd =~ /^showcas?$/) { 1600 $app->show('ca-certs'); 1601 } elsif ($cmd =~ /^import$/) { 1602 $app->import(); 1603 } else { 1604 NetSNMP::Cert::usage(); 1605 } 1606} 1607 1608sub processExtraArgs { 1609 my $app = shift; 1610 my $opts = $app->{'OPTS'}; 1611 my @args; 1612 1613 NetSNMP::Cert::dprint("processing extra args...\n", $opts); 1614 1615 while (@{$opts->{'cmdargs'}}) { 1616 my $arg = shift(@{$opts->{'cmdargs'}}); 1617 NetSNMP::Cert::dprint("found: arg --> $arg\n", $opts); 1618 if ($arg =~ /^-+(\w+)(?:=(.*?))?\s*$/) { 1619 NetSNMP::Cert::dprint("found: arg($1) val($2)\n", $opts); 1620 if (exists $opts->{$1}) { 1621 NetSNMP::Cert::vwarn("option ($1) already set: overwriting\n", $opts); 1622 } 1623 $opts->{$1} = (defined $2 ? $2 : 1); 1624 } else { 1625 push(@args, $arg); 1626 } 1627 } 1628 @{$opts->{'cmdargs'}} = @args; 1629} 1630 1631sub resolveCrtArgs { 1632 my $app = shift; 1633 my $opts = $app->{'OPTS'}; 1634 1635 # find ca, crt, csr args if present and return fully qualified path 1636 if (defined $opts->{'with-ca'}) { 1637 my $ca; 1638 $ca = NetSNMP::Cert::find_certs($opts, 'ca-certs', $opts->{'with-ca'}); 1639 die("unable to locate CA certificate ($opts->{'with-ca'})\n") unless -e $ca; 1640 die("unable read CA certificate ($opts->{'with-ca'})\n") unless -r $ca; 1641 $opts->{'with-ca'} = $ca; 1642 } 1643 1644 if (defined $opts->{'from-crt'}) { 1645 my $crt; 1646 $crt = NetSNMP::Cert::find_certs($opts, 'certs', $opts->{'from-crt'}); 1647 die("unable to locate certificate ($opts->{'from-crt'})\n") unless -e $crt; 1648 die("unable read certificate ($opts->{'from-crt'})\n") unless -r $crt; 1649 $opts->{'from-crt'} = $crt; 1650 } 1651 1652 if (defined $opts->{'csr'}) { 1653 my $csr; 1654 $csr = NetSNMP::Cert::find_certs($opts, 'csrs', $opts->{'csr'}); 1655 die("unable to locate CSR certificate ($opts->{csr})\n") unless -e $csr; 1656 die("unable read CSR certificate ($opts->{csr})\n") unless -r $csr; 1657 $opts->{'csr'} = $csr; 1658 } 1659} 1660 1661sub opensslCfg { 1662 my $app = shift; 1663 my $config = $app->{'config'}; 1664 my $opts = $app->{'OPTS'}; 1665 my $san = $config->inherit('san') || $config->inherit('subjectAltName'); 1666 my $ssl_cfg_in = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGIN); 1667 my $ssl_cfg_out = NetSNMP::Cert::fq_rel_path($NetSNMP::Cert::SSLCFGOUT); 1668 1669 if (not -f $ssl_cfg_in) { 1670 NetSNMP::Cert::vwarn("OpenSSL template not found: $ssl_cfg_in\n", $opts); 1671 die("no OpenSSL template"); 1672 } 1673 1674 open(IN, $ssl_cfg_in) or die("unable to open OpenSSL template: $ssl_cfg_in\n"); 1675 open(OUT, ">$ssl_cfg_out") or die("unable to open OpenSSL config: $ssl_cfg_out\n"); 1676 1677 print OUT "#######################################################\n"; 1678 print OUT "##### Warning: Do Not Edit - Generated File #####\n"; 1679 print OUT "#######################################################\n"; 1680 while (<IN>) { 1681 if ($san) { 1682 s/\%\[([^\]]*?)\]/$1/; 1683 } else { 1684 s/\%\[([^\]]*?)\]//; 1685 } 1686 print OUT $_; 1687 } 1688 close(IN); 1689 close(OUT); 1690 1691 return $ssl_cfg_out; 1692} 1693 1694sub opensslEnv { 1695 my $app = shift; 1696 my $config = $app->{'config'}; 1697 my $opts = $app->{'OPTS'}; 1698 1699 my $cn = shift; 1700 my $days = shift; 1701 my $dir = shift || "."; 1702 # XXX - need to handle config'd identity here 1703 my $name = $config->inherit("name"); 1704 my $host = $config->inherit("host"); 1705 my $email = $config->inherit("email"); 1706 my $country = $config->inherit("country"); 1707 my $state = $config->inherit("state"); 1708 my $locality = $config->inherit("locality"); 1709 my $org = $config->inherit("org"); 1710 my $org_unit = $config->inherit("unit") || $config->inherit("orgUnit"); 1711 my $san; 1712 my $san_arr_ref; 1713 my $md = $config->inherit("msgDigest"); 1714 my $ksize = $config->inherit("keySize"); 1715 1716 my $env; 1717 1718 $env .= " KSIZE=$ksize" if defined $ksize; 1719 $env .= " DAYS=$days" if defined $days; 1720 $env .= " MD=$md" if defined $md; 1721 1722 $env .= " DIR='$dir'" if defined $dir; 1723 1724 $env .= " EMAIL=$email" if defined $email; 1725 $env .= " CN='$cn'" if defined $cn; 1726 $env .= " ORG='$org'" if defined $org; 1727 $env .= " ORG_UNIT='$org_unit'" if defined $org_unit; 1728 $env .= " COUNTRY=$country" if defined $country; 1729 $env .= " STATE=$state" if defined $state; 1730 $env .= " LOCALITY=$locality" if defined $locality; 1731 1732 $san_arr_ref = $config->inherit('subjectAltName'); 1733 $san = join('\,\ ', @{$san_arr_ref}) if ref $san_arr_ref; 1734 $san_arr_ref = $config->inherit('san'); 1735 $san .= join('\,\ ', @{$san_arr_ref}) if ref $san_arr_ref; 1736 $san =~ s/EMAIL:/email:/g; 1737 $env .= " SAN=$san" if defined $san; 1738 1739 NetSNMP::Cert::dprint("opensslEnv: $env\n", $opts); 1740 1741 return $env; 1742} 1743our @san_prefix = (['dirName:', "e.g., dirName:/usr/share/snmp/tls"], 1744 ['DNS:', "e.g., DNS:test.net-snmp.org)"], 1745 ['email:', "e.g., email:admin\@net-snmp.org"], 1746 ['IP:', "e.g., IP:192.168.1.1"], 1747 ['RID:', "e.g., RID:1.1.3.6.20"], 1748 ['URI:', "e.g., URI:http://www.net-snmp.org"]); 1749 1750sub userInputDN { 1751 my $app = shift; 1752 my $config = $app->{'config'}; 1753 my $opts = $app->{'OPTS'}; 1754 my $prompt; 1755 my $ret; 1756 my $host = $config->inherit("host") || ::hostname(); 1757 my $email = $config->inherit('email') || getlogin() . "\@$host"; 1758 1759 # get EMAIL 1760 $prompt = "Enter Email"; 1761 $ret = $email; 1762 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1763 $ret = NetSNMP::Term::Complete($prompt, $ret, 1764 "\tEmail Address - (e.g., <name>@<domain>)"); 1765 $email = $opts->{'email'} = $ret if defined $ret; 1766 # get CN 1767 $prompt = "Enter Common Name"; 1768 $ret = ($opts->{'cmd'} eq 'genca' ? "ca-$host" : $email); 1769 $ret = $config->inherit('cn') || $config->inherit('commonName') || $ret; 1770 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1771 $ret = NetSNMP::Term::Complete($prompt, $ret, 1772 "\tCommon Name - (e.g., net-snmp.org)"); 1773 $opts->{'cn'} = $ret if defined $ret; 1774 # get ORG 1775 $prompt = "Enter Organization"; 1776 $ret = $config->inherit('org') || 'Net-SNMP'; 1777 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1778 $ret = NetSNMP::Term::Complete($prompt, $ret, 1779 "\tOrganization - (e.g., Net-SNMP)"); 1780 $opts->{'org'} = $ret if defined $ret; 1781 # get ORG_UNIT 1782 $prompt = "Enter Organizational Unit"; 1783 $ret = $config->inherit('unit') || 'Development'; 1784 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1785 $ret = NetSNMP::Term::Complete($prompt, $ret, 1786 "\tOrganizational Unit - (e.g., Development)"); 1787 $opts->{'unit'} = $ret if defined $ret; 1788 # get COUNTRY 1789 $prompt = "Enter Country Code"; 1790 $ret = $config->inherit('country') || 'US'; 1791 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1792 $ret = NetSNMP::Term::Complete($prompt, $ret, 1793 "Country Code, 2 letters (<tab> for options)", 1794 $NetSNMP::Cert::MATCH, \@CC); 1795 $opts->{'country'} = $ret if defined $ret; 1796 # get STATE(Province) 1797 $prompt = "Enter State or Province"; 1798 $ret = $config->inherit('state') || 'CA'; 1799 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1800 $ret = NetSNMP::Term::Complete($prompt, $ret, 1801 "\tState or Province - (e.g., CA)"); 1802 $opts->{'state'} = $ret if defined $ret; 1803 # get LOCALITY 1804 $prompt = "Enter Locality"; 1805 $ret = $config->inherit('locality') || 'Davis'; 1806 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1807 $ret = NetSNMP::Term::Complete($prompt, $ret, "\tLocality - (e.g., Davis)"); 1808 $opts->{'locality'} = $ret if defined $ret; 1809 # get SAN (loop) 1810 if (!$config->{'brief'}) { 1811 print "Enter Subject Alt Names. Examples:\n"; 1812 foreach my $pair (@san_prefix) { 1813 printf("\t%-10.10s %s\n", $pair->[0], $pair->[1]); 1814 } 1815 } 1816 do { 1817 $ret = 'done'; 1818 $prompt = "Enter Subject Alt Name (enter 'done' when finished) [$ret]: "; 1819 $ret = NetSNMP::Term::Complete ($prompt, $ret, 1820 "\tSubject Alt Name - (<type>:<val>)", 1821 $NetSNMP::Cert::PREMATCH, \@san_prefix); 1822 push(@{$opts->{'san'}}, $ret) if defined $ret and $ret ne 'done'; 1823 } while (defined $ret and $ret ne 'done'); 1824} 1825 1826our @snmp_apps = (['snmp', 'Generic Certificate'], 1827 ['snmpapp','Client Certificate'], 1828 ['snmpd','Agent Certificate'], 1829 ['snmptrapd','Trap-agent Certificate']); 1830 1831sub userInputTag { 1832 my $app = shift; 1833 my $config = $app->{'config'}; 1834 my $opts = $app->{'OPTS'}; 1835 my $ret; 1836 my $prompt; 1837 1838 print "Application Tag:\n\tused to name the certificate and dictate its filename\n"; 1839 print "\tIt may also associate it with a particular application (eg \"snmpd\")\n"; 1840 print "\tif 'none', a name will be generated from other parameters\n"; 1841 print "\tenter <tab><tab> for typical options, or enter new name\n"; 1842 $prompt = "Enter Application Tag"; 1843 $ret = $config->inherit('tag') || 'none'; 1844 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1845 $ret = NetSNMP::Term::Complete($prompt, $ret, 1846 "Application Tag assocaiated with certificate", 1847 (not $NetSNMP::Cert::MATCH), \@snmp_apps); 1848 $opts->{'tag'} = $ret if defined $ret and $ret ne 'none'; 1849} 1850 1851sub userInput { 1852 my $app = shift; 1853 my $config = $app->{'config'}; 1854 my $opts = $app->{'OPTS'}; 1855 my $prompt; 1856 my $ret; 1857 1858 print "Choose an operation:\n"; 1859 foreach my $op (@interactive_ops) { 1860 print "\t$op->[0]\t- $op->[1]\n"; 1861 } 1862 1863 $prompt = "Operation"; 1864 $ret = $config->inherit('cmd') || $interactive_ops[0][0]; 1865 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1866 $ret = NetSNMP::Term::Complete($prompt, $ret, 1867 "Certifciate Operation to perform", 1868 $NetSNMP::Cert::MATCH, \@interactive_ops); 1869 1870 $opts->{'cmd'} = $ret; 1871 1872 if ($ret =~ /^gencert$/) { 1873 # get tag 1874 $app->userInputTag(); 1875 # get DN 1876 $app->userInputDN(); 1877 # self-signed/CA-signed(ca-cert) 1878 } elsif ($ret =~ /^genca$/) { 1879 # get DN 1880 $app->userInputDN(); 1881 } elsif ($ret =~ /^gencsr$/) { 1882 # get tag 1883 $app->userInputTag(); 1884 # get DN 1885 $app->userInputDN(); 1886 } elsif ($ret =~ /^signcsr$/) { 1887 # get csr 1888 $prompt = "Choose Certificate Signing Request"; 1889 $ret = $config->inherit('csr'); 1890 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1891 $ret = NetSNMP::Term::Complete($prompt, $ret); 1892 $opts->{'csr'} = $ret if defined $ret; 1893 # get ca 1894 $prompt = "Choose CA Certificate"; 1895 $ret = $config->inherit('with-ca'); 1896 $prompt .= (defined $ret ? " [$ret]: " : ": "); 1897 $ret = NetSNMP::Term::Complete($prompt, $ret); 1898 $opts->{'with-ca'} = $ret if defined $ret; 1899 } else { 1900 NetSNMP::Cert::vwarn("aborting operation: exiting...\n", $opts); 1901 exit(1); 1902 } 1903} 1904 1905sub createCaDir { 1906 my $app = shift; 1907 my $dir = shift; 1908 my $config = $app->{'config'}; 1909 my $opts = $app->{'OPTS'}; 1910 my $file; 1911 my $cmd; 1912 1913 NetSNMP::Cert::make_dirs($opts, $dir, 0700,'newcerts','private'); 1914 1915 $file = "$dir/$NetSNMP::Cert::SERIAL"; 1916 if (not -f $file) { 1917 $cmd = "echo '01' > '$file'"; 1918 NetSNMP::Cert::dprint("$cmd\n", $opts); 1919 NetSNMP::Cert::usystem($cmd, $opts); 1920 chmod(0600, $file); 1921 } 1922 1923 $file = "$dir/$NetSNMP::Cert::INDEX"; 1924 if (not -f $file) { 1925 $cmd = "touch '$file'"; 1926 NetSNMP::Cert::dprint("$cmd\n", $opts); 1927 NetSNMP::Cert::usystem($cmd, $opts); 1928 chmod(0600, $file); 1929 } 1930} 1931 1932sub genCa { 1933 my $app = shift; 1934 my $config = $app->{'config'}; 1935 my $opts = $app->{'OPTS'}; 1936 my $host = $config->inherit('host') || ::hostname(); 1937 my $days = $config->inherit('days') || $config->inherit('caDays') || 1938 $NetSNMP::Cert::DEFCADAYS; 1939 my $cn = $config->inherit('cn') || $config->inherit('commonName') || 1940 "ca-$host"; 1941 my $ca = $config->inherit('with-ca'); 1942 my $tag = $config->inherit('tag') || $cn; 1943 1944 # create CA dir 1945 my $dir = ".ca/$tag"; 1946 $app->createCaDir($dir); 1947 1948 my $outCrt = "$NetSNMP::Cert::CADIR/$tag.crt"; 1949 my $outKey = "$NetSNMP::Cert::PRIVDIR/$tag.key"; 1950 1951 # set command env 1952 my $env = $app->opensslEnv($cn, $days); 1953 1954 NetSNMP::Cert::check_output_file($opts, $outCrt, 1955 $config->inherit('interactive'), 1956 $config->inherit('force')); 1957 NetSNMP::Cert::check_output_file($opts, $outKey, 1958 $config->inherit('interactive'), 1959 $config->inherit('force')); 1960 1961 my $cmd = "$env openssl req -extensions v3_ca_create -new -days $days -batch -config $NetSNMP::Cert::SSLCFG -keyout '$outKey'"; 1962 $cmd .= " -nodes"; 1963 1964 if (defined $ca) { 1965 # we have to gen a csr and then sign it, must preserve CA:TRUE 1966 my $outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr"; 1967 NetSNMP::Cert::check_output_file($opts, $outCsr, 1968 $config->inherit('interactive'), 1969 $config->inherit('force')); 1970 $cmd .= " -out '$outCsr'"; 1971 1972 NetSNMP::Cert::dprint("genCa (gencsr): $cmd\n", $opts); 1973 NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts); 1974 1975 my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES); 1976 NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts); 1977 1978 # set command env 1979 $env = $app->opensslEnv($cn, $days, ".ca/$ca_base"); 1980 1981 $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'"; 1982 1983 NetSNMP::Cert::dprint("genCa (signcsr): $cmd\n", $opts); 1984 NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts); 1985 } else { 1986 $cmd .= " -x509 -out '$outCrt'"; 1987 1988 NetSNMP::Cert::dprint("genCa: $cmd\n", $opts); 1989 NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts); 1990 } 1991 1992 NetSNMP::Cert::vprint("CA Generated:\n", $opts); 1993 NetSNMP::Cert::vprint(" $outCrt\n", $opts); 1994 NetSNMP::Cert::vprint(" $outKey\n", $opts); 1995} 1996 1997sub genCert { 1998 my $app = shift; 1999 my $config = $app->{'config'}; 2000 my $opts = $app->{'OPTS'}; 2001 my $host = $config->inherit("host") || ::hostname(); 2002 my $email = $config->inherit("email") || getlogin() . "\@$host"; 2003 my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS; 2004 my $cn = $config->inherit('cn') || $config->inherit('commonName'); 2005 my $ca = $config->inherit('with-ca'); 2006 my $cmd; 2007 2008 my $tag = $opts->{'tag'} || 'snmp'; 2009 $cn ||= (NetSNMP::Cert::is_server($tag) ? $host : $email); 2010 2011 my $env = $app->opensslEnv($cn, $days); 2012 2013 my $outCrt = "$NetSNMP::Cert::CRTDIR/$tag.crt"; 2014 my $outKey = "$NetSNMP::Cert::PRIVDIR/$tag.key"; 2015 2016 NetSNMP::Cert::check_output_file($opts, $outCrt, 2017 $config->inherit('interactive'), 2018 $config->inherit('force')); 2019 NetSNMP::Cert::check_output_file($opts, $outKey, 2020 $config->inherit('interactive'), 2021 $config->inherit('force')); 2022 2023 $cmd = "$env openssl req -extensions v3_user_create -new -days $days -keyout '$outKey' -batch -config $NetSNMP::Cert::SSLCFG"; 2024 $cmd .= " -nodes"; 2025 2026 if (defined $ca) { 2027 my $outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr"; 2028 NetSNMP::Cert::check_output_file($opts, $outCsr, 2029 $config->inherit('interactive'), 2030 $config->inherit('force')); 2031 $cmd .= " -out '$outCsr'"; 2032 2033 NetSNMP::Cert::dprint("genCert (gencsr): $cmd\n", $opts); 2034 NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts); 2035 2036 # XXX cleanup this temp CSR 2037 my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES); 2038 NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts); 2039 2040 # set command env 2041 $env = $app->opensslEnv($cn, $days, ".ca/$ca_base"); 2042 2043 $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'"; 2044 2045 NetSNMP::Cert::dprint("gencert (signcsr): $cmd\n", $opts); 2046 NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts); 2047 } else { 2048 $cmd .= " -x509 -out '$outCrt'"; 2049 2050 NetSNMP::Cert::dprint("genCert: $cmd\n", $opts); 2051 NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts); 2052 } 2053 2054 NetSNMP::Cert::vprint("Certificate Generated:\n", $opts); 2055 NetSNMP::Cert::vprint(" $outCrt\n", $opts); 2056 NetSNMP::Cert::vprint(" $outKey\n", $opts); 2057} 2058 2059sub genCsr { 2060 my $app = shift; 2061 my $isCa = shift; # XXX - not implemented yet 2062 my $config = $app->{'config'}; 2063 my $opts = $app->{'OPTS'}; 2064 my $host = $config->inherit("host") || ::hostname(); 2065 my $email = $config->inherit("email") || getlogin() . "\@$host"; 2066 my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS; 2067 my $cn = $config->inherit('cn') || $config->inherit('commonName'); 2068 my $tag = $config->inherit('tag'); 2069 my $inCrt = $config->inherit('from-crt') || $config->inherit('fromCert'); 2070 my $outCsr; 2071 my $csrKey; 2072 2073 if (defined $inCrt) { 2074 $inCrt = NetSNMP::Cert::find_certs($opts, 'certs', $inCrt); 2075 my $crt = ::basename($inCrt, @NetSNMP::Cert::CRTSUFFIXES); 2076 $csrKey = "$NetSNMP::Cert::PRIVDIR/$crt.key"; 2077 $tag ||= $crt; 2078 } else { 2079 $tag ||= 'snmp'; 2080 $csrKey ||= "$NetSNMP::Cert::PRIVDIR/$tag.key"; 2081 } 2082 2083 $outCsr = "$NetSNMP::Cert::NEWCRTDIR/$tag.csr"; 2084 2085 $cn ||= (NetSNMP::Cert::is_server($tag) ? $host : $email); 2086 2087 my $env = $app->opensslEnv($cn, $days); 2088 2089 NetSNMP::Cert::check_output_file($opts, $outCsr, 2090 $config->inherit('interactive'), 2091 $config->inherit('force')); 2092 NetSNMP::Cert::check_output_file($opts, $csrKey, 2093 $config->inherit('interactive'), 2094 $config->inherit('force')); 2095 2096 my $cmd = (defined $inCrt ? 2097 "$env openssl x509 -x509toreq -in $inCrt -out '$outCsr' -signkey '$csrKey' -nodes -days $days -batch -config $NetSNMP::Cert::SSLCFG" : 2098 "$env openssl req -new -nodes -days $days -batch -keyout '$csrKey' -out '$outCsr' -config $NetSNMP::Cert::SSLCFG"); 2099 2100 $cmd .= ($isCa ? " -extensions v3_ca_create" : " -extensions v3_user_create"); 2101 2102 NetSNMP::Cert::dprint("genCsr: $cmd\n", $opts); 2103 2104 NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts); 2105 2106 NetSNMP::Cert::vprint("Certificate Signing Request Generated:\n", $opts); 2107 NetSNMP::Cert::vprint(" $outCsr\n", $opts); 2108 NetSNMP::Cert::vprint(" $csrKey\n", $opts); 2109} 2110 2111sub signCsr { 2112 my $app = shift; 2113 my $isCa = shift; 2114 my $config = $app->{'config'}; 2115 my $opts = $app->{'OPTS'}; 2116 my $host = $config->inherit("host") || ::hostname(); 2117 my $email = $config->inherit("email") || getlogin() . "\@$host"; 2118 my $days = $config->inherit('days') || $config->inherit('crtDays') || $NetSNMP::Cert::DEFCRTDAYS; 2119 my $cn = $config->inherit('cn') || $config->inherit('commonName'); 2120 my $tag = $config->inherit('tag') || 'snmp'; 2121 my $install = $config->inherit('install'); 2122 2123 $cn = (NetSNMP::Cert::is_server($tag) ? $host : $email); 2124 2125 my $ca = $opts->{'with-ca'}; 2126 NetSNMP::Cert::dprint("ca: $ca\n", $opts); 2127 my $ca_base = ::basename($ca, @NetSNMP::Cert::CRTSUFFIXES); 2128 NetSNMP::Cert::dprint("ca_base: $ca_base\n", $opts); 2129 my $ca_key = "$NetSNMP::Cert::PRIVDIR/$ca_base.key"; 2130 2131 my $csr = $opts->{'csr'}; 2132 NetSNMP::Cert::dprint("csr: $csr\n", $opts); 2133 my $csr_base = ::basename($csr, @NetSNMP::Cert::CRTSUFFIXES); 2134 NetSNMP::Cert::dprint("csr_base: $csr_base\n", $opts); 2135 my $outdir = ($install ? $NetSNMP::Cert::CRTDIR : $NetSNMP::Cert::NEWCRTDIR); 2136 my $outCrt = "$outdir/$csr_base.crt"; 2137 2138 my $env = $app->opensslEnv($cn, $days, ".ca/$ca_base"); 2139 2140 NetSNMP::Cert::check_output_file($opts, $outCrt, 2141 $config->inherit('interactive'), 2142 $config->inherit('force')); 2143 2144 # XXX - handle keyfile search?? 2145 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"; 2146 2147 # $cmd .= ($isCa ? " -extensions v3_ca_sign_ca" : " -extensions v3_ca_sign"); 2148 2149 NetSNMP::Cert::dprint("signCsr: $cmd\n", $opts); 2150 2151 NetSNMP::Cert::usystem("$cmd $opts->{out} $opts->{err}", $opts); 2152 2153 NetSNMP::Cert::vprint("Signed Certificate Signing Request:\n", $opts); 2154 NetSNMP::Cert::vprint(" $csr\n", $opts); 2155 NetSNMP::Cert::vprint("with CA:\n", $opts); 2156 NetSNMP::Cert::vprint(" $ca\n", $opts); 2157 NetSNMP::Cert::vprint(" $ca_key\n", $opts); 2158 NetSNMP::Cert::vprint("Generated Certificate:\n", $opts); 2159 NetSNMP::Cert::vprint(" $outCrt\n", $opts); 2160} 2161 2162sub show { 2163 my $app = shift; 2164 my $type = shift || 'certs'; 2165 my $config = $app->{'config'}; 2166 my $opts = $app->{'OPTS'}; 2167 my $stag = shift(@{$opts->{'cmdargs'}}); 2168 my $fmt = NetSNMP::Cert::x509_format($opts) || '-subject'; 2169 my $brief = $config->inherit('brief'); 2170 my $output; 2171 my $cmd; 2172 2173 my $cwd = ::getcwd(); 2174 NetSNMP::Cert::dprint("show ($cwd):$type:$stag:$fmt\n", $opts); 2175 NetSNMP::Cert::vprint("$opts->{'tlsdir'}:\n", $opts) unless $brief; 2176 2177 foreach my $c (NetSNMP::Cert::find_certs($opts, $type, $stag)) { 2178 print "\n$c:\n" unless $brief; 2179 $cmd = "openssl x509 -in '$c' -noout $fmt"; 2180 NetSNMP::Cert::dprint("show: $cmd\n", $opts); 2181 2182 $output = `$cmd`; chomp $output; 2183 NetSNMP::Cert::vwarn("show-$type failed ($?): $output\n", $opts) if $?; 2184 2185 $output =~ s/^[^\n=]+=// if $brief; 2186 2187 print "$output\n"; 2188 print "\n" unless $brief; 2189 } 2190} 2191 2192sub import_file { 2193 my ($file, $suffix, $targ, $rdir, $tag) = @_; 2194 2195 if (NetSNMP::Cert::is_url($file)) { 2196 if ($NetSNMP::Cert::haveUserAgent) { 2197 2198 require File::Temp; 2199 import File::Temp qw(tempfile); 2200 2201 my ($fh, $newfilename) = tempfile(SUFFIX => $suffix); 2202 return if (!$fh || !$newfilename); 2203 my $ua = LWP::UserAgent->new; 2204 my $response = $ua->get($file); 2205 if ($response->is_success) { 2206 print $fh $response->decoded_content(); 2207 } else { 2208 NetSNMP::Cert::vwarn("failed to download a certificate from $file"); 2209 return; 2210 } 2211 $fh->close; 2212 $file = $newfilename; 2213 } else { 2214 NetSNMP::Cert::vwarn("LWP::UserAgent not installed: unable to import certificate"); 2215 return; 2216 } 2217 } 2218 2219 $file = NetSNMP::Cert::fq_rel_path($file, $rdir); 2220 die("file unreadable: $file\n") unless -r $file; 2221 2222 2223 if (! $targ) { 2224 $targ = (NetSNMP::Cert::is_ca_cert($file) ? $NetSNMP::Cert::CADIR : $NetSNMP::Cert::CRTDIR); 2225 } 2226 2227 $targ .= "/" . $tag . $suffix if ($tag); 2228 ::copy($file, $targ); 2229} 2230 2231 2232sub import { 2233 my $app = shift; 2234 my $config = $app->{'config'}; 2235 my $opts = $app->{'OPTS'}; 2236 my $carg = shift(@{$opts->{'cmdargs'}}); 2237 my $karg = shift(@{$opts->{'cmdargs'}}); 2238 my $targ; 2239 2240 if (not defined $carg) { 2241 NetSNMP::Cert::vwarn("import: no certificate supplied\n", $opts); 2242 NetSNMP::Cert::usage(1); 2243 } 2244 2245 import_file($carg, '.crt', '',, 2246 $opts->{'rdir'}, $opts->{'tag'}); 2247 2248 return unless defined $karg; 2249 2250 import_file($karg, '.key', 'private',, 2251 $opts->{'rdir'}, $opts->{'tag'}); 2252} 2253 2254 2255package NetSNMP::Cert::Config; 2256use vars qw(@ISA); 2257@ISA = qw(NetSNMP::Cert::Obj); 2258 2259sub new { 2260 my $class = shift; 2261 my $parent = shift; 2262 my $this = $class->SUPER::new('config', $parent); 2263 2264 bless($this, $class); 2265} 2266 2267sub parse { 2268 my $config = shift; 2269 my $app = $config->{'APP'}; 2270 my $opts = $app->{'OPTS'}; 2271 my $cfgfile = shift; 2272 $cfgfile ||= $opts->{'cfgfile'}; 2273 2274 return '0 but true' if $config->{'PARSED'}; 2275 return '0 but true' unless defined $cfgfile; 2276 2277 open( CONFIG, "<$cfgfile" ) 2278 or die "error - could not open configuration file: $cfgfile"; 2279 2280 while (<CONFIG>) { 2281 next if /^\s*#/ or /^\s*$/; 2282 2283 if (/^\s*(\w+)(?:\(([\)\(\d\,\.]+)\))?\s*=\s*{/) { 2284 my $obj = $1; 2285 2286 NetSNMP::Cert::dprint("object: $obj ($2) = {\n", $opts); 2287 die "error - identity: indices not supported: $2" if defined $2; 2288 2289 # Found an object. 2290 if ( $obj eq 'identity' ) { 2291 my $identity = NetSNMP::Cert::Identity::parse(*CONFIG, $config); 2292 my $id = $identity->{'id'}; 2293 die "error - identity: 'id' not defined" unless defined $id; 2294 die "error - identity: duplicate '$id'" if exists $config->{$obj}{$id}; 2295 $config->{$obj}{$id} = $identity; 2296 } else { 2297 die "error - unrecognized object ($1) at scope (config)"; 2298 } 2299 } elsif (/^\s*(\w+)\s*=?\s*(.*?)\s*$/) { 2300 my $key = $1; 2301 my $val = $2; 2302 2303 $val = $config->resolve($val) if $val =~ /\$\w+|\&\{.*?\}/; 2304 # Found a symbol. 2305 NetSNMP::Cert::dprint(" $key = $val\n", $opts); 2306 if ($key eq 'subjectAltName' or $key eq 'san') { 2307 push(@{$config->{$key}}, $val); 2308 } elsif ( defined $config->{$key} ) { 2309 die "error - duplicate symbol $key"; 2310 } else { 2311 $config->{$key} = (defined $val ? NetSNMP::Cert::map_bool($val) : 1 ); 2312 } 2313 } elsif (/^\s*env\s*(\w+=\S+)\s*$/) { 2314 # Found an environment variable. 2315 NetSNMP::Cert::dprint("$&\n", $opts); 2316 push(@{$config->{'env'}}, $1); 2317 } else { 2318 die("error in config file [$cfgfile:line $.]"); 2319 } 2320 } 2321 2322 NetSNMP::Cert::dprint("end parse config\n", $opts); 2323 close(CONFIG); 2324 2325 # augment with any config directives supplied in opts 2326 foreach my $cfg (@{$opts->{'config'}}) { 2327 NetSNMP::Cert::dprint("augmenting config: $cfg\n", $opts); 2328 2329 $config->autoSet($1, (defined($2) ? $2 : 1 )) 2330 if $cfg =~ /^\s*(\w+)\s*=?\s*(.*?)\s*$/; 2331 } 2332 $config->autoSet('PARSED', 1); 2333 return $config->{'PARSED'}; 2334} 2335 2336package NetSNMP::Cert::Identity; 2337use vars qw(@ISA); 2338@ISA = qw(NetSNMP::Cert::Obj); 2339 2340sub new { 2341 my $class = shift; 2342 my $this = shift || $class->SUPER::new('identity', @_); 2343 my $ind = $this->{'INDEX'} || 1; 2344 2345 $this->autoSet('name', "$this->{type}.$ind") unless exists $this->{'name'}; 2346 2347 $this->autoSet('LOG','') unless exists $this->{'LOG'}; 2348 $this->autoSet('TTY_LOG','') unless exists $this->{TTY_LOG}; 2349 2350 bless($this, $class); 2351} 2352 2353 2354sub parse { 2355 my $FILE = shift; 2356 my $parent = shift; 2357 my $opts = $parent->inherit('OPTS'); 2358 my $identity = new NetSNMP::Cert::Obj('identity', $parent); 2359 2360 NetSNMP::Cert::dprint("parse identity\n", $opts); 2361 2362 while (<$FILE>) { 2363 next if /^\s*#/ or /^\s*$/; 2364 2365 if (/^\s*(\w+)\s*=\s*{/) { 2366 # Found an object. 2367 die "error - can't have nested $1"; 2368 } elsif (/^\s*(\w+)\s*=?\s*(.*?)\s*$/) { 2369 my $key = $1; 2370 my $val = $2; 2371 2372 # Found a symbol. 2373 NetSNMP::Cert::dprint(" $key = $val\n", $opts); 2374 2375 $val = $identity->resolve($val) if $val =~ /\$\w+|\&\{.*?\}/; 2376 2377 if ( $key eq 'subjectAltName' or $key eq 'san') { 2378 push(@{$identity->{$key}}, $val); 2379 } elsif ( defined $identity->{$key} ) { 2380 die "error - duplicate symbol $key"; 2381 } else { 2382 $identity->{$key} = (defined $val ? NetSNMP::Cert::map_bool($val) : 1 ); 2383 } 2384 } elsif (/\s*\}\s*\;/) { 2385 # End of definition. 2386 NetSNMP::Cert::dprint("end parse identity\n", $opts); 2387 return new NetSNMP::Cert::Identity($identity); 2388 } else { 2389 die("error in config file [$opts->{cfgfile}:line $.]"); 2390 } 2391 } 2392 die "error - unexpected end of conf file"; 2393} 2394 2395package main; 2396 2397my $app = new NetSNMP::Cert::App(); 2398$app->init(@ARGV); 2399$app->run(); 2400 2401__END__ 2402=pod 2403 2404=head1 NAME 2405 2406net-snmp-cert - Net-SNMP Certificate Management Tool 2407 2408=head1 SYNOPSIS 2409 2410=over 2411 2412=item $ net-snmp-cert genca -I --cn ca-Net-SNMP 2413 2414=item $ net-snmp-cert gencert -I -t snmpapp --with-ca ca-Net-SNMP 2415 2416=item $ net-snmp-cert gencert -I -t snmpd --cn net-snmp.org 2417 2418=item $ net-snmp-cert showcas 2419 2420=item $ net-snmp-cert showcerts 2421 2422=back 2423 2424=head1 DESCRIPTION 2425 2426net-snmp-cert creates, signs, installs and displays X.509 2427certificates used in the operation of Net-SNMP/(D)TLS. 2428 2429=head1 SYNTAX 2430 2431=over 2432 2433=item net-snmp-cert [--help|-?] 2434 2435=item net-snmp-cert [--version|-V] 2436 2437=item net-snmp-cert genca [<flags>] [<dn-opts>] [--with-ca <ca>] 2438 2439=item net-snmp-cert gencert [<flags>] [<dn-opts>] [--with-ca <ca>] 2440 2441=item net-snmp-cert gencsr [<flags>] [<dn-opts>] [--from-crt <crt>] 2442 2443=item net-snmp-cert signcsr [<flags>] [--install] --csr <csr> --with-ca <ca> 2444 2445=item net-snmp-cert showca [<flags>] [<format-opts>] [<file>|<search-tag>] 2446 2447=item net-snmp-cert showcert [<flags>] [<format-opts>] [<file>|<search-tag>] 2448 2449=item net-snmp-cert import [<flags>] <file|url> [<key>] 2450 2451=back 2452 2453=head1 COMMANDS 2454 2455=over 2456 2457=item genca 2458 2459generate a signed CA certificate suitable for signing other 2460certificates. default: self-signed unless --with-ca <ca> supplied 2461 2462=item gencert 2463 2464generate a signed certificate suitable for identification, or 2465validation. default: self-signed unless --with-ca <ca> supplied 2466 2467=item gencsr 2468 2469generate a certificate signing request. will create a new 2470key and certificate unless --from-crt <crt> supplied 2471 2472=item signcsr 2473 2474sign a certificate signing request specified by --csr <csr> 2475with the CA certificate specified by --with-ca <ca> 2476 2477=item import 2478 2479import an identity or CA certificate, optionally import <key> 2480if an URL is passed, will attempt to import certificate from site 2481 2482=item showca, showcert 2483 2484show CA or identity certificate(s). may pass fully qualified 2485file or directory name, or a search-tag which prefix matches 2486installed CA or identity certificate file name(s) 2487see FORMAT OPTIONS to specify output format 2488 2489 2490=back 2491 2492=head1 FLAGS 2493 2494=over 2495 2496=item -?, --help -- show this text and exit 2497 2498=item -V, --version -- show version string and exit 2499 2500=item -D, --debug -- raise debug level (-D -D for higher level) 2501 2502=item -F, --force -- force overwrite of existing output files 2503 2504=item -I, --nointeractive -- non-interactive run (default interactive) 2505 2506=item -Q, --noverbose -- non-verbose output (default verbose) 2507 2508=item -C, --cfgdir <dir> -- configuration dir (see man(5) snmp_config) 2509 2510=item -T, --tlsdir <dir> -- root for cert storage (default <cfgdir>/tls) 2511 2512=item -f, --cfgfile <file> -- config (default <cfgdir>/net-snmp-cert.conf) 2513 2514=item -i, --identity <id> -- identity to use from config 2515 2516=item -t, --tag <tag> -- application tag (default 'snmp') 2517 2518=item --<cfg-param>[=<val>] -- additional config params 2519 2520=back 2521 2522=head1 CERTIFICATE OPTIONS (<cert-opts>) 2523 2524=over 2525 2526=item -a, --with-ca <ca> -- CA certificate used to sign request 2527 2528=item -A, --ca <ca> -- explicit output CA certificate 2529 2530=item -r, --csr <csr> -- certificate signing request 2531 2532=item -x, --from-crt <crt> -- input certificate for current operation 2533 2534=item -X, --crt <crt> -- explicit output certificate 2535 2536=item -y, --install -- install result in local repository 2537 2538=back 2539 2540=head1 DISTINGUISHED NAME OPTIONS (<dn-opts>) 2541 2542=over 2543 2544=item -e, --email <email> -- email name 2545 2546=item -h, --host <host> -- DNS host name, or IP address 2547 2548=item -d, --days <days> -- number of days certificate is valid 2549 2550=item -n, --cn <cn> -- common name (CN) 2551 2552=item -o, --org <org> -- organiztion name 2553 2554=item -u, --unit <unit> -- organiztion unit name 2555 2556=item -c, --country <country> -- country code (e.g., US) 2557 2558=item -p, --province <province> -- province name (synomynous w/ state) 2559 2560=item -p, --state <state> -- state name (synomynous w/ province) 2561 2562=item -l, --locality <locality> -- locality name (e.g, town) 2563 2564=item -s, --san <san> -- subjectAltName, repeat for each <san> 2565 2566=over 2 2567 2568=item <san> value format (<FMT>:<VAL>): 2569 2570=over 3 2571 2572=item dirName:/usr/share/snmp/tls 2573 2574=item DNS:net-snmp.org 2575 2576=item email:admin@net-snmp.org 2577 2578=item IP:192.168.1.1 2579 2580=item RID:1.1.3.6 2581 2582=item URI:http://net-snmp.org 2583 2584=back 2585 2586=back 2587 2588=back 2589 2590=head1 FORMAT OPTIONS (<format-opts>) 2591 2592=over 2593 2594=item --brief -- minimized output (values only where applicable) 2595 2596=item --text -- full text description 2597 2598=item --subject -- subject description 2599 2600=item --subject_hash -- hash of subject for indexing 2601 2602=item --issuer -- issuer description 2603 2604=item --issuer_hash -- hash of issuer for indexing 2605 2606=item --fingerprint -- SHA1 digest of DER 2607 2608=item --serial -- serial number 2609 2610=item --modulus -- modulus of the public key 2611 2612=item --dates -- start and end dates 2613 2614=item --purpose -- displays allowed uses 2615 2616=item --C -- C code description 2617 2618=back 2619 2620=head1 OPERATIONAL NOTES 2621 2622 2623=head2 Interactive Mode 2624 2625The application operates in interactive mode by default. In this mode 2626basic operations of offered and required input is queried through the 2627command line. 2628 2629Typical <tab> completion is provided when possible and field specific 2630help may be obtained by entering '?'. 2631 2632To turn off interactive mode, supply '--nointeractive' or '-I' on the 2633initial command line. Equivalantly, 'interactive = false' maybe placed 2634in the configuration file (see below). 2635 2636=head2 Configuration 2637 2638A configuration file may be supplied on the command line or found in a 2639default location (<snmpconfpath>/net-snmp-cert.conf). This file may 2640contain configuration parameters equivalent to those supplied on the 2641command line and effectively provides default values for these 2642values. If a command line parameter is supplied it will override the 2643value in the config file. If neither is present then an application 2644value will be used. 2645 2646=head2 Certificate Naming 2647 2648Filenames of created certificates, as stored in the configuration 2649directory, are chosen in the following manner. If and application tag 2650is supplied, it will be used as the basename for the certificate and 2651key generated. Otherwise, for CA certificates, the basename will be 2652derived from the Common Name. For non-CA certificates the application 2653tag defaults to 'snmp' which will then be used as the basename of the 2654certificate and key. 2655 2656=head1 EXAMPLES 2657 2658=over 2659 2660=item net-snmp-cert genca --cn ca-net-snmp.org --days 1000 2661 2662=item net-snmp-cert genca -f .snmp/net-snmp-cert.conf -i nocadm 2663 2664=item net-snmp-cert gencert -t snmpd --cn host.net-snmp.org 2665 2666=item net-snmp-cert gencsr -t snmpapp 2667 2668=item net-snmp-cert signcsr --csr snmpapp --with-ca ca-net-snmp.org 2669 2670=item net-snmp-cert showcerts --subject --issuer --dates snmpapp 2671 2672=item net-snmp-cert showcas --fingerprint ca-net-snmp.org --brief 2673 2674=item net-snmp-cert import ca-third-party.crt 2675 2676=item net-snmp-cert import signed-request.crt signed-request.key 2677 2678=back 2679 2680=head1 COPYRIGHT 2681 2682Copyright (c) 2010 Cobham Analytic Solutions - All rights reserved. 2683Copyright (c) 2010 G. S. Marzot - All rights reserved. 2684 2685=head1 AUTHOR 2686 2687G. S. Marzot (marz@users.sourceforge.net) 2688 2689=cut 2690 2691