## OpenXPKI::Crypto::Backend::OpenSSL::Config
## Written 2005 and 2006 by Julia Dubenskaya and Michael Bell for the OpenXPKI project
## Improved 2011 by Martin Bartosch for the OpenXPKI project
## (C) Copyright 2005-2011 by The OpenXPKI Project

use strict;
use warnings;

package OpenXPKI::Crypto::Backend::OpenSSL::Config;

use OpenXPKI::Server::Context qw( CTX );

use OpenXPKI::Debug;
use OpenXPKI::Exception;
use OpenXPKI::DN;
use OpenXPKI::DateTime;
use English;
use OpenXPKI::FileUtils;


sub new
{
    my $that = shift;
    my $class = ref($that) || $that;

    my $self = shift || {};
    bless $self, $class;

    return $self;
}

############################
##     Public setters     ##
############################

sub set_engine
{
    my $self = shift;
    $self->{ENGINE} = shift;
    return $self;
}

sub set_profile
{
    my $self = shift;
    $self->{PROFILE} = shift;
    return $self;
}

sub set_crl_items {

    my $self = shift;
    my $list = shift;
    $self->{INDEX_TXT} = "";

    my @index;
    foreach my $item (@$list) {

        ##! 64: 'CRL item ' . Dumper $item
        my $rawserial = shift @{$item};
        # Constructor detects leading 0x as hex, unpack creates a paired hex output
        my $serial = unpack('H*', Math::BigInt->new( $rawserial )->to_bytes );

        my $timestamp = '700101000000Z';
        my $reason_code = 'unspecified';

        if (scalar @{$item}) {
            my $epoch = shift @{$item};
            $timestamp = OpenXPKI::DateTime::convert_date({
                DATE      => DateTime->from_epoch(epoch => $epoch),
                OUTFORMAT => 'openssltime',
            }) if ($epoch);

            if (scalar @{$item}) {
                $reason_code = shift @{$item};
                if ($reason_code !~ m{ \A (?: unspecified | keyCompromise | CACompromise | affiliationChanged | superseded | cessationOfOperation | certificateHold | removeFromCRL ) \z }xms) {
                    CTX('log')->application()->warn("Invalid reason code '" . $reason_code . "' specified");
                    $reason_code = 'unspecified';
                }

                if ($reason_code eq 'keyCompromise' && scalar @{$item} && $item->[0]) {
                    my $invalidity_time = shift @{$item};
                    eval {
                        my $invalidity_date = OpenXPKI::DateTime::convert_date({
                            DATE      => DateTime->from_epoch( epoch => $invalidity_time ),
                            OUTFORMAT => 'generalizedtime',
                        });
                        ##! 16: 'invalidity date is present: ' . $invalidity_date . ' / ' . $invalidity_time

                        # openssl needs a special reason code keyword to pickup
                        # the invalidity time parameter
                        $reason_code = 'keyTime,'.$invalidity_date;
                    };
                    if ($EVAL_ERROR) {
                        CTX('log')->application()->warn("Unparsable invalidity_date given: " . $invalidity_time);
                    }
                }
            }
        }

        my $revocation = "$timestamp,$reason_code";

        push @index, join("\t",('R', '700101000000Z', $revocation, $serial, 'unknown', '/DC=org/DC=openxpki/CN=Dummy'));

    }
    push @index,"";

    $self->{INDEX_TXT} = join("\n", @index);

    ##! 1: "end"
    return $self;
}

sub __get_fu {

    my $self = shift;
    $self->{FU} ||= OpenXPKI::FileUtils->new({ TMP => $self->{TMP} });
    return $self->{FU};
}

################################
##     Dump configuration     ##
################################

