App-CamelPKI

 view release on metacpan or  search on metacpan

lib/App/CamelPKI/CA.pm  view on Meta::CPAN

    push @{$checks->{$_->{opts}->{template}}}, $_ foreach @signed;
    $_->test_issued_certs_coherent(@{$checks->{$_}}) foreach
        (keys %$checks);

    my @retval;
    foreach my $signed (@signed) {
        $self->{db}->add($signed->{cert}, %{$signed->{opts}});
        push(@retval, $signed->{cert});
    }
    $self->{db}->commit;

    return @retval;
}

=head2 issue_crl(-option1 => $val1, ...)

Builds a CRL taking account of previously marked as revoked certificates
in database, and returns it in the form of an L<App::CamelPKI::CRL> object.

Recognized named options are:

=over

=item I<< -validity => $days >>

Allows to specify the validity duration of the CRL. Default value is 7
days.

=item I<< -signature_hash => $hashname >>

Allows to specify the cryptographic algorithm to use for the CRL
signing, on the form of a name (for example "sha256").
The default value is "sha256", as "md5" and "sha1" are not recommanded
due to progress done in their cryptanalysis
(L<http://www.win.tue.nl/~bdeweger/CollidingCertificates/>).

=back

=cut

sub issue_crl {
    throw App::CamelPKI::Error::Internal("WRONG_NUMBER_ARGS")
        unless (@_ % 2);
    my ($self, %opts) = @_;
    $opts{-validity} ||= 7;
    $opts{-signature_hash} ||= "sha256";

    my $crl = new Crypt::OpenSSL::CA::X509_CRL;
    $crl->set_issuer_DN($self->certificate->as_crypt_openssl_ca_x509
                        ->get_subject_DN);
    my $now = App::CamelPKI::Time->now;
    $crl->set_lastUpdate($now);
    $crl->set_nextUpdate($now->advance_days($opts{-validity}));
    $crl->set_extension
        ("crlNumber", sprintf("0x%x", $self->{db}->next_serial("crl")),
         -critical => 1);
    $crl->set_extension("authorityKeyIdentifier",
                        { keyid => $self->certificate->
                          as_crypt_openssl_ca_x509->get_subject_keyid });

    for(my $cursor = $self->{db}->search(-initially_valid_at => "now",
                                   -revoked => 1);
        $cursor->has_more; $cursor->next) {
        my $serial = $cursor->certificate->get_serial;
        my $time = $cursor->revocation_time;
        my $reason = $cursor->revocation_reason;
        my $ctime = $cursor->compromise_time;
        my $holdoid = $cursor->hold_instruction;

        $crl->add_entry
            ($serial, $time,
             (defined($reason) ? (-reason => $reason) : ()),
             (defined($ctime) ? (-compromise_time => $ctime) : ()),
             (defined($holdoid) ? (-hold_instruction => $holdoid) : ()),
            );
    }

    return App::CamelPKI::CRL->parse($crl->sign($self->_private_key,
                                           $opts{-signature_hash}));
}

=head2 get_certificates_issued()

Builds a list of certificates already issued by the CA and not revoked.
Certificates are returned as an array of L<App::CamelPKI::Certificate>.

=cut

sub get_certificates_issued(){
	my ($self) = @_;
	my @certs;

    for(my $cursor = $self->{db}->search();        
        $cursor->has_more; $cursor->next) {
        	push @certs, $cursor->certificate;
    }
	return @certs;
}

=head2 get_certificates_revoked()

Builds a list of certificates already issued by the CA and not revoked.
Certificates are returned as an array of L<App::CamelPKI::Certificate>.

=cut

sub get_certificates_revoked(){
	my ($self) = @_;
	my @certs;

    for(my $cursor = $self->{db}->search(-revoked => 1);        
        $cursor->has_more; $cursor->next) {
        	push @certs, $cursor->certificate;
    }
	return @certs;
}

=head2 get_certificate_by_serial($serial)

Builds a list of certificates already issued by the CA and not revoked.
Certificates are returned as an array of L<App::CamelPKI::Certificate>.

=cut

sub get_certificate_by_serial(){
	my ($self, $serial) = @_;
	
    for(my $cursor = $self->{db}->search( -serial=>$serial, -revoked=>undef ); $cursor->has_more; $cursor->next) {
        	warn "on est bon";
        	return $cursor->certificate;
    }
}

=head2 rescind()

Cancels the ingoing transaction and let the object in an unusable
status. Invoked automatically in case of a template exception.

=cut

sub rescind { die "UNIMPLEMENTED" }

=head1 FACETS

=head2 database_facet($certtemplate)

Returns a facet of the CA database (as passed to L</load>) resticted
in read only and using a filter that only allow to consult certificates
generated using $certtemplate as first parameters issued to L</issue>.

=cut

sub database_facet {
    my ($self, $template) = @_;

    my $retval = Class::Facet->make("App::CamelPKI::CA::CADBFacet",
                                    $self->database);
    $retval->{template} = $template;
    return $retval;

    package App::CamelPKI::CA::CADBFacet;

    use Class::Facet from => "App::CamelPKI::CADB",
        on_error => \&App::CamelPKI::Error::Privilege::on_facet_error,
        delegate => [ qw(max_serial) ];

    sub search {
        my ($facetself, $trueself) = Class::Facet->selves(\@_);
        return $trueself->search(template => $facetself->{template}, @_);
    }
}

=head2 facet_readonly()

Returns a copy of this object in read only: only L</certificate> and
L</database> methods can be invoked.

=cut

sub facet_readonly {
    return Class::Facet->make("App::CamelPKI::CA::FacetReadonly", shift);

    package App::CamelPKI::CA::FacetReadonly;

    use Class::Facet from => "App::CamelPKI::CA",
        on_error => \&App::CamelPKI::Error::Privilege::on_facet_error,
            delegate => [qw(rescind certificate is_operational database
                            database_facet)];

    # Cascading facets (yow!)

lib/App/CamelPKI/CA.pm  view on Meta::CPAN

    }
}

