]> eyrie.org Git - kerberos/perl-kerberos.git/commitdiff
Add initial support for obtaining credentials
authorRuss Allbery <eagle@eyrie.org>
Sat, 8 Mar 2014 01:32:05 +0000 (17:32 -0800)
committerRuss Allbery <eagle@eyrie.org>
Sat, 8 Mar 2014 01:32:05 +0000 (17:32 -0800)
Add a new authenticate API that obtains initial credentials from
a keytab.  Only a few options are currently supported.  Also add
a basic implementation of Authen::Kerberos::Creds that's just
sufficient to check that authenticate is doing the right thing.

.gitignore
MANIFEST.SKIP
lib/Authen/Kerberos.xs
t/config/README [new file with mode: 0644]
t/kerberos/authenticate.t [new file with mode: 0644]
typemap

index da78e78cbbc09563209540cd5253cd314dcf51c1..395870452cbe64e82235bf9e4692b33c88d1b6b5 100644 (file)
@@ -9,4 +9,5 @@
 /glue/
 /lib/Authen/Kerberos.c
 /lib/Authen/Kerberos/Kadmin.c
+/t/config/keytab
 *.o
index d30d0f45d3a37db1c974c7c87a88d2bcb4d142c5..2b274925837c98fe30323fe1c1089e8812fb80f7 100644 (file)
@@ -11,6 +11,9 @@
 ^lib/Authen/Kerberos/Kadmin\.c$
 \.o$
 
+# Ignore test configuration files.
+^t/config/keytab$
+
 # Avoid Module::Build generated and utility files.
 \bBuild$
 \b_build/
index 14a6967f6a881f8a0e118c205af764b0fa029a31..c8613531954257639551e0143f52c1fbb841e574 100644 (file)
@@ -39,6 +39,9 @@
 
 #include <util/util.h>
 
+/* Small helper macro to check if an SV is an object derived from a type. */
+#define IS_OBJ(sv, type) (sv_isobject(sv) && sv_derived_from((sv), (type)))
+
 /*
  * This typedefs are needed for xsubpp to work its magic with type translation
  * to Perl objects.  This strategy can only be used for objects that don't
@@ -55,6 +58,11 @@ typedef krb5_context Authen__Kerberos;
  * Kerberos context is not freed before the secondary object and that the same
  * Kerberos context is used for all operations on that object.
  */