# dump finally writes the main configuration file
sub dump
{

    ##! 1: "start"
    my $self = shift;
    my $config = $self->build_config();

    # directory mode for ca operations
    if ($self->{CONFDIR}) {
        $self->{FILENAME}->{CONFIG} = $self->{CONFDIR}."/openssl.cnf";

        ##! 2: "write configuration file " . $self->{FILENAME}->{CONFIG}
        # writes a "non-temporary" file in the temporary directory
        $self->__get_fu()->write_file ({
            FILENAME => $self->{FILENAME}->{CONFIG},
            CONTENT  => $config
        });

    # single file mode
    } else {

        $self->{FILENAME}->{CONFIG} = $self->__get_fu()->write_temp_file( $config );

    }
    return $self->{FILENAME}->{CONFIG};

}

# this builds and returns the content of the main configuration file
# and - for ca operations - writes the required artefacts to a tempdir
sub build_config
{
    ##! 1: "start"
    my $self = shift;
    my $config = "";

    ##! 2: "dump common part"

    my $engines = $self->__get_engine();

    $config =
        "## OpenSSL configuration\n".
        "## dynamically generated by OpenXPKI::Crypto::Backend::OpenSSL::Config\n".
        "\n".
        "default_ca   = ca\n";

    # add init with engine section if needed
    $config .=
        "openssl_conf = openssl_init\n".
        "[ openssl_init ]\n".
        "engines = engine_section\n".
        $engines
    if ($engines);

        # We overwrite the DN by -subj so this is irrelevant but OpenSSL
        # requires a policy item to be set
    $config .=
        "[ dn_policy ]\n".
        "domainComponent = optional\n";

    if (!exists $self->{PROFILE})
    {
        # as we might have pkcs10 req without profile we need
        # some req related dummy data here
        $config .= $self->__get_req_section();

    } elsif (ref $self->{PROFILE} eq 'OpenXPKI::Crypto::Profile::CSR') {

        $config .= $self->__get_req_section();

    } else {

        # recreate in case it was unset
        $self->__get_fu();

        my $tmpdir = $self->{FU}->get_tmp_dirhandle()->dirname();
        $self->{CONFDIR} = $tmpdir;

        ##! 4: "write serial file (for CRL and certs)"

        my $serial = $self->{PROFILE}->get_serial();
        if (defined $serial)
        {
            ##! 8: "get tempfilename for serial"
            $self->{FILENAME}->{SERIAL} = "$tmpdir/serial";

            ##! 8: "serial present"
            $serial = Math::BigInt->new ($serial);
            if (not defined $serial)
            {
                OpenXPKI::Exception->throw (
                    message => "I18N_OPENXPKI_CRYPTO_OPENSSL_CONFIG_DUMP_WRONG_SERIAL");
            }
            ##! 8: "serial accepted by Math::BigInt"
            my $hex = unpack('H*', $serial->to_bytes );
            ##! 8: "hex serial is $hex"
            $self->{FU}->write_file ({FILENAME => $self->{FILENAME}->{SERIAL},
                               CONTENT  => $hex});

            ##! 8: "specify a special filename to remove the new cert from the temp file area (see new_certs_dir)"
            $self->{FILENAME}->{NEW_CERT} = "$tmpdir/$hex.pem";

        }

        ##! 4: "write database files"

        ##! 4: "WARNING:FIXME: ATTR file of index.txt is not safe!"
        # ATTRFILE should be databasefile.attr
        # FIXME: we assume this file does not exist
        # FIXME: is this really safe? OpenSSL require it
        $self->{FILENAME}->{DATABASE} = "$tmpdir/index.txt";
        $self->{FILENAME}->{ATTR} = $self->{FILENAME}->{DATABASE}.".attr";
        if (exists $self->{INDEX_TXT})
        {
            ##! 8: "INDEX_TXT present => this is a CRL"
            $self->{FU}->write_file ({FILENAME => $self->{FILENAME}->{DATABASE},
                               CONTENT  => $self->{INDEX_TXT}});
        }
        else
        {
            ##! 8: "no INDEX_TXT => this is a certificate"
            $self->{FU}->write_file ({FILENAME => $self->{FILENAME}->{DATABASE},
                               CONTENT  => ""});
        }
        $self->{FU}->write_file ({FILENAME => $self->{FILENAME}->{ATTR},
                           CONTENT  => "unique_subject = no\n"});

        ##! 4: "PROFILE exists => CRL or cert generation"
        $config .= $self->__get_ca();
        # there might be cases where we do not have any extensions but
        # as the section is hard coded as reference we need an empty one
        $config .= $self->__get_extensions('v3ca') || "[ v3ca ]\n";

    }

    ##! 16: $config
    ##! 1: "end"

    return $config;
}

