RT notifier: parse templates without header correctly
[openxpki.git] / trunk / deployment / bin / openxpkiadm.in
blobdb7e40b1565a66aa2084de63914870a6b7542741
1 #!/usr/bin/env perl
3 # Written by Martin Bartosch and Alexander Klink
4 # for the OpenXPKI project 2006
5 # Copyright (c) 2006 by The OpenXPKI Project
8 our $VERSION = "[% deployment.version %]";
10 use strict;
11 use warnings;
12 use English;
13 use Getopt::Long;
14 use Pod::Usage;
15 use File::Spec;
16 use File::Copy;
17 use File::Path;
18 use IO::Prompt;
20 #use Data::Dumper;
21 #use Smart::Comments;
23 use OpenXPKI::VERSION;
24 use OpenXPKI::Debug;
25 use OpenXPKI::Server::Context qw( CTX );
27 # settings determined by openxpki-metaconf
28 my %config = (
29 prefix => "[% dir.prefix %]",
30 exec_prefix => "[% dir.exec_prefix %]",
31 template_prefix => "[% dir.templatedir %]",
32 sysconfdir => "[% dir.sysconfdir %]",
33 localedir => "[% dir.localedir %]",
34 openxpkiconfdir => "[% dir.openxpkiconfdir %]",
37 my $configfile = "$config{openxpkiconfdir}/config.xml";
39 # read configuration from deployed OpenXPKI instance
40 sub get_config {
41 my $cfgfile = shift;
42 my @additional_tasks = @_;
44 return OpenXPKI::Server::Init::init(
46 CONFIG => $cfgfile,
47 TASKS => [
48 'current_xml_config',
49 'i18n',
50 'dbi_log',
51 'log',
52 'api',
53 @additional_tasks,
55 SILENT => 1,
56 });
59 sub initdb {
60 my $args = shift;
62 no warnings;
63 my $type = $OpenXPKI::Server::Init::current_xml_config->get_xpath (
64 XPATH => [ 'common/database/type' ],
65 COUNTER => [ 0 ]);
66 use warnings;
68 print STDERR "Database type: $type\n";
70 my @databases = qw( log );
72 # SQLite needs special treatment: three databases instead of one must
73 # be initialized
74 if ($type =~ m{ SQLite }xms) {
75 push @databases, 'workflow', 'backend';
78 DB:
79 foreach my $db (@databases) {
80 my $params = {};
81 $params->{PURPOSE} = $db;
83 if (defined $params->{PURPOSE}) {
84 print STDERR "Setting up database '$db'\n";
86 my $dbi = OpenXPKI::Server::Init::get_dbi($params);
88 eval {
89 $dbi->connect()
91 if ($EVAL_ERROR)
93 print STDERR "ERROR: Could not connect to '$db' database ($EVAL_ERROR)\n";
94 return;
97 if ($args->{DRYRUN}) {
98 print $dbi->init_schema(MODE => 'DRYRUN') . "\n";
99 last DB;
100 } else {
101 my %args = ();
102 if ($args->{FORCE}) {
103 $args{MODE} = 'FORCE';
106 #### args for init_schema : Dumper(\%args)
107 eval {
108 $dbi->init_schema(%args);
110 if ($EVAL_ERROR) {
111 print STDERR "ERROR: init_schema on '$db' failed (${EVAL_ERROR})\n";
112 return;
114 print STDERR "Database '$db' initialized.\n";
117 return 1;
121 sub deploy {
122 my $args = shift;
124 my $targetdir = $args->{TARGETDIR};
125 my $template_prefix = $args->{TEMPLATE_PREFIX};
126 my $template = $args->{TEMPLATE};
127 my @metaconf_opts = @{$args->{METACONF_OPTS}};
129 if (! defined $targetdir || ($targetdir eq '')) {
130 print STDERR "No target directory specified.\n";
131 return;
134 if (! (-d $targetdir
135 && -r $targetdir
136 && -x $targetdir
137 && -w $targetdir)) {
138 print STDERR "Directory $targetdir does not exist or is not writable.\n";
139 return;
142 print STDERR "Deploying OpenXPKI configuration file set.\n";
143 print STDERR "Template set: $template\n";
144 print STDERR "Template source directory: $template_prefix\n";
145 print STDERR "Target directory: $targetdir\n";
146 if (scalar @metaconf_opts) {
147 print STDERR "openxpki-metaconf options: " . join(' ', @metaconf_opts) . "\n";
150 if ($args->{DRYRUN}) {
151 return 1;
154 if (! -d $template_prefix) {
155 print STDERR "ERROR: template directory $template_prefix not found\n";
156 return;
159 my $srcfile = File::Spec->catfile($template_prefix,
160 $template,
161 'openxpki.conf');
163 # 2006-06-21 Martin Bartosch:
164 # The deployment procedure now will create a new meta configuration
165 # file from the one that is installed in the template directory.
167 # determine new configuration file directory (below $targetdir)
168 my @cmd;
169 @cmd = (
170 'openxpki-metaconf',
171 '--config', qq( $srcfile ),
172 '--setcfg', "dir.prefix='$targetdir'",
173 '--getcfg', 'dir.openxpkiconfdir',
174 @metaconf_opts,
177 my $cmd = join(' ', @cmd);
178 my $openxpkiconfdir = `$cmd`;
179 chomp($openxpkiconfdir);
181 ### $openxpkiconfdir
182 if (! -d $openxpkiconfdir) {
183 if (! mkpath($openxpkiconfdir, 1, 0750)) {
184 print STDERR "Could not create configuration directory $openxpkiconfdir\n";
185 return;
189 my $dstfile = File::Spec->catfile($openxpkiconfdir, 'openxpki.conf');
191 if (-e $dstfile) {
192 if (! $args->{FORCE}) {
193 print STDERR "ERROR: $dstfile already exists\n";
194 return;
196 move($dstfile, $dstfile . '.last');
199 if (! -e $srcfile) {
200 print STDERR "ERROR: $srcfile not found\n";
201 return;
204 # the new configuration file will reference two types of files/dirs:
205 # - files/dirs that are specific for this particular deployment
206 # (e. g. configuration, log files, server socket...)
207 # - files/dirs that are shared among ALL installed instances
208 # (e. g. locales)
211 ### $config{localedir}
212 @cmd = (
213 'openxpki-metaconf',
214 '--config', qq( $srcfile ),
215 '--writecfg', qq( $dstfile ),
216 '--setcfg', "dir.prefix='$targetdir'",
217 '--setcfg', "dir.localedir='$config{localedir}'",
218 @metaconf_opts,
221 if (system(join(' ', @cmd)) != 0) {
222 print STDERR "ERROR: could not deploy target configuration file $dstfile\n";
223 return;
226 print STDERR "wrote $dstfile\n";
228 return 1;
231 sub list_realms {
232 my @realms;
233 my $config = OpenXPKI::XML::Config->new(
234 CONFIG => $configfile,
236 my $nr_of_realms = $config->get_xpath_count(
237 XPATH => [ 'pki_realm' ],
238 COUNTER => [ ],
241 my $realm_index;
242 for (my $i=0; $i < $nr_of_realms; $i++) {
243 push @realms, $config->get_xpath(
244 XPATH => [ 'pki_realm', 'name' ],
245 COUNTER => [ $i , 0 ],
248 return @realms;
251 sub list_keys {
252 my $arg_ref = shift;
254 my $config = OpenXPKI::XML::Config->new(
255 CONFIG => $configfile,
258 my $nr_of_realms = $config->get_xpath_count(
259 XPATH => [ 'pki_realm' ],
260 COUNTER => [ ],
263 # find the realm index we have to use
264 my $realm_index;
265 for (my $i=0; $i < $nr_of_realms; $i++) {
266 next if ($arg_ref->{REALM} ne $config->get_xpath(
267 XPATH => [ 'pki_realm', 'name' ],
268 COUNTER => [ $i , 0 ]));
269 $realm_index = $i;
270 last;
273 foreach my $type qw( ca scep ) { # iterate over ca and scep entries
274 print uc($type) . " keys:\n";
275 my $type_count = $config->get_xpath_count(
276 XPATH => [ 'pki_realm', $type ],
277 COUNTER => [ $realm_index ],
280 for (my $i = 0; $i < $type_count; $i++) { # iterate over CAs, SCEPs
281 my $type_id = $config->get_xpath(
282 XPATH => [ 'pki_realm' , $type, 'id' ],
283 COUNTER => [ $realm_index, $i , 0 ],
285 print ' Key for purpose ' . uc($type) . ' with ID: '
286 . $type_id . "\n";
288 my $token_count = $config->get_xpath_count(
289 XPATH => [ 'pki_realm' , $type, 'token' ],
290 COUNTER => [ $realm_index, $i ],
293 # base path and counter for later use
294 my @token_path = ( 'pki_realm' , $type, 'token' );
295 my @token_counter = ( $realm_index, $i , 0 );
297 my $key_count = $config->get_xpath_count(
298 XPATH => [ @token_path , 'key' ],
299 COUNTER => [ @token_counter ],
301 my $secret_count = $config->get_xpath_count(
302 XPATH => [ @token_path , 'secret' ],
303 COUNTER => [ @token_counter ],
306 if ($key_count != 1) { # there should only be one key per token!
307 print STDERR " ! Misconfiguration detected: $key_count"
308 . " keys configured!\n";
310 if ($secret_count != 1) { # there should only be one secret
311 # definition per token
312 print STDERR " ! Misconfiguration detected: $secret_count"
313 . " secret definitions configured!\n";
316 if ($key_count == 1 && $secret_count == 1) { # everything is fine
317 my $key = $config->get_xpath(
318 XPATH => [ @token_path , 'key' ],
319 COUNTER => [ @token_counter, 0 ],
321 my $status_flag = '?';
322 if (-e $key && (! -s $key)) {
323 $status_flag = '0'; # file exists but is of size zero
325 elsif (-e $key) { # file exists and is non-zero
326 $status_flag = '+';
328 else { # file does not exist (yet)
329 $status_flag = '!';
331 print ' ' . $status_flag . ' ' . $key . "\n";
333 my $secret_group_name = $config->get_xpath(
334 XPATH => [ @token_path , 'secret' ],
335 COUNTER => [ @token_counter, 0 ],
337 my @secret_path = ( 'pki_realm' , 'common', 'secret' );
338 my @secret_counter = ( $realm_index, 0 , 0 );
339 my $secret_group_count = $config->get_xpath_count(
340 XPATH => [ @secret_path, 'group' ],
341 COUNTER => [ @secret_counter, ],
343 my $group_index;
344 SEARCH_GROUP_ID:
345 for (my $i = 0; $i < $secret_group_count; $i++) {
346 my $group_id = $config->get_xpath(
347 XPATH => [ @secret_path , 'group', 'id' ],
348 COUNTER => [ @secret_counter, $i , 0 ],
350 if ($group_id eq $secret_group_name) {
351 $group_index = $i;
352 last SEARCH_GROUP_ID;
355 if (! defined $group_index) {
356 print STDERR "Could not find configuration for secret group '$secret_group_name'.\n";
357 exit 1;
360 my $secret_method = $config->get_xpath(
361 XPATH => [ @secret_path , 'group' , 'method', 'id' ],
362 COUNTER => [ @secret_counter, $group_index, 0 , 0 ],
364 if ($secret_method ne 'literal') { # all others have total_shares
365 my $quorum_n = $config->get_xpath(
366 XPATH => [ @secret_path , 'group' , 'method', 'total_shares' ],
367 COUNTER => [ @secret_counter, $group_index, 0 , 0 ],
370 my $quorum_k = __get_required_shares({
371 CONFIG => $config,
372 TOKEN_PATH => [ @secret_path, 'group' ],
373 TOKEN_COUNTER => [ @secret_counter, $group_index ],
375 if (! defined $quorum_k) { # if it is not defined, n is
376 # the default value
377 $quorum_k = $quorum_n;
379 $secret_method .= ' (n = ' . $quorum_n . ', k = '
380 . $quorum_k . ')';
382 print ' Secret group: ' . $secret_group_name . "\n";
383 print ' Secret method: ' . $secret_method . "\n";
390 sub generate_keys {
391 my $arg_ref = shift;
393 my $realm = $arg_ref->{REALM};
394 my $secret_group_name = $arg_ref->{GROUP};
396 my $config = OpenXPKI::XML::Config->new(
397 CONFIG => $configfile,
400 my $nr_of_realms = $config->get_xpath_count(
401 XPATH => [ 'pki_realm' ],
402 COUNTER => [ ],
405 # figure out realm index
406 my $realm_index;
407 for (my $i=0; $i < $nr_of_realms; $i++) {
408 next if ($realm ne $config->get_xpath(
409 XPATH => [ 'pki_realm', 'name' ],
410 COUNTER => [ $i , 0 ]));
411 $realm_index = $i;
412 last;
414 # default token which will be used to create the keys
415 my $default_token = CTX('crypto_layer')->get_token(
416 TYPE => 'DEFAULT',
417 PKI_REALM => $realm,
419 # figure out secret group index
420 my @secret_path = ( 'pki_realm' , 'common', 'secret' );
421 my @secret_counter = ( $realm_index, 0 , 0 );
422 my $secret_group_count = $config->get_xpath_count(
423 XPATH => [ @secret_path, 'group' ],
424 COUNTER => [ @secret_counter, ],
426 my $group_index;
427 SEARCH_GROUP_ID:
428 for (my $i = 0; $i < $secret_group_count; $i++) {
429 my $group_id = $config->get_xpath(
430 XPATH => [ @secret_path , 'group', 'id' ],
431 COUNTER => [ @secret_counter, $i , 0 ],
433 if ($group_id eq $secret_group_name) {
434 $group_index = $i;
435 last SEARCH_GROUP_ID;
438 if (! defined $group_index) {
439 print STDERR "Could not find configuration for secret group '$secret_group_name'.\n";
440 exit 1;
442 my $secret_method = $config->get_xpath(
443 XPATH => [ @secret_path , 'group' , 'method', 'id' ],
444 COUNTER => [ @secret_counter, $group_index, 0 , 0 ],
446 my $quorum_n;
447 my $quorum_k;
448 my $display_secret_method = $secret_method;
449 if ($secret_method ne 'literal') { # all others have total_shares
450 $quorum_n = $config->get_xpath(
451 XPATH => [ @secret_path , 'group' , 'method', 'total_shares' ],
452 COUNTER => [ @secret_counter, $group_index, 0 , 0 ]
455 $quorum_k = __get_required_shares({
456 CONFIG => $config,
457 TOKEN_PATH => [ @secret_path, 'group' ],
458 TOKEN_COUNTER => [ @secret_counter, $group_index ],
460 if (! defined $quorum_k) { # if it is not defined, n is
461 # the default value
462 $quorum_k = $quorum_n;
464 $display_secret_method .= ' (n = ' . $quorum_n
465 . ', k = ' . $quorum_k . ')';
467 print "Generating keys for secret group " . $secret_group_name . "\n";
468 print "Secret method is: " . $display_secret_method . "\n\n";
470 my $passwd;
471 if ($secret_method eq 'literal') {
472 # in the literal case, this is just the config entry
473 $passwd = $config->get_xpath(
474 XPATH => [ @secret_path , 'group' , 'method' ],
475 COUNTER => [ @secret_counter, $group_index, 0 ],
478 else {
479 my $crypto_secret;
481 if ($secret_method eq 'plain') {
482 $crypto_secret = OpenXPKI::Crypto::Secret->new({
483 TYPE => 'Plain',
484 PARTS => $quorum_n,
486 for (my $i = 1; $i <= $quorum_n; $i++) {
487 my $secret = __prompt_password("Please enter "
488 . "password share $i/$quorum_n: ");
489 $crypto_secret->set_secret({
490 PART => $i,
491 SECRET => "$secret", # $secret is of type IO::Prompt::Return
493 print "\n";
495 $passwd = $crypto_secret->get_secret();
497 elsif ($secret_method eq 'split') {
498 $crypto_secret = OpenXPKI::Crypto::Secret->new({
499 TYPE => 'Split',
500 QUORUM => {
501 N => $quorum_n,
502 K => $quorum_k,
504 TOKEN => $default_token,
506 my @shares = $crypto_secret->compute(); # TODO: use bitlength
507 # based on chosen algo
508 for (my $i = 0; $i < scalar(@shares); $i++) {
509 my $nr = $i+1;
510 prompt("Please make sure that share holder number $nr is ready"
511 . " to copy the share,\n"
512 . "then press enter to view the share.");
513 for (my $j = 0; $j < 500; $j++) {
514 print "\n"; # pseudo clearscreen
516 print "Please copy the following share:\n";
517 print $shares[$i] . "\n";
518 print "Press enter to continue (next share will not "
519 . "yet be shown).\n";
520 prompt();
521 for (my $j = 0; $j < 500; $j++) {
522 print "\n"; # pseudo clearscreen
525 $passwd = $crypto_secret->get_secret();
528 foreach my $type qw( ca scep ) { # iterate over ca and scep entries
529 my $type_count = $config->get_xpath_count(
530 XPATH => [ 'pki_realm', $type ],
531 COUNTER => [ $realm_index ],
534 KEYS:
535 for (my $i = 0; $i < $type_count; $i++) { # iterate over CAs, SCEPs
536 my $type_id = $config->get_xpath(
537 XPATH => [ 'pki_realm' , $type, 'id' ],
538 COUNTER => [ $realm_index, $i , 0 ],
541 my $token_count = $config->get_xpath_count(
542 XPATH => [ 'pki_realm' , $type, 'token' ],
543 COUNTER => [ $realm_index, $i ],
546 # base path and counter for later use
547 my @token_path = ( 'pki_realm' , $type, 'token' );
548 my @token_counter = ( $realm_index, $i , 0 );
550 my $key_count = $config->get_xpath_count(
551 XPATH => [ @token_path , 'key' ],
552 COUNTER => [ @token_counter ],
554 my $secret_count = $config->get_xpath_count(
555 XPATH => [ @token_path , 'secret' ],
556 COUNTER => [ @token_counter ],
559 if ($key_count != 1) { # there should only be one key per token!
560 print STDERR "Misconfiguration detected: $key_count "
561 . "key files configured for id $type_id, "
562 . "purpose" . uc $type . "!\n";
563 exit 1;
565 if ($secret_count != 1) { # there should only be one secret
566 # definition per token
567 print STDERR "Misconfiguration detected: $secret_count "
568 . "secret groups configured for id $type_id, "
569 . "purpose" . uc $type . "!\n";
570 exit 1;
573 my $curr_secret_group_name = $config->get_xpath(
574 XPATH => [ @token_path , 'secret' ],
575 COUNTER => [ @token_counter, 0 ],
577 next KEYS if ($curr_secret_group_name ne $secret_group_name);
579 my $key_filename = $config->get_xpath(
580 XPATH => [ @token_path , 'key' ],
581 COUNTER => [ @token_counter, 0 ],
583 if (-s $key_filename) {
584 print STDERR "Key for purpose $type with id $type_id "
585 . "already exists in $key_filename, cowardly "
586 . "refusing to overwrite it.\n";
587 next KEYS;
589 my $key;
590 my $fu = OpenXPKI::FileUtils->new();
592 # get possible types and options from token
593 my %command_params = %{$default_token->get_cmd_param('create_key')};
594 print "Choose options for key for purpose '" . uc($type)
595 . "' with id '$type_id'\n";
597 print "Please choose one of the following key types:\n";
598 foreach my $possible_type ( @{$command_params{TYPE}} ) {
599 print " - $possible_type\n";
601 my $type = prompt('Key type: ');
602 my $type_parameters = $command_params{PARAMETERS}->{"TYPE:" . $type};
604 while (! defined $type_parameters) {
605 # no parameter entry in command_params available for the chosen type
606 print "Invalid type chosen, please try again.\n";
607 $type = prompt('Key type: ');
608 $type_parameters = $command_params{PARAMETERS}->{"TYPE:" . $type};
611 my $parameters; # parameter hash used in the token command
612 my %already_configured_parameters;
614 if (defined $type_parameters->{KEY_LENGTH}) {
615 # key length is only possible for some algos
616 print "\nPlease choose one of the following key lengths:\n";
617 my %valid_keylength = ( );
618 foreach my $possible_kl ( @{$type_parameters->{KEY_LENGTH}} ) {
619 print " - $possible_kl\n";
620 $valid_keylength{$possible_kl} = 1;
623 my $key_length = prompt('Key length: ');
624 while (! defined $valid_keylength{$key_length}) {
625 print "Invalid key length chosen, please try again.\n";
626 $key_length = prompt('Key length: ');
628 # implicit cast, $key_length is of type IO::Prompt::ReturnVal!
629 $parameters->{KEY_LENGTH} = "$key_length";
631 $already_configured_parameters{KEY_LENGTH} = 1;
634 if (defined $type_parameters->{ENC_ALG}) { # currently defined for all,
635 # but you never know
636 print "\nPlease choose one of the following key encryption "
637 . "algorithms:\n";
638 my %valid_enc_alg = ( );
639 foreach my $possible_enc_alg ( @{$type_parameters->{ENC_ALG}} ) {
640 if ($possible_enc_alg eq '__undef') {
641 $possible_enc_alg = 'default'; # present __undef as default
643 print " - $possible_enc_alg\n";
644 $valid_enc_alg{$possible_enc_alg} = 1;
646 my $enc_alg = prompt('Encryption algorithm: ');
647 while (! defined $valid_enc_alg{$enc_alg}) {
648 print "Invalid encryption algorithm chosen, please try "
649 . "again.\n";
650 $enc_alg = prompt('Encryption algorithm: ');
652 if ("$enc_alg" ne 'default') { # specific algorithm requested
653 $parameters->{ENC_ALG} = "$enc_alg"; # implicit type cast!
655 $already_configured_parameters{ENC_ALG} = 1;
658 # configure optional parameters
659 foreach my $param (keys %{$type_parameters}) {
660 if (! defined $already_configured_parameters{$param} ) {
661 my $optional = 0;
662 if (ref $type_parameters->{$param} eq '') { # type param is int
663 if ($type_parameters->{$param} == 0) {
664 $optional = 1;
667 elsif (ref $type_parameters->{$param} eq 'ARRAY') {
668 $optional = __string_is_in_array({
669 STRING => '__undef',
670 ARRAY => $type_parameters->{$param},
673 my $want_config = 0;
674 if ($optional == 1) {
675 my $answer = prompt "Do you want to configure the optional"
676 . " parameter $param (y/n)? ", '-y';
677 if ($answer =~ /^[yY]$/) {
678 $want_config = 1;
681 if ($optional != 1 || $want_config == 1) {
682 if (ref $type_parameters->{$param} eq 'ARRAY') {
683 print "Please choose one of the following values "
684 . "for $param: \n";
685 foreach my $elem (@{$type_parameters->{$param}}) {
686 print " - $elem\n";
689 my $param_value = prompt("Value for $param: ");
690 if (ref $type_parameters->{$param} eq 'ARRAY') {
691 # check validity
692 while (! __string_is_in_array({
693 STRING => $param_value,
694 ARRAY => $type_parameters->{$param},
695 })) {
696 print "Invalid value, please try again.\n";
697 $param_value = prompt("Value for $param: ");
700 $parameters->{$param} = "$param_value"; # implicit cast!
705 print "Creating key, please be patient ...\n";
707 $key = $default_token->command({
708 COMMAND => 'create_key',
709 TYPE => "$type",
710 PASSWD => $passwd,
711 PARAMETERS => $parameters,
713 if ($key ne '') {
714 my $key_path = $key_filename;
715 $key_path =~ s/(.*)\/.*/$1/; # perl is greedy, so this is the path
716 if (! -d $key_path) { # key path does not yet exist, create it
717 eval { # try to create
718 mkpath($key_path);
720 if ($EVAL_ERROR) {
721 print STDERR "Could not create key directory: $key_path";
722 exit 1;
725 $fu->write_file({
726 FILENAME => $key_filename,
727 CONTENT => $key,
729 if (-s $key_filename) { # key file exists and is nonzero
730 print "Key successfully written to $key_filename\n\n\n";
732 else {
733 print STDERR "Key creation failed!\n";
734 exit 1;
737 else {
738 print STDERR "Key creation failed!\n";
739 exit 1;
746 sub __get_required_shares {
747 # returns required_shares from config file or undefined if
748 # required_shares is not defined in the configuration
749 my $arg_ref = shift;
750 my $config = $arg_ref->{CONFIG};
751 my @token_path = @{$arg_ref->{TOKEN_PATH}};
752 my @token_counter = @{$arg_ref->{TOKEN_COUNTER}};
754 my $quorum_k_count;
755 my $quorum_k;
756 eval { # This crashes when k is not present!
757 push @token_path , ( 'method', 'required_shares' );
758 push @token_counter, ( 0 , 0 );
759 $quorum_k_count = $config->get_xpath_count(
760 XPATH => [ @token_path ],
761 COUNTER => [ @token_counter ],
764 if ($EVAL_ERROR) { # k is not present, noticed the hard way
765 $quorum_k_count = 0;
766 $quorum_k = undef;
768 if ($quorum_k_count != 0) { # 'k' configured,
769 push @token_counter, (0);
770 $quorum_k = $config->get_xpath(
771 XPATH => [ @token_path ],
772 COUNTER => [ @token_counter ],
775 return $quorum_k;
778 sub __prompt_password {
779 # prompt for password until verification password and password match
780 my $question = shift;
782 my $passwords_match = 0;
783 my $password1;
784 while (! $passwords_match) {
785 $password1 = prompt($question, -echo => '');
786 print "Please enter the same password share again to make sure it was typed correctly.\n";
787 my $password2 = prompt($question, -echo => '');
788 if ($password1 eq $password2) {
789 $passwords_match = 1;
791 if (! $passwords_match) {
792 print "Passwords do not match, please try again!\n\n";
795 return $password1;
798 sub __string_is_in_array {
799 my $arg_ref = shift;
801 my $entry = $arg_ref->{STRING};
802 my @array = @{$arg_ref->{ARRAY}};
804 foreach my $elem (@array) {
805 if ($entry eq $elem) {
806 return 1;
809 return 0;
812 sub __resolve_alias {
813 my $arg_ref = shift;
814 my $dbi = $arg_ref->{DBI};
815 my $name = $arg_ref->{NAME};
816 my $realm = $arg_ref->{REALM};
818 my $alias = $dbi->first(
819 TABLE => 'ALIASES',
820 DYNAMIC => {
821 ALIAS => $name,
822 PKI_REALM => $realm,
825 if (defined $alias) {
826 return $alias->{IDENTIFIER};
828 else {
829 return $name;
833 ###########################################################################
835 my @options_spec = qw(
836 cfg|cfgfile|config|config=s
837 debug=s@
838 ); # config is the only global option
840 my $cmd = shift @ARGV || '';
841 my $subcmd;
842 if ($cmd eq 'certificate' || $cmd eq 'key') { # those have subcommands
843 $subcmd = shift @ARGV || '';
846 # set command/subcommand specific allowed options
847 if ($cmd eq 'initdb') {
848 push @options_spec, qw(
849 force
850 dryrun
854 if ($cmd eq 'deploy') {
855 push @options_spec, qw(
856 prefix=s
857 templatedir=s
858 template=s
859 force
863 if ($cmd eq 'key' || $cmd eq 'certificate') {
864 push @options_spec, qw(
865 realm=s
869 if ($cmd eq 'key' && $subcmd eq 'generate') {
870 push @options_spec, qw(
871 group=s
875 if ($cmd eq 'key' && $subcmd eq 'import') {
876 push @options_spec, qw(
877 purpose=s
878 id=s
879 file=s
883 if ($cmd eq 'key' && $subcmd eq 'use') {
884 push @options_spec, qw(
885 purpose=s
886 id=s
887 command=s
890 if ($cmd eq 'certificate' && $subcmd eq 'import') {
891 push @options_spec, qw(
892 file=s
893 issuer=s
894 issuer-realm=s
895 role=s
896 force-really-self-signed
897 force-issuer-not-found
898 force-certificate-already-exists
902 if ($cmd eq 'certificate' && $subcmd eq 'list') {
903 push @options_spec, qw(
909 if ($cmd eq 'certificate' && $subcmd eq 'alias') {
910 push @options_spec, qw(
911 alias=s
912 identifier=s
913 force-certificate-not-found
917 if ($cmd eq 'certificate' && $subcmd eq 'remove') {
918 push @options_spec, qw(
919 name=s
920 force-is-issuer
924 if ($cmd eq 'certificate' && $subcmd eq 'chain') {
925 push @options_spec, qw(
926 issuer=s
927 issuer-realm=s
928 name=s
929 force-certificate-not-found
930 force-issuer-certificate-not-found
935 my %params;
936 GetOptions(\%params, @options_spec) or pod2usage(-verbose => 0);
938 my ($vol, $dir, $file) = File::Spec->splitpath($0);
939 if ($cmd eq 'version') {
940 print "OpenXPKI Core Version: $OpenXPKI::VERSION::VERSION\n";
941 print "$file Version: $VERSION\n";
942 exit 0;
945 pod2usage(-exitstatus => 0, -verbose => 2) if ($cmd eq 'man');
946 pod2usage(-verbose => 99, -sections => 'NAME|USAGE') if ($cmd eq 'help');
947 if ($cmd eq '') {
948 print STDERR "Usage: $file COMMAND [SUBCOMMAND] [OPTIONS]\n";
949 print STDERR "Hint: '$file help'\n";
950 exit 0;
953 if (defined $params{debug}) {
954 @{$params{debug}} = split(m{,}, join(',', @{$params{debug}}));
956 foreach my $param (@{$params{debug}}) {
957 my ($module, $level) = ($param =~ m{ \A (.*?):?(\d*) \z }xms);
958 if ($level eq '') {
959 $level = 1;
961 if ($module eq '') {
962 # if modules are not specified, debug everything except for
963 # XML::Cache and XML::Config (if you really want to debug these,
964 # just use --debug .*:<debug level>)
965 $OpenXPKI::Debug::LEVEL{'.*'} = $level;
966 $OpenXPKI::Debug::LEVEL{'OpenXPKI::XML::Cache'} = 0;
967 $OpenXPKI::Debug::LEVEL{'OpenXPKI::XML::Config'} = 0;
969 else {
970 print STDERR "Debug level for module '$module': $level\n";
971 $OpenXPKI::Debug::LEVEL{$module} = $level;
976 require OpenXPKI::FileUtils;
977 require OpenXPKI::Server::Init;
978 require OpenXPKI::XML::Config;
979 require OpenXPKI::Crypto::Secret;
980 require OpenXPKI::DateTime;
983 if ((($cmd eq 'certificate' || $cmd eq 'key') && $subcmd eq '') ||
984 (($cmd eq 'certificate' || $cmd eq 'key') && substr($subcmd, 0, 1) eq '-')
986 print STDERR "Usage: openxpkiadm $cmd SUBCOMMAND [OPTIONS]\n";
987 exit 0;
990 if (defined $params{cfg}) {
991 $configfile = $params{cfg};
995 ###########################################################################
997 #my $cmd = shift;
999 if ($cmd eq 'initdb') {
1000 if (! get_config($configfile)) {
1001 print STDERR "Could not obtain OpenXPKI instance configuration\n";
1002 exit 1;
1004 if (! initdb(
1006 DRYRUN => $params{dryrun},
1007 FORCE => $params{force},
1008 })) {
1009 print STDERR "Could not initialize database.\n";
1010 exit 1;
1012 exit 0;
1015 if ($cmd eq 'deploy') {
1016 # get arguments following --
1017 my @metaconf_opts = @ARGV;
1019 ### @metaconf_opts
1021 # first non-options argument is target prefix
1022 my $dir;
1023 if ($params{prefix}) {
1024 $dir = $params{prefix};
1027 if (! defined $dir) {
1028 $dir = $config{prefix};
1031 if ($params{templatedir}) {
1032 $config{template_prefix} = $params{templatedir};
1035 my $template = $params{template} || 'default';
1037 if (! deploy(
1039 TEMPLATE_PREFIX => $config{template_prefix},
1040 TEMPLATE => $template,
1041 TARGETDIR => File::Spec->rel2abs($dir),
1042 METACONF_OPTS => \@metaconf_opts,
1043 FORCE => $params{force},
1044 DRYRUN => $params{dryrun},
1045 })) {
1046 print STDERR "Could not deploy OpenXPKI instance.\n";
1047 exit 1;
1050 print STDERR "OpenXPKI instance successfully deployed to $dir.\n";
1051 print STDERR "You may now want to run\n\n";
1052 print STDERR "cd $dir\n";
1053 print STDERR "openxpki-configure\n";
1055 exit 0;
1058 if ($cmd eq 'certificate') {
1059 if (! get_config($configfile, qw( dbi_backend xml_config crypto_layer ))) {
1060 print STDERR "Could not obtain OpenXPKI instance configuration\n";
1061 exit 1;
1064 ## CTX('pki_realm')
1066 # FIXME: compare OpenXPKI::Server::Init, we use the first realm if none
1067 # is available, this is a completely arbitrary choice!
1068 my @realms = list_realms();
1069 my $firstrealm = $realms[0];
1071 my $realm = $params{realm};
1072 if (! defined $realm || $realm eq '') {
1073 $realm = $firstrealm;
1075 my $defaulttoken = CTX('crypto_layer')->get_token(
1076 TYPE => 'DEFAULT',
1077 ID => 'default',
1078 PKI_REALM => $realm,
1081 if (! defined $defaulttoken) {
1082 print STDERR "ERROR: Could not get default token for specified realm\n";
1083 exit 1;
1086 my $dbi = CTX('dbi_backend');
1087 if (! defined $dbi) {
1088 print STDERR "ERROR: Could not instantiate database backend\n";
1089 exit 1;
1091 $dbi->connect();
1093 if ($subcmd eq 'list') {
1094 my @realms;
1095 if (defined $params{realm}) {
1096 push @realms, $params{realm};
1098 else {
1099 @realms = list_realms();
1100 push @realms, undef; # add the magic empty realm
1103 foreach my $realm (@realms) {
1104 if (defined $realm) {
1105 print "\nCertificates in $realm:\n";
1107 else {
1108 print "\nCertificates in self-signed pseudo-realm:\n";
1110 my $certificates;
1111 if (defined $params{all}) {
1112 $certificates = $dbi->select(
1113 TABLE => 'CERTIFICATE',
1114 DYNAMIC => {
1115 PKI_REALM => $realm,
1119 else {
1120 $certificates = $dbi->select(
1121 TABLE => [ 'ALIASES', 'CERTIFICATE' ],
1122 COLUMNS => [ 'ALIASES.ALIAS',
1123 'ALIASES.IDENTIFIER',
1124 'CERTIFICATE.SUBJECT',
1125 'CERTIFICATE.ISSUER_DN',
1126 'CERTIFICATE.CERTIFICATE_SERIAL',
1127 'CERTIFICATE.ISSUER_IDENTIFIER',
1128 'CERTIFICATE.DATA',
1129 'CERTIFICATE.EMAIL',
1130 'CERTIFICATE.STATUS',
1131 'CERTIFICATE.ROLE',
1132 'CERTIFICATE.PUBKEY',
1133 'CERTIFICATE.SUBJECT_KEY_IDENTIFIER',
1134 'CERTIFICATE.AUTHORITY_KEY_IDENTIFIER',
1135 'CERTIFICATE.NOTAFTER',
1136 'CERTIFICATE.LOA',
1137 'CERTIFICATE.NOTBEFORE',
1138 'CERTIFICATE.CSR_SERIAL',
1140 JOIN => [
1141 [ 'IDENTIFIER',
1142 'IDENTIFIER',
1145 DYNAMIC => {
1146 'ALIASES.PKI_REALM' => $realm,
1150 for (my $i = 0; $i < scalar @{$certificates}; $i++) {
1151 my $cert = $certificates->[$i];
1152 my $identifier;
1153 if (defined $params{all}) { # look up aliases
1154 $identifier = $cert->{IDENTIFIER};
1155 my $status = $cert->{STATUS};
1156 if (defined $status && $status eq 'REVOKED') {
1157 print "\n Identifier: " . $cert->{IDENTIFIER}
1158 . " (REVOKED)\n";
1160 else {
1161 print "\n Identifier: " . $cert->{IDENTIFIER} . "\n";
1163 my $aliases = $dbi->select(
1164 TABLE => 'ALIASES',
1165 DYNAMIC => {
1166 IDENTIFIER => $cert->{IDENTIFIER},
1169 for (my $j = 0; $j < scalar @{$aliases}; $j++) {
1170 print " Alias:\n "
1171 . $aliases->[$j]->{ALIAS}
1172 . " (in realm: " . $aliases->[$j]->{PKI_REALM}
1173 . ")\n";
1176 else {
1177 $identifier = $cert->{'ALIASES.IDENTIFIER'};
1178 my $status = $cert->{'CERTIFICATE.STATUS'};
1179 if (defined $status && $status eq 'REVOKED') {
1180 print "\n Identifier: "
1181 . $cert->{'ALIASES.IDENTIFIER'} . " (REVOKED)\n";
1183 else {
1184 print "\n Identifier: " . $cert->{'ALIASES.IDENTIFIER'}
1185 . "\n";
1187 print " Alias:\n "
1188 . $cert->{'ALIASES.ALIAS'} . "\n";
1190 my $prefix = '';
1191 if (!defined $params{all}) {
1192 $prefix = 'CERTIFICATE.';
1194 if (defined $params{v} && $params{v} > 0) {
1195 # show subject and issuer dn
1196 my $subject = $cert->{$prefix . 'SUBJECT'};
1197 my $issuer_dn = $cert->{$prefix . 'ISSUER_DN'};
1198 print " Subject:\n " . $subject . "\n";
1199 print " Issuer DN:\n " . $issuer_dn . "\n";
1201 if (defined $params{v} && $params{v} > 1) {
1202 # show chain
1203 my $api = CTX('api');
1204 my $chain = $api->get_chain({
1205 START_IDENTIFIER => $identifier,
1207 my $chain_str
1208 = join (' -> ', @{$chain->{IDENTIFIERS}});
1210 print " Chain: $chain_str ";
1211 if ($chain->{COMPLETE} == 1) {
1212 print "(complete)\n";
1214 else {
1215 print "(INcomplete!)\n";
1218 if (defined $params{v} && $params{v} > 2) {
1219 # show database entry
1220 my @fields = qw(
1221 SUBJECT_KEY_IDENTIFIER
1222 AUTHORITY_KEY_IDENTIFIER
1223 CERTIFICATE_SERIAL
1224 ISSUER_IDENTIFIER
1225 EMAIL
1226 STATUS
1227 ROLE
1228 NOTAFTER
1229 NOTBEFORE
1230 CSR_SERIAL
1233 if ($params{v} > 3) {
1234 push @fields, qw(PUBKEY DATA);
1237 foreach my $field (@fields) {
1238 my $value;
1239 if (defined $cert->{$prefix . $field}) {
1240 $value = $cert->{$prefix . $field};
1242 else {
1243 $value = 'NULL';
1245 print " $field:\n "
1246 . $value . "\n";
1251 exit 0;
1254 if ($subcmd eq 'remove') {
1255 my $name = $params{name};
1256 my $realm = $params{realm};
1258 my $identifier = $name;
1259 if (defined $realm) {
1260 $identifier = __resolve_alias({
1261 DBI => $dbi,
1262 NAME => $name,
1263 REALM => $realm,
1266 # check if certificate is issuer of something
1267 my $children_dbi = $dbi->select(
1268 TABLE => 'CERTIFICATE',
1269 DYNAMIC => {
1270 ISSUER_IDENTIFIER => $identifier,
1273 my @children = @{$children_dbi};
1275 my $is_issuer = 0;
1276 if (scalar @children > 0) {
1277 $is_issuer = 1;
1278 if (scalar @children == 1) {
1279 if ($children[0]->{'ISSUER_IDENTIFIER'} eq $identifier) {
1280 # only self-signed certificate, delete even though
1281 # it formally is the issuer of a certificate in the DB
1282 $is_issuer = 0;
1287 if ($is_issuer && ! defined $params{'force-is-issuer'}) {
1288 print STDERR "ERROR: Certificate not deleted because it is referenced as the issuer of " . scalar @children . " certificate(s) in the database.\n";
1289 exit 1;
1291 my $certificate = $dbi->first(
1292 TABLE => 'CERTIFICATE',
1293 DYNAMIC => {
1294 IDENTIFIER => $identifier,
1297 if (defined $certificate) {
1298 $dbi->delete(
1299 TABLE => 'CERTIFICATE',
1300 DATA => {
1301 IDENTIFIER => $identifier,
1304 $dbi->commit();
1305 print "Successfully deleted certificate $name "
1306 . "(identifier: $identifier) from database.\n";
1307 exit 0;
1309 else {
1310 print STDERR "ERROR: Certificate $name "
1311 . "(identifier: $identifier) not found in database.\n";
1312 exit 1;
1316 if ($subcmd eq 'import') {
1317 my $filename = $params{file};
1319 if (! -r $filename) {
1320 print STDERR "ERROR: filename '$filename' is not readable\n";
1321 exit 1;
1324 my $FileUtils = OpenXPKI::FileUtils->new();
1325 my $certdata = $FileUtils->read_file($filename);
1327 if (! defined $certdata) {
1328 print STDERR "ERROR: Could not parse certificate data\n";
1329 exit 1;
1332 if ((! defined $params{issuer} || $params{issuer} eq '')
1333 && (defined $params{realm})) {
1334 print STDERR "ERROR: You have to specify an issuer (or leave "
1335 . "out --realm for self-signed certificates).\n";
1336 exit 1;
1339 my ($extracted_certdata) = $certdata =~ m{ \A .* (-----BEGIN\ CERTIFICATE----- .* -----END\ CERTIFICATE-----) .* \z}xms;
1341 my $cert = OpenXPKI::Crypto::X509->new(
1342 TOKEN => $defaulttoken,
1343 DATA => $extracted_certdata,
1345 #### cert : Dumper($cert)
1347 my $realm;
1348 my $issuer_identifier;
1349 if (! defined $params{realm} || $params{realm} eq '') {
1350 # user wants to import a self-signed
1351 # cert, let's check if it really is one
1352 ### subject key id : $cert->get_subject_key_id()
1353 ### authority key id : $cert->get_authority_key_id()
1354 if (defined $cert->get_subject_key_id()
1355 && defined $cert->get_authority_key_id()
1356 && ref $cert->get_authority_key_id() eq '' # TODO: check if hash
1357 && ($cert->get_subject_key_id()
1358 ne $cert->get_authority_key_id())) {
1359 if (! defined $params{'force-really-self-signed'}) {
1360 print STDERR "ERROR: This is not a self-signed "
1361 . "certificate, "
1362 . "(subject key id and authority key id do not match) "
1363 . "please specify --realm if you want to import a "
1364 . "normal certificate.\n";
1365 exit 1;
1368 if ( $cert->{PARSED}->{BODY}->{SUBJECT}
1369 ne $cert->{PARSED}->{BODY}->{ISSUER}) {
1370 if (! defined $params{'force-really-self-signed'}) {
1371 print STDERR "ERROR: This is not a self-signed "
1372 . "certificate, "
1373 . "(subject and issuer do not match) "
1374 . "please specify --realm if you want to import a "
1375 . "normal certificate.\n";
1376 exit 1;
1379 # we are our own issuer
1380 $issuer_identifier = $cert->get_identifier();
1382 else { # we have a "normal" certificate
1383 if (defined $params{'issuer-realm'}) {
1384 $realm = $params{'issuer-realm'};
1386 else {
1387 $realm = $params{realm};
1389 # maybe the issuer name is an alias, try to resolve it
1390 $issuer_identifier = __resolve_alias({
1391 DBI => $dbi,
1392 NAME => $params{issuer},
1393 REALM => $realm,
1395 ### issuer_identifier : $issuer_identifier
1396 # check whether the certificate is in the DB
1397 my $issuer = $dbi->first(
1398 TABLE => 'CERTIFICATE',
1399 DYNAMIC => {
1400 IDENTIFIER => $issuer_identifier,
1403 if (! defined $issuer &&
1404 ! defined $params{'force-issuer-not-found'}) {
1405 print STDERR "ERROR: Issuer '$params{issuer}' not found in "
1406 . "the database.\n";
1407 exit 1;
1411 # make sure the self-signed realm is specified as 'undef'
1412 if (defined $realm && ($realm eq '')) {
1413 $realm = undef;
1416 # compile all relevant data for the database
1417 # TODO: use $cert->to_db_hash();
1418 my %insert_hash;
1419 $insert_hash{STATUS} = 'ISSUED';
1420 $insert_hash{PKI_REALM} = $realm;
1421 $insert_hash{CERTIFICATE_SERIAL} = $cert->get_serial();
1422 $insert_hash{IDENTIFIER} = $cert->get_identifier();
1423 $insert_hash{DATA} = $extracted_certdata;
1424 $insert_hash{SUBJECT} = $cert->{PARSED}->{BODY}->{SUBJECT};
1425 $insert_hash{ISSUER_DN} = $cert->{PARSED}->{BODY}->{ISSUER};
1426 $insert_hash{ISSUER_IDENTIFIER} = $issuer_identifier;
1427 # combine email addresses
1428 if (exists $cert->{PARSED}->{BODY}->{EMAILADDRESSES}) {
1429 $insert_hash{EMAIL} = '';
1430 foreach my $email (@{$cert->{PARSED}->{BODY}->{EMAILADDRESSES}}) {
1431 $insert_hash{EMAIL} .= "," if ($insert_hash{EMAIL} ne '');
1432 $insert_hash{EMAIL} .= $email;
1435 $insert_hash{PUBKEY} = $cert->{PARSED}->{BODY}->{PUBKEY};
1436 # set subject key id and authority key id, if defined.
1437 if (defined $cert->get_subject_key_id()) {
1438 $insert_hash{SUBJECT_KEY_IDENTIFIER}
1439 = $cert->get_subject_key_id();
1441 if (defined $cert->get_authority_key_id() &&
1442 ref $cert->get_authority_key_id() eq '') {
1443 # TODO: do we save if authority key id is hash, and if
1444 # yes, in which format?
1445 $insert_hash{AUTHORITY_KEY_IDENTIFIER}
1446 = $cert->get_authority_key_id();
1448 if (defined $params{role} && $params{role} ne '') {
1449 # TODO: check if role is valid (from acl.xml)
1450 $insert_hash{ROLE} = $params{role}
1453 $insert_hash{NOTAFTER}
1454 = OpenXPKI::DateTime::convert_date({
1455 DATE => $cert->{PARSED}->{BODY}->{NOTAFTER},
1456 OUTFORMAT => 'epoch',
1458 $insert_hash{NOTBEFORE}
1459 = OpenXPKI::DateTime::convert_date({
1460 DATE => $cert->{PARSED}->{BODY}->{NOTBEFORE},
1461 OUTFORMAT => 'epoch',
1463 # fields which are explicitly NOT set:
1464 # LOA (we don't know it)
1465 # CSR_SERIAL ( " " )
1467 # check whether there is already a certificate with the given
1468 # identifier anywhere
1469 my $certificate = $dbi->first(
1470 TABLE => 'CERTIFICATE',
1471 DYNAMIC => {
1472 IDENTIFIER => $insert_hash{IDENTIFIER},
1475 if (defined $certificate &&
1476 ! defined $params{'force-certificate-already-exists'}) {
1477 if ($certificate->{PKI_REALM} ne '') {
1478 print STDERR "ERROR: The same certificate already exists "
1479 . "in the $certificate->{PKI_REALM} realm. Use openxpkiadm "
1480 . "certificate alias to reference it.\n";
1482 else {
1483 print STDERR "ERROR: The same certificate already exists "
1484 . "as a global self-signed certificate. Use openxpkiadm "
1485 . "certificate alias to reference it.\n";
1487 exit 1;
1489 $dbi->insert(
1490 TABLE => 'CERTIFICATE', # use hash method
1491 HASH => \%insert_hash,
1493 $dbi->commit();
1495 print "Successfully imported certificate into database:\n";
1496 print " Subject: " . $insert_hash{SUBJECT} . "\n";
1497 print " Issuer: " . $insert_hash{ISSUER_DN} . "\n";
1498 print " Identifier: " . $insert_hash{IDENTIFIER} . "\n";
1499 exit 0;
1502 if ($subcmd eq 'alias') {
1503 my %insert_hash = ();
1505 $insert_hash{PKI_REALM} = $params{realm};
1507 if (! exists($params{alias}) || $params{alias} eq '') {
1508 print STDERR "Please specify an alias with --alias\n";
1509 exit 1;
1511 else {
1512 $insert_hash{ALIAS} = $params{alias};
1514 if (! exists($params{identifier}) || $params{identifier} eq '') {
1515 print STDERR "Please specify an identifier with --identifier\n";
1516 exit 1;
1518 else {
1519 $insert_hash{IDENTIFIER} = $params{identifier};
1521 # TODO: check for --realm?
1523 # query certificate table to check whether --identifer actually exists
1524 my $certificate = $dbi->first(
1525 TABLE => 'CERTIFICATE',
1526 DYNAMIC => {
1527 IDENTIFIER => $insert_hash{IDENTIFIER},
1531 if (! defined $certificate &&
1532 ! defined $params{'force-certificate-not-found'}) { # there is no cert with given identifier
1533 print STDERR "ERROR: Could not find a certificate with "
1534 . "identifier '$insert_hash{IDENTIFIER}', "
1535 . "are you sure it is correct?\n";
1536 exit 1;
1538 #### insert_hash : Dumper(\%insert_hash)
1539 $dbi->insert(
1540 TABLE => 'ALIASES',
1541 HASH => \%insert_hash,
1543 $dbi->commit();
1544 print "Successfully created alias in realm $params{realm}:\n";
1545 print " Alias : $insert_hash{ALIAS}\n";
1546 print " Identifier: $insert_hash{IDENTIFIER}\n";
1547 exit 0;
1550 if ($subcmd eq 'chain') {
1551 my $cert_name;
1552 my $issuer_name;
1553 if (! exists($params{name}) || $params{name} eq '') {
1554 print STDERR "Please specify a certificate name with --name\n";
1555 exit 1;
1557 else {
1558 $cert_name = $params{name};
1560 if (! exists($params{issuer}) || $params{issuer} eq '') {
1561 print STDERR "Please specify an issuer name with --issuer\n";
1562 exit 1;
1564 else {
1565 $issuer_name = $params{issuer};
1568 # maybe the certificate name is an alias, try to resolve it
1569 my $cert_identifier = __resolve_alias({
1570 DBI => $dbi,
1571 NAME => $cert_name,
1572 REALM => $params{realm},
1574 # check whether the certificate is in the DB
1575 my $certificate = $dbi->first(
1576 TABLE => 'CERTIFICATE',
1577 DYNAMIC => {
1578 IDENTIFIER => $cert_identifier,
1579 PKI_REALM => $params{realm}
1582 if (! defined $certificate &&
1583 ! defined $params{'force-certificate-not-found'}) {
1584 print STDERR "ERROR: Certificate '$cert_name' not found in realm "
1585 . "$params{realm}.\n";
1586 exit 1;
1589 my $issuer_identifier;
1590 # maybe the issuer name is an alias, try resolve it
1591 my $realm;
1592 if (defined $params{'issuer-realm'}) {
1593 $realm = $params{'issuer-realm'};
1595 else {
1596 $realm = $params{realm};
1598 $issuer_identifier = __resolve_alias({
1599 DBI => $dbi,
1600 NAME => $issuer_name,
1601 REALM => $realm,
1603 # check whether the issuer is in the DB
1604 my $issuer = $dbi->first(
1605 TABLE => 'CERTIFICATE',
1606 DYNAMIC => {
1607 IDENTIFIER => $issuer_identifier,
1610 if (! defined $issuer &&
1611 ! defined $params{'force-issuer-certificate-not-found'}) {
1612 print STDERR "ERROR: Issuer certificate '$issuer_name' "
1613 . "(identifier: $issuer_identifier) not found in database.\n";
1614 exit 1;
1617 # set the issuer_identifier for the given certificate
1618 $dbi->update(
1619 TABLE => 'CERTIFICATE',
1620 DATA => {
1621 ISSUER_IDENTIFIER => $issuer_identifier,
1623 WHERE => {
1624 CERTIFICATE_SERIAL => $certificate->{CERTIFICATE_SERIAL},
1625 IDENTIFIER => $cert_identifier,
1626 PKI_REALM => $certificate->{PKI_REALM},
1629 $dbi->commit();
1630 print "Successfully set $issuer_name (identifier: $issuer_identifier) "
1631 . "as issuer of certificate $cert_name (identifier: "
1632 . "$cert_identifier).\n";
1633 # TODO: maybe don't warn only, but let the user use --force to
1634 # specify that he knows what he is doing ...?
1635 if ($issuer->{SUBJECT_KEY_IDENTIFIER}
1636 ne $certificate->{AUTHORITY_KEY_IDENTIFIER}) {
1637 print STDERR "WARNING: The issuer's subject key identifier "
1638 . "extension ($issuer->{SUBJECT_KEY_IDENTIFIER}) does not "
1639 . "match the authority key identifier extension contained "
1640 . "in the certificate "
1641 . "($certificate->{AUTHORITY_KEY_IDENTIFIER}). Are you sure "
1642 . "your chain is correct?\n";
1644 if ($issuer->{SUBJECT} ne $certificate->{ISSUER_DN}) {
1645 print STDERR "WARNING: The issuer's subject ($issuer->{SUBJECT}) "
1646 . "does not match the issuer DN contained in the certificate "
1647 . "($certificate->{ISSUER_DN}). Are you sure your chain is "
1648 . "correct?\n";
1650 exit 0;
1653 print STDERR "Unknown certificate subcommand '$subcmd'.\n";
1654 exit 1;
1657 if ($cmd eq 'key') {
1658 if (! get_config($configfile, qw( dbi_backend xml_config crypto_layer))) {
1659 print STDERR "Could not obtain OpenXPKI instance configuration\n";
1660 exit 1;
1663 my @pki_realms = list_realms();
1664 if (! defined $params{realm} ||
1665 ! grep {$params{realm} eq $_} @pki_realms) {
1666 print STDERR "Please specify one of the following PKI realms via --realm:\n";
1667 foreach my $realm (@pki_realms) {
1668 print " $realm\n";
1671 exit 1;
1673 ## CTX('pki_realm')
1675 my $defaulttoken = CTX('crypto_layer')->get_token(
1676 TYPE => 'DEFAULT',
1677 ID => 'default',
1678 PKI_REALM => $params{realm},
1681 if (! defined $defaulttoken) {
1682 print STDERR "ERROR: Could not get default token for specified realm\n";
1683 exit 1;
1686 if ($subcmd eq 'list') {
1687 my $rc = list_keys(
1689 REALM => $params{realm},
1691 exit 0;
1694 if ($subcmd eq 'use') {
1695 my $key_id = $params{id};
1696 my $purpose = lc $params{purpose};
1697 my $realm = $params{realm};
1698 my $command = $params{command};
1699 if (! defined $command) {
1700 $command = '/bin/sh';
1702 my $default_token = CTX('crypto_layer')->get_token(
1703 TYPE => 'DEFAULT',
1704 PKI_REALM => $realm,
1707 if ($key_id eq '') {
1708 print STDERR "Please specify a token ID with --id (see output of key list for a list of possible values).\n";
1709 exit 1;
1711 if ($purpose eq '') {
1712 print STDERR "Please specify a purpose (CA, SCEP, ...) with --purpose (see output of key list for a list of possible values).\n";
1714 my $config = OpenXPKI::XML::Config->new(
1715 CONFIG => $configfile,
1718 my $nr_of_realms = $config->get_xpath_count(
1719 XPATH => [ 'pki_realm' ],
1720 COUNTER => [ ],
1723 my $realm_index;
1724 for (my $i=0; $i < $nr_of_realms; $i++) {
1725 next if ($realm ne $config->get_xpath(
1726 XPATH => [ 'pki_realm', 'name' ],
1727 COUNTER => [ $i , 0 ]));
1728 $realm_index = $i;
1729 last;
1731 my $nr_of_purpose_items = $config->get_xpath_count(
1732 XPATH => [ 'pki_realm', $purpose ],
1733 COUNTER => [ $realm_index ],
1735 my $purpose_index;
1736 for (my $i=0; $i < $nr_of_purpose_items; $i++) { # look for matching id
1737 next if ($key_id ne $config->get_xpath(
1738 XPATH => [ 'pki_realm' , $purpose, 'token', 'id' ],
1739 COUNTER => [ $realm_index, $i , 0 , 0 ],
1741 $purpose_index = $i;
1742 last;
1744 if (! defined($purpose_index)) {
1745 print STDERR "Could not find configuration for this ID and purpose.\n";
1746 exit 1;
1748 my @token_path = ( 'pki_realm' , $purpose , 'token' );
1749 my @token_counter = ( $realm_index, $purpose_index, 0 );
1750 my $key_filename;
1751 eval {
1752 $key_filename = $config->get_xpath(
1753 XPATH => [ @token_path , 'key' ],
1754 COUNTER => [ @token_counter, 0 ],
1757 if ($EVAL_ERROR) {
1758 print STDERR "Could not read key filename from config file, "
1759 . "configuration error?\n";
1760 print STDERR "$EVAL_ERROR\n";
1761 exit 1;
1763 if (! -s $key_filename) {
1764 print STDERR "Key file does not exist or is empty\n";
1765 exit 1;
1767 my $secret_group_name;
1768 eval {
1769 $secret_group_name = $config->get_xpath(
1770 XPATH => [ @token_path , 'secret' ],
1771 COUNTER => [ @token_counter, 0 ],
1774 if ($EVAL_ERROR) {
1775 print STDERR "Could not read secret group from config file, "
1776 . "configuration error?\n";
1777 print STDERR "$EVAL_ERROR\n";
1778 exit 1;
1781 my @secret_path = ( 'pki_realm' , 'common', 'secret' );
1782 my @secret_counter = ( $realm_index, 0 , 0 );
1783 my $secret_group_count;
1784 eval {
1785 $secret_group_count = $config->get_xpath_count(
1786 XPATH => [ @secret_path , 'group' ],
1787 COUNTER => [ @secret_counter ],
1790 my $group_index;
1791 SEARCH_GROUP_ID:
1792 for (my $i = 0; $i < $secret_group_count; $i++) {
1793 my $group_id = $config->get_xpath(
1794 XPATH => [ @secret_path , 'group', 'id' ],
1795 COUNTER => [ @secret_counter, $i , 0 ],
1797 if ($group_id eq $secret_group_name) {
1798 $group_index = $i;
1799 last SEARCH_GROUP_ID;
1802 if (! defined $group_index) {
1803 print STDERR "Could not find configuration for secret group '$secret_group_name'.\n";
1804 exit 1;
1806 my $secret_method = $config->get_xpath(
1807 XPATH => [ @secret_path , 'group' , 'method', 'id' ],
1808 COUNTER => [ @secret_counter, $group_index, 0 , 0 ],
1810 my $passphrase;
1811 if ($secret_method ne 'literal') { # all others have total_shares
1812 my $quorum_n = $config->get_xpath(
1813 XPATH => [ @secret_path , 'group' , 'method', 'total_shares' ],
1814 COUNTER => [ @secret_counter, $group_index, 0 , 0 ],
1817 my $quorum_k = __get_required_shares({
1818 CONFIG => $config,
1819 TOKEN_PATH => [ @secret_path, 'group' ],
1820 TOKEN_COUNTER => [ @secret_counter, $group_index ],
1822 if (! defined $quorum_k) { # if it is not defined, n is
1823 # the default value
1824 $quorum_k = $quorum_n;
1826 my $display_secret_method = $secret_method;
1827 $display_secret_method .= ' (n = ' . $quorum_n . ', k = '
1828 . $quorum_k . ')';
1829 print 'Secret method: ' . $display_secret_method . "\n";
1830 my $secret_method = ucfirst($secret_method);
1831 my $secret = OpenXPKI::Crypto::Secret->new({
1832 TYPE => $secret_method,
1833 QUORUM => {
1834 K => $quorum_k,
1835 N => $quorum_n,
1837 TOKEN => $default_token,
1839 SECRET_INPUT:
1840 while (! $secret->is_complete()) {
1841 print "Secret not yet complete, please enter a share.\n";
1842 my $part;
1843 if ($secret_method eq 'Plain') {
1844 $part = prompt "Share number: ";
1846 my $share = prompt('Share: ');
1847 my $share2 = prompt('Share (again): ');
1848 if ($share ne $share2) {
1849 print "Shares input do not match, please try again!\n";
1850 next SECRET_INPUT;
1852 else {
1853 if ($secret_method eq 'Plain') {
1854 $secret->set_secret({
1855 PART => $part,
1856 SECRET => "$share",
1859 elsif ($secret_method eq 'Split') {
1860 $secret->set_secret("$share");
1863 for (my $j = 0; $j < 500; $j++) {
1864 print "\n"; # pseudo clearscreen
1867 $passphrase = $secret->get_secret();
1869 else {
1870 $passphrase = $config->get_xpath(
1871 XPATH => [ @secret_path , 'group' , 'method' ],
1872 COUNTER => [ @secret_counter, $group_index, 0 ],
1875 $ENV{'passphrase'} = $passphrase;
1876 $ENV{'keyfile'} = $key_filename;
1877 delete $ENV{'OPENSSL_CONF'};
1878 exec $command;
1879 exit 0;
1881 if ($subcmd eq 'import') {
1882 my $key_id = $params{id};
1883 my $purpose = lc $params{purpose};
1884 my $realm = $params{realm};
1885 my $import_filename = $params{file};
1887 if ($key_id eq '') {
1888 print STDERR "Please specify a token ID with --id (see output of key list for a list of possible values).\n";
1889 exit 1;
1891 if ($purpose eq '') {
1892 print STDERR "Please specify a purpose (CA, SCEP, ...) with --purpose (see output of key list for a list of possible values).\n";
1894 my $config = OpenXPKI::XML::Config->new(
1895 CONFIG => $configfile,
1898 my $nr_of_realms = $config->get_xpath_count(
1899 XPATH => [ 'pki_realm' ],
1900 COUNTER => [ ],
1903 my $realm_index;
1904 for (my $i=0; $i < $nr_of_realms; $i++) {
1905 next if ($realm ne $config->get_xpath(
1906 XPATH => [ 'pki_realm', 'name' ],
1907 COUNTER => [ $i , 0 ]));
1908 $realm_index = $i;
1909 last;
1911 my $nr_of_purpose_items = $config->get_xpath_count(
1912 XPATH => [ 'pki_realm', $purpose ],
1913 COUNTER => [ $realm_index ],
1915 my $purpose_index;
1916 for (my $i=0; $i < $nr_of_purpose_items; $i++) { # look for matching id
1917 next if ($key_id ne $config->get_xpath(
1918 XPATH => [ 'pki_realm' , $purpose, 'token', 'id' ],
1919 COUNTER => [ $realm_index, $i , 0 , 0 ],
1921 $purpose_index = $i;
1922 last;
1924 if (! defined($purpose_index)) {
1925 print STDERR "Could not find configuration for this ID and purpose.\n";
1926 exit 1;
1928 my @token_path = ( 'pki_realm' , $purpose , 'token' );
1929 my @token_counter = ( $realm_index, $purpose_index, 0 );
1930 my $key_filename;
1931 eval {
1932 $key_filename = $config->get_xpath(
1933 XPATH => [ @token_path , 'key' ],
1934 COUNTER => [ @token_counter, 0 ],
1937 if ($EVAL_ERROR) {
1938 print STDERR "Could not read key filename from config file, "
1939 . "configuration error?\n";
1940 print STDERR "$EVAL_ERROR\n";
1941 exit 1;
1943 if (-s $key_filename) {
1944 print STDERR "Key file is non-empty, cowardly refusing to create "
1945 . "new key.\n";
1946 exit 1;
1948 my $key;
1949 my $fu = OpenXPKI::FileUtils->new();
1950 $key = $fu->read_file($import_filename);
1952 if ($key ne '') {
1953 my $key_path = $key_filename;
1954 $key_path =~ s/(.*)\/.*/$1/; # perl is greedy, so this is the path
1955 if (! -d $key_path) { # key path does not yet exist, create it
1956 eval { # try to create
1957 mkpath($key_path);
1959 if ($EVAL_ERROR) {
1960 print STDERR "Could not create key directory: $key_path";
1961 exit 1;
1964 $fu->write_file({
1965 FILENAME => $key_filename,
1966 CONTENT => $key,
1968 if (-s $key_filename) { # key file exists and is nonzero
1969 print "Key successfully written to $key_filename\n";
1972 else {
1973 print STDERR "Key import failed.\n";
1974 exit 1;
1976 exit 0;
1979 if ($subcmd eq 'generate') {
1980 my $secret_group = $params{group};
1981 if ($secret_group eq '') {
1982 print STDERR "Please specify a secret group with --group (see output of key list for a list of possible values).\n";
1983 exit 1;
1985 my $rc = generate_keys({
1986 REALM => $params{realm},
1987 GROUP => $secret_group,
1989 exit 0;
1992 print STDERR "Unknown key subcommand '$subcmd'.\n";
1993 exit 1;
1996 print STDERR "Unknown command '$cmd'.\n";
1997 exit 1;
1999 __END__
2001 =head1 NAME
2003 openxpkiadm - tool for management operations of OpenXPKI instances
2005 =head1 USAGE
2007 openxpkiadm COMMAND [SUBCOMMAND] [OPTIONS]
2009 Global options:
2010 --config FILE use configuration from FILE
2012 Commands:
2013 help brief help message
2014 man full documentation
2015 version print program version and exit
2016 deploy Deploy a new OpenXPKI installation
2017 initdb Initialize database
2018 key Manage keys
2019 certificate Manage certificates
2021 =head1 ARGUMENTS
2023 Available commands:
2025 =head2 deploy
2027 Command options:
2029 --prefix DIR Use specified prefix during deployment
2030 --templatedir DIR Use specified directory as base directory for
2031 templates
2032 --template TEMPLATE Use specified template (defaults to 'default')
2033 --force Force operation (may be destructive)
2036 Creates a new OpenXPKI server configuration file set below the specified
2037 prefix directory (defaults to [% dir.prefix %]
2038 if no other directory is specified via --prefix).
2039 This command will not overwrite existing configuration files unless --force
2040 is specified.
2042 If the --templatedir argument is given the specified directory is used
2043 as template base directory.
2045 If --template is specified, its argument is used instead of 'default' for
2046 the source of the templates used.
2048 All options following -- are literally passed to openxpki-metaconf during
2049 deployment.
2052 =head2 initdb
2054 Command options:
2056 --force Force operation (may be destructive)
2057 --dryrun Don't change anything, just print what would
2058 be done
2060 Initializes the OpenXPKI database schema. Will not destroy existing data
2061 unless called with --force.
2063 =head2 key
2065 Key generation for OpenXPKI Tokens (including issuing CAs and subsystems).
2067 Command options:
2069 --realm PKI Realm to operate on
2071 =head3 key management subcommands
2073 =over 8
2075 =item B<list>
2077 Shows token key information for the specified realm, including
2078 key algorithm, key length and secret splitting information.
2080 Lists keys together with a status flag, which can be one of the
2081 following:
2083 + - key exists and file is non-empty
2084 0 - key exists but file is empty
2085 ! - key files does not exist (yet)
2087 Example:
2089 openxpkiadm key list --realm 'Root CA'
2091 =item B<generate>
2093 Command options:
2095 --realm PKI Realm to operate on
2096 --group The secret group to generate keys for
2098 Generates asymmetric key pairs for the given secret group. The command
2099 will use the secret splitting method specified in the token
2100 configuration. For valid secret groups, see the output of key list.
2102 The command will refuse to overwrite an existing key.
2104 If multiple secret password parts are configured for the specified key,
2105 the key generation routine will automatically create the configured
2106 number of password secret parts.
2108 This command only supports key generation in software.
2109 For HSM protected keys please refer to the HSM product documentation
2110 regarding key generation with the particular product.
2112 Example:
2114 openxpkiadm key generate --realm 'Root CA' --group default
2116 =item B<import>
2118 Command options:
2120 --realm PKI Realm to operate on
2121 --purpose The purpose of the key (e.g. CA, SCEP)
2122 --id The name of the configured key
2123 --file The file to import the key from
2125 This command allows to import a key that has been generated using
2126 a different method then using openxpkiadm key generate. It effectively
2127 copies the key file to the location given in the config file. The options
2128 are the same as in key generate, except for --file, which gives the
2129 location of the externally generated key.
2131 =item B<use>
2133 Command options:
2135 --realm PKI Realm to operate on
2136 --purpose The purpose of the key (e.g. CA, SCEP)
2137 --id The name of the configured key
2138 --command The command to start (defaults to '/bin/sh')
2140 This command asks for the needed key passphrase shares and exports the
2141 recovered passphrase into the passphrase environment variable as well
2142 as the key file location into the keyfile environment variable. Typical
2143 usage is to create a certificate request for a secret-splitted key:
2145 openxpkiadm key use --realm 'Root CA' --purpose ca --id testdummyca1 \
2146 --command 'openssl req -new -passin env:passphrase -key $keyfile'
2148 =back
2150 =head2 certificate
2152 Starts a certificate management command and allows to list, install,
2153 delete and connect certificates for the configured PKI Realms.
2155 openxpkiadm certificate <subcommand> <options>
2157 =head3 certificate management subcommands
2159 =over 8
2161 =item B<list>
2163 Subcommand options (optional):
2165 --realm PKI realm to operate on
2166 --all Show all certificates
2167 -v Show subject and issuer DN as well
2168 -v -v Show chain as well
2169 -v -v -v Show (nearly complete) database entry
2170 -v -v -v -v Show pubkey and certificate data, too
2172 Lists certificates present in the database for
2173 the specified realm. If --all is not specified, only certificates
2174 that have an alias defined for them are listed. --all lists all
2175 certificates, regardless of whether they have an alias or not.
2176 If --realm is left out, the certificates in all realms are listed
2177 The number of -v's increases the verbosity (see above for what is
2178 listed in which case).
2180 =item B<import>
2182 Subcommand options:
2184 Mandatory:
2185 --realm PKI realm to import certificate to
2186 --file the PEM file to import from
2187 --issuer the issuer alias or identifier
2189 Optional:
2190 --issuer-realm the realm where the issuer alias
2191 is defined
2192 --role the role of the certificate owner
2194 Force options (use only if you exactly now what you are doing!):
2195 --force-really-self-signed
2196 The certificate is really self-signed
2197 --force-issuer-not-found
2198 Don't care that the issuer is not in the database
2199 --force-certificate-already-exists
2200 Don't care that the certificate is already in database
2202 Once again, only use these options if you actually have to (the
2203 occasions where this happens should be really, really rare).
2205 Adds a certificate to the database. There are two different ways to
2206 call it, depending on whether you have a self-signed certificate
2207 or not. With a self-signed certificate, the --realm and --issuer options
2208 are left out, with a "normal" certificate, they are mandatory.
2210 The command outputs the subject's DN and the issuer's DN for you to
2211 verify that you imported the correct certificate as well as a unique
2212 identifier which can be used to globally reference the certificate
2213 (i.e. for configuration or as an issuer). If you don't want to remember
2214 the identifier, look into openxpkiadm certificate alias to find out
2215 how to create a symbolic name for an identifier.
2217 Examples:
2219 openxpkiadm certificate import --file cacert.pem
2221 Imports a self-signed CA certificate.
2223 openxpkiadm certificate import --realm 'Root CA' \
2224 --file subca1.pem --issuer 'Root CA 1'
2226 Imports a Sub CA certificate which is signed by Root CA 1.
2228 =item B<remove>
2230 Subcommand options:
2232 Mandatory:
2233 --name The alias or identifier of the certificate
2235 Optional:
2236 --realm The PKI realm in which the alias is defined
2238 Force options (use only if you now what you are doing!):
2239 --force-is-issuer Delete certificate even though it is the
2240 issuer of another certificate in the database
2242 Removes a certificate from the database.
2244 Example:
2246 openxpkiadm certificate remove --realm 'Root CA' \
2247 --name 'Root CA 1'
2249 =item B<alias>
2251 Subcommand options:
2253 Mandatory:
2254 --realm PKI realm to create the alias in
2255 --alias The symbolic name for the certificate
2256 --identifier The identifier of the certificate
2258 Force options (use only if you now what you are doing!):
2259 --force-certificate-not-found
2260 Ignore that the certificate for which to create an
2261 alias was not found in the DB
2263 Only use these options if you actually have to (the occasions where
2264 this happens should be really, really rare).
2266 Using openxpkiadm certificate alias, you can create a symbolic name
2267 for a certificate, which is associated with a specific PKI realm.
2268 This symbolic name can then be used in some of the openxpkiadm commands
2269 as well as in the configuration files.
2271 Example:
2273 openxpkiadm certificate alias --realm 'Root CA' \
2274 --identifier FpzZptRsa/444Acs/Nrdmo1Fo1s --alias 'root1'
2276 =item B<chain>
2278 Subcommand options:
2280 Mandatory:
2281 --realm The PKI realm to operate in
2282 --name The alias or identifier of the child
2283 --issuer The alias or identifier of the parent
2285 Optional:
2286 --issuer-realm The realm in which the issuer alias
2287 is defined
2289 Force options (use only if you now what you are doing!):
2290 --force-certificate-not-found
2291 Ignore that the certificate of the child was not found
2292 in the DB
2293 --force-issuer-certificate-not-found
2294 Ignore that the certificate of the parent was not found
2295 in the DB
2297 Once again, only use these options if you actually have to (the
2298 occasions where this happens should be really, really rare).
2300 Specifies subject/issuer relationship in order to set up certificate
2301 chains. The certificates to be connected must already be present in
2302 the database (see B<import>). As those connections are already set up
2303 during --import, this command exists for changing the issuer if you
2304 made an error. It also allows to specify an issuer that does not
2305 agree with the information contained in the certificate (but outputs
2306 a warning)
2308 Example:
2310 openxpkiadm certificate chain --realm 'Root CA' \
2311 --name 'Subordinate CA 1' --issuer 'root1'
2313 =head1 OPTIONS
2315 =over 8
2317 =item B<--config FILE>
2319 Read configuration file FILE. Uses built-in default if not specified.
2321 =item B<--force>
2323 Force execution of command.
2325 WARNING: This may destroy existing data!
2327 =item B<--dryrun>
2329 Prints effects of a command without actually modifying anything.
2331 =item B<--prefix>
2333 Specify deployment prefix for deploy command.
2335 =item B<--templatedir>
2337 Specify template directory to use for configuration files.
2339 =item B<--template>
2341 Specify template to use during deployment.
2343 =back
2345 =head1 DESCRIPTION
2347 B<openxpkiadm> is the administrative frontend for controlling the OpenXPKI
2348 installation.
2350 =over 8
2352 NOTE: This script was customized to the paths specified during
2353 installation.
2354 You will have to modify this script to reflect any changes to the
2355 installation directories.
2357 The openxpkiadm script returns a 0 exit value on success, and >0 if an
2358 error occurs.
2360 =back