+typedef struct {
+    SV *ctx;
+    krb5_creds creds;
+} *Authen__Kerberos__Creds;
+
 typedef struct {
     SV *ctx;
     krb5_keytab keytab;
@@ -71,6 +79,120 @@ typedef struct {
 } *Authen__Kerberos__Principal;
 
 
+/*
+ * Given a krb5_principal, convert it to an Authen::Kerberos::Principal
+ * object.  Make a copy of the principal so that it can be stored and used
+ * independently of whatever object we generated it from.  Takes both the
+ * context and the underlying Authen::Kerberos object so that we can stash the
+ * latter in the created struct.
+ */
+static Authen__Kerberos__Principal
+wrap_principal(krb5_context ctx, SV *krb5, krb5_principal princ)
+{
+    krb5_principal copy;
+    krb5_error_code code;
+    Authen__Kerberos__Principal principal;
+
+    code = krb5_copy_principal(ctx, princ, &copy);
+    if (code != 0)
+        krb5_croak(ctx, code, "krb5_copy_principal", FALSE);
+    principal = malloc(sizeof(*principal));
+    if (principal == NULL)
+        croak("cannot allocate memory");
+    principal->ctx = krb5;
+    SvREFCNT_inc_simple_void_NN(principal->ctx);
+    principal->principal = copy;
+    return principal;
+}
+
+
+/*
+ * Helper function to convert an argument to a keytab.  Accept either an
+ * Authen::Kerberos::Keytab object or some other SV, which will be treated as
+ * a string to use as the keytab name.
+ */
+static krb5_keytab
+sv_to_keytab(SV *krb5, SV *sv)
+{
+    Authen__Kerberos__Keytab keytab;
+    krb5_keytab kt;
+    IV iv;
+
+    /*
+     * If the SV is not already an Authen::Kerberos::Keytab object, we have to
+     * create a new krb5_keytab.  Do so by calling the normal constructor and
+     * then making the resulting object mortal.  This allows us to return its
+     * contents and rely on Perl garbage collection to free it later.
+     *
+     * Temporaries will be freed by the caller's FREETMPS/LEAVE.  Stay within
+     * the caller's temporary frame.
+     */
+    if (!IS_OBJ(sv, "Authen::Kerberos::Keytab")) {
+        int count;
+        dSP;
+
+        PUSHMARK(sp);
+        XPUSHs(krb5);
+        XPUSHs(sv);
+        PUTBACK;
+        count = call_method("keytab", G_SCALAR);
+        SPAGAIN;
+        if (count != 1)
+            croak("Authen::Kerberos::keytab returned %d values", count);
+        sv = POPs;
+        PUTBACK;
+    }
+
+    /* Now extract the keytab from the object and return it. */
+    iv = SvIV((SV *) SvRV(sv));
+    keytab = INT2PTR(Authen__Kerberos__Keytab, iv);
+    return keytab->keytab;
+}
+
+
+/*
+ * The same as sv_to_keytab but for principals.  Return a krb5_principal given
+ * either an Authen::Kerberos::Principal object or a string.
+ */
+static krb5_principal
+sv_to_principal(SV *krb5, SV *sv)
+{
+    Authen__Kerberos__Principal principal;
+    krb5_principal princ;
+    IV iv;
+
+    /*
+     * If the SV is not already an Authen::Kerberos::Principal object, convert
+     * it to one using the principal method, as in sv_to_keytab above.
+     */
+    if (!IS_OBJ(sv, "Authen::Kerberos::Principal")) {
+        int count;
+        dSP;
+
+        /* Set up the stack for the Perl call. */
+        PUSHMARK(sp);
+        XPUSHs(krb5);
+        XPUSHs(sv);
+        PUTBACK;
+
+        /* Turn the scalar into a principal using the regular method. */
+        count = call_method("principal", G_SCALAR);
+
+        /* Retrieve the returned object from the stack. */
+        SPAGAIN;
+        if (count != 1)
+            croak("Authen::Kerberos::principal returned %d values", count);
+        sv = POPs;
+        PUTBACK;
+    }
+
+    /* Now extract the principal from the object and return it. */
+    iv = SvIV((SV *) SvRV(sv));
+    principal = INT2PTR(Authen__Kerberos__Principal, iv);
+    return principal->principal;
+}
+
+
 /* XS code below this point. */
 
 MODULE = Authen::Kerberos       PACKAGE = Authen::Kerberos
@@ -105,6 +227,72 @@ DESTROY(self)
 }
 
 