sub __get_engine
{
    ##! 4: "start"
    my $self = shift;

    return unless($self->{ENGINE});

    my $engine_name = $self->{ENGINE}->get_engine();
    return unless($engine_name);

    return "\n[ engine_section ]\n".
        "$engine_name = engine_config\n".
        "\n".
        "[ engine_config ]\n".
        $self->{ENGINE}->get_engine_section().
        "\n\n";

}

# the EV oids are in OpenSSL since 1.0.2 so we dont need them anymore
# we should find a configurable way of adding external oids

sub __get_req_section
{
    ##! 4: "start"
    my $self = shift;

    my $req_dn = $self->__get_subject_dn();
    my $req_ext = $self->__get_extensions('req_ext') // '';

    # we need a dummy section if there is no real data
    $req_dn //= "[ req_distinguished_name ]\n".
        "domainComponent = optional\n\n";

    return
        "[ req ]\n".
        "utf8 = yes\n".
        "string_mask = utf8only\n".
        "prompt = no\n".
        "distinguished_name = req_distinguished_name\n".
        ($req_ext ? "req_extensions = req_ext\n" : '').
        "\n".
        $req_dn.
        $req_ext;

}

sub __get_subject_dn
{
    ##! 4: "start"
    my $self = shift;

    return unless($self->{PROFILE});

    my $subject = $self->{PROFILE}->get_subject();
    return unless($subject);

    # we need the parsed subject to write it to the config
    if (!ref $subject) {
        my @rdnlist = OpenXPKI::DN->new( $subject )->get_parsed();
        $subject = [reverse @rdnlist];
    }

    my @tmp_array;
    # it seems that openssl just strips any leading digit+dot
    # so we can have a common index over all RDNs
    my $rdnidx = 0;
    foreach my $rdn (@{$subject}) {
        # rdn is a list of two-element objects holding tagname and value
        my @item = @$rdn;
        my $first = shift @item;
        push @tmp_array, sprintf '%01d.%s = "%s"', $rdnidx++, $first->[0], $first->[1];
        push @tmp_array, map {
            # multivalued RDNs are build with the "plus" sign
            sprintf '+%s = "%s"', $_->[0], $_->[1];
        } @item if (@item);
    }

    return unless(scalar @tmp_array);

    return "[ req_distinguished_name ]\n" . join("\n", @tmp_array). "\n";

}

sub __get_ca
{
    ##! 4: "start"
    my $self = shift;

    my $config = "\n[ ca ]\n";

    $config .= "new_certs_dir     = ".$self->{CONFDIR}."\n";
    $config .= "certificate       = ".$self->{ENGINE}->get_certfile()."\n";
    $config .= "private_key       = ".$self->{ENGINE}->get_keyfile."\n";

    if (my $notbefore = $self->{PROFILE}->get_notbefore()) {
    $config .= "default_startdate = "
        . OpenXPKI::DateTime::convert_date(
        {
        OUTFORMAT => 'openssltime',
        DATE      => $notbefore,
        })
        . "\n";
    }

    if (my $notafter = $self->{PROFILE}->get_notafter()) {
    $config .= "default_enddate = "
        . OpenXPKI::DateTime::convert_date(
        {
        OUTFORMAT => 'openssltime',
        DATE      => $notafter,
        })
        . "\n";
    }

    if (exists $self->{FILENAME}->{SERIAL})
    {
        $config .= "crlnumber         = ".$self->{FILENAME}->{SERIAL}."\n".
                   "serial            = ".$self->{FILENAME}->{SERIAL}."\n";
    }

    my $digest = $self->{PROFILE}->get_digest();
    if ($digest =~ /md5/) {
        OpenXPKI::Exception->throw(
            message => 'I18N_OPENXPKI_CRYPTO_BACKEND_OPENSSL_CONFIG_MD5_DIGEST_BROKEN',
        );
    }
    $config .= "default_md        = ".$self->{PROFILE}->get_digest()."\n".
               "database          = ".$self->{FILENAME}->{DATABASE}."\n".
               "default_crl_days  = ".$self->{PROFILE}->get_nextupdate_in_days()."\n".
               "default_crl_hours = ".$self->{PROFILE}->get_nextupdate_in_hours()."\n".
               "x509_extensions   = v3ca\n".
               "crl_extensions    = v3ca\n".
               "preserve          = YES\n".
               "policy            = dn_policy\n".
               "name_opt          = RFC2253,-esc_msb\n".
               "utf8              = yes\n".
               "string_mask       = ".$self->{PROFILE}->get_string_mask()."\n".
               "\n";

    # add the copy_extensions only if set, this prevents adding it to the CRL config
    my $copy_ext = $self->{PROFILE}->get_copy_extensions();
    if ($copy_ext && $copy_ext ne 'none') {
        $config .= "copy_extensions = $copy_ext\n";
    }

    ##! 4: "end"
    return $config;
}