test "synopsis" => sub {
    my $code = My::Tests::Below->pod_code_snippet("synopsis");
    $code =~ s/my //g;
    my $directory = $cadir;
    my $pubkey = App::CamelPKI::PublicKey->parse($test_public_keys{"rsa1024"});
    my ($ca, $joecert, $fredcert, $crl);
    my $cadb = load App::CamelPKI::CADB($cadir);
    eval $code; die $@ if $@;

    ok($joecert->isa("App::CamelPKI::Certificate"));
    like($joecert->get_subject_DN->to_string, qr/Joe/);
    ok($fredcert->isa("App::CamelPKI::Certificate"));
    like($fredcert->get_subject_DN->to_string, qr/Fred/);
    ok($crl->isa("App::CamelPKI::CRL"));
    ok($crl->is_member($joecert));
    ok(! $crl->is_member($fredcert));
};

test "->facet_operational" => sub {
    my $ca = load_ca->facet_operational;
    my $cacert = $ca->certificate;
    try {
        $ca->set_keys(-certificate => $cacert);
        fail("this method is not allowed by the facet");
    } catch App::CamelPKI::Error::Privilege with {
        pass;
    };
    ok($ca->issue_crl->isa("App::CamelPKI::CRL"),
       "the facet_operational is operational");
    ok($ca->facet_operational->facet_operational->certificate
       ->isa("App::CamelPKI::Certificate"), "facet_operational idempotent");
};

test "Coherence and forced revocation" => sub {
    my $ca = load_ca;
    my $pubkey = App::CamelPKI::PublicKey->parse($test_public_keys{"rsa1024"});
    $ca->issue("App::CamelPKI::CertTemplate::Foo", $pubkey,
               name => "user1", uid => 1);

    # I freely pick in the $cert private fields: 
    my $cert = $ca->{signed}->[0]->{cert};
    is(ref($cert), "App::CamelPKI::Certificate");
    try {
        $ca->revoke("App::CamelPKI::CertTemplate::Foo", $cert);
        fail("It's prohibited to revoke certificates "
             . "in the current transaction");
    } catch App::CamelPKI::Error::Privilege with {
        pass;
    };

    # This operation may have made the $ca object unusable, so we
    # try again:
    $ca = load_ca->facet_certtemplate("App::CamelPKI::CertTemplate::Foo");
    $ca->issue("App::CamelPKI::CertTemplate::Foo", $pubkey,
               name => "user1", uid => 1);

    # A new certificate for UID 43 must revoke the old one:
    my $cursor = $ca->database->search(name => "Fred");
    is($cursor->revocation_time(), undef,
       "The Fred's certificate is not yet revoked");
    is(my $fredid = $cursor->infos->{uid}->[0], 43,
       "Using CADB to get the Fred's UID")
        or warn Data::Dumper::Dumper(scalar($cursor->infos));
    # Fred got his operation, so he need a new certificate:
    $ca->issue("App::CamelPKI::CertTemplate::Foo", $pubkey,
               name => "Frida", uid => $fredid);
    $cursor = $ca->database->search(name => "Fred", -revoked => undef);
    isnt($cursor->revocation_time(), undef,
       "the Fred certificate is revoked");
    is($ca->database->search(-revoked => undef, name => "Frida")->count, 0,
       q"No means to use $ca->databae to get "
       . q"new certificats in preview");

    $ca->issue("App::CamelPKI::CertTemplate::Foo", $pubkey,
               name => "Frida", uid => 555);
    pass("the template did not catched the trickery...");

    try {
        $ca->commit();
        fail("the coherence check should been triggered now");
    } catch App::CamelPKI::Error::User with {
        pass("two certificates for Frida, that's a bad thing");
    };
};

test "->facet_certtemplate" => sub {
    my $ca = load_ca->facet_certtemplate("No::Such::CertTemplate");
    my @no_certs = $ca->database->search(-revoked => 0);
    is(scalar(@no_certs), 0, "no certificate in the dummy template");
};

test "facets intersection" => sub {
    my $ca = load_ca->facet_certtemplate("No::Such::CertTemplate")
        ->facet_readonly;

    my @no_certs = $ca->database->search(-revoked => 0);
    is(scalar(@no_certs), 0, "no certificate in the dummy template");

    try {
        $ca->issue_crl();
        fail("this method is not in the facet");
    } catch App::CamelPKI::Error::Privilege with {
        pass;
    };
};

test "capability discipline "
    . "sur le CertTemplate->test_certificate_conflict" => sub {
    my $pubkey = App::CamelPKI::PublicKey->parse($test_public_keys{"rsa1024"});
    our $ca = load_ca;
    our ($cert_in_other_template) = $ca->database->search();
    ok($cert_in_other_template->isa("App::CamelPKI::Certificate"));
    {
        package Bogus::CertTemplate;

        our @ISA = qw(App::CamelPKI::CertTemplate::Foo); # The same as
                                                    # hereafter
        sub test_certificate_conflict {
            my ($class, $db, @keyvals) = @_;

            use Test::More;
            is($db->search(-revoked => undef,
                           -certificate => $cert_in_other_template)
              ->count(), 0, <<"MESSAGE");
test_certificate_conflict must not see other templates's certificates.
MESSAGE
            foreach my $cert (map {$_->{cert}} @{$ca->{signed}}) {
                is($db->search(-revoked => undef,



( run in 3.525 seconds using v1.01-cache-2.11-cpan-cdf2f3d4e48 )