Subject: [PATCH] credential/osxkeychain: store new attributes

From: "M Hickford via GitGitGadget" <[email-masked]>
Date: Tue, 13 Feb 2024 21:43:38 +0000

From: M Hickford <[email-masked]>

d208bfd (credential: new attribute password_expiry_utc, 2023-02-18)
and a5c76569e7 (credential: new attribute oauth_refresh_token)
introduced new credential attributes.

Similar to 7144dee3 (credential/libsecret: erase matching creds only,
2023-07-26), we encode the new attributes in the secret, separated by
newline:

    hunter2
    password_expiry_utc=1684189401
    oauth_refresh_token=xyzzy

This is extensible and backwards compatible. The credential protocol
already assumes that attribute values do not contain newlines.

Signed-off-by: M Hickford <[email-masked]>
---
    [RFC] contrib/credential/osxkeychain: store new attributes
    
    Is any keen MacOS user interested in building and testing this RFC
    patch? I personally don't have a MacOS machine, so haven't tried
    building it. Fixes are surely necessary. Once it builds, you can test
    the feature with:
    
    GIT_TEST_CREDENTIAL_HELPER=osxkeychain ./t0303-credential-external.sh

    The feature would help git-credential-oauth users on MacOS
    https://github.com/hickford/git-credential-oauth/issues/42

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1663%2Fhickford%2Fosxkeychain-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1663/hickford/osxkeychain-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1663

 .../osxkeychain/git-credential-osxkeychain.c  | 56 ++++++++++++++++++-
 1 file changed, 54 insertions(+), 2 deletions(-)

diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
index 5f2e5f16c88..25ffa84f4ba 100644
--- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c
+++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -8,6 +8,8 @@ static char *host;
 static char *path;
 static char *username;
 static char *password;
+static char *password_expiry_utc;
+static char *oauth_refresh_token;
 static UInt16 port;
 
 __attribute__((format (printf, 1, 2)))
@@ -22,6 +24,17 @@ static void die(const char *err, ...)
 	exit(1);
 }
 
+
+static void *xmalloc(size_t size)
+{
+	void *ret = malloc(size);
+	if (!ret && !size)
+		ret = malloc(1);
+	if (!ret)
+		 die("Out of memory");
+	return ret;
+}
+
 static void *xstrdup(const char *s1)
 {
 	void *ret = strdup(s1);
@@ -69,11 +82,27 @@ static void find_internet_password(void)
 	void *buf;
 	UInt32 len;
 	SecKeychainItemRef item;
+	char *line;
+	char *remaining_lines;
+	char *part;
+	char *remaining_parts;
 
 	if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
 		return;
 
-	write_item("password", buf, len);
+	line = strtok_r(buf, "\n", &remaining_lines);
+	write_item("password", line, strlen(line));
+	while(line != NULL) {
+		part = strtok_r(line, "=", &remaining_parts);
+		if (!strcmp(part, "oauth_refresh_token")) {
+			write_item("oauth_refresh_token", remaining_parts, strlen(remaining_parts));
+		}
+		if (!strcmp(part, "password_expiry_utc")) {
+			write_item("password_expiry_utc", remaining_parts, strlen(remaining_parts));
+		}
+		line = strtok_r(NULL, "\n", &remaining_lines);
+	}
+
 	if (!username)
 		find_username_in_item(item);
 
@@ -100,13 +129,32 @@ static void delete_internet_password(void)
 
 static void add_internet_password(void)
 {
+	int len;
+
 	/* Only store complete credentials */
 	if (!protocol || !host || !username || !password)
 		return;
 
+	char *secret;
+	if (password_expiry_utc && oauth_refresh_token) {
+		len = strlen(password) + strlen(password_expiry_utc) + strlen(oauth_refresh_token) + strlen("\npassword_expiry_utc=\noauth_refresh_token=");
+		secret = xmalloc(len);
+		snprintf(secret, len, len, "%s\npassword_expiry_utc=%s\noauth_refresh_token=%s", password, oauth_refresh_token);
+	} else if (oauth_refresh_token) {
+		len = strlen(password) + strlen(oauth_refresh_token) + strlen("\noauth_refresh_token=");
+		secret = xmalloc(len);
+		snprintf(secret, len, len, "%s\noauth_refresh_token=%s", password, oauth_refresh_token);
+	} else if (password_expiry_utc) {
+		len = strlen(password) + strlen(password_expiry_utc) + strlen("\npassword_expiry_utc=");
+		secret = xmalloc(len);
+		snprintf(secret, len, len, "%s\npassword_expiry_utc=%s", password, password_expiry_utc);
+	} else {
+		secret = xstrdup(password);
+	}
+
 	if (SecKeychainAddInternetPassword(
 	      KEYCHAIN_ARGS,
-	      KEYCHAIN_ITEM(password),
+	      KEYCHAIN_ITEM(secret),
 	      NULL))
 		return;
 }
@@ -161,6 +209,10 @@ static void read_credential(void)
 			username = xstrdup(v);
 		else if (!strcmp(buf, "password"))
 			password = xstrdup(v);
+		else if (!strcmp(buf, "password_expiry_utc"))
+			password_expiry_utc = xstrdup(v);
+		else if (!strcmp(buf, "oauth_refresh_token"))
+			oauth_refresh_token = xstrdup(v);
 		/*
 		 * Ignore other lines; we don't know what they mean, but
 		 * this future-proofs us when later versions of git do

base-commit: c875e0b8e036c12cfbf6531962108a063c7a821c
-- 
gitgitgadget