+Authen::Kerberos::Creds
+authenticate(self, args)
+    Authen::Kerberos self
+    HV *args
+  PREINIT:
+    krb5_context ctx;
+    krb5_error_code code;
+    krb5_keytab kt;
+    krb5_principal princ;
+    krb5_creds creds;
+    krb5_get_init_creds_opt *opts;
+    Authen__Kerberos__Keytab keytab;
+    const char *realm, *path;
+    const char *service = NULL;
+    SV **value;
+    IV iv;
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos", "authenticate");
+    if (args == NULL)
+        croak("no arguments given to Authen::Kerberos::authenticate");
+
+    /* Retrieve the principal as which to authenticate. */
+    value = hv_fetchs(args, "principal", 0);
+    if (value == NULL || !SvOK(*value))
+        croak("principal required in Authen::Kerberos::authenticate");
+    princ = sv_to_principal(ST(0), *value);
+
+    /*
+     * Retrieve the keytab.  Currently, only keytab authentication is
+     * supported.
+     */
+    value = hv_fetchs(args, "keytab", 0);
+    if (value == NULL || !SvOK(*value))
+        croak("keytab path required in Authen::Kerberos::authenticate");
+    kt = sv_to_keytab(ST(0), *value);
+
+    /* Get the target service, if specified. */
+    value = hv_fetchs(args, "service", 0);
+    if (value != NULL && SvOK(*value))
+        service = SvPV_nolen(*value);
+
+    /* Obtain credentials. */
+    code = krb5_get_init_creds_opt_alloc(ctx, &opts);
+    if (code != 0)
+        krb5_croak(self, code, "krb5_get_init_creds_opt_alloc", FALSE);
+    code = krb5_get_init_creds_keytab(self, &creds, princ, kt, 0, service,
+                                      opts);
+    krb5_get_init_creds_opt_free(ctx, opts);
+    if (code != 0)
+        krb5_croak(self, code, "krb5_get_init_creds_keytab", FALSE);
+
+    /* Allocate the return data structure. */
+    RETVAL = malloc(sizeof(*RETVAL));
+    if (RETVAL == NULL) {
+        krb5_free_cred_contents(self, &creds);
+        croak("cannot allocate memory");
+    }
+    RETVAL->creds = creds;
+    RETVAL->ctx = SvRV(ST(0));
+    SvREFCNT_inc_simple_void_NN(RETVAL->ctx);
+}
+  OUTPUT:
+    RETVAL
+
+
 Authen::Kerberos::Keytab
 keytab(self, name)
     Authen::Kerberos self
@@ -115,7 +303,7 @@ keytab(self, name)
     krb5_keytab keytab;
   CODE:
 {
-    CROAK_NULL_SELF(self, "Authen::Kerberos", "keytab_open");
+    CROAK_NULL_SELF(self, "Authen::Kerberos", "keytab");
     code = krb5_kt_resolve(self, name, &keytab);
     if (code != 0)
         krb5_croak(self, code, "krb5_kt_resolve", FALSE);
@@ -137,21 +325,75 @@ principal(self, name)
     const char *name
   PREINIT:
     krb5_error_code code;
-    krb5_principal principal;
-    Authen__Kerberos__Principal princ;
+    krb5_principal princ;
+    Authen__Kerberos__Principal principal;
   CODE:
 {
     CROAK_NULL_SELF(self, "Authen::Kerberos", "principal");
-    code = krb5_parse_name(self, name, &principal);
+    code = krb5_parse_name(self, name, &princ);
     if (code != 0)
         krb5_croak(self, code, "krb5_parse_name", FALSE);
-    princ = malloc(sizeof(*princ));
-    if (princ == NULL)
+    principal = malloc(sizeof(*principal));
+    if (principal == NULL)
         croak("cannot allocate memory");
-    princ->principal = principal;
-    princ->ctx = SvRV(ST(0));
-    SvREFCNT_inc_simple_void_NN(princ->ctx);
-    RETVAL = princ;
+    principal->principal = princ;
+    principal->ctx = SvRV(ST(0));
+    SvREFCNT_inc_simple_void_NN(principal->ctx);
+    RETVAL = principal;
+}
+  OUTPUT:
+    RETVAL
+
+
+MODULE = Authen::Kerberos       PACKAGE = Authen::Kerberos::Creds
+
+void
+DESTROY(self)
+    Authen::Kerberos::Creds self
+  PREINIT:
+    krb5_context ctx;
+  CODE:
+{
+    if (self == NULL)
+        return;
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::Creds");
+    krb5_free_cred_contents(ctx, &self->creds);
+    SvREFCNT_dec(self->ctx);
+    free(self);
+}
+
+
+Authen::Kerberos::Principal
+client(self)
+    Authen::Kerberos::Creds self
+  PREINIT:
+    krb5_context ctx;
+    krb5_principal princ;
+    krb5_error_code code;
+    Authen__Kerberos__Principal principal;
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos::Creds", "client");
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::Creds");
+    RETVAL = wrap_principal(ctx, self->ctx, self->creds.client);
+}
+  OUTPUT:
+    RETVAL
+
+
+Authen::Kerberos::Principal
+server(self)
+    Authen::Kerberos::Creds self
+  PREINIT:
+    krb5_context ctx;
+    krb5_principal princ;
+    krb5_error_code code;
+    Authen__Kerberos__Principal principal;
+  CODE:
+{
+    CROAK_NULL_SELF(self, "Authen::Kerberos::Creds", "server");
+    ctx = krb5_context_from_sv(self->ctx, "Authen::Kerberos::Creds");
+    RETVAL = wrap_principal(ctx, self->ctx, self->creds.server);
 }
   OUTPUT:
     RETVAL
