summaryrefslogtreecommitdiff
path: root/perl/lib/Wallet/Kadmin/Heimdal.pm
diff options
context:
space:
mode:
authorRuss Allbery <eagle@eyrie.org>2014-07-11 21:39:23 -0700
committerRuss Allbery <rra@stanford.edu>2014-07-11 22:39:05 -0700
commit1575d5c34a2c6235bbf6a5010f8a8c142fe47079 (patch)
tree29e51ed64f28a37530ec0b21fc24b6d20de1d6ca /perl/lib/Wallet/Kadmin/Heimdal.pm
parentda0aba21779529d98436e42323fc12f702390969 (diff)
Switch to Module::Build for the Perl module
The wallet server now requires Perl 5.8 or later (instead of 5.006 in previous versions) and is now built with Module::Build instead of ExtUtils::MakeMaker. This should be transparent to anyone not working with the source code, since Perl 5.8 was released in 2002, but Module::Build is now required to build the wallet server. It is included in some versions of Perl, or can be installed separately from CPAN, distribution packages, or other sources. Also reorganize the test suite to use subdirectories. Change-Id: Id06120ba2bad1ebbfee3d8a48ca2f25869463165 Reviewed-on: https://gerrit.stanford.edu/1530 Reviewed-by: Russ Allbery <rra@stanford.edu> Tested-by: Russ Allbery <rra@stanford.edu>
Diffstat (limited to 'perl/lib/Wallet/Kadmin/Heimdal.pm')
-rw-r--r--perl/lib/Wallet/Kadmin/Heimdal.pm314
1 files changed, 314 insertions, 0 deletions
diff --git a/perl/lib/Wallet/Kadmin/Heimdal.pm b/perl/lib/Wallet/Kadmin/Heimdal.pm
new file mode 100644
index 0000000..42de8e0
--- /dev/null
+++ b/perl/lib/Wallet/Kadmin/Heimdal.pm
@@ -0,0 +1,314 @@
+# Wallet::Kadmin::Heimdal -- Wallet Kerberos administration API for Heimdal.
+#
+# Written by Jon Robertson <jonrober@stanford.edu>
+# Copyright 2009, 2010, 2014
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# See LICENSE for licensing terms.
+
+##############################################################################
+# Modules and declarations
+##############################################################################
+
+package Wallet::Kadmin::Heimdal;
+require 5.006;
+
+use strict;
+use vars qw(@ISA $VERSION);
+
+use Heimdal::Kadm5 qw(KRB5_KDB_DISALLOW_ALL_TIX);
+use Wallet::Config ();
+use Wallet::Kadmin ();
+
+@ISA = qw(Wallet::Kadmin);
+
+# This version should be increased on any code change to this module. Always
+# use two digits for the minor version with a leading zero if necessary so
+# that it will sort properly.
+$VERSION = '0.04';
+
+##############################################################################
+# Utility functions
+##############################################################################
+
+# Add the realm to the end of the principal if no realm is currently present.
+sub canonicalize_principal {
+ my ($self, $principal) = @_;
+ if ($Wallet::Config::KEYTAB_REALM && $principal !~ /\@/) {
+ $principal .= '@' . $Wallet::Config::KEYTAB_REALM;
+ }
+ return $principal;
+}
+
+# Generate a long random password.
+#
+# Please note: This is not a cryptographically secure password! It's used
+# only because the Heimdal kadmin interface requires a password on create.
+# The keys will be set before the principal is ever set active, so it will
+# never be possible to use the password. It just needs to be random in case
+# password quality checks are applied to it.
+#
+# Make the password reasonably long and include a variety of character classes
+# so that it should pass any password strength checking.
+sub insecure_random_password {
+ my ($self) = @_;
+ my @classes = (
+ 'abcdefghijklmnopqrstuvwxyz',
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+ '0123456789',
+ '~`!@#$%^&*()-_+={[}]|:;<,>.?/'
+ );
+ my $password = q{};
+ for my $i (1..20) {
+ my $class = $i % scalar (@classes);
+ my $alphabet = $classes[$class];
+ my $letter = substr ($alphabet, int (rand (length $alphabet)), 1);
+ $password .= $letter;
+ }
+ return $password;
+}
+
+##############################################################################
+# Public interfaces
+##############################################################################
+
+# Check whether a given principal already exists in Kerberos. Returns true if
+# so, false otherwise.
+sub exists {
+ my ($self, $principal) = @_;
+ $principal = $self->canonicalize_principal ($principal);
+ my $kadmin = $self->{client};
+ my $princdata = eval { $kadmin->getPrincipal ($principal) };
+ if ($@) {
+ $self->error ("error getting principal: $@");
+ return;
+ }
+ return $princdata ? 1 : 0;
+}
+
+# Create a principal in Kerberos. If there is an error, return undef and set
+# the error. Return 1 on success or the principal already existing.
+sub create {
+ my ($self, $principal) = @_;
+ $principal = $self->canonicalize_principal ($principal);
+ my $exists = eval { $self->exists ($principal) };
+ if ($@) {
+ $self->error ("error adding principal $principal: $@");
+ return;
+ }
+ return 1 if $exists;
+
+ # The way Heimdal::Kadm5 works, we create a principal object, create the
+ # actual principal set inactive, then randomize it and activate it. We
+ # have to set a password, even though we're about to replace it with
+ # random keys, but since the principal is created inactive, it doesn't
+ # have to be a very good one.
+ my $kadmin = $self->{client};
+ eval {
+ my $princdata = $kadmin->makePrincipal ($principal);
+ my $attrs = $princdata->getAttributes;
+ $attrs |= KRB5_KDB_DISALLOW_ALL_TIX;
+ $princdata->setAttributes ($attrs);
+ my $password = $self->insecure_random_password;
+ $kadmin->createPrincipal ($princdata, $password, 0);
+ $kadmin->randKeyPrincipal ($principal);
+ $kadmin->enablePrincipal ($principal);
+ };
+ if ($@) {
+ $self->error ("error adding principal $principal: $@");
+ return;
+ }
+ return 1;
+}
+
+# Create a keytab for a principal. Returns the keytab as binary data or undef
+# on failure, setting the error.
+sub keytab {
+ my ($self, $principal) = @_;
+ $principal = $self->canonicalize_principal ($principal);
+ my $kadmin = $self->{client};
+ my $file = $Wallet::Config::KEYTAB_TMP . "/keytab.$$";
+ unlink $file;
+ my $princdata = eval { $kadmin->getPrincipal ($principal) };
+ if ($@) {
+ $self->error ("error creating keytab for $principal: $@");
+ return;
+ } elsif (!$princdata) {
+ $self->error ("error creating keytab for $principal: principal does"
+ . " not exist");
+ return;
+ }
+ eval { $kadmin->extractKeytab ($princdata, $file) };
+ if ($@) {
+ $self->error ("error creating keytab for principal: $@");
+ return;
+ }
+ return $self->read_keytab ($file);
+}
+
+# Create a keytab for a principal, randomizing the keys for that principal at
+# the same time. Takes the principal and an optional list of encryption types
+# to which to limit the keytab. Return the keytab data on success and undef
+# on failure. If the keytab creation fails, sets the error.
+sub keytab_rekey {
+ my ($self, $principal, @enctypes) = @_;
+ $principal = $self->canonicalize_principal ($principal);
+
+ # The way Heimdal works, you can only remove enctypes from a principal,
+ # not add them back in. So we need to run randkeyPrincipal first each
+ # time to restore all possible enctypes and then whittle them back down
+ # to those we have been asked for this time.
+ my $kadmin = $self->{client};
+ eval { $kadmin->randKeyPrincipal ($principal) };
+ if ($@) {
+ $self->error ("error creating keytab for $principal: could not"
+ . " reinit enctypes: $@");
+ return;
+ }
+ my $princdata = eval { $kadmin->getPrincipal ($principal) };
+ if ($@) {
+ $self->error ("error creating keytab for $principal: $@");
+ return;
+ } elsif (!$princdata) {
+ $self->error ("error creating keytab for $principal: principal does"
+ . " not exist");
+ return;
+ }
+
+ # Now actually remove any non-requested enctypes, if we requested any.
+ if (@enctypes) {
+ my $alltypes = $princdata->getKeytypes;
+ my %wanted = map { $_ => 1 } @enctypes;
+ for my $key (@{ $alltypes }) {
+ my $keytype = $key->[0];
+ next if exists $wanted{$keytype};
+ eval { $princdata->delKeytypes ($keytype) };
+ if ($@) {
+ $self->error ("error removing keytype $keytype from the"
+ . " keytab: $@");
+ return;
+ }
+ }
+ eval { $kadmin->modifyPrincipal ($princdata) };
+ if ($@) {
+ $self->error ("error saving principal modifications: $@");
+ return;
+ }
+ }
+
+ # Create the keytab.
+ my $file = $Wallet::Config::KEYTAB_TMP . "/keytab.$$";
+ unlink $file;
+ eval { $kadmin->extractKeytab ($princdata, $file) };
+ if ($@) {
+ $self->error ("error creating keytab for principal: $@");
+ return;
+ }
+ return $self->read_keytab ($file);
+}
+
+# Delete a principal from Kerberos. Return true if successful, false
+# otherwise. If the deletion fails, sets the error. If the principal doesn't
+# exist, return success; we're bringing reality in line with our expectations.
+sub destroy {
+ my ($self, $principal) = @_;
+ $principal = $self->canonicalize_principal ($principal);
+ my $exists = eval { $self->exists ($principal) };
+ if ($@) {
+ $self->error ("error checking principal existance: $@");
+ return;
+ } elsif (not $exists) {
+ return 1;
+ }
+ my $kadmin = $self->{client};
+ my $retval = eval { $kadmin->deletePrincipal ($principal) };
+ if ($@) {
+ $self->error ("error deleting $principal: $@");
+ return;
+ }
+ return 1;
+}
+
+# Create a new Wallet::Kadmin::Heimdal object and its underlying
+# Heimdal::Kadm5 object.
+sub new {
+ my ($class) = @_;
+ unless (defined ($Wallet::Config::KEYTAB_PRINCIPAL)
+ and defined ($Wallet::Config::KEYTAB_FILE)
+ and defined ($Wallet::Config::KEYTAB_REALM)) {
+ die "keytab object implementation not configured\n";
+ }
+ unless (defined ($Wallet::Config::KEYTAB_TMP)) {
+ die "KEYTAB_TMP configuration variable not set\n";
+ }
+ my @options = (RaiseError => 1,
+ Principal => $Wallet::Config::KEYTAB_PRINCIPAL,
+ Realm => $Wallet::Config::KEYTAB_REALM,
+ Keytab => $Wallet::Config::KEYTAB_FILE);
+ if ($Wallet::Config::KEYTAB_HOST) {
+ push (@options, Server => $Wallet::Config::KEYTAB_HOST);
+ }
+ my $client = Heimdal::Kadm5::Client->new (@options);
+ my $self = { client => $client };
+ bless ($self, $class);
+ return $self;
+}
+
+1;
+__END__
+
+##############################################################################
+# Documentation
+##############################################################################
+
+=for stopwords
+keytabs keytab kadmin KDC API Allbery Heimdal unlinked
+
+=head1 NAME
+
+Wallet::Kadmin::Heimdal - Wallet Kerberos administration API for Heimdal
+
+=head1 SYNOPSIS
+
+ my $kadmin = Wallet::Kadmin::Heimdal->new;
+ $kadmin->create ('host/foo.example.com');
+ $kadmin->keytab_rekey ('host/foo.example.com', 'keytab',
+ 'aes256-cts-hmac-sha1-96');
+ my $data = $kadmin->keytab ('host/foo.example.com');
+ my $exists = $kadmin->exists ('host/oldshell.example.com');
+ $kadmin->destroy ('host/oldshell.example.com') if $exists;
+
+=head1 DESCRIPTION
+
+Wallet::Kadmin::Heimdal implements the Wallet::Kadmin API for Heimdal,
+providing an interface to create and delete principals and create keytabs.
+It provides the API documented in L<Wallet::Kadmin> for a Heimdal KDC.
+
+To use this class, several configuration parameters must be set. See
+L<Wallet::Config/"KEYTAB OBJECT CONFIGURATION"> for details.
+
+=head1 FILES
+
+=over 4
+
+=item KEYTAB_TMP/keytab.<pid>
+
+The keytab is created in this file and then read into memory. KEYTAB_TMP
+is set in the wallet configuration, and <pid> is the process ID of the
+current process. The file is unlinked after being read.
+
+=back
+
+=head1 SEE ALSO
+
+kadmin(8), Wallet::Config(3), Wallet::Kadmin(3),
+Wallet::Object::Keytab(3), wallet-backend(8)
+
+This module is part of the wallet system. The current version is
+available from L<http://www.eyrie.org/~eagle/software/wallet/>.
+
+=head1 AUTHORS
+
+Russ Allbery <eagle@eyrie.org> and Jon Robertson <jonrober@stanford.edu>.
+
+=cut