sub __get_extensions
{
    ##! 4: "start"
    my $self = shift;
    my $section_name = shift || 'v3ca';

    my $config;
    my $profile  = $self->{PROFILE};
    my $sections = "";

    return unless($profile);

   EXTENSIONS:
    foreach my $name (sort $profile->get_named_extensions())
    {
        ##! 64: 'name: ' . $name
        my $critical = "";
        $critical = "critical," if ($profile->is_critical_extension ($name));

        if ($name eq "authority_info_access")
        {
            $config .= "authorityInfoAccess = $critical";
            foreach my $pair (@{$profile->get_extension("authority_info_access")})
            {
                my $type;
                $type = "caIssuers" if ($pair->[0] eq "CA_ISSUERS");
                $type = "OCSP"       if ($pair->[0] eq "OCSP");
                foreach my $http (@{$pair->[1]})
                {
                    # substitute commas and semicolons in the URI,
                    # as they will otherwise be misinterpreted by
                    # openssl as seperators
                    $http =~ s{,}{%2C}xmsg;
                    $http =~ s{;}{%3B}xmsg;
                    $config .= "$type;URI:$http,";
                }
            }
            $config = substr ($config, 0, length ($config)-1); ## remove trailing ,
            $config .= "\n";
        }
        elsif ($name eq "authority_key_identifier")
        {
            $config .= "authorityKeyIdentifier = $critical";
            foreach my $param (@{$profile->get_extension("authority_key_identifier")})
            {
                $config .= "issuer:always," if ($param eq "issuer");
                $config .= "keyid:always,"  if ($param eq "keyid");
            }
            $config = substr ($config, 0, length ($config)-1); ## remove trailing ,
            $config .= "\n";
        }
        elsif ($name eq "basic_constraints")
        {
            $config .= "basicConstraints = $critical";
            foreach my $pair (@{$profile->get_extension("basic_constraints")})
            {
                if ($pair->[0] eq "CA")
                {
                    if ($pair->[1] eq "true")
                    {
                        $config .= "CA:true,";
                    } else {
                        $config .= "CA:false,";
                    }
                }
                if ($pair->[0] eq "PATH_LENGTH")
                {
                    $config .= "pathlen:".$pair->[1].",";
                }
            }
            $config = substr ($config, 0, length ($config)-1); ## remove trailing ,
            $config .= "\n";
        }
        elsif ($name eq "cdp")
        {
            $config .= "crlDistributionPoints = $critical\@cdp\n";
            $sections .= "\n[ cdp ]\n";
            my $i = 0;
            foreach my $cdp (@{$profile->get_extension("cdp")})
            {
                $sections .= "URI.$i=$cdp\n";
                $i++;
            }
            $sections .= "\n";
        }
        elsif ($name eq 'policy_identifier') {

            my $i = 0;
            my @policy = @{ $profile->get_extension('policy_identifier') };
            ##! 16: '@policy : ' . Dumper \@policy

            my $sn = 0;
            my $nn = 0;
            my @policies;
            my @psection;
            my @notices;

            foreach my $item (@policy) {
                if (!ref $item) {
                    push @policies, $item;
                } else {
                    $sn++;
                    push @policies, '@policysection'.$sn;

                    push @psection, "[ policysection$sn ]";
                    push @psection, "policyIdentifier = " . $item->{oid};

                    my $cc = 0;
                    foreach my $cps (@{$item->{cps}}) {
                        $cc++;
                        push @psection, "CPS.$cc=\"$cps\"";
                    }

                    foreach my $note (@{$item->{user_notice}}) {
                        $nn++;
                        push @psection, "userNotice.$nn = \@notice$nn";
                        push @notices, "[ notice$nn ]";
                        push @notices, "explicitText = \"$note\"";
                        # Note - RFC recommends this to be an UTF8 string but
                        # openssl 1.0 seems not to be able to do so. The UTF8
                        # prefix was introduced with openssl 1.1
                    }
                }
            }

            if (@policies) {

                ##! 32: 'sections: ' . Dumper \@psection
                ##! 32: 'notices: ' . Dumper \@notices

                $config .= "certificatePolicies = $critical".join(",",@policies)."\n\n";
                $sections .= join("\n", @psection) . "\n\n";
                $sections .= join("\n", @notices) . "\n\n";
            }


        }
        elsif ($name eq "extended_key_usage")
        {
            $config .= "extendedKeyUsage = $critical";
            my @bits = @{$profile->get_extension("extended_key_usage")};
            $config .= "clientAuth,"      if (grep /client_auth/,      @bits);
            $config .= "serverAuth,"      if (grep /server_auth/,      @bits);
            $config .= "emailProtection," if (grep /email_protection/, @bits);
            $config .= "codeSigning,"     if (grep /code_signing/, @bits);
            $config .= "timeStamping,"    if (grep /time_stamping/, @bits);
            $config .= "OCSPSigning,"     if (grep /ocsp_signing/, @bits);
            my @oids = grep m{\.}, @bits;
            foreach my $oid (@oids)
            {
                $config .= "$oid,";
            }
            $config = substr ($config, 0, length ($config)-1); ## remove trailing ,
            $config .= "\n";
        }
        elsif ($name eq "issuer_alt_name")
        {
            $config .= "issuerAltName = $critical";
            my $issuer = join (",", @{$profile->get_extension("issuer_alt_name")});
            $config .= "issuer:copy" if ($issuer eq "copy");
            # FIXME: issuer:copy apparently does not work!
            $config .= "\n";
        }
        elsif ($name eq "key_usage")
        {
            my @bits = @{$profile->get_extension("key_usage")};
            if (scalar @bits > 0) {
                # only add keyUsage to config if configuration entries are present
                $config .= "keyUsage = $critical";
                $config .= "digitalSignature," if (grep /digital_signature/, @bits);
                $config .= "nonRepudiation,"   if (grep /non_repudiation/,   @bits);
                $config .= "keyEncipherment,"  if (grep /key_encipherment/,  @bits);
                $config .= "dataEncipherment," if (grep /data_encipherment/, @bits);
                $config .= "keyAgreement,"     if (grep /key_agreement/,     @bits);
                $config .= "keyCertSign,"      if (grep /key_cert_sign/,     @bits);
                $config .= "cRLSign,"          if (grep /crl_sign/,          @bits);
                $config .= "encipherOnly,"     if (grep /encipher_only/,     @bits);
                $config .= "decipherOnly,"     if (grep /decipher_only/,     @bits);
                $config = substr ($config, 0, length ($config)-1); ## remove trailing ,
                $config .= "\n";
            }
        }

        elsif ($name eq "subject_alt_name")
        {
            my $subj_alt_name = $profile->get_extension("subject_alt_name");
            my @tmp_array;
            my $san_idx = {};
            foreach my $entry (@{$subj_alt_name}) {

                # init hash element for san type
                $san_idx->{$entry->[0]} = 0 unless($san_idx->{$entry->[0]});
                my $sectidx = ++$san_idx->{$entry->[0]};

                # Handle dirName
                if ($entry->[0] eq 'dirName') {
                    # If the value is a scalar, we assume that it is a formatted string.
                    if (ref $entry->[1] eq '') {
                        # split at comma, this has the side effect that we can
                        # not handle DNs with comma in the subparts
                        my %idx;
                        # multi valued components need a prefix in dirName
                        my @sec = map {
                            my($k,$v) = split /=/, $_,2;
                            ($idx{$k}++).'.'.$_;
                        } reverse split(/,/, $entry->[1]);
                        unshift @sec, "", "[dirname_sect_${sectidx}]";
                        $sections .= join("\n", @sec). "\n";
                    } else {
                        # ...otherwise we assume that it is a decoded ASN.1 representation
                        # of the DN.
                        my $idx = 0;
                        my @sec;
                        # walk through each rdn
                        foreach my $rdn (@{$entry->[1]{'rdnSequence'}}) {
                            my $attr_prefix = '';
                            # walk through each attribute of the rnd
                            foreach my $attr (@{$rdn}) {
                                push @sec, ($idx++).'.'.$attr_prefix.$attr->{'type'}.'='.(%{$attr->{'value'}})[1];
                                $attr_prefix = '+'; # the subsequent attributes start with a + prefix
                            }
                            unshift @sec, "", "[dirname_sect_${sectidx}]";
                            $sections .= join("\n", @sec). "\n";
                        }
                    }
                    push @tmp_array, "dirName.${sectidx}=dirname_sect_${sectidx}";
                } else {
                    push @tmp_array, sprintf '%s.%01d = "%s"', $entry->[0], $sectidx, $entry->[1];
                }

            }

            if (scalar @tmp_array) {
                $config .= "subjectAltName=\@san_section\n";
                $sections .= "\n[san_section]\n" . join("\n", @tmp_array). "\n";
            }

        }
        elsif ($name eq "subject_key_identifier")
        {
            $config .= "subjectKeyIdentifier = $critical";
            my @bits = @{$profile->get_extension("subject_key_identifier")};
            $config .= "hash" if (grep /hash/, @bits);
            $config .= "\n";
        }
        elsif ($name eq "netscape_ca_cdp")
        {
            $config .= "nsCaRevocationUrl = $critical".
                       join ("", @{$profile->get_extension("netscape_ca_cdp")})."\n";
        }
        elsif ($name eq "netscape_cdp")
        {
            $config .= "nsRevocationUrl = $critical".
                       join ("", @{$profile->get_extension("netscape_cdp")})."\n";
        }
        elsif ($name eq "netscape_certificate_type")
        {
            $config .= "nsCertType = $critical";
            my @bits = @{$profile->get_extension("netscape_certificate_type")};
            $config .= "client,"  if (grep /ssl_client/, @bits);
            $config .= "objsign," if (grep /object_signing/, @bits);
            $config .= "email,"   if (grep /smime_client/, @bits);
            $config .= "sslCA,"   if (grep /ssl_client_ca/, @bits);
            $config .= "objCA,"   if (grep /object_signing_ca/, @bits);
            $config .= "emailCA," if (grep /smime_client_ca/, @bits);
            $config .= "server," if (grep /ssl_server/, @bits);
            $config .= "reserved," if (grep /reserved/, @bits);
            $config = substr ($config, 0, length ($config)-1); ## remove trailing ,
            $config .= "\n";
        }
        elsif ($name eq "netscape_comment")
        {
            $config .= "nsComment = $critical\"";
            my $string =  join ("", @{$profile->get_extension("netscape_comment")});
            # FIXME: this inserts a literal \n - is this intended?
            $string =~ s/\n/\\\\n/g;
            $config .= "$string\"\n";
        }
        else
        {
            OpenXPKI::Exception->throw (
                message => "I18N_OPENXPKI_CRYPTO_OPENSSL_COMMAND_WRITE_CONFIG_UNKNOWN_NAMED_EXTENSION",
                params  => {NAME => $name});
        }
    }

    foreach my $oid (sort $profile->get_oid_extensions()) {

        # Single line OIDs have only one element in the array
        # Additional lines define a sequence
        my @val = @{$profile->get_extension($oid)};
        my $string = shift @val;

        if ($profile->is_critical_extension($oid)) {
            $config .= "$oid=critical,$string\n";
        } else {
            $config .= "$oid=$string\n";
        }

        # if there are lines left, it goes into the section part
        $sections .= join ("\n", @val)."\n\n";
    }

    return unless($config);

    $config .= "\n".$sections;
    ##! 16: "extensions ::= $config"

    ##! 4: "end"
    return "\n[ $section_name ]\n$config\n";

}

