[PECL-CVS] [pecl-authentication-krb5] master: Add GSSAPIContext::storeCredentials() (#15)

[email protected] (David Härdeman via GitHub) Tue, 9 Jun 2026 08:48:20 +0000
Newsgroups php.pecl.cvs
Message-ID <[email protected]>
Author: David Härdeman (Alphix)
Committer: GitHub (web-flow)
Pusher: mbechler
Date: 2026-06-09T10:48:18+02:00

Commit: https://github.com/php/pecl-authentication-krb5/commit/3089f06a05068000782b52c0710776799e1e76ae
Raw diff: https://github.com/php/pecl-authentication-krb5/commit/3089f06a05068000782b52c0710776799e1e76ae.diff

Add GSSAPIContext::storeCredentials() (#15)

Complements gss_acquire_cred_from(): stores the context's credentials into
a named ccache supplied as a "ccache" key/value element, following the same
gss_key_value_set_desc pattern used in negotiate_auth.c.

Co-authored-by: Claude Sonnet 4.6 <[email protected]>

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


Diff:

diff --git a/config.m4 b/config.m4
index 2c686df..b05bab3 100644
--- a/config.m4
+++ b/config.m4
@@ -91,6 +91,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(gss_store_cred_into)
 	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 ebf2c68..2423491 100644
--- a/gssapi.c
+++ b/gssapi.c
@@ -98,6 +98,15 @@ ZEND_BEGIN_ARG_INFO_EX(krb5_GSSAPIContext_importCredentials, 0, 0, 1)
 ZEND_END_ARG_INFO()
 #endif
 
+#ifdef HAVE_GSS_STORE_CRED_INTO
+ZEND_BEGIN_ARG_INFO_EX(krb5_GSSAPIContext_storeCredentials, 0, 0, 1)
+	ZEND_ARG_INFO(0, ccache)
+	ZEND_ARG_INFO(0, usage)
+	ZEND_ARG_INFO(0, overwrite)
+	ZEND_ARG_INFO(0, default_cred)
+ZEND_END_ARG_INFO()
+#endif
+
 PHP_METHOD(GSSAPIContext, registerAcceptorIdentity);
 PHP_METHOD(GSSAPIContext, acquireCredentials);
 PHP_METHOD(GSSAPIContext, inquireCredentials);
@@ -112,6 +121,9 @@ PHP_METHOD(GSSAPIContext, getTimeRemaining);
 PHP_METHOD(GSSAPIContext, exportCredentials);
 PHP_METHOD(GSSAPIContext, importCredentials);
 #endif
+#ifdef HAVE_GSS_STORE_CRED_INTO
+PHP_METHOD(GSSAPIContext, storeCredentials);
+#endif
 
 static zend_function_entry krb5_gssapi_context_functions[] = {
 	PHP_ME(GSSAPIContext, registerAcceptorIdentity, krb5_GSSAPIContext_registerAcceptorIdentity, ZEND_ACC_PUBLIC)
@@ -127,6 +139,9 @@ static zend_function_entry krb5_gssapi_context_functions[] = {
 #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
+#ifdef HAVE_GSS_STORE_CRED_INTO
+	PHP_ME(GSSAPIContext, storeCredentials,         krb5_GSSAPIContext_storeCredentials,         ZEND_ACC_PUBLIC)
 #endif
 	PHP_FE_END
 };
@@ -1010,3 +1025,47 @@ PHP_METHOD(GSSAPIContext, importCredentials)
 	RETURN_TRUE;
 } /* }}} */
 #endif
+
+#ifdef HAVE_GSS_STORE_CRED_INTO
+/* {{{ proto bool GSSAPIContext::storeCredentials( string $ccache [, int $usage = GSS_C_BOTH [, bool $overwrite = true [, bool $default_cred = false ]]] )
+   Stores the current credentials into the named ccache using gss_store_cred_into() */
+PHP_METHOD(GSSAPIContext, storeCredentials)
+{
+	OM_uint32 status = 0;
+	OM_uint32 minor_status = 0;
+	krb5_gssapi_context_object *context = KRB5_THIS_GSSAPI_CONTEXT;
+	char *ccache_name = NULL;
+	strsize_t ccache_len = 0;
+	zend_long cred_usage = GSS_C_BOTH;
+	zend_bool overwrite = 1;
+	zend_bool default_cred = 0;
+	gss_key_value_element_desc ccache_element;
+	gss_key_value_set_desc cred_store;
+
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|lbb",
+			&ccache_name, &ccache_len,
+			&cred_usage,
+			&overwrite,
+			&default_cred) == FAILURE) {
+		RETURN_FALSE;
+	}
+
+	if (context->creds == GSS_C_NO_CREDENTIAL) {
+		zend_throw_exception(NULL, "No credentials to store", 0 TSRMLS_CC);
+		return;
+	}
+
+	ccache_element.key = "ccache";
+	ccache_element.value = ccache_name;
+	cred_store.count = 1;
+	cred_store.elements = &ccache_element;
+
+	status = gss_store_cred_into(&minor_status, context->creds,
+	                              (gss_cred_usage_t)cred_usage, GSS_C_NO_OID,
+	                              (OM_uint32)overwrite, (OM_uint32)default_cred,
+	                              &cred_store, NULL, NULL);
+	ASSERT_GSS_SUCCESS(status, minor_status,);
+
+	RETURN_TRUE;
+} /* }}} */
+#endif
diff --git a/tests/008.phpt b/tests/008.phpt
new file mode 100644
index 0000000..8fc7425
--- /dev/null
+++ b/tests/008.phpt
@@ -0,0 +1,93 @@
+--TEST--
+Testing for credential store via storeCredentials()
+--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', 'storeCredentials')) { echo "skip gss_store_cred_into not available"; 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 and re-import them
+$dgssapi = new GSSAPIContext();
+$dgssapi->acquireCredentials($deleg, $principal, GSS_C_INITIATE);
+
+$exported = $dgssapi->exportCredentials();
+var_dump(is_string($exported) && strlen($exported) > 0);
+
+$igssapi = new GSSAPIContext();
+var_dump($igssapi->importCredentials($exported));
+
+// Store the imported credentials into a FILE ccache
+$tmpcc_path = tempnam(sys_get_temp_dir(), 'krb5cc_pecl_');
+unlink($tmpcc_path); // let gss_store_cred_into create the ccache fresh
+$tmpcc = 'FILE:' . $tmpcc_path;
+var_dump($igssapi->storeCredentials($tmpcc));
+
+// Verify the stored ccache contains credentials
+$storedcc = new KRB5CCache();
+$storedcc->open($tmpcc);
+var_dump(count($storedcc->getEntries()) > 0);
+
+// Use the stored credentials to authenticate to the server
+$s2gssapi = new GSSAPIContext();
+$s2gssapi->acquireCredentials($server);
+
+$ngssapi = new GSSAPIContext();
+$ngssapi->acquireCredentials($storedcc, $principal, GSS_C_INITIATE);
+
+$token = '';
+$token2 = '';
+$principal2 = '';
+
+var_dump($ngssapi->initSecContext($server_principal, null, null, null, $token));
+var_dump($s2gssapi->acceptSecContext($token, $token2, $principal2, $ret_flags, $time_rec, $deleg));
+var_dump($principal2 === $principal);
+
+@unlink($tmpcc_path);
+?>
+--EXPECTF--
+bool(true)
+bool(true)
+int(1)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)