summaryrefslogtreecommitdiff
path: root/perl/Wallet/Object/WAKeyring.pm
diff options
context:
space:
mode:
Diffstat (limited to 'perl/Wallet/Object/WAKeyring.pm')
-rw-r--r--perl/Wallet/Object/WAKeyring.pm370
1 files changed, 0 insertions, 370 deletions
diff --git a/perl/Wallet/Object/WAKeyring.pm b/perl/Wallet/Object/WAKeyring.pm
deleted file mode 100644
index f8bd0f7..0000000
--- a/perl/Wallet/Object/WAKeyring.pm
+++ /dev/null
@@ -1,370 +0,0 @@
-# Wallet::Object::WAKeyring -- WebAuth keyring object implementation.
-#
-# Written by Russ Allbery <eagle@eyrie.org>
-# Copyright 2012, 2013
-# The Board of Trustees of the Leland Stanford Junior University
-#
-# See LICENSE for licensing terms.
-
-##############################################################################
-# Modules and declarations
-##############################################################################
-
-package Wallet::Object::WAKeyring;
-require 5.006;
-
-use strict;
-use vars qw(@ISA $VERSION);
-
-use Digest::MD5 qw(md5_hex);
-use Fcntl qw(LOCK_EX);
-use Wallet::Config ();
-use Wallet::Object::Base;
-use WebAuth 3.06 qw(WA_KEY_AES WA_AES_128);
-
-@ISA = qw(Wallet::Object::Base);
-
-# 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.01';
-
-##############################################################################
-# File naming
-##############################################################################
-
-# Returns the path into which that keyring object will be stored or undef on
-# error. On error, sets the internal error.
-sub file_path {
- my ($self) = @_;
- my $name = $self->{name};
- unless ($Wallet::Config::WAKEYRING_BUCKET) {
- $self->error ('WebAuth keyring support not configured');
- return;
- }
- unless ($name) {
- $self->error ('WebAuth keyring objects may not have empty names');
- return;
- }
- my $hash = substr (md5_hex ($name), 0, 2);
- $name =~ s/([^\w-])/sprintf ('%%%02X', ord ($1))/ge;
- my $parent = "$Wallet::Config::WAKEYRING_BUCKET/$hash";
- unless (-d $parent || mkdir ($parent, 0700)) {
- $self->error ("cannot create keyring bucket $hash: $!");
- return;
- }
- return "$Wallet::Config::WAKEYRING_BUCKET/$hash/$name";
-}
-
-##############################################################################
-# Core methods
-##############################################################################
-
-# Override destroy to delete the file as well.
-sub destroy {
- my ($self, $user, $host, $time) = @_;
- my $id = $self->{type} . ':' . $self->{name};
- my $path = $self->file_path;
- if (defined ($path) && -f $path && !unlink ($path)) {
- $self->error ("cannot delete $id: $!");
- return;
- }
- return $self->SUPER::destroy ($user, $host, $time);
-}
-
-# Update the keyring if needed, and then return the contents of the current
-# keyring.
-sub get {
- my ($self, $user, $host, $time) = @_;
- $time ||= time;
- my $id = $self->{type} . ':' . $self->{name};
- if ($self->flag_check ('locked')) {
- $self->error ("cannot get $id: object is locked");
- return;
- }
- my $path = $self->file_path;
- return unless defined $path;
-
- # Create a WebAuth context and ensure we can load the relevant modules.
- my $wa = eval { WebAuth->new };
- if ($@) {
- $self->error ("cannot initialize WebAuth: $@");
- return;
- }
-
- # Check if the keyring already exists. If not, create a new one with a
- # single key that's immediately valid and two more that will become valid
- # in the future.
- #
- # If the keyring does already exist, get a lock on the file. At the end
- # of this process, we'll do an atomic update and then drop our lock.
- #
- # FIXME: There are probably better ways to do this. There are some race
- # conditions here, particularly with new keyrings.
- unless (open (FILE, '+<', $path)) {
- my $data;
- eval {
- my $key = $wa->key_create (WA_KEY_AES, WA_AES_128);
- my $ring = $wa->keyring_new ($key);
- $key = $wa->key_create (WA_KEY_AES, WA_AES_128);
- my $valid = time + $Wallet::Config::WAKEYRING_REKEY_INTERVAL;
- $ring->add (time, $valid, $key);
- $key = $wa->key_create (WA_KEY_AES, WA_AES_128);
- $valid += $Wallet::Config::WAKEYRING_REKEY_INTERVAL;
- $ring->add (time, $valid, $key);
- $data = $ring->encode;
- $ring->write ($path);
- };
- if ($@) {
- $self->error ("cannot create new keyring");
- return;
- };
- $self->log_action ('get', $user, $host, $time);
- return $data;
- }
- unless (flock (FILE, LOCK_EX)) {
- $self->error ("cannot get lock on keyring: $!");
- return;
- }
-
- # Read the keyring.
- my $ring = eval { WebAuth::Keyring->read ($wa, $path) };
- if ($@) {
- $self->error ("cannot read keyring: $@");
- return;
- }
-
- # If the most recent key has a valid-after older than now +
- # WAKEYRING_REKEY_INTERVAL, we generate a new key with a valid_after of
- # now + 2 * WAKEYRING_REKEY_INTERVAL.
- my ($count, $newest) = (0, 0);
- for my $entry ($ring->entries) {
- $count++;
- if ($entry->valid_after > $newest) {
- $newest = $entry->valid_after;
- }
- }
- eval {
- if ($newest <= time + $Wallet::Config::WAKEYRING_REKEY_INTERVAL) {
- my $valid = time + 2 * $Wallet::Config::WAKEYRING_REKEY_INTERVAL;
- my $key = $wa->key_create (WA_KEY_AES, WA_AES_128);
- $ring->add (time, $valid, $key);
- }
- };
- if ($@) {
- $self->error ("cannot add new key: $@");
- return;
- }
-
- # If there are any keys older than the purge interval, remove them, but
- # only do so if we have more than three keys (the one that's currently
- # active, the one that's going to come active in the rekey interval, and
- # the one that's going to come active after that.
- #
- # FIXME: Be sure that we don't remove the last currently-valid key.
- my $cutoff = time - $Wallet::Config::WAKEYRING_PURGE_INTERVAL;
- my $i = 0;
- my @purge;
- if ($count > 3) {
- for my $entry ($ring->entries) {
- if ($entry->creation < $cutoff) {
- push (@purge, $i);
- }
- $i++;
- }
- }
- if (@purge && $count - @purge >= 3) {
- eval {
- for my $key (reverse @purge) {
- $ring->remove ($key);
- }
- };
- if ($@) {
- $self->error ("cannot remove old keys: $@");
- return;
- }
- }
-
- # Encode the key.
- my $data = eval { $ring->encode };
- if ($@) {
- $self->error ("cannot encode keyring: $@");
- return;
- }
-
- # Write the new keyring to the path.
- eval { $ring->write ($path) };
- if ($@) {
- $self->error ("cannot store new keyring: $@");
- return;
- }
- close FILE;
- $self->log_action ('get', $user, $host, $time);
- return $data;
-}
-
-# Store the file on the wallet server.
-#
-# FIXME: Check the provided keyring for validity.
-sub store {
- my ($self, $data, $user, $host, $time) = @_;
- $time ||= time;
- my $id = $self->{type} . ':' . $self->{name};
- if ($self->flag_check ('locked')) {
- $self->error ("cannot store $id: object is locked");
- return;
- }
- if ($Wallet::Config::FILE_MAX_SIZE) {
- my $max = $Wallet::Config::FILE_MAX_SIZE;
- if (length ($data) > $max) {
- $self->error ("data exceeds maximum of $max bytes");
- return;
- }
- }
- my $path = $self->file_path;
- return unless $path;
- unless (open (FILE, '>', $path)) {
- $self->error ("cannot store $id: $!");
- return;
- }
- unless (print FILE ($data) and close FILE) {
- $self->error ("cannot store $id: $!");
- close FILE;
- return;
- }
- $self->log_action ('store', $user, $host, $time);
- return 1;
-}
-
-1;
-__END__
-
-##############################################################################
-# Documentation
-##############################################################################
-
-=for stopwords
-WebAuth keyring keyrings API HOSTNAME DATETIME keytab AES rekey Allbery
-
-=head1 NAME
-
-Wallet::Object::WAKeyring - WebAuth keyring object implementation for wallet
-
-=head1 SYNOPSIS
-
- my ($user, $host, $time);
- my @name = qw(wa-keyring www.stanford.edu);
- my @trace = ($user, $host, $time);
- my $object = Wallet::Object::WAKeyring->create (@name, $schema, $trace);
- my $keyring = $object->get (@trace);
- unless ($object->store ($keyring)) {
- die $object->error, "\n";
- }
- $object->destroy (@trace);
-
-=head1 DESCRIPTION
-
-Wallet::Object::WAKeyring is a representation of a WebAuth keyring in the
-wallet. It implements the wallet object API and provides the necessary
-glue to store a keyring on the wallet server, retrieve it, update the
-keyring with new keys automatically as needed, purge old keys
-automatically, and delete the keyring when the object is deleted.
-
-WebAuth keyrings hold one or more keys. Each key has a creation time and
-a validity time. The key cannot be used until its validity time has been
-reached. This permits safe key rotation: a new key is added with a
-validity time in the future, and then the keyring is updated everywhere it
-needs to be before that validity time is reached. This wallet object
-automatically handles key rotation by adding keys with validity dates in
-the future and removing keys with creation dates substantially in the
-past.
-
-To use this object, various configuration options specifying where to
-store the keyrings and how to handle key rotation must be set. See
-Wallet::Config for details on these configuration parameters and
-information about how to set wallet configuration.
-
-=head1 METHODS
-
-This object mostly inherits from Wallet::Object::Base. See the
-documentation for that class for all generic methods. Below are only
-those methods that are overridden or behave specially for this
-implementation.
-
-=over 4
-
-=item destroy(PRINCIPAL, HOSTNAME [, DATETIME])
-
-Destroys a WebAuth keyring object by removing it from the database and
-deleting the corresponding file on the wallet server. Returns true on
-success and false on failure. The caller should call error() to get the
-error message after a failure. PRINCIPAL, HOSTNAME, and DATETIME are
-stored as history information. PRINCIPAL should be the user who is
-destroying the object. If DATETIME isn't given, the current time is used.
-
-=item get(PRINCIPAL, HOSTNAME [, DATETIME])
-
-Either creates a new WebAuth keyring (if this object has not bee stored or
-retrieved before) or does any necessary periodic maintenance on the
-keyring and then returns its data. The caller should call error() to get
-the error message if get() returns undef. PRINCIPAL, HOSTNAME, and
-DATETIME are stored as history information. PRINCIPAL should be the user
-who is downloading the keytab. If DATETIME isn't given, the current time
-is used.
-
-If this object has never been stored or retrieved before, a new keyring
-will be created with three 128-bit AES keys: one that is immediately
-valid, one that will become valid after the rekey interval, and one that
-will become valid after twice the rekey interval.
-
-If keyring data for this object already exists, the creation and validity
-dates for each key in the keyring will be examined. If the key with the
-validity date the farthest into the future has a date that's less than or
-equal to the current time plus the rekey interval, a new 128-bit AES key
-will be added to the keyring with a validity time of twice the rekey
-interval in the future. Finally, all keys with a creation date older than
-the configured purge interval will be removed provided that the keyring
-has at least three keys
-
-=item store(DATA, PRINCIPAL, HOSTNAME [, DATETIME])
-
-Store DATA as the current contents of the WebAuth keyring object. Note
-that this is not checked for validity, just assumed to be a valid keyring.
-Any existing data will be overwritten. Returns true on success and false
-on failure. The caller should call error() to get the error message after
-a failure. PRINCIPAL, HOSTNAME, and DATETIME are stored as history
-information. PRINCIPAL should be the user who is destroying the object.
-If DATETIME isn't given, the current time is used.
-
-If FILE_MAX_SIZE is set in the wallet configuration, a store() of DATA
-larger than that configuration setting will be rejected.
-
-=back
-
-=head1 FILES
-
-=over 4
-
-=item WAKEYRING_BUCKET/<hash>/<file>
-
-WebAuth keyrings are stored on the wallet server under the directory
-WAKEYRING_BUCKET as set in the wallet configuration. <hash> is the first
-two characters of the hex-encoded MD5 hash of the wallet file object name,
-used to not put too many files in the same directory. <file> is the name
-of the file object with all characters other than alphanumerics,
-underscores, and dashes replaced by "%" and the hex code of the character.
-
-=back
-
-=head1 SEE ALSO
-
-Wallet::Config(3), Wallet::Object::Base(3), wallet-backend(8), WebAuth(3)
-
-This module is part of the wallet system. The current version is available
-from <http://www.eyrie.org/~eagle/software/wallet/>.
-
-=head1 AUTHOR
-
-Russ Allbery <eagle@eyrie.org>
-
-=cut