sub get_config_filename
{
    my $self = shift;
    return $self->{FILENAME}->{CONFIG};
}

#####################
##     cleanup     ##
#####################

sub cleanup
{
    ##! 1: "start"
    my $self = shift;

    ##! 2: "delete profile"
    delete $self->{PROFILE} if (exists $self->{PROFILE});

    ##! 2: "delete index.txt database"
    delete $self->{INDEX_TXT} if (exists $self->{INDEX_TXT});

    $self->__cleanup_files();

    ##! 1: "end"
    return 1;
}

sub __cleanup_files
{
    ##! 1: "start"
    my $self = shift;

    if ($self->{CONFDIR}) {
        delete $self->{CONFDIR};
    }
    if ($self->{FU}) {
        delete $self->{FU};
    }
    delete $self->{FILENAME};

    ##! 1: "end"
    return 1;
}

1;
__END__

=head1 Name

OpenXPKI::Crypto::Backend::OpenSSL::Config

=head1 Description

This module was designed to create an OpenSSL configuration on the fly for
the various operations of OpenXPKI. The module support the following
different section types:

=over

=item - general OpenSSL configuration

=item - engine configuration

=item - new OIDs

=item - CA configuration

=item - CRL extension configuration

=item - certificate extension configuration

=item - CRL distribution points

