[PECL-CVS] [pecl-authentication-krb5] master: Add GSSAPIContext::exportCredentials() and importCredentials()

[email protected] (David Härdeman via Moritz Bechler) Mon, 8 Jun 2026 15:50:16 +0000
Newsgroups php.pecl.cvs
Message-ID <[email protected]>
Author: David Härdeman (Alphix)
Committer: Moritz Bechler (mbechler)
Date: 2026-06-08T17:22:59+02:00

Commit: https://github.com/php/pecl-authentication-krb5/commit/5c572644e778f35e940f731998fc14f85632ba3c
Raw diff: https://github.com/php/pecl-authentication-krb5/commit/5c572644e778f35e940f731998fc14f85632ba3c.diff

Add GSSAPIContext::exportCredentials() and importCredentials()

Expose gss_export_cred()/gss_import_cred() to PHP, allowing delegated
credentials to be serialised and restored across process boundaries.
Both methods are conditionally compiled under HAVE_GSS_EXPORT_CRED.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

Changed paths:
  A  tests/007.phpt
  M  config.m4
  M  gssapi.c


Diff:

diff --git a/config.m4 b/config.m4
index e6972be..40f2e7c 100644
--- a/config.m4
+++ b/config.m4
@@ -62,6 +62,7 @@ if test "$PHP_KRB5" != "no" -o "$PHP_KRB5KADM" != "no"; then
 
 	AC_CHECK_FUNCS(krb5_free_string)
 	AC_CHECK_FUNCS(gss_acquire_cred_from)
+	AC_CHECK_FUNCS(gss_export_cred)
 	AC_CHECK_FUNCS(krb5_chpw_message)
 	AC_CHECK_FUNCS(krb5_principal_get_realm)
 	AC_CHECK_FUNCS(krb5_get_init_creds_opt_set_expire_callback)
diff --git a/gssapi.c b/gssapi.c
index 627dfd1..ebf2c68 100644
--- a/gssapi.c
+++ b/gssapi.c
@@ -21,6 +21,9 @@
 **/
 
 
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
 #include "php.h"
 #include "php_krb5.h"
 
@@ -89,6 +92,11 @@ ZEND_BEGIN_ARG_INFO_EX(krb5_GSSAPIContext_unwrapArgs, 0, 0, 2)
 	ZEND_ARG_INFO(1, output)
 ZEND_END_ARG_INFO()
 
+#ifdef HAVE_GSS_EXPORT_CRED
+ZEND_BEGIN_ARG_INFO_EX(krb5_GSSAPIContext_importCredentials, 0, 0, 1)
+	ZEND_ARG_INFO(0, token)
+ZEND_END_ARG_INFO()
+#endif
 
 PHP_METHOD(GSSAPIContext, registerAcceptorIdentity);
 PHP_METHOD(GSSAPIContext, acquireCredentials);
@@ -100,6 +108,10 @@ PHP_METHOD(GSSAPIContext, verifyMic);
 PHP_METHOD(GSSAPIContext, wrap);
 PHP_METHOD(GSSAPIContext, unwrap);
 PHP_METHOD(GSSAPIContext, getTimeRemaining);
+#ifdef HAVE_GSS_EXPORT_CRED
+PHP_METHOD(GSSAPIContext, exportCredentials);
+PHP_METHOD(GSSAPIContext, importCredentials);
+#endif
 
 static zend_function_entry krb5_gssapi_context_functions[] = {
 	PHP_ME(GSSAPIContext, registerAcceptorIdentity, krb5_GSSAPIContext_registerAcceptorIdentity, ZEND_ACC_PUBLIC)
@@ -112,6 +124,10 @@ static zend_function_entry krb5_gssapi_context_functions[] = {
 	PHP_ME(GSSAPIContext, wrap,                     krb5_GSSAPIContext_wrapArgs,                 ZEND_ACC_PUBLIC)
 	PHP_ME(GSSAPIContext, unwrap,                   krb5_GSSAPIContext_unwrapArgs,               ZEND_ACC_PUBLIC)
 	PHP_ME(GSSAPIContext, getTimeRemaining,         krb5_GSSAPIContext_none,                     ZEND_ACC_PUBLIC)
+#ifdef HAVE_GSS_EXPORT_CRED
+	PHP_ME(GSSAPIContext, exportCredentials,        krb5_GSSAPIContext_none,                     ZEND_ACC_PUBLIC)
+	PHP_ME(GSSAPIContext, importCredentials,        krb5_GSSAPIContext_importCredentials,        ZEND_ACC_PUBLIC)
+#endif
 	PHP_FE_END
 };
 
@@ -933,3 +949,64 @@ PHP_METHOD(GSSAPIContext, unwrap)
 	status = gss_release_buffer(&minor_status, &output);
 	ASSERT_GSS_SUCCESS(status,minor_status,);
 } /* }}} */
