#!/usr/bin/env perl # # Written by Martin Bartosch and Alexander Klink # for the OpenXPKI project 2006 # Copyright (c) 2006 by The OpenXPKI Project # $Revision: 80 $ # our $VERSION = "[% deployment.version %]"; use strict; use warnings; use English; use Getopt::Long; use Pod::Usage; use File::Spec; use File::Copy; use File::Path; use IO::Prompt; #use Data::Dumper; #use Smart::Comments; use OpenXPKI::VERSION; use OpenXPKI::Debug; use OpenXPKI::Server::Context qw( CTX ); # settings determined by openxpki-metaconf my %config = ( prefix => "[% dir.prefix %]", exec_prefix => "[% dir.exec_prefix %]", template_prefix => "[% dir.templatedir %]", sysconfdir => "[% dir.sysconfdir %]", localedir => "[% dir.localedir %]", openxpkiconfdir => "[% dir.openxpkiconfdir %]", ); my $configfile = "$config{openxpkiconfdir}/config.xml"; # read configuration from deployed OpenXPKI instance sub get_config { my $cfgfile = shift; my @additional_tasks = @_; return OpenXPKI::Server::Init::init( { CONFIG => $cfgfile, TASKS => [ 'current_xml_config', 'i18n', 'dbi_log', 'log', 'api', @additional_tasks, ], SILENT => 1, }); } sub initdb { my $args = shift; no warnings; my $type = $OpenXPKI::Server::Init::current_xml_config->get_xpath ( XPATH => [ 'common/database/type' ], COUNTER => [ 0 ]); use warnings; print STDERR "Database type: $type\n"; my @databases = qw( log ); # SQLite needs special treatment: three databases instead of one must # be initialized if ($type =~ m{ SQLite }xms) { push @databases, 'workflow', 'backend'; } DB: foreach my $db (@databases) { my $params = {}; $params->{PURPOSE} = $db; if (defined $params->{PURPOSE}) { print STDERR "Setting up database '$db'\n"; } my $dbi = OpenXPKI::Server::Init::get_dbi($params); eval { $dbi->connect() }; if ($EVAL_ERROR) { print STDERR "ERROR: Could not connect to '$db' database ($EVAL_ERROR)\n"; return; } if ($args->{DRYRUN}) { print $dbi->init_schema(MODE => 'DRYRUN') . "\n"; last DB; } else { my %args = (); if ($args->{FORCE}) { $args{MODE} = 'FORCE'; } #### args for init_schema : Dumper(\%args) eval { $dbi->init_schema(%args); }; if ($EVAL_ERROR) { print STDERR "ERROR: init_schema on '$db' failed (${EVAL_ERROR})\n"; return; } print STDERR "Database '$db' initialized.\n"; } } return 1; } sub deploy { my $args = shift; my $targetdir = $args->{TARGETDIR}; my $template_prefix = $args->{TEMPLATE_PREFIX}; my $template = $args->{TEMPLATE}; my @metaconf_opts = @{$args->{METACONF_OPTS}}; if (! defined $targetdir || ($targetdir eq '')) { print STDERR "No target directory specified.\n"; return; } if (! (-d $targetdir && -r $targetdir && -x $targetdir && -w $targetdir)) { print STDERR "Directory $targetdir does not exist or is not writable.\n"; return; } print STDERR "Deploying OpenXPKI configuration file set.\n"; print STDERR "Template set: $template\n"; print STDERR "Template source directory: $template_prefix\n"; print STDERR "Target directory: $targetdir\n"; if (scalar @metaconf_opts) { print STDERR "openxpki-metaconf options: " . join(' ', @metaconf_opts) . "\n"; } if ($args->{DRYRUN}) { return 1; } if (! -d $template_prefix) { print STDERR "ERROR: template directory $template_prefix not found\n"; return; } my $srcfile = File::Spec->catfile($template_prefix, $template, 'openxpki.conf'); # 2006-06-21 Martin Bartosch: # The deployment procedure now will create a new meta configuration # file from the one that is installed in the template directory. # determine new configuration file directory (below $targetdir) my @cmd; @cmd = ( 'openxpki-metaconf', '--config', qq( $srcfile ), '--setcfg', "dir.prefix='$targetdir'", '--getcfg', 'dir.openxpkiconfdir', @metaconf_opts, ); my $cmd = join(' ', @cmd); my $openxpkiconfdir = `$cmd`; chomp($openxpkiconfdir); ### $openxpkiconfdir if (! -d $openxpkiconfdir) { if (! mkpath($openxpkiconfdir, 1, 0750)) { print STDERR "Could not create configuration directory $openxpkiconfdir\n"; return; } } my $dstfile = File::Spec->catfile($openxpkiconfdir, 'openxpki.conf'); if (-e $dstfile) { if (! $args->{FORCE}) { print STDERR "ERROR: $dstfile already exists\n"; return; } move($dstfile, $dstfile . '.last'); } if (! -e $srcfile) { print STDERR "ERROR: $srcfile not found\n"; return; } # the new configuration file will reference two types of files/dirs: # - files/dirs that are specific for this particular deployment # (e. g. configuration, log files, server socket...) # - files/dirs that are shared among ALL installed instances # (e. g. locales) # ### $config{localedir} @cmd = ( 'openxpki-metaconf', '--config', qq( $srcfile ), '--writecfg', qq( $dstfile ), '--setcfg', "dir.prefix='$targetdir'", '--setcfg', "dir.localedir='$config{localedir}'", @metaconf_opts, ); if (system(join(' ', @cmd)) != 0) { print STDERR "ERROR: could not deploy target configuration file $dstfile\n"; return; } print STDERR "wrote $dstfile\n"; return 1; } sub list_realms { my @realms; my $config = OpenXPKI::XML::Config->new( CONFIG => $configfile, ); my $nr_of_realms = $config->get_xpath_count( XPATH => [ 'pki_realm' ], COUNTER => [ ], ); my $realm_index; for (my $i=0; $i < $nr_of_realms; $i++) { push @realms, $config->get_xpath( XPATH => [ 'pki_realm', 'name' ], COUNTER => [ $i , 0 ], ); } return @realms; } sub list_keys { my $arg_ref = shift; my $config = OpenXPKI::XML::Config->new( CONFIG => $configfile, ); my $nr_of_realms = $config->get_xpath_count( XPATH => [ 'pki_realm' ], COUNTER => [ ], ); # find the realm index we have to use my $realm_index; for (my $i=0; $i < $nr_of_realms; $i++) { next if ($arg_ref->{REALM} ne $config->get_xpath( XPATH => [ 'pki_realm', 'name' ], COUNTER => [ $i , 0 ])); $realm_index = $i; last; } foreach my $type qw( ca scep ) { # iterate over ca and scep entries print uc($type) . " keys:\n"; my $type_count = $config->get_xpath_count( XPATH => [ 'pki_realm', $type ], COUNTER => [ $realm_index ], ); for (my $i = 0; $i < $type_count; $i++) { # iterate over CAs, SCEPs my $type_id = $config->get_xpath( XPATH => [ 'pki_realm' , $type, 'id' ], COUNTER => [ $realm_index, $i , 0 ], ); print ' Key for purpose ' . uc($type) . ' with ID: ' . $type_id . "\n"; my $token_count = $config->get_xpath_count( XPATH => [ 'pki_realm' , $type, 'token' ], COUNTER => [ $realm_index, $i ], ); # base path and counter for later use my @token_path = ( 'pki_realm' , $type, 'token' ); my @token_counter = ( $realm_index, $i , 0 ); my $key_count = $config->get_xpath_count( XPATH => [ @token_path , 'key' ], COUNTER => [ @token_counter ], ); my $secret_count = $config->get_xpath_count( XPATH => [ @token_path , 'secret' ], COUNTER => [ @token_counter ], ); if ($key_count != 1) { # there should only be one key per token! print STDERR " ! Misconfiguration detected: $key_count" . " keys configured!\n"; } if ($secret_count != 1) { # there should only be one secret # definition per token print STDERR " ! Misconfiguration detected: $secret_count" . " secret definitions configured!\n"; } if ($key_count == 1 && $secret_count == 1) { # everything is fine my $key = $config->get_xpath( XPATH => [ @token_path , 'key' ], COUNTER => [ @token_counter, 0 ], ); my $status_flag = '?'; if (-e $key && (! -s $key)) { $status_flag = '0'; # file exists but is of size zero } elsif (-e $key) { # file exists and is non-zero $status_flag = '+'; } else { # file does not exist (yet) $status_flag = '!'; } print ' ' . $status_flag . ' ' . $key . "\n"; my $secret_group_name = $config->get_xpath( XPATH => [ @token_path , 'secret' ], COUNTER => [ @token_counter, 0 ], ); my @secret_path = ( 'pki_realm' , 'common', 'secret' ); my @secret_counter = ( $realm_index, 0 , 0 ); my $secret_group_count = $config->get_xpath_count( XPATH => [ @secret_path, 'group' ], COUNTER => [ @secret_counter, ], ); my $group_index; SEARCH_GROUP_ID: for (my $i = 0; $i < $secret_group_count; $i++) { my $group_id = $config->get_xpath( XPATH => [ @secret_path , 'group', 'id' ], COUNTER => [ @secret_counter, $i , 0 ], ); if ($group_id eq $secret_group_name) { $group_index = $i; last SEARCH_GROUP_ID; } } if (! defined $group_index) { print STDERR "Could not find configuration for secret group '$secret_group_name'.\n"; exit 1; } my $secret_method = $config->get_xpath( XPATH => [ @secret_path , 'group' , 'method', 'id' ], COUNTER => [ @secret_counter, $group_index, 0 , 0 ], ); if ($secret_method ne 'literal') { # all others have total_shares my $quorum_n = $config->get_xpath( XPATH => [ @secret_path , 'group', 'method', 'total_shares' ], COUNTER => [ @secret_counter, 0 , 0 , 0 ], ); my $quorum_k = __get_required_shares({ CONFIG => $config, TOKEN_PATH => \@secret_path, TOKEN_COUNTER => \@secret_counter, }); if (! defined $quorum_k) { # if it is not defined, n is # the default value $quorum_k = $quorum_n; } $secret_method .= ' (n = ' . $quorum_n . ', k = ' . $quorum_k . ')'; } print ' Secret group: ' . $secret_group_name . "\n"; print ' Secret method: ' . $secret_method . "\n"; } } } 1; } sub generate_keys { my $arg_ref = shift; my $realm = $arg_ref->{REALM}; my $secret_group_name = $arg_ref->{GROUP}; my $config = OpenXPKI::XML::Config->new( CONFIG => $configfile, ); my $nr_of_realms = $config->get_xpath_count( XPATH => [ 'pki_realm' ], COUNTER => [ ], ); # figure out realm index my $realm_index; for (my $i=0; $i < $nr_of_realms; $i++) { next if ($realm ne $config->get_xpath( XPATH => [ 'pki_realm', 'name' ], COUNTER => [ $i , 0 ])); $realm_index = $i; last; } # default token which will be used to create the keys my $default_token = CTX('crypto_layer')->get_token( TYPE => 'DEFAULT', PKI_REALM => $realm, ); # figure out secret group index my @secret_path = ( 'pki_realm' , 'common', 'secret' ); my @secret_counter = ( $realm_index, 0 , 0 ); my $secret_group_count = $config->get_xpath_count( XPATH => [ @secret_path, 'group' ], COUNTER => [ @secret_counter, ], ); my $group_index; SEARCH_GROUP_ID: for (my $i = 0; $i < $secret_group_count; $i++) { my $group_id = $config->get_xpath( XPATH => [ @secret_path , 'group', 'id' ], COUNTER => [ @secret_counter, $i , 0 ], ); if ($group_id eq $secret_group_name) { $group_index = $i; last SEARCH_GROUP_ID; } } if (! defined $group_index) { print STDERR "Could not find configuration for secret group '$secret_group_name'.\n"; exit 1; } my $secret_method = $config->get_xpath( XPATH => [ @secret_path , 'group' , 'method', 'id' ], COUNTER => [ @secret_counter, $group_index, 0 , 0 ], ); my $quorum_n; my $quorum_k; my $display_secret_method = $secret_method; if ($secret_method ne 'literal') { # all others have total_shares $quorum_n = $config->get_xpath( XPATH => [ @secret_path , 'group', 'method', 'total_shares' ], COUNTER => [ @secret_counter, 0 , 0 , 0 ] ); $quorum_k = __get_required_shares({ CONFIG => $config, TOKEN_PATH => \@secret_path, TOKEN_COUNTER => \@secret_counter, }); if (! defined $quorum_k) { # if it is not defined, n is # the default value $quorum_k = $quorum_n; } $display_secret_method .= ' (n = ' . $quorum_n . ', k = ' . $quorum_k . ')'; } print "Generating keys for secret group " . $secret_group_name . "\n"; print "Secret method is: " . $display_secret_method . "\n\n"; my $passwd; if ($secret_method eq 'literal') { # in the literal case, this is just the config entry $passwd = $config->get_xpath( XPATH => [ @secret_path , 'group' , 'method' ], COUNTER => [ @secret_counter, $group_index, 0 ], ); } else { my $crypto_secret; if ($secret_method eq 'plain') { $crypto_secret = OpenXPKI::Crypto::Secret->new({ TYPE => 'Plain', PARTS => $quorum_n, }); for (my $i = 1; $i <= $quorum_n; $i++) { my $secret = __prompt_password("Please enter " . "password share $i/$quorum_n: "); $crypto_secret->set_secret({ PART => $i, SECRET => "$secret", # $secret is of type IO::Prompt::Return }); print "\n"; } $passwd = $crypto_secret->get_secret(); } elsif ($secret_method eq 'split') { $crypto_secret = OpenXPKI::Crypto::Secret->new({ TYPE => 'Split', QUORUM => { N => $quorum_n, K => $quorum_k, }, TOKEN => $default_token, }); my @shares = $crypto_secret->compute(); # TODO: use bitlength # based on chosen algo for (my $i = 0; $i < scalar(@shares); $i++) { my $nr = $i+1; prompt("Please make sure that share holder number $nr is ready" . " to copy the share,\n" . "then press enter to view the share."); for (my $j = 0; $j < 100; $j++) { print "\n"; # pseudo clearscreen } print "Please copy the following share:\n"; print $shares[$i] . "\n"; print "Press enter to continue (next share will not " . "yet be shown).\n"; prompt(); for (my $j = 0; $j < 100; $j++) { print "\n"; # pseudo clearscreen } } $passwd = $crypto_secret->get_secret(); } } foreach my $type qw( ca scep ) { # iterate over ca and scep entries my $type_count = $config->get_xpath_count( XPATH => [ 'pki_realm', $type ], COUNTER => [ $realm_index ], ); KEYS: for (my $i = 0; $i < $type_count; $i++) { # iterate over CAs, SCEPs my $type_id = $config->get_xpath( XPATH => [ 'pki_realm' , $type, 'id' ], COUNTER => [ $realm_index, $i , 0 ], ); my $token_count = $config->get_xpath_count( XPATH => [ 'pki_realm' , $type, 'token' ], COUNTER => [ $realm_index, $i ], ); # base path and counter for later use my @token_path = ( 'pki_realm' , $type, 'token' ); my @token_counter = ( $realm_index, $i , 0 ); my $key_count = $config->get_xpath_count( XPATH => [ @token_path , 'key' ], COUNTER => [ @token_counter ], ); my $secret_count = $config->get_xpath_count( XPATH => [ @token_path , 'secret' ], COUNTER => [ @token_counter ], ); if ($key_count != 1) { # there should only be one key per token! print STDERR "Misconfiguration detected: $key_count " . "key files configured for id $type_id, " . "purpose" . uc $type . "!\n"; exit 1; } if ($secret_count != 1) { # there should only be one secret # definition per token print STDERR "Misconfiguration detected: $secret_count " . "secret groups configured for id $type_id, " . "purpose" . uc $type . "!\n"; exit 1; } my $curr_secret_group_name = $config->get_xpath( XPATH => [ @token_path , 'secret' ], COUNTER => [ @token_counter, 0 ], ); next KEYS if ($curr_secret_group_name ne $secret_group_name); my $key_filename = $config->get_xpath( XPATH => [ @token_path , 'key' ], COUNTER => [ @token_counter, 0 ], ); if (-s $key_filename) { print STDERR "Key for purpose $type with id $type_id " . "already exists in $key_filename, cowardly " . "refusing to overwrite it.\n"; next KEYS; } my $key; my $fu = OpenXPKI::FileUtils->new(); # get possible types and options from token my %command_params = %{$default_token->get_cmd_param('create_key')}; print "Choose options for key for purpose '" . uc($type) . "' with id '$type_id'\n"; print "Please choose one of the following key types:\n"; foreach my $possible_type ( @{$command_params{TYPE}} ) { print " - $possible_type\n"; } my $type = prompt('Key type: '); my $type_parameters = $command_params{PARAMETERS}->{"TYPE:" . $type}; while (! defined $type_parameters) { # no parameter entry in command_params available for the chosen type print "Invalid type chosen, please try again.\n"; $type = prompt('Key type: '); $type_parameters = $command_params{PARAMETERS}->{"TYPE:" . $type}; } my $parameters; # parameter hash used in the token command my %already_configured_parameters; if (defined $type_parameters->{KEY_LENGTH}) { # key length is only possible for some algos print "\nPlease choose one of the following key lengths:\n"; my %valid_keylength = ( ); foreach my $possible_kl ( @{$type_parameters->{KEY_LENGTH}} ) { print " - $possible_kl\n"; $valid_keylength{$possible_kl} = 1; } my $key_length = prompt('Key length: '); while (! defined $valid_keylength{$key_length}) { print "Invalid key length chosen, please try again.\n"; $key_length = prompt('Key length: '); } # implicit cast, $key_length is of type IO::Prompt::ReturnVal! $parameters->{KEY_LENGTH} = "$key_length"; $already_configured_parameters{KEY_LENGTH} = 1; } if (defined $type_parameters->{ENC_ALG}) { # currently defined for all, # but you never know print "\nPlease choose one of the following key encryption " . "algorithms:\n"; my %valid_enc_alg = ( ); foreach my $possible_enc_alg ( @{$type_parameters->{ENC_ALG}} ) { if ($possible_enc_alg eq '__undef') { $possible_enc_alg = 'default'; # present __undef as default } print " - $possible_enc_alg\n"; $valid_enc_alg{$possible_enc_alg} = 1; } my $enc_alg = prompt('Encryption algorithm: '); while (! defined $valid_enc_alg{$enc_alg}) { print "Invalid encryption algorithm chosen, please try " . "again.\n"; $enc_alg = prompt('Encryption algorithm: '); } if ("$enc_alg" ne 'default') { # specific algorithm requested $parameters->{ENC_ALG} = "$enc_alg"; # implicit type cast! } $already_configured_parameters{ENC_ALG} = 1; } # configure optional parameters foreach my $param (keys %{$type_parameters}) { if (! defined $already_configured_parameters{$param} ) { my $optional = 0; if (ref $type_parameters->{$param} eq '') { # type param is int if ($type_parameters->{$param} == 0) { $optional = 1; } } elsif (ref $type_parameters->{$param} eq 'ARRAY') { $optional = __string_is_in_array({ STRING => '__undef', ARRAY => $type_parameters->{$param}, }); } my $want_config = 0; if ($optional == 1) { my $answer = prompt "Do you want to configure the optional" . " parameter $param (y/n)? ", '-y'; if ($answer =~ /^[yY]$/) { $want_config = 1; } } if ($optional != 1 || $want_config == 1) { if (ref $type_parameters->{$param} eq 'ARRAY') { print "Please choose one of the following values " . "for $param: \n"; foreach my $elem (@{$type_parameters->{$param}}) { print " - $elem\n"; } } my $param_value = prompt("Value for $param: "); if (ref $type_parameters->{$param} eq 'ARRAY') { # check validity while (! __string_is_in_array({ STRING => $param_value, ARRAY => $type_parameters->{$param}, })) { print "Invalid value, please try again.\n"; $param_value = prompt("Value for $param: "); } } $parameters->{$param} = "$param_value"; # implicit cast! } } } print "Creating key, please be patient ...\n"; $key = $default_token->command({ COMMAND => 'create_key', TYPE => "$type", PASSWD => $passwd, PARAMETERS => $parameters, }); if ($key ne '') { my $key_path = $key_filename; $key_path =~ s/(.*)\/.*/$1/; # perl is greedy, so this is the path if (! -d $key_path) { # key path does not yet exist, create it eval { # try to create mkpath($key_path); }; if ($EVAL_ERROR) { print STDERR "Could not create key directory: $key_path"; exit 1; } } $fu->write_file({ FILENAME => $key_filename, CONTENT => $key, }); if (-s $key_filename) { # key file exists and is nonzero print "Key successfully written to $key_filename\n\n\n"; } else { print STDERR "Key creation failed!\n"; exit 1; } } else { print STDERR "Key creation failed!\n"; exit 1; } } } 1; } sub __get_required_shares { # returns required_shares from config file or undefined if # required_shares is not defined in the configuration my $arg_ref = shift; my $config = $arg_ref->{CONFIG}; my @token_path = @{$arg_ref->{TOKEN_PATH}}; my @token_counter = @{$arg_ref->{TOKEN_COUNTER}}; my $quorum_k_count; my $quorum_k; eval { # This crashes when k is not present! push @token_path , ('group', 'method', 'quorum', 'k'); push @token_counter, (0 , 0 , 0); $quorum_k_count = $config->get_xpath_count( XPATH => [ @token_path ], COUNTER => [ @token_counter ], ); }; if ($EVAL_ERROR) { # k is not present, noticed the hard way $quorum_k_count = 0; $quorum_k = undef; } if ($quorum_k_count != 0) { # 'k' configured, push @token_counter, (0); $quorum_k = $config->get_xpath( XPATH => [ @token_path ], COUNTER => [ @token_counter ], ); } return $quorum_k; } sub __prompt_password { # prompt for password until verification password and password match my $question = shift; my $passwords_match = 0; my $password1; while (! $passwords_match) { $password1 = prompt($question, -echo => ''); print "Please enter the same password share again to make sure it was typed correctly.\n"; my $password2 = prompt($question, -echo => ''); if ($password1 eq $password2) { $passwords_match = 1; } if (! $passwords_match) { print "Passwords do not match, please try again!\n\n"; } } return $password1; } sub __string_is_in_array { my $arg_ref = shift; my $entry = $arg_ref->{STRING}; my @array = @{$arg_ref->{ARRAY}}; foreach my $elem (@array) { if ($entry eq $elem) { return 1; } } return 0; } sub __resolve_alias { my $arg_ref = shift; my $dbi = $arg_ref->{DBI}; my $name = $arg_ref->{NAME}; my $realm = $arg_ref->{REALM}; my $alias = $dbi->first( TABLE => 'ALIASES', DYNAMIC => { ALIAS => $name, PKI_REALM => $realm, }, ); if (defined $alias) { return $alias->{IDENTIFIER}; } else { return $name; } } ########################################################################### my @options_spec = qw( cfg|cfgfile|config|config=s debug=s@ ); # config is the only global option my $cmd = shift @ARGV || ''; my $subcmd; if ($cmd eq 'certificate' || $cmd eq 'key') { # those have subcommands $subcmd = shift @ARGV || ''; } # set command/subcommand specific allowed options if ($cmd eq 'initdb') { push @options_spec, qw( force dryrun ); } if ($cmd eq 'deploy') { push @options_spec, qw( prefix=s templatedir=s template=s force ); } if ($cmd eq 'key' || $cmd eq 'certificate') { push @options_spec, qw( realm=s ); } if ($cmd eq 'key' && $subcmd eq 'generate') { push @options_spec, qw( group=s ); } if ($cmd eq 'key' && $subcmd eq 'import') { push @options_spec, qw( purpose=s id=s file=s ); } if ($cmd eq 'certificate' && $subcmd eq 'import') { push @options_spec, qw( file=s issuer=s issuer-realm=s role=s force-really-self-signed force-issuer-not-found force-certificate-already-exists ); } if ($cmd eq 'certificate' && $subcmd eq 'list') { push @options_spec, qw( all v+ ); } if ($cmd eq 'certificate' && $subcmd eq 'alias') { push @options_spec, qw( alias=s identifier=s force-certificate-not-found ); } if ($cmd eq 'certificate' && $subcmd eq 'remove') { push @options_spec, qw( name=s force-is-issuer ); } if ($cmd eq 'certificate' && $subcmd eq 'chain') { push @options_spec, qw( issuer=s issuer-realm=s name=s force-certificate-not-found force-issuer-certificate-not-found ); } my %params; GetOptions(\%params, @options_spec) or pod2usage(-verbose => 0); my ($vol, $dir, $file) = File::Spec->splitpath($0); if ($cmd eq 'version') { print "OpenXPKI Core Version: $OpenXPKI::VERSION::VERSION\n"; print "$file Version: $VERSION\n"; exit 0; } pod2usage(-exitstatus => 0, -verbose => 2) if ($cmd eq 'man'); pod2usage(-verbose => 99, -sections => 'NAME|USAGE') if ($cmd eq 'help'); if ($cmd eq '') { print STDERR "Usage: $file COMMAND [SUBCOMMAND] [OPTIONS]\n"; print STDERR "Hint: '$file help'\n"; exit 0; } if (defined $params{debug}) { @{$params{debug}} = split(m{,}, join(',', @{$params{debug}})); foreach my $param (@{$params{debug}}) { my ($module, $level) = ($param =~ m{ \A (.*?):?(\d*) \z }xms); if ($level eq '') { $level = 1; } if ($module eq '') { # if modules are not specified, debug everything except for # XML::Cache and XML::Config (if you really want to debug these, # just use --debug .*:) $OpenXPKI::Debug::LEVEL{'.*'} = $level; $OpenXPKI::Debug::LEVEL{'OpenXPKI::XML::Cache'} = 0; $OpenXPKI::Debug::LEVEL{'OpenXPKI::XML::Config'} = 0; } else { print STDERR "Debug level for module '$module': $level\n"; $OpenXPKI::Debug::LEVEL{$module} = $level; } } } require OpenXPKI::FileUtils; require OpenXPKI::Server::Init; require OpenXPKI::XML::Config; require OpenXPKI::Crypto::Secret; require OpenXPKI::DateTime; if ((($cmd eq 'certificate' || $cmd eq 'key') && $subcmd eq '') || (($cmd eq 'certificate' || $cmd eq 'key') && substr($subcmd, 0, 1) eq '-') ) { print STDERR "Usage: openxpkiadm $cmd SUBCOMMAND [OPTIONS]\n"; exit 0; } if (defined $params{cfg}) { $configfile = $params{cfg}; } ########################################################################### #my $cmd = shift; if ($cmd eq 'initdb') { if (! get_config($configfile)) { print STDERR "Could not obtain OpenXPKI instance configuration\n"; exit 1; } if (! initdb( { DRYRUN => $params{dryrun}, FORCE => $params{force}, })) { print STDERR "Could not initialize database.\n"; exit 1; } exit 0; } if ($cmd eq 'deploy') { # get arguments following -- my @metaconf_opts = @ARGV; ### @metaconf_opts # first non-options argument is target prefix my $dir; if ($params{prefix}) { $dir = $params{prefix}; } if (! defined $dir) { $dir = $config{prefix}; } if ($params{templatedir}) { $config{template_prefix} = $params{templatedir}; } my $template = $params{template} || 'default'; if (! deploy( { TEMPLATE_PREFIX => $config{template_prefix}, TEMPLATE => $template, TARGETDIR => File::Spec->rel2abs($dir), METACONF_OPTS => \@metaconf_opts, FORCE => $params{force}, DRYRUN => $params{dryrun}, })) { print STDERR "Could not deploy OpenXPKI instance.\n"; exit 1; } print STDERR "OpenXPKI instance successfully deployed to $dir.\n"; print STDERR "You may now want to run\n\n"; print STDERR "cd $dir\n"; print STDERR "openxpki-configure\n"; exit 0; } if ($cmd eq 'certificate') { if (! get_config($configfile, qw( dbi_backend xml_config crypto_layer ))) { print STDERR "Could not obtain OpenXPKI instance configuration\n"; exit 1; } ## CTX('pki_realm') # FIXME: compare OpenXPKI::Server::Init, we use the first realm if none # is available, this is a completely arbitrary choice! my @realms = list_realms(); my $firstrealm = $realms[0]; my $realm = $params{realm}; if (! defined $realm || $realm eq '') { $realm = $firstrealm; } my $defaulttoken = CTX('crypto_layer')->get_token( TYPE => 'DEFAULT', ID => 'default', PKI_REALM => $realm, ); if (! defined $defaulttoken) { print STDERR "ERROR: Could not get default token for specified realm\n"; exit 1; } my $dbi = CTX('dbi_backend'); if (! defined $dbi) { print STDERR "ERROR: Could not instantiate database backend\n"; exit 1; } $dbi->connect(); if ($subcmd eq 'list') { my @realms; if (defined $params{realm}) { push @realms, $params{realm}; } else { @realms = list_realms(); push @realms, undef; # add the magic empty realm } foreach my $realm (@realms) { if (defined $realm) { print "\nCertificates in $realm:\n"; } else { print "\nCertificates in self-signed pseudo-realm:\n"; } my $certificates; if (defined $params{all}) { $certificates = $dbi->select( TABLE => 'CERTIFICATE', DYNAMIC => { PKI_REALM => $realm, }, ); } else { $certificates = $dbi->select( TABLE => [ 'ALIASES', 'CERTIFICATE' ], COLUMNS => [ 'ALIASES.ALIAS', 'ALIASES.IDENTIFIER', 'CERTIFICATE.SUBJECT', 'CERTIFICATE.ISSUER_DN', 'CERTIFICATE.CERTIFICATE_SERIAL', 'CERTIFICATE.ISSUER_IDENTIFIER', 'CERTIFICATE.DATA', 'CERTIFICATE.EMAIL', 'CERTIFICATE.STATUS', 'CERTIFICATE.ROLE', 'CERTIFICATE.PUBKEY', 'CERTIFICATE.SUBJECT_KEY_IDENTIFIER', 'CERTIFICATE.AUTHORITY_KEY_IDENTIFIER', 'CERTIFICATE.NOTAFTER', 'CERTIFICATE.LOA', 'CERTIFICATE.NOTBEFORE', 'CERTIFICATE.CSR_SERIAL', ], JOIN => [ [ 'IDENTIFIER', 'IDENTIFIER', ], ], DYNAMIC => { 'ALIASES.PKI_REALM' => $realm, }, ); } for (my $i = 0; $i < scalar @{$certificates}; $i++) { my $cert = $certificates->[$i]; my $identifier; if (defined $params{all}) { # look up aliases $identifier = $cert->{IDENTIFIER}; my $status = $cert->{STATUS}; if (defined $status && $status eq 'REVOKED') { print "\n Identifier: " . $cert->{IDENTIFIER} . " (REVOKED)\n"; } else { print "\n Identifier: " . $cert->{IDENTIFIER} . "\n"; } my $aliases = $dbi->select( TABLE => 'ALIASES', DYNAMIC => { IDENTIFIER => $cert->{IDENTIFIER}, }, ); for (my $j = 0; $j < scalar @{$aliases}; $j++) { print " Alias:\n " . $aliases->[$j]->{ALIAS} . " (in realm: " . $aliases->[$j]->{PKI_REALM} . ")\n"; } } else { $identifier = $cert->{'ALIASES.IDENTIFIER'}; my $status = $cert->{'CERTIFICATE.STATUS'}; if (defined $status && $status eq 'REVOKED') { print "\n Identifier: " . $cert->{'ALIASES.IDENTIFIER'} . " (REVOKED)\n"; } else { print "\n Identifier: " . $cert->{'ALIASES.IDENTIFIER'} . "\n"; } print " Alias:\n " . $cert->{'ALIASES.ALIAS'} . "\n"; } my $prefix = ''; if (!defined $params{all}) { $prefix = 'CERTIFICATE.'; } if (defined $params{v} && $params{v} > 0) { # show subject and issuer dn my $subject = $cert->{$prefix . 'SUBJECT'}; my $issuer_dn = $cert->{$prefix . 'ISSUER_DN'}; print " Subject:\n " . $subject . "\n"; print " Issuer DN:\n " . $issuer_dn . "\n"; } if (defined $params{v} && $params{v} > 1) { # show chain my $api = CTX('api'); my $chain = $api->get_chain({ START_IDENTIFIER => $identifier, }); my $chain_str = join (' -> ', @{$chain->{IDENTIFIERS}}); print " Chain: $chain_str "; if ($chain->{COMPLETE} == 1) { print "(complete)\n"; } else { print "(INcomplete!)\n"; } } if (defined $params{v} && $params{v} > 2) { # show database entry my @fields = qw( SUBJECT_KEY_IDENTIFIER AUTHORITY_KEY_IDENTIFIER CERTIFICATE_SERIAL ISSUER_IDENTIFIER EMAIL STATUS ROLE NOTAFTER NOTBEFORE CSR_SERIAL LOA ); if ($params{v} > 3) { push @fields, qw(PUBKEY DATA); } foreach my $field (@fields) { my $value; if (defined $cert->{$prefix . $field}) { $value = $cert->{$prefix . $field}; } else { $value = 'NULL'; } print " $field:\n " . $value . "\n"; } } } } exit 0; } if ($subcmd eq 'remove') { my $name = $params{name}; my $realm = $params{realm}; my $identifier = $name; if (defined $realm) { $identifier = __resolve_alias({ DBI => $dbi, NAME => $name, REALM => $realm, }); } # check if certificate is issuer of something my $children_dbi = $dbi->select( TABLE => 'CERTIFICATE', DYNAMIC => { ISSUER_IDENTIFIER => $identifier, }, ); my @children = @{$children_dbi}; my $is_issuer = 0; if (scalar @children > 0) { $is_issuer = 1; if (scalar @children == 1) { if ($children[0]->{'ISSUER_IDENTIFIER'} eq $identifier) { # only self-signed certificate, delete even though # it formally is the issuer of a certificate in the DB $is_issuer = 0; } } } if ($is_issuer && ! defined $params{'force-is-issuer'}) { print STDERR "ERROR: Certificate not deleted because it is referenced as the issuer of " . scalar @children . " certificate(s) in the database.\n"; exit 1; } my $certificate = $dbi->first( TABLE => 'CERTIFICATE', DYNAMIC => { IDENTIFIER => $identifier, }, ); if (defined $certificate) { $dbi->delete( TABLE => 'CERTIFICATE', DATA => { IDENTIFIER => $identifier, }, ); $dbi->commit(); print "Successfully deleted certificate $name " . "(identifier: $identifier) from database.\n"; exit 0; } else { print STDERR "ERROR: Certificate $name " . "(identifier: $identifier) not found in database.\n"; exit 1; } } if ($subcmd eq 'import') { my $filename = $params{file}; if (! -r $filename) { print STDERR "ERROR: filename '$filename' is not readable\n"; exit 1; } my $FileUtils = OpenXPKI::FileUtils->new(); my $certdata = $FileUtils->read_file($filename); if (! defined $certdata) { print STDERR "ERROR: Could not parse certificate data\n"; exit 1; } if ((! defined $params{issuer} || $params{issuer} eq '') && (defined $params{realm})) { print STDERR "ERROR: You have to specify an issuer (or leave " . "out --realm for self-signed certificates).\n"; exit 1; } my $cert = OpenXPKI::Crypto::X509->new(TOKEN => $defaulttoken, DATA => $certdata); #### cert : Dumper($cert) my $realm; my $issuer_identifier; if (! defined $params{realm} || $params{realm} eq '') { # user wants to import a self-signed # cert, let's check if it really is one ### subject key id : $cert->get_subject_key_id() ### authority key id : $cert->get_authority_key_id() if (defined $cert->get_subject_key_id() && defined $cert->get_authority_key_id() && ref $cert->get_authority_key_id() eq '' # TODO: check if hash && ($cert->get_subject_key_id() ne $cert->get_authority_key_id())) { if (! defined $params{'force-really-self-signed'}) { print STDERR "ERROR: This is not a self-signed " . "certificate, " . "(subject key id and authority key id do not match) " . "please specify --realm if you want to import a " . "normal certificate.\n"; exit 1; } } if ( $cert->{PARSED}->{BODY}->{SUBJECT} ne $cert->{PARSED}->{BODY}->{ISSUER}) { if (! defined $params{'force-really-self-signed'}) { print STDERR "ERROR: This is not a self-signed " . "certificate, " . "(subject and issuer do not match) " . "please specify --realm if you want to import a " . "normal certificate.\n"; exit 1; } } # we are our own issuer $issuer_identifier = $cert->get_identifier(); } else { # we have a "normal" certificate if (defined $params{'issuer-realm'}) { $realm = $params{'issuer-realm'}; } else { $realm = $params{realm}; } # maybe the issuer name is an alias, try to resolve it $issuer_identifier = __resolve_alias({ DBI => $dbi, NAME => $params{issuer}, REALM => $realm, }); ### issuer_identifier : $issuer_identifier # check whether the certificate is in the DB my $issuer = $dbi->first( TABLE => 'CERTIFICATE', DYNAMIC => { IDENTIFIER => $issuer_identifier, }, ); if (! defined $issuer && ! defined $params{'force-issuer-not-found'}) { print STDERR "ERROR: Issuer '$params{issuer}' not found in " . "the database.\n"; exit 1; } } # make sure the self-signed realm is specified as 'undef' if (defined $realm && ($realm eq '')) { $realm = undef; } # compile all relevant data for the database # TODO: use $cert->to_db_hash(); my %insert_hash; $insert_hash{STATUS} = 'ISSUED'; $insert_hash{PKI_REALM} = $realm; $insert_hash{CERTIFICATE_SERIAL} = $cert->get_serial(); $insert_hash{IDENTIFIER} = $cert->get_identifier(); $insert_hash{DATA} = $certdata; $insert_hash{SUBJECT} = $cert->{PARSED}->{BODY}->{SUBJECT}; $insert_hash{ISSUER_DN} = $cert->{PARSED}->{BODY}->{ISSUER}; $insert_hash{ISSUER_IDENTIFIER} = $issuer_identifier; # combine email addresses if (exists $cert->{PARSED}->{BODY}->{EMAILADDRESSES}) { $insert_hash{EMAIL} = ''; foreach my $email (@{$cert->{PARSED}->{BODY}->{EMAILADDRESSES}}) { $insert_hash{EMAIL} .= "," if ($insert_hash{EMAIL} ne ''); $insert_hash{EMAIL} .= $email; } } $insert_hash{PUBKEY} = $cert->{PARSED}->{BODY}->{PUBKEY}; # set subject key id and authority key id, if defined. if (defined $cert->get_subject_key_id()) { $insert_hash{SUBJECT_KEY_IDENTIFIER} = $cert->get_subject_key_id(); } if (defined $cert->get_authority_key_id() && ref $cert->get_authority_key_id() eq '') { # TODO: do we save if authority key id is hash, and if # yes, in which format? $insert_hash{AUTHORITY_KEY_IDENTIFIER} = $cert->get_authority_key_id(); } if (defined $params{role} && $params{role} ne '') { # TODO: check if role is valid (from acl.xml) $insert_hash{ROLE} = $params{role} } $insert_hash{NOTAFTER} = OpenXPKI::DateTime::convert_date({ DATE => $cert->{PARSED}->{BODY}->{NOTAFTER}, OUTFORMAT => 'epoch', }); $insert_hash{NOTBEFORE} = OpenXPKI::DateTime::convert_date({ DATE => $cert->{PARSED}->{BODY}->{NOTBEFORE}, OUTFORMAT => 'epoch', }); # fields which are explicitly NOT set: # LOA (we don't know it) # CSR_SERIAL ( " " ) # check whether there is already a certificate with the given # identifier anywhere my $certificate = $dbi->first( TABLE => 'CERTIFICATE', DYNAMIC => { IDENTIFIER => $insert_hash{IDENTIFIER}, }, ); if (defined $certificate && ! defined $params{'force-certificate-already-exists'}) { if ($certificate->{PKI_REALM} ne '') { print STDERR "ERROR: The same certificate already exists " . "in the $certificate->{PKI_REALM} realm. Use openxpkiadm " . "certificate alias to reference it.\n"; } else { print STDERR "ERROR: The same certificate already exists " . "as a global self-signed certificate. Use openxpkiadm " . "certificate alias to reference it.\n"; } exit 1; } $dbi->insert( TABLE => 'CERTIFICATE', # use hash method HASH => \%insert_hash, ); $dbi->commit(); print "Successfully imported certificate into database:\n"; print " Subject: " . $insert_hash{SUBJECT} . "\n"; print " Issuer: " . $insert_hash{ISSUER_DN} . "\n"; print " Identifier: " . $insert_hash{IDENTIFIER} . "\n"; exit 0; } if ($subcmd eq 'alias') { my %insert_hash = (); $insert_hash{PKI_REALM} = $params{realm}; if (! exists($params{alias}) || $params{alias} eq '') { print STDERR "Please specify an alias with --alias\n"; exit 1; } else { $insert_hash{ALIAS} = $params{alias}; } if (! exists($params{identifier}) || $params{identifier} eq '') { print STDERR "Please specify an identifier with --identifier\n"; exit 1; } else { $insert_hash{IDENTIFIER} = $params{identifier}; } # TODO: check for --realm? # query certificate table to check whether --identifer actually exists my $certificate = $dbi->first( TABLE => 'CERTIFICATE', DYNAMIC => { IDENTIFIER => $insert_hash{IDENTIFIER}, }, ); if (! defined $certificate && ! defined $params{'force-certificate-not-found'}) { # there is no cert with given identifier print STDERR "ERROR: Could not find a certificate with " . "identifier '$insert_hash{IDENTIFIER}', " . "are you sure it is correct?\n"; exit 1; } #### insert_hash : Dumper(\%insert_hash) $dbi->insert( TABLE => 'ALIASES', HASH => \%insert_hash, ); $dbi->commit(); print "Successfully created alias in realm $params{realm}:\n"; print " Alias : $insert_hash{ALIAS}\n"; print " Identifier: $insert_hash{IDENTIFIER}\n"; exit 0; } if ($subcmd eq 'chain') { my $cert_name; my $issuer_name; if (! exists($params{name}) || $params{name} eq '') { print STDERR "Please specify a certificate name with --name\n"; exit 1; } else { $cert_name = $params{name}; } if (! exists($params{issuer}) || $params{issuer} eq '') { print STDERR "Please specify an issuer name with --issuer\n"; exit 1; } else { $issuer_name = $params{issuer}; } # maybe the certificate name is an alias, try to resolve it my $cert_identifier = __resolve_alias({ DBI => $dbi, NAME => $cert_name, REALM => $params{realm}, }); # check whether the certificate is in the DB my $certificate = $dbi->first( TABLE => 'CERTIFICATE', DYNAMIC => { IDENTIFIER => $cert_identifier, PKI_REALM => $params{realm} }, ); if (! defined $certificate && ! defined $params{'force-certificate-not-found'}) { print STDERR "ERROR: Certificate '$cert_name' not found in realm " . "$params{realm}.\n"; exit 1; } my $issuer_identifier; # maybe the issuer name is an alias, try resolve it my $realm; if (defined $params{'issuer-realm'}) { $realm = $params{'issuer-realm'}; } else { $realm = $params{realm}; } $issuer_identifier = __resolve_alias({ DBI => $dbi, NAME => $issuer_name, REALM => $realm, }); # check whether the issuer is in the DB my $issuer = $dbi->first( TABLE => 'CERTIFICATE', DYNAMIC => { IDENTIFIER => $issuer_identifier, }, ); if (! defined $issuer && ! defined $params{'force-issuer-certificate-not-found'}) { print STDERR "ERROR: Issuer certificate '$issuer_name' " . "(identifier: $issuer_identifier) not found in database.\n"; exit 1; } # set the issuer_identifier for the given certificate $dbi->update( TABLE => 'CERTIFICATE', DATA => { ISSUER_IDENTIFIER => $issuer_identifier, }, WHERE => { CERTIFICATE_SERIAL => $certificate->{CERTIFICATE_SERIAL}, IDENTIFIER => $cert_identifier, PKI_REALM => $certificate->{PKI_REALM}, }, ); $dbi->commit(); print "Successfully set $issuer_name (identifier: $issuer_identifier) " . "as issuer of certificate $cert_name (identifier: " . "$cert_identifier).\n"; # TODO: maybe don't warn only, but let the user use --force to # specify that he knows what he is doing ...? if ($issuer->{SUBJECT_KEY_IDENTIFIER} ne $certificate->{AUTHORITY_KEY_IDENTIFIER}) { print STDERR "WARNING: The issuer's subject key identifier " . "extension ($issuer->{SUBJECT_KEY_IDENTIFIER}) does not " . "match the authority key identifier extension contained " . "in the certificate " . "($certificate->{AUTHORITY_KEY_IDENTIFIER}). Are you sure " . "your chain is correct?\n"; } if ($issuer->{SUBJECT} ne $certificate->{ISSUER_DN}) { print STDERR "WARNING: The issuer's subject ($issuer->{SUBJECT}) " . "does not match the issuer DN contained in the certificate " . "($certificate->{ISSUER_DN}). Are you sure your chain is " . "correct?\n"; } exit 0; } print STDERR "Unknown certificate subcommand '$subcmd'.\n"; exit 1; } if ($cmd eq 'key') { if (! get_config($configfile, qw( dbi_backend xml_config crypto_layer))) { print STDERR "Could not obtain OpenXPKI instance configuration\n"; exit 1; } my @pki_realms = list_realms(); if (! defined $params{realm} || ! grep {$params{realm} eq $_} @pki_realms) { print STDERR "Please specify one of the following PKI realms via --realm:\n"; foreach my $realm (@pki_realms) { print " $realm\n"; } exit 1; } ## CTX('pki_realm') my $defaulttoken = CTX('crypto_layer')->get_token( TYPE => 'DEFAULT', ID => 'default', PKI_REALM => $params{realm}, ); if (! defined $defaulttoken) { print STDERR "ERROR: Could not get default token for specified realm\n"; exit 1; } if ($subcmd eq 'list') { my $rc = list_keys( { REALM => $params{realm}, }); exit 0; } if ($subcmd eq 'import') { my $key_id = $params{id}; my $purpose = lc $params{purpose}; my $realm = $params{realm}; my $import_filename = $params{file}; if ($key_id eq '') { print STDERR "Please specify a token ID with --id (see output of key list for a list of possible values).\n"; exit 1; } if ($purpose eq '') { print STDERR "Please specify a purpose (CA, SCEP, ...) with --purpose (see output of key list for a list of possible values).\n"; } my $config = OpenXPKI::XML::Config->new( CONFIG => $configfile, ); my $nr_of_realms = $config->get_xpath_count( XPATH => [ 'pki_realm' ], COUNTER => [ ], ); my $realm_index; for (my $i=0; $i < $nr_of_realms; $i++) { next if ($realm ne $config->get_xpath( XPATH => [ 'pki_realm', 'name' ], COUNTER => [ $i , 0 ])); $realm_index = $i; last; } my $nr_of_purpose_items = $config->get_xpath_count( XPATH => [ 'pki_realm', $purpose ], COUNTER => [ $realm_index ], ); my $purpose_index; for (my $i=0; $i < $nr_of_purpose_items; $i++) { # look for matching id next if ($key_id ne $config->get_xpath( XPATH => [ 'pki_realm' , $purpose, 'token', 'id' ], COUNTER => [ $realm_index, $i , 0 , 0 ], )); $purpose_index = $i; last; } if (! defined($purpose_index)) { print STDERR "Could not find configuration for this ID and purpose.\n"; exit 1; } my @token_path = ( 'pki_realm' , $purpose , 'token' ); my @token_counter = ( $realm_index, $purpose_index, 0 ); my $key_filename; eval { $key_filename = $config->get_xpath( XPATH => [ @token_path , 'key' ], COUNTER => [ @token_counter, 0 ], ); }; if ($EVAL_ERROR) { print STDERR "Could not read key filename from config file, " . "configuration error?\n"; print STDERR "$EVAL_ERROR\n"; exit 1; } if (-s $key_filename) { print STDERR "Key file is non-empty, cowardly refusing to create " . "new key.\n"; exit 1; } my $key; my $fu = OpenXPKI::FileUtils->new(); $key = $fu->read_file($import_filename); if ($key ne '') { my $key_path = $key_filename; $key_path =~ s/(.*)\/.*/$1/; # perl is greedy, so this is the path if (! -d $key_path) { # key path does not yet exist, create it eval { # try to create mkpath($key_path); }; if ($EVAL_ERROR) { print STDERR "Could not create key directory: $key_path"; exit 1; } } $fu->write_file({ FILENAME => $key_filename, CONTENT => $key, }); if (-s $key_filename) { # key file exists and is nonzero print "Key successfully written to $key_filename\n"; } } else { print STDERR "Key import failed.\n"; exit 1; } exit 0; } if ($subcmd eq 'generate') { my $secret_group = $params{group}; if ($secret_group eq '') { print STDERR "Please specify a secret group with --group (see output of key list for a list of possible values).\n"; exit 1; } my $rc = generate_keys({ REALM => $params{realm}, GROUP => $secret_group, }); exit 0; } print STDERR "Unknown key subcommand '$subcmd'.\n"; exit 1; } print STDERR "Unknown command '$cmd'.\n"; exit 1; __END__ =head1 NAME openxpkiadm - tool for management operations of OpenXPKI instances =head1 USAGE openxpkiadm COMMAND [SUBCOMMAND] [OPTIONS] Global options: --config FILE use configuration from FILE Commands: help brief help message man full documentation version print program version and exit deploy Deploy a new OpenXPKI installation initdb Initialize database key Manage keys certificate Manage certificates =head1 ARGUMENTS Available commands: =head2 deploy Command options: --prefix DIR Use specified prefix during deployment --templatedir DIR Use specified directory as base directory for templates --template TEMPLATE Use specified template (defaults to 'default') --force Force operation (may be destructive) Creates a new OpenXPKI server configuration file set below the specified prefix directory (defaults to [% dir.prefix %] if no other directory is specified via --prefix). This command will not overwrite existing configuration files unless --force is specified. If the --templatedir argument is given the specified directory is used as template base directory. If --template is specified, its argument is used instead of 'default' for the source of the templates used. All options following -- are literally passed to openxpki-metaconf during deployment. =head2 initdb Command options: --force Force operation (may be destructive) --dryrun Don't change anything, just print what would be done Initializes the OpenXPKI database schema. Will not destroy existing data unless called with --force. =head2 key Key generation for OpenXPKI Tokens (including issuing CAs and subsystems). Command options: --realm PKI Realm to operate on =head3 key management subcommands =over 8 =item B Shows token key information for the specified realm, including key algorithm, key length and secret splitting information. Lists keys together with a status flag, which can be one of the following: + - key exists and file is non-empty 0 - key exists but file is empty ! - key files does not exist (yet) Example: openxpkiadm key list --realm 'Root CA' =item B Command options: --realm PKI Realm to operate on --group The secret group to generate keys for Generates asymmetric key pairs for the given secret group. The command will use the secret splitting method specified in the token configuration. For valid secret groups, see the output of key list. The command will refuse to overwrite an existing key. If multiple secret password parts are configured for the specified key, the key generation routine will automatically create the configured number of password secret parts. This command only supports key generation in software. For HSM protected keys please refer to the HSM product documentation regarding key generation with the particular product. Example: openxpkiadm key generate --realm 'Root CA' --group default =item B Command options: --realm PKI Realm to operate on --purpose The purpose of the key (e.g. CA, SCEP) --id The name of the configured key --file The file to import the key from This command allows to import a key that has been generated using a different method then using openxpkiadm key generate. It effectively copies the key file to the location given in the config file. The options are the same as in key generate, except for --file, which gives the location of the externally generated key. =back =head2 certificate Starts a certificate management command and allows to list, install, delete and connect certificates for the configured PKI Realms. openxpkiadm certificate =head3 certificate management subcommands =over 8 =item B Subcommand options (optional): --realm PKI realm to operate on --all Show all certificates -v Show subject and issuer DN as well -v -v Show chain as well -v -v -v Show (nearly complete) database entry -v -v -v -v Show pubkey and certificate data, too Lists certificates present in the database for the specified realm. If --all is not specified, only certificates that have an alias defined for them are listed. --all lists all certificates, regardless of whether they have an alias or not. If --realm is left out, the certificates in all realms are listed The number of -v's increases the verbosity (see above for what is listed in which case). =item B Subcommand options: Mandatory: --realm PKI realm to import certificate to --file the PEM file to import from --issuer the issuer alias or identifier Optional: --issuer-realm the realm where the issuer alias is defined --role the role of the certificate owner Force options (use only if you exactly now what you are doing!): --force-really-self-signed The certificate is really self-signed --force-issuer-not-found Don't care that the issuer is not in the database --force-certificate-already-exists Don't care that the certificate is already in database Once again, only use these options if you actually have to (the occasions where this happens should be really, really rare). Adds a certificate to the database. There are two different ways to call it, depending on whether you have a self-signed certificate or not. With a self-signed certificate, the --realm and --issuer options are left out, with a "normal" certificate, they are mandatory. The command outputs the subject's DN and the issuer's DN for you to verify that you imported the correct certificate as well as a unique identifier which can be used to globally reference the certificate (i.e. for configuration or as an issuer). If you don't want to remember the identifier, look into openxpkiadm certificate alias to find out how to create a symbolic name for an identifier. Examples: openxpkiadm certificate import --file cacert.pem Imports a self-signed CA certificate. openxpkiadm certificate import --realm 'Root CA' \ --file subca1.pem --issuer 'Root CA 1' Imports a Sub CA certificate which is signed by Root CA 1. =item B Subcommand options: Mandatory: --name The alias or identifier of the certificate Optional: --realm The PKI realm in which the alias is defined Force options (use only if you now what you are doing!): --force-is-issuer Delete certificate even though it is the issuer of another certificate in the database Removes a certificate from the database. Example: openxpkiadm certificate remove --realm 'Root CA' \ --name 'Root CA 1' =item B Subcommand options: Mandatory: --realm PKI realm to create the alias in --alias The symbolic name for the certificate --identifier The identifier of the certificate Force options (use only if you now what you are doing!): --force-certificate-not-found Ignore that the certificate for which to create an alias was not found in the DB Only use these options if you actually have to (the occasions where this happens should be really, really rare). Using openxpkiadm certificate alias, you can create a symbolic name for a certificate, which is associated with a specific PKI realm. This symbolic name can then be used in some of the openxpkiadm commands as well as in the configuration files. Example: openxpkiadm certificate alias --realm 'Root CA' \ --identifier FpzZptRsa/444Acs/Nrdmo1Fo1s --alias 'root1' =item B Subcommand options: Mandatory: --realm The PKI realm to operate in --name The alias or identifier of the child --issuer The alias or identifier of the parent Optional: --issuer-realm The realm in which the issuer alias is defined Force options (use only if you now what you are doing!): --force-certificate-not-found Ignore that the certificate of the child was not found in the DB --force-issuer-certificate-not-found Ignore that the certificate of the parent was not found in the DB Once again, only use these options if you actually have to (the occasions where this happens should be really, really rare). Specifies subject/issuer relationship in order to set up certificate chains. The certificates to be connected must already be present in the database (see B). As those connections are already set up during --import, this command exists for changing the issuer if you made an error. It also allows to specify an issuer that does not agree with the information contained in the certificate (but outputs a warning) Example: openxpkiadm certificate chain --realm 'Root CA' \ --name 'Subordinate CA 1' --issuer 'root1' =head1 OPTIONS =over 8 =item B<--config FILE> Read configuration file FILE. Uses built-in default if not specified. =item B<--force> Force execution of command. WARNING: This may destroy existing data! =item B<--dryrun> Prints effects of a command without actually modifying anything. =item B<--prefix> Specify deployment prefix for deploy command. =item B<--templatedir> Specify template directory to use for configuration files. =item B<--template> Specify template to use during deployment. =back =head1 DESCRIPTION B is the administrative frontend for controlling the OpenXPKI installation. =over 8 NOTE: This script was customized to the paths specified during installation. You will have to modify this script to reflect any changes to the installation directories. The openxpkiadm script returns a 0 exit value on success, and >0 if an error occurs. =back