diff options
Diffstat (limited to 'perl/lib/Wallet/Kadmin')
| -rw-r--r-- | perl/lib/Wallet/Kadmin/Heimdal.pm | 314 | ||||
| -rw-r--r-- | perl/lib/Wallet/Kadmin/MIT.pm | 323 | 
2 files changed, 637 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 diff --git a/perl/lib/Wallet/Kadmin/MIT.pm b/perl/lib/Wallet/Kadmin/MIT.pm new file mode 100644 index 0000000..1ae01bf --- /dev/null +++ b/perl/lib/Wallet/Kadmin/MIT.pm @@ -0,0 +1,323 @@ +# Wallet::Kadmin::MIT -- Wallet Kerberos administration API for MIT. +# +# Written by Russ Allbery <eagle@eyrie.org> +# Pulled into a module by Jon Robertson <jonrober@stanford.edu> +# Copyright 2007, 2008, 2009, 2010 +#     The Board of Trustees of the Leland Stanford Junior University +# +# See LICENSE for licensing terms. + +############################################################################## +# Modules and declarations +############################################################################## + +package Wallet::Kadmin::MIT; +require 5.006; + +use strict; +use vars qw(@ISA $VERSION); + +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.03'; + +############################################################################## +# kadmin Interaction +############################################################################## + +# Make sure that principals are well-formed and don't contain characters that +# will cause us problems when talking to kadmin.  Takes a principal and +# returns true if it's okay, false otherwise.  Note that we do not permit +# realm information here. +sub valid_principal { +    my ($self, $principal) = @_; +    return scalar ($principal =~ m,^[\w-]+(/[\w_.-]+)?\z,); +} + +# Run a kadmin command and capture the output.  Returns the output, either as +# a list of lines or, in scalar context, as one string.  The exit status of +# kadmin is often worthless. +sub kadmin { +    my ($self, $command) = @_; +    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"; +    } +    my @args = ('-p', $Wallet::Config::KEYTAB_PRINCIPAL, '-k', '-t', +                $Wallet::Config::KEYTAB_FILE, '-q', $command); +    push (@args, '-s', $Wallet::Config::KEYTAB_HOST) +        if $Wallet::Config::KEYTAB_HOST; +    push (@args, '-r', $Wallet::Config::KEYTAB_REALM) +        if $Wallet::Config::KEYTAB_REALM; +    my $pid = open (KADMIN, '-|'); +    if (not defined $pid) { +        $self->error ("cannot fork: $!"); +        return; +    } elsif ($pid == 0) { +        $self->{fork_callback} () if $self->{fork_callback}; +        unless (open (STDERR, '>&STDOUT')) { +            warn "wallet: cannot dup stdout: $!\n"; +            exit 1; +        } +        unless (exec ($Wallet::Config::KEYTAB_KADMIN, @args)) { +            warn "wallet: cannot run $Wallet::Config::KEYTAB_KADMIN: $!\n"; +            exit 1; +        } +    } +    local $_; +    my @output; +    while (<KADMIN>) { +        if (/^wallet: cannot /) { +            s/^wallet: //; +            $self->error ($_); +            return; +        } +        push (@output, $_) unless /Authenticating as principal/; +    } +    close KADMIN; +    return wantarray ? @output : join ('', @output); +} + +############################################################################## +# Public interfaces +############################################################################## + +# Set a callback to be called for forked kadmin processes. +sub fork_callback { +    my ($self, $callback) = @_; +    $self->{fork_callback} = $callback; +} + +# Check whether a given principal already exists in Kerberos.  Returns true if +# so, false otherwise.  Returns undef if kadmin fails, with the error already +# set by kadmin. +sub exists { +    my ($self, $principal) = @_; +    return unless $self->valid_principal ($principal); +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $output = $self->kadmin ("getprinc $principal"); +    if (!defined $output) { +        return; +    } elsif ($output =~ /^get_principal: /) { +        return 0; +    } else { +        return 1; +    } +} + +# Create a principal in Kerberos.  Sets the error and returns undef on failure, +# and returns 1 on either success or the principal already existing. +sub create { +    my ($self, $principal) = @_; +    unless ($self->valid_principal ($principal)) { +        $self->error ("invalid principal name $principal"); +        return; +    } +    return 1 if $self->exists ($principal); +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $flags = $Wallet::Config::KEYTAB_FLAGS || ''; +    my $output = $self->kadmin ("addprinc -randkey $flags $principal"); +    if (!defined $output) { +        return; +    } elsif ($output =~ /^add_principal: (.*)/m) { +        $self->error ("error adding principal $principal: $1"); +        return; +    } +    return 1; +} + +# Retrieve an existing keytab from the KDC via a remctl call.  The KDC needs +# to be running the keytab-backend script and support the keytab retrieve +# remctl command.  In addition, the user must have configured us with the path +# to a ticket cache and the host to which to connect with remctl.  Returns the +# keytab on success and undef on failure. +sub keytab { +    my ($self, $principal) = @_; +    my $host = $Wallet::Config::KEYTAB_REMCTL_HOST; +    unless ($host and $Wallet::Config::KEYTAB_REMCTL_CACHE) { +        $self->error ('keytab unchanging support not configured'); +        return; +    } +    eval { require Net::Remctl }; +    if ($@) { +        $self->error ("keytab unchanging support not available: $@"); +        return; +    } +    if ($principal !~ /\@/ && $Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    local $ENV{KRB5CCNAME} = $Wallet::Config::KEYTAB_REMCTL_CACHE; +    my $port = $Wallet::Config::KEYTAB_REMCTL_PORT || 0; +    my $remctl_princ = $Wallet::Config::KEYTAB_REMCTL_PRINCIPAL || ''; +    my @command = ('keytab', 'retrieve', $principal); +    my $result = Net::Remctl::remctl ($host, $port, $remctl_princ, @command); +    if ($result->error) { +        $self->error ("cannot retrieve keytab for $principal: ", +                      $result->error); +        return; +    } elsif ($result->status != 0) { +        my $error = $result->stderr; +        $error =~ s/\s+$//; +        $error =~ s/\n/ /g; +        $self->error ("cannot retrieve keytab for $principal: $error"); +        return; +    } else { +        return $result->stdout; +    } +} + +# Create a keytab for a principal, randomizing the keys for that principal +# in the process.  Takes the principal and an optional list of encryption +# types to which to limit the keytab.  Return the keytab data on success +# and undef otherwise.  If the keytab creation fails, sets the error. +sub keytab_rekey { +    my ($self, $principal, @enctypes) = @_; +    unless ($self->valid_principal ($principal)) { +        $self->error ("invalid principal name: $principal"); +        return; +    } +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $file = $Wallet::Config::KEYTAB_TMP . "/keytab.$$"; +    unlink $file; +    my $command = "ktadd -q -k $file"; +    if (@enctypes) { +        @enctypes = map { /:/ ? $_ : "$_:normal" } @enctypes; +        $command .= ' -e "' . join (' ', @enctypes) . '"'; +    } +    my $output = $self->kadmin ("$command $principal"); +    if (!defined $output) { +        return; +    } elsif ($output =~ /^(?:kadmin|ktadd): (.*)/m) { +        $self->error ("error creating keytab for $principal: $1"); +        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) = @_; +    unless ($self->valid_principal ($principal)) { +        $self->error ("invalid principal name: $principal"); +    } +    my $exists = $self->exists ($principal); +    if (!defined $exists) { +        return; +    } elsif (not $exists) { +        return 1; +    } +    if ($Wallet::Config::KEYTAB_REALM) { +        $principal .= '@' . $Wallet::Config::KEYTAB_REALM; +    } +    my $output = $self->kadmin ("delprinc -force $principal"); +    if (!defined $output) { +        return; +    } elsif ($output =~ /^delete_principal: (.*)/m) { +        $self->error ("error deleting $principal: $1"); +        return; +    } +    return 1; +} + +# Create a new MIT kadmin object.  Very empty for the moment, but later it +# will probably fill out if we go to using a module rather than calling +# kadmin directly. +sub new { +    my ($class) = @_; +    unless (defined ($Wallet::Config::KEYTAB_TMP)) { +        die "KEYTAB_TMP configuration variable not set\n"; +    } +    my $self = {}; +    bless ($self, $class); +    return $self; +} + +1; +__END__ + +############################################################################## +# Documentation +############################################################################## + +=for stopwords +rekeying rekeys remctl backend keytabs keytab kadmin KDC API Allbery +unlinked + +=head1 NAME + +Wallet::Kadmin::MIT - Wallet Kerberos administration API for MIT + +=head1 SYNOPSIS + +    my $kadmin = Wallet::Kadmin::MIT->new; +    $kadmin->create ('host/foo.example.com'); +    my $data = $kadmin->keytab_rekey ('host/foo.example.com', +                                      'aes256-cts-hmac-sha1-96'); +    $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::MIT implements the Wallet::Kadmin API for MIT Kerberos, +providing an interface to create and delete principals and create keytabs. +It provides the API documented in L<Wallet::Kadmin> for an MIT Kerberos +KDC. + +MIT Kerberos does not provide any method via the kadmin network protocol +to retrieve a keytab for a principal without rekeying it, so the keytab() +method (as opposed to keytab_rekey(), which rekeys the principal) is +implemented using a remctl backend.  For that method (used for unchanging +keytab objects) to work, the necessary wallet configuration and remctl +interface on the KDC must be set up. + +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 LIMITATIONS + +Currently, this implementation calls an external B<kadmin> program rather +than using a native Perl module and therefore requires B<kadmin> be +installed and parses its output.  It may miss some error conditions if the +output of B<kadmin> ever changes. + +=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 | 