+
+#ifdef HAVE_GSS_EXPORT_CRED
+/* {{{ proto string GSSAPIContext::exportCredentials( )
+   Exports the current credentials to an opaque token that can be stored or passed to another process */
+PHP_METHOD(GSSAPIContext, exportCredentials)
+{
+	OM_uint32 status = 0;
+	OM_uint32 minor_status = 0;
+	gss_buffer_desc token;
+	krb5_gssapi_context_object *context = KRB5_THIS_GSSAPI_CONTEXT;
+
+	memset(&token, 0, sizeof(token));
+
+	if (zend_parse_parameters_none() == FAILURE) {
+		RETURN_FALSE;
+	}
+
+	if (context->creds == GSS_C_NO_CREDENTIAL) {
+		zend_throw_exception(NULL, "No credentials to export", 0 TSRMLS_CC);
+		return;
+	}
+
+	status = gss_export_cred(&minor_status, context->creds, &token);
+	ASSERT_GSS_SUCCESS(status, minor_status,);
+
+	_RETVAL_STRINGL(token.value, token.length);
+
+	status = gss_release_buffer(&minor_status, &token);
+	ASSERT_GSS_SUCCESS(status, minor_status,);
+} /* }}} */
+
+/* {{{ proto bool GSSAPIContext::importCredentials( string $token )
+   Imports credentials from a token previously created by exportCredentials() */
+PHP_METHOD(GSSAPIContext, importCredentials)
+{
+	OM_uint32 status = 0;
+	OM_uint32 minor_status = 0;
+	gss_buffer_desc token;
+	gss_cred_id_t new_creds = GSS_C_NO_CREDENTIAL;
+	krb5_gssapi_context_object *context = KRB5_THIS_GSSAPI_CONTEXT;
+	strsize_t token_len = 0;
+
+	memset(&token, 0, sizeof(token));
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
+			&(token.value), &token_len) == FAILURE) {
+		RETURN_FALSE;
+	}
+	token.length = token_len;
+
+	status = gss_import_cred(&minor_status, &token, &new_creds);
+	ASSERT_GSS_SUCCESS(status, minor_status,);
+
+	if (context->creds != GSS_C_NO_CREDENTIAL) {
+		gss_release_cred(&minor_status, &(context->creds));
+	}
+	context->creds = new_creds;
+
+	RETURN_TRUE;
+} /* }}} */
+#endif
diff --git a/tests/007.phpt b/tests/007.phpt
new file mode 100644
index 0000000..447ba55
--- /dev/null
+++ b/tests/007.phpt
@@ -0,0 +1,75 @@
+--TEST--
+Testing for credential export and import
+--SKIPIF--
+<?php
+if(!file_exists(dirname(__FILE__) . '/config.php')) { echo "skip config missing"; return; }
+if(!include(dirname(__FILE__) . '/config.php')) return;
+if(!method_exists('GSSAPIContext', 'exportCredentials')) { echo "skip gss_export_cred not available"; return; }
+?>
+--FILE--
+<?php
+include(dirname(__FILE__) . '/config.php');
+$client = new KRB5CCache();
+if($use_config) {
+	$client->setConfig(dirname(__FILE__) . '/krb5.ini');
+}
+
+$client->initPassword($client_principal, $client_password, array('forwardable' => true, 'proxiable' => true));
+
+$server = new KRB5CCache();
+if($use_config) {
+	$server->setConfig(dirname(__FILE__) . '/krb5.ini');
+}
+
+$server->initKeytab($server_principal, $server_keytab);
+
+$cgssapi = new GSSAPIContext();
+$sgssapi = new GSSAPIContext();
+
+$cgssapi->acquireCredentials($client);
+$sgssapi->acquireCredentials($server);
+
+$token = '';
+$token2 = '';
+$principal = '';
+$ret_flags = 0;
+$time_rec = 0;
+$deleg = new KRB5CCache();
+
+// Establish a context with delegation to obtain delegated credentials
+var_dump($cgssapi->initSecContext($server_principal, null, GSS_C_DELEG_FLAG, null, $token));
+var_dump($sgssapi->acceptSecContext($token, $token2, $principal, $ret_flags, $time_rec, $deleg));
+var_dump(count($deleg->getEntries()));
+
+// Acquire credentials from the delegated ccache, then export them
+$dgssapi = new GSSAPIContext();
+$dgssapi->acquireCredentials($deleg, $principal, GSS_C_INITIATE);
+
+$exported = $dgssapi->exportCredentials();
+var_dump(is_string($exported) && strlen($exported) > 0);
+
+// Import the exported credentials into a fresh context and use them
+$igssapi = new GSSAPIContext();
+var_dump($igssapi->importCredentials($exported));
+
+$s2gssapi = new GSSAPIContext();
+$s2gssapi->acquireCredentials($server);
+
+$token = '';
+$token2 = '';
+$principal2 = '';
+
+var_dump($igssapi->initSecContext($server_principal, null, null, null, $token));
+var_dump($s2gssapi->acceptSecContext($token, $token2, $principal2, $ret_flags, $time_rec, $deleg));
+var_dump($principal2 === $principal);
+
+?>
+--EXPECTF--
+bool(true)
+bool(true)
+int(1)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)