aboutsummaryrefslogtreecommitdiff
path: root/perl
diff options
context:
space:
mode:
Diffstat (limited to 'perl')
-rw-r--r--perl/Wallet/Object/Keytab.pm196
-rwxr-xr-xperl/t/keytab.t117
2 files changed, 271 insertions, 42 deletions
diff --git a/perl/Wallet/Object/Keytab.pm b/perl/Wallet/Object/Keytab.pm
index 34f580f..4727590 100644
--- a/perl/Wallet/Object/Keytab.pm
+++ b/perl/Wallet/Object/Keytab.pm
@@ -121,10 +121,12 @@ sub kadmin_addprinc {
return 1;
}
-# Create a keytab from a principal. Return true if successful, false
-# otherwise. If the keytab creation fails, sets the error.
+# Create a keytab from a principal. Takes the principal, the file, and
+# optionally a list of encryption types to which to limit the keytab. Return
+# true if successful, false otherwise. If the keytab creation fails, sets the
+# error.
sub kadmin_ktadd {
- my ($self, $principal, $file) = @_;
+ my ($self, $principal, $file, @enctypes) = @_;
unless ($self->valid_principal ($principal)) {
$self->error ("invalid principal name: $principal");
return undef;
@@ -132,7 +134,12 @@ sub kadmin_ktadd {
if ($Wallet::Config::KEYTAB_REALM) {
$principal .= '@' . $Wallet::Config::KEYTAB_REALM;
}
- my $output = eval { $self->kadmin ("ktadd -q -k $file $principal") };
+ my $command = "ktadd -q -k $file";
+ if (@enctypes) {
+ @enctypes = map { /:/ ? $_ : "$_:normal" } @enctypes;
+ $command .= ' -e "' . join (' ', @enctypes) . '"';
+ }
+ my $output = eval { $self->kadmin ("$command $principal") };
if ($@) {
$self->error ($@);
return undef;
@@ -388,6 +395,77 @@ sub kaserver_clear {
}
##############################################################################
+# Enctype restriction
+##############################################################################
+
+# Set the enctype restrictions for a keytab. Called by attr() and takes a
+# reference to the encryption types to set. Returns true on success and false
+# on failure, setting the object error if it fails.
+sub enctypes_set {
+ my ($self, $enctypes, $user, $host, $time) = @_;
+ $time ||= time;
+ my @trace = ($user, $host, $time);
+ my $name = $self->{name};
+ my %enctypes = map { $_ => 1 } @$enctypes;
+ eval {
+ my $sql = 'select ke_enctype from keytab_enctypes where ke_name = ?';
+ my $sth = $self->{dbh}->prepare ($sql);
+ $sth->execute ($name);
+ my (@current, $entry);
+ while (defined ($entry = $sth->fetchrow_arrayref)) {
+ push (@current, @$entry);
+ }
+ for my $enctype (@current) {
+ if ($enctypes{$enctype}) {
+ delete $enctypes{$enctype};
+ } else {
+ $sql = 'delete from keytab_enctypes where ke_name = ? and
+ ke_enctype = ?';
+ $self->{dbh}->do ($sql, undef, $name, $enctype);
+ $self->log_set ('type_data enctypes', $enctype, undef, @trace);
+ }
+ }
+ for my $enctype (keys %enctypes) {
+ $sql = 'insert into keytab_enctypes (ke_name, ke_enctype) values
+ (?, ?)';
+ $self->{dbh}->do ($sql, undef, $name, $enctype);
+ $self->log_set ('type_data enctypes', undef, $enctype, @trace);
+ }
+ $self->{dbh}->commit;
+ };
+ if ($@) {
+ $self->error ($@);
+ $self->{dbh}->rollback;
+ return undef;
+ }
+ return 1;
+}
+
+# Return a list of the encryption types current set for a keytab. Called by
+# attr() or get(). Returns the empty list on failure or on an empty list of
+# enctype restrictions, but sets the object error on failure so the caller
+# should use that to determine success.
+sub enctypes_list {
+ my ($self) = @_;
+ my @enctypes;
+ eval {
+ my $sql = 'select ke_enctype from keytab_enctypes where ke_name = ?
+ order by ke_enctype';
+ my $sth = $self->{dbh}->prepare ($sql);
+ $sth->execute ($self->{name});
+ my $entry;
+ while (defined ($entry = $sth->fetchrow_arrayref)) {
+ push (@enctypes, @$entry);
+ }
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
+ }
+ return @enctypes;
+}
+
+##############################################################################
# Keytab retrieval
##############################################################################
@@ -434,60 +512,77 @@ sub keytab_retrieve {
# Core methods
##############################################################################
-# Override attr to support setting the sync attribute.
+# Override attr to support setting the enctypes and sync attributes.
sub attr {
my ($self, $attribute, $values, $user, $host, $time) = @_;
+ my %known = map { $_ => 1 } qw(enctypes sync);
undef $self->{error};
- if ($attribute ne 'sync') {
+ unless ($known{$attribute}) {
$self->error ("unknown attribute $attribute");
return;
}
if ($values) {
- if (@$values > 1) {
- $self->error ('only one synchronization target supported');
- return;
- } elsif (@$values and $values->[0] ne 'kaserver') {
- $self->error ("unsupported synchronization target $values->[0]");
- return;
- }
- $time ||= time;
- my @trace = ($user, $host, $time);
- if (@$values) {
- return $self->kaserver_set ($user, $host, $time);
- } else {
- return $self->kaserver_clear ($user, $host, $time);
+ if ($attribute eq 'enctypes') {
+ $self->enctypes_set ($values, $user, $host, $time);
+ } elsif ($attribute eq 'sync') {
+ if (@$values > 1) {
+ $self->error ('only one synchronization target supported');
+ return;
+ } elsif (@$values and $values->[0] ne 'kaserver') {
+ my $target = $values->[0];
+ $self->error ("unsupported synchronization target $target");
+ return;
+ } elsif (@$values) {
+ return $self->kaserver_set ($user, $host, $time);
+ } else {
+ return $self->kaserver_clear ($user, $host, $time);
+ }
}
} else {
- my @targets;
- eval {
- my $sql = 'select ks_target from keytab_sync where ks_name = ?
- order by ks_target';
- my $sth = $self->{dbh}->prepare ($sql);
- $sth->execute ($self->{name});
- my $target;
- while (defined ($target = $sth->fetchrow_array)) {
- push (@targets, $target);
+ if ($attribute eq 'enctypes') {
+ return $self->enctypes_list;
+ } elsif ($attribute eq 'sync') {
+ my @targets;
+ eval {
+ my $sql = 'select ks_target from keytab_sync where ks_name = ?
+ order by ks_target';
+ my $sth = $self->{dbh}->prepare ($sql);
+ $sth->execute ($self->{name});
+ my $target;
+ while (defined ($target = $sth->fetchrow_array)) {
+ push (@targets, $target);
+ }
+ };
+ if ($@) {
+ $self->error ($@);
+ return;
}
- };
- if ($@) {
- $self->error ($@);
- return;
+ return @targets;
}
- return @targets;
}
}
-# Override attr_show to display the sync attribute.
+# Override attr_show to display the enctypes and sync attributes.
sub attr_show {
my ($self) = @_;
+ my $output = '';
my @targets = $self->attr ('sync');
if (not @targets and $self->error) {
return undef;
} elsif (@targets) {
- return sprintf ("%15s: %s\n", 'Synced with', "@targets");
- } else {
- return '';
+ $output .= sprintf ("%15s: %s\n", 'Synced with', "@targets");
+ }
+ my @enctypes = $self->attr ('enctypes');
+ if (not @enctypes and $self->error) {
+ return undef;
+ } elsif (@enctypes) {
+ $output .= sprintf ("%15s: %s\n", 'Enctypes', $enctypes[0]);
+ shift @enctypes;
+ for my $enctype (@enctypes) {
+ $output .= (' ' x 17) . $enctype . "\n";
+ }
}
+ return $output;
}
# Override create to start by creating the principal in Kerberos and only
@@ -514,6 +609,18 @@ sub destroy {
return undef;
}
}
+ eval {
+ my $sql = 'delete from keytab_sync where ks_name = ?';
+ $self->{dbh}->do ($sql, undef, $self->{name});
+ $sql = 'delete from keytab_enctypes where ke_name = ?';
+ $self->{dbh}->do ($sql, undef, $self->{name});
+ $self->{dbh}->commit;
+ };
+ if ($@) {
+ $self->error ($@);
+ $self->{dbh}->rollback;
+ return undef;
+ }
return undef if not $self->kadmin_delprinc ($self->{name});
return $self->SUPER::destroy ($user, $host, $time);
}
@@ -541,7 +648,8 @@ sub get {
}
my $file = $Wallet::Config::KEYTAB_TMP . "/keytab.$$";
unlink $file;
- return undef if not $self->kadmin_ktadd ($self->{name}, $file);
+ my @enctypes = $self->attr ('enctypes');
+ return undef if not $self->kadmin_ktadd ($self->{name}, $file, @enctypes);
local *KEYTAB;
unless (open (KEYTAB, '<', $file)) {
my $princ = $self->{name};
@@ -627,6 +735,18 @@ supported:
=over 4
+=item enctypes
+
+Restricts the generated keytab to a specific set of encryption types. The
+values of this attribute must be enctype strings recognized by Kerberos
+(strings like C<aes256-cts> or C<des-cbc-crc>). Note that the salt should
+not be included; since the salt is irrelevant for keytab keys, it will
+always be set to C<normal> by the wallet.
+
+If this attribute is set, the specified enctype list will be passed to
+ktadd when get() is called for that keytab. If it is not set, the default
+set in the KDC will be used.
+
=item sync
Sets the external systems to which the key of a given principal is
diff --git a/perl/t/keytab.t b/perl/t/keytab.t
index abe7bf5..89e1440 100755
--- a/perl/t/keytab.t
+++ b/perl/t/keytab.t
@@ -8,12 +8,22 @@
#
# See LICENSE for licensing terms.
-use Test::More tests => 160;
+use Test::More tests => 172;
use Wallet::Config;
use Wallet::Object::Keytab;
use Wallet::Server;
+# Mapping of klist -ke encryption type names to the strings that Kerberos uses
+# internally. It's very annoying to have to maintain this, and it probably
+# breaks with Heimdal.
+my %enctype =
+ ('triple des cbc mode with hmac/sha1' => 'des3-cbc-sha1',
+ 'des cbc mode with crc-32' => 'des-cbc-crc',
+ 'des cbc mode with rsa-md5' => 'des-cbc-md5',
+ 'aes-256 cts mode with 96-bit sha-1 hmac' => 'aes256-cts',
+ 'arcfour with hmac/md5' => 'rc4-hmac');
+
# Use a local SQLite database for testing.
$Wallet::Config::DB_DRIVER = 'SQLite';
$Wallet::Config::DB_INFO = 'wallet-db';
@@ -116,6 +126,31 @@ sub valid {
return $result;
}
+# Given keytab data, write it to a file and try to determine the enctypes of
+# the keys present in that file. Returns the enctypes as a list, with UNKNOWN
+# for encryption types that weren't recognized. This is an ugly way of doing
+# this.
+sub enctypes {
+ my ($keytab) = @_;
+ open (KEYTAB, '>', 'keytab') or die "cannot create keytab: $!\n";
+ print KEYTAB $keytab;
+ close KEYTAB;
+ open (KLIST, '-|', 'klist', '-ke', 'keytab')
+ or die "cannot run klist: $!\n";
+ my @enctypes;
+ local $_;
+ while (<KLIST>) {
+ next unless /^ *\d+ /;
+ my ($string) = /\((.*)\)\s*$/;
+ next unless $string;
+ $enctype = $enctype{lc $string} || 'UNKNOWN';
+ push (@enctypes, $enctype);
+ }
+ close KLIST;
+ unlink 'keytab';
+ return @enctypes;
+}
+
# Given a Wallet::Object::Keytab object, the keytab data, the Kerberos v5
# principal, and the Kerberos v4 principal, write the keytab to a file,
# generate a srvtab, and try authenticating using k4start.
@@ -402,7 +437,7 @@ SKIP: {
# Tests for kaserver synchronization support.
SKIP: {
- skip 'no keytab configuration', 94 unless -f 't/data/test.keytab';
+ skip 'no keytab configuration', 98 unless -f 't/data/test.keytab';
# Test the principal mapping. We can do this without having a kaserver
# configuration. We only need a basic keytab object configuration. Do
@@ -492,7 +527,7 @@ EOO
is ($show, $expected, ' and show now displays the attribute');
# Set up our configuration.
- skip 'no AFS kaserver configuration', 27 unless -f 't/data/test.srvtab';
+ skip 'no AFS kaserver configuration', 31 unless -f 't/data/test.srvtab';
$Wallet::Config::KEYTAB_FILE = 't/data/test.keytab';
$Wallet::Config::KEYTAB_PRINCIPAL = contents ('t/data/test.principal');
$Wallet::Config::KEYTAB_REALM = contents ('t/data/test.realm');
@@ -565,10 +600,84 @@ EOO
ok (! -f "keytab.$$", ' and the temporary file was cleaned up');
$Wallet::Config::KEYTAB_AFS_KASETKEY = '../kasetkey/kasetkey';
- # Destroy the principal.
+ # Destroy the principal and recreate it and make sure we cleaned up.
is ($one->destroy (@trace), 1, 'Destroying wallet/one works');
ok (! valid_srvtab ($one, $keytab, $k5, $k4),
' and the principal is gone');
+ $one = eval {
+ Wallet::Object::Keytab->create ('keytab', 'wallet/one', $dbh, @trace)
+ };
+ ok (defined ($one), ' and recreating it succeeds');
+ @targets = $one->attr ('sync');
+ is (scalar (@targets), 0, ' and now there is no attribute');
+ is ($one->error, undef, ' and no error');
+
+ # Now destroy it for good.
+ is ($one->destroy (@trace), 1, 'Destroying wallet/one works');
+}
+
+# Tests for enctype restriction.
+SKIP: {
+ skip 'no keytab configuration', 8 unless -f 't/data/test.keytab';
+
+ # Set up our configuration.
+ $Wallet::Config::KEYTAB_FILE = 't/data/test.keytab';
+ $Wallet::Config::KEYTAB_PRINCIPAL = contents ('t/data/test.principal');
+ $Wallet::Config::KEYTAB_REALM = contents ('t/data/test.realm');
+ $Wallet::Config::KEYTAB_TMP = '.';
+ my $realm = $Wallet::Config::KEYTAB_REALM;
+ my $principal = $Wallet::Config::KEYTAB_PRINCIPAL;
+
+ # Create an object for testing and determine the enctypes we have to work
+ # with.
+ my $one = eval {
+ Wallet::Object::Keytab->create ('keytab', 'wallet/one', $dbh, @trace)
+ };
+ ok (defined ($one), 'Creating wallet/one succeeds');
+ my $keytab = $one->get (@trace);
+ ok (defined ($keytab), ' and retrieving the keytab works');
+ my @enctypes = sort grep { $_ ne 'UNKNOWN' } enctypes ($keytab);
+
+ # No enctypes we recognize?
+ skip 'no recognized enctypes', 6 unless @enctypes;
+
+ # We can test. Add the enctypes we recognized to the enctypes table so
+ # that we'll be allowed to use them.
+ for (@enctypes) {
+ my $sql = "insert into keytab_enctypes (ke_name, ke_enctype)
+ values ('wallet/one', ?)";
+ $dbh->do ($sql, undef, $_);
+ }
+
+ # Set those encryption types and make sure we get back a limited keytab.
+ is ($one->attr ('enctypes', [ @enctypes ], @trace), 1,
+ 'Setting enctypes works');
+ my @values = $one->attr ('enctypes');
+ is ("@values", "@enctypes", ' and we get back the right enctype list');
+ my $eshow = join ("\n" . (' ' x 17), @enctypes);
+ $eshow =~ s/\s+\z/\n/;
+ my $show = $one->show;
+ $show =~ s/^(\s*(Created|Downloaded) on:) \d+$/$1 0/mg;
+ $expected = <<"EOO";
+ Type: keytab
+ Name: wallet/one
+ Enctypes: $eshow
+ Created by: $user
+ Created from: $host
+ Created on: 0
+ Downloaded by: $user
+Downloaded from: $host
+ Downloaded on: 0
+EOO
+ is ($show, $expected, ' and show now displays the enctype list');
+ $keytab = $one->get (@trace);
+ ok (defined ($keytab), ' and retrieving the keytab still works');
+ @values = enctypes ($keytab);
+ @values = sort @values;
+ is ("@values", "@enctypes", ' and the keytab has the right keys');
+
+ # All done. Clean up.
+ is ($one->destroy (@trace), 1, 'Destroying wallet/one works');
}
# Clean up.