diff --git a/t/config/README b/t/config/README
new file mode 100644 (file)
index 0000000..d523400
--- /dev/null
@@ -0,0 +1,10 @@
+This directory contains configuration required to run the complete
+Authen::Kerberos test suite.  If there is no configuration in this
+directory, some of the tests will be skipped.  To enable the full test
+suite, create the following files:
+
+keytab
+
+    A valid Kerberos keytab for a principal, preferrably in your local
+    realm.  This will be used to test APIs that require a full
+    authentication against a KDC or require current tickets.
diff --git a/t/kerberos/authenticate.t b/t/kerberos/authenticate.t
new file mode 100644 (file)
index 0000000..e5d6528
--- /dev/null
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+#
+# Test suite for Authen::Kerberos initial authentication.
+#
+# Written by Russ Allbery <eagle@eyrie.org>
+# Copyright 2014
+#     The Board of Trustees of the Leland Stanford Junior University
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+use 5.010;
+use autodie;
+use strict;
+use warnings;
+
+use Test::More;
+
+# We can only run this test if we have a local keytab.
+if (!-f 't/config/keytab') {
+    plan skip_all => 'Authentication tests not configured';
+}
+
+# We can proceed.  Output the plan.
+plan tests => 5;
+
+# Load the relevant modules.
+require_ok('Authen::Kerberos');
+require_ok('Authen::Kerberos::Keytab');
+
+# Open the keytab.
+my $krb5   = Authen::Kerberos->new;
+my $keytab = $krb5->keytab('FILE:t/config/keytab');
+
+# Get the principal of the first entry.
+my ($entry) = $keytab->entries;
+my $principal = $entry->principal->to_string;
+
+# Authenticate and request the krbtgt in the realm of the principal.
+my ($realm) = ($principal =~ m{ \@ (.*) \z }xms);
+my $args = {
+    principal => $principal,
+    keytab    => $keytab,
+    service   => "krbtgt/$realm\@$realm",
+};
+my $creds = $krb5->authenticate($args);
+isa_ok($creds, 'Authen::Kerberos::Creds', 'Return from authenticate');
+
+# Check whether the credentials look correct.
+is($creds->client->to_string, $principal,              'Creds client');
+is($creds->server->to_string, "krbtgt/$realm\@$realm", 'Creds server');
diff --git a/typemap b/typemap
index 9b834c46ca111595d9708245bb20eca29e729d8b..fb80ff88b683822fe8b5498d7abb63734ad05d01 100644 (file)
--- a/typemap
+++ b/typemap
@@ -28,6 +28,7 @@ krb5_kvno                       T_IV
 krb5_timestamp                  T_IV
 
 Authen::Kerberos                T_PTROBJ_NU
+Authen::Kerberos::Creds         T_PTROBJ_NU
 Authen::Kerberos::Kadmin        T_PTROBJ_NU
 Authen::Kerberos::Keytab        T_PTROBJ_NU
 Authen::Kerberos::KeytabEntry   T_PTROBJ_NU