MT#5199 Store client cert in DB and offer P12 fmt.

For browser imports, you need a PKCS12 format, so offer the cert
in that format for download also.
gjungwirth/fix_tests
Andreas Granig 12 years ago
parent 12edcd6f01
commit 883cd9a8b3

@ -224,28 +224,50 @@ sub delete :Chained('base') :PathPart('delete') :Args(0) {
sub api_key :Chained('base') :PathPart('api_key') :Args(0) {
my ($self, $c) = @_;
my $serial = $c->stash->{administrator}->ssl_client_m_serial;
if ($c->req->body_parameters->{'key_actions.generate'}) {
my $cert;
if ($c->req->body_parameters->{'gen.generate'}) {
$serial = time;
$cert = $c->model('CA')->make_client($c, $serial);
my $updated;
while (!$updated) {
try {
$c->stash->{administrator}->update({ ssl_client_m_serial => $serial });
$c->stash->{administrator}->update({
ssl_client_m_serial => $serial,
ssl_client_certificate => $cert,
});
$updated = 1;
} catch(DBIx::Class::Exception $e where { "$_" =~ qr'Duplicate entry' }) {
$serial++;
};
}
$c->model('CA')->make_client($serial);
} elsif ($c->req->body_parameters->{'key_actions.delete'}) {
} elsif ($c->req->body_parameters->{'del.delete'}) {
undef $serial;
$c->stash->{administrator}->update({ ssl_client_m_serial => $serial });
} elsif ($c->req->body_parameters->{'key_actions.download'}) {
my $cert_file = $c->model('CA')->client_cert_file($serial);
undef $cert;
$c->stash->{administrator}->update({
ssl_client_m_serial => $serial,
ssl_client_certificate => $cert,
});
} elsif ($c->req->body_parameters->{'pem.download'}) {
$cert = $c->stash->{administrator}->ssl_client_certificate;
$serial = $c->stash->{administrator}->ssl_client_m_serial;
$c->res->headers(HTTP::Headers->new(
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => sprintf('attachment; filename=%s', $cert_file->basename)
'Content-Disposition' => sprintf('attachment; filename=%s', "NGCP-API-client-certificate-$serial.pem")
));
$c->res->body($cert_file->openr);
$c->res->body($cert);
return;
} elsif ($c->req->body_parameters->{'p12.download'}) {
$cert = $c->stash->{administrator}->ssl_client_certificate;
$serial = $c->stash->{administrator}->ssl_client_m_serial;
my $p12 = $c->model('CA')->make_pkcs12($c, $serial, $cert, 'sipwise');
$c->res->headers(HTTP::Headers->new(
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => sprintf('attachment; filename=%s', "NGCP-API-client-certificate-$serial.p12")
));
$c->res->body($p12);
return;
} elsif ($c->req->body_parameters->{'close'}) {
NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for('/administrator'));
return;
}
my $form;

@ -5,30 +5,99 @@ use Moose::Util::TypeConstraints;
extends 'HTML::FormHandler';
has '+widget_wrapper' => (default => 'Bootstrap');
sub build_render_list {[qw(actions)]}
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class {[qw(form-horizontal)]}
has_field 'key_actions' => (
has_field 'pem' => (
type => 'Compound',
do_label => 0,
label => 'Download in PEM Format',
do_label => 1,
do_wrapper => 1,
wrapper_class => [qw(row pull-right)],
wrapper_class => [qw(row)],
);
has_field 'pem.download' => (
type => 'Submit',
value => 'Download PEM',
element_class => [qw(btn btn-primary)],
do_wrapper => 0,
do_label => 0,
);
has_field 'key_actions.download' => (
has_field 'pem.description' => (
type => 'Display',
html => '<div class="ngcp-form-desc">X.509 Client Certificate for API Clients (perl, php etc.)</div>',
do_wrapper => 0,
do_label => 0,
);
has_field 'p12' => (
type => 'Compound',
label => 'Download in PKCS12 Format',
do_label => 1,
do_wrapper => 1,
wrapper_class => [qw(row)],
);
has_field 'p12.download' => (
type => 'Submit',
value => 'Download',
value => 'Download PKCS12',
element_class => [qw(btn btn-primary)],
wrapper_class => [qw(pull-right)],
do_wrapper => 0,
do_label => 0,
);
has_field 'key_actions.delete' => (
has_field 'p12.description' => (
type => 'Display',
html => '<div class="ngcp-form-desc">X.509 Client Certificate for Browsers (Firefox, Chrome etc.). The <strong>password</strong> for the browser import is <strong>sipwise</strong>.</div>',
do_wrapper => 0,
do_label => 0,
);
has_field 'del' => (
type => 'Compound',
label => 'Delete Key',
do_label => 1,
do_wrapper => 1,
wrapper_class => [qw(row)],
);
has_field 'del.delete' => (
type => 'Submit',
value => 'Delete',
element_class => [qw(btn btn-secondary)],
wrapper_class => [qw(pull-right)],
do_wrapper => 0,
do_label => 0,
);
has_field 'del.description' => (
type => 'Display',
html => '<div class="ngcp-form-desc">Remove Certificate and revoke API Access for this user.</div>',
do_wrapper => 0,
do_label => 0,
);
has_block 'actions' => (tag => 'div', class => [qw(modal-footer)], render_list => [qw(key_actions)],);
has_block 'fields' => (
tag => 'div',
class => [qw(modal-body)],
render_list => [qw(pem p12 del)],
);
has_field 'close' => (
type => 'Submit',
do_label => 0,
value => 'Close',
element_class => [qw(btn btn-tertiary)],
);
has_block 'actions' => (
tag => 'div',
class => [qw(modal-footer)],
render_list => [qw(close)],
);
1;
# vim: set tabstop=4 expandtab:

@ -5,22 +5,52 @@ use Moose::Util::TypeConstraints;
extends 'HTML::FormHandler';
has '+widget_wrapper' => (default => 'Bootstrap');
sub build_render_list {[qw(actions)]}
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {[qw/submitid fields actions/]}
sub build_form_element_class {[qw(form-horizontal)]}
has_field 'key_actions' => (
has_field 'gen' => (
type => 'Compound',
do_label => 0,
label => 'Generate Certificate',
do_label => 1,
do_wrapper => 1,
wrapper_class => [qw(row pull-right)],
wrapper_class => [qw(row)],
);
has_field 'key_actions.generate' => (
has_field 'gen.generate' => (
type => 'Submit',
value => 'Generate',
element_class => [qw(btn btn-primary)],
wrapper_class => [qw(pull-right)],
do_wrapper => 0,
do_label => 0,
);
has_block 'actions' => (tag => 'div', class => [qw(modal-footer)], render_list => [qw(key_actions)],);
has_field 'gen.description' => (
type => 'Display',
html => '<div class="ngcp-form-desc">Generates an X.509 Client Certificate for API Clients (PEM Format) and for Browser Import (PKCS12 Format).</div>',
do_wrapper => 0,
do_label => 0,
);
has_block 'fields' => (
tag => 'div',
class => [qw(modal-body)],
render_list => [qw(gen)],
);
has_field 'close' => (
type => 'Submit',
do_label => 0,
value => 'Close',
element_class => [qw(btn btn-tertiary)],
);
has_block 'actions' => (
tag => 'div',
class => [qw(modal-footer)],
render_list => [qw(close)],
);
1;
# vim: set tabstop=4 expandtab:

@ -1,39 +1,16 @@
package NGCP::Panel::Model::CA;
use Sipwise::Base;
use MIME::Base64 qw(decode_base64);
use Path::Tiny qw();
use Time::HiRes qw();
use Types::Path::Tiny qw(AbsDir);
use Path::Tiny qw();
use Sys::Hostname qw(hostname);
extends 'Catalyst::Component';
has('ca_selfsign_template', is => 'ro', isa => 'Str', default => sub { <<'' });
organization = "Sipwise GmbH"
unit = "Dept. of Issuing Snakeoil Certificates"
locality = "Brunn am Gebirge"
state = "Niederösterreich"
country = AT
cn = "*.sipwise.com"
expiration_days = 7300
ca
cert_signing_key
has('server_signingrequest_template', is => 'ro', isa => 'Str', default => sub { <<"" });
cn = "@{[ hostname ]}"
expiration_days = 7300
tls_www_server
signing_key
encryption_key
has('server_signing_template', is => 'ro', isa => 'Str', default => sub { <<'' });
expiration_days = 7300
honor_crq_extensions
sub client_signing_template {
my ($self, $serial) = @_;
return <<"";
cn = "Sipwise NGCP API client certificate"
expiration_days = 7300
expiration_days = 3650
serial = $serial
tls_www_client
signing_key
@ -41,78 +18,51 @@ encryption_key
}
has('log', is => 'rw', isa => 'Log::Log4perl::Catalyst',);
has('prefix', is => 'ro', isa => AbsDir, coerce => 1, default => '/etc/ssl/ngcp/api');
sub COMPONENT {
my ($class, $app, $args) = @_;
$args = $class->merge_config_hashes($class->config, $args);
my $self = $class->new($app, $args);
no autobox::Core; # wonky initialisation order
$self->log($app->log);
return $self;
}
sub make_ca {
my ($self) = @_;
my $command = sprintf 'certtool -p --bits 3248 --outfile %s 1>&- 2>&-', $self->prefix->child('ca-key.pem');
warn "$command\n";
system $command;
my $ca_selfsign_template = Path::Tiny->tempfile;
$ca_selfsign_template->spew_utf8($self->ca_selfsign_template);
$command = sprintf 'certtool -s --load-privkey %s --outfile %s --template %s 1>&- 2>&-',
$self->prefix->child('ca-key.pem'), $self->prefix->child('ca-cert.pem'), $ca_selfsign_template->stringify;
warn "$command\n";
system $command;
return;
}
sub make_server {
my ($self) = @_;
my $command = sprintf 'certtool -p --bits 3248 --outfile %s 1>&- 2>&-', $self->prefix->child('server-key.pem');
warn "$command\n";
system $command;
my $server_signingrequest_template = Path::Tiny->tempfile;
$server_signingrequest_template->spew($self->server_signingrequest_template);
$command = sprintf 'certtool -q --load-privkey %s --outfile %s --template %s 1>&- 2>&-',
$self->prefix->child('server-key.pem'), $self->prefix->child('server-csr.pem'),
$server_signingrequest_template->stringify;
warn "$command\n";
system $command;
my $server_signing_template = Path::Tiny->tempfile;
$server_signing_template->spew($self->server_signing_template);
$command = sprintf 'certtool -c --load-request %s --outfile %s --load-ca-certificate %s --load-ca-privkey %s ' .
'--template %s 1>&- 2>&-', $self->prefix->child('server-csr.pem'), $self->prefix->child('server-cert.pem'),
$self->prefix->child('ca-cert.pem'), $self->prefix->child('ca-key.pem'), $server_signing_template->stringify;
warn "$command\n";
system $command;
return;
}
sub make_client {
my ($self, $serial) = @_;
my ($self, $c, $serial) = @_;
my $client_key = Path::Tiny->tempfile;
my $command = sprintf 'certtool -p --bits 3248 --outfile %s 1>&- 2>&-', $client_key->stringify;
$self->log->debug($command);
$c->log->debug($command);
system $command;
my $client_signing_template = Path::Tiny->tempfile;
my $tmpl = $self->client_signing_template($serial);
$c->log->debug("++++ creating client cert with template: $tmpl");
$c->log->debug($tmpl);
$client_signing_template->spew($tmpl);
my $client_cert = Path::Tiny->tempfile;
$command = sprintf 'certtool -c --load-privkey %s --outfile %s --load-ca-certificate %s --load-ca-privkey %s ' .
'--template %s 1>&- 2>&-', $client_key->stringify, $client_cert->stringify, $self->prefix->child('ca-cert.pem'),
$self->prefix->child('ca-key.pem'), $client_signing_template->stringify;
$self->log->debug($command);
'--template %s 1>&- 2>&-', $client_key->stringify, $client_cert->stringify, $c->config->{ssl}->{certfile},
$c->config->{ssl}->{keyfile}, $client_signing_template->stringify;
$c->log->debug($command);
system $command;
my $cert_file = $self->client_cert_file($serial);
$cert_file->spew($client_cert->slurp . $client_key->slurp =~ s/.*(?=-----BEGIN RSA PRIVATE KEY-----)//mrs);
return;
my $cert = $client_cert->slurp . $client_key->slurp =~ s/.*(?=-----BEGIN RSA PRIVATE KEY-----)//mrs;
$client_cert->remove;
$client_key->remove;
return $cert;
}
sub client_cert_file {
my ($self, $serial) = @_;
return $self->prefix->child("NGCP-API-client-certificate-$serial.pem");
sub make_pkcs12 {
my ($self, $c, $serial, $cert, $pass) = @_;
my $cert_file = Path::Tiny->tempfile;
$cert_file->spew($cert);
my $p12_file = Path::Tiny->tempfile;
my $command = sprintf 'openssl pkcs12 -export -in %s -inkey %s -out %s -password pass:%s -name "NGCP API Client Certificate %d"', $cert_file->stringify, $cert_file->stringify, $p12_file->stringify, $pass, $serial;
$c->log->debug($command);
system $command;
my $p12 = $p12_file->slurp({binmode => ":raw"});
$cert_file->remove;
$p12_file->remove;
return $p12;
}
__END__
@ -122,12 +72,3 @@ __END__
=head1 NAME
NGCP::Panel::Model::CA - certificate management model
=head1 DESCRIPTION
=head2 Generating prerequisite root certificates
perl -mNGCP::Panel::Model::CA -e'
NGCP::Panel::Model::CA->new->make_ca;
NGCP::Panel::Model::CA->new->make_server;
'

@ -78,4 +78,9 @@ log_crash_state 0
app 127.0.0.1:5070
</callflow>
<ssl>
keyfile /etc/ssl/ngcp/api/ca-key.pem
certfile /etc/ssl/ngcp/api/ca-cert.pem
</ssl>
cache_root /run/shm

Loading…
Cancel
Save