summaryrefslogtreecommitdiff
path: root/perl/lib/Wallet/Kadmin/Heimdal.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perl/lib/Wallet/Kadmin/Heimdal.pm')
-rw-r--r--perl/lib/Wallet/Kadmin/Heimdal.pm315
1 files changed, 315 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..1208801
--- /dev/null
+++ b/perl/lib/Wallet/Kadmin/Heimdal.pm
@@ -0,0 +1,315 @@
+# 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 warnings;
+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