=item - subject alternative names

=back

=head1 Functions

=over

=item - new

=item - set_engine

=item - set_profile

=item - set_crl_items

This method prepares the OpenSSL-specific representation of the certificate
database (index.txt). The method expects an arrayref containing a list
of all certificates to revoke.

Each item in the array must be an array with one or more elements:

=over

=item * certificate serial number, either binary or as hex prefixed with 0x

=item * time of revocation (epoch)

=item * reason_code

=item * time of invalidity (epoch)

=back

The first argument is mandatory, all other element can be empty or
even left out.

If a revocation time is specified, it is used as the revocation
timestamp in the generated CRL. The timestamp is specified in seconds since
epoch.

The reason code is accepted literally. It should be one of
  'unspecified',
  'keyCompromise',
  'CACompromise',
  'affiliationChanged',
  'superseded',
  'cessationOfOperation',

The reason codes
  'certificateHold',
  'removeFromCRL'.

are currently not handled correctly and should be avoided. However, they
will currently simply be passed in the CRL which may not have the desired
result.

If the reason code is incorrect, a warning is logged and the reason code
is set to 'unspecified' in order to make sure the certificate gets revoked
at all.

Invalidity timestamp is only used in conjunction with a reason code of
keyCompromise. The timestamp is specified in seconds since epoch.

=item - dump

=item - get_config_filename

=back

=head1 Example

my $profile = OpenXPKI::Crypto::Backend::OpenSSL::Config->new (
              {
                  TMP    => '/tmp',
              });
$profile->set_engine($engine);
$profile->set_profile($crl_profile);
$profile->dump();
my $conf = $profile->get_config_filename();
... execute an OpenSSL command with "-config $conf" ...
... or execute an OpenSSL command with "OPENSSL_CONF=$conf openssl" ...

=head1 See Also

OpenXPKI::Crypto::Profile::Base, OpenXPKI::Crypto::Profile::CRL,
OpenXPKI::Crypto::Profile::Certificate and OpenXPKI::Crypto::Backend::OpenSSL
