git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 13/14] credentials: add "store" helper

From
Jeff King <peff@peff.net>
Date
Jul 18, 2011, 07:58 UTC
Message-ID
<20110718075814.GM12341@sigill.intra.peff.net>
In-Reply-To
<20110718074642.GA11678@sigill.intra.peff.net>

This is like "cache", except that we actually put the credentials on disk. This can be terribly insecure, of course, but we do what we can to protect them by filesystem permissions, and we warn the user in the documentation.

This is not unlike using .netrc to store entries, but it's a little more user-friendly. Instead of putting credentials in place ahead of time, we transparently store them after prompting the user for them once.

Signed-off-by: Jeff King <peff@peff.net>
---
This reuses the gitconfig format for storage. Which is really kind of
limiting, because I want an extra layer of hierarchy:
  credential.$context.$username.password

So as it is now, it just looks up the username and password for a context. But in theory, it should be able to handle multiple usernames for a given context, and correctly look up passwords for user1@example.com versus user2@example.com. In practice, I doubt it really matters, though.

 .gitignore                             |    1 +
 Documentation/git-credential-store.txt |   69 +++++++++++++++++++++++++
 Documentation/gitcredentials.txt       |    5 ++
 Makefile                               |    1 +
 credential-store.c                     |   87 ++++++++++++++++++++++++++++++++
 t/t0300-credentials.sh                 |   55 ++++++++++++++++++++
 6 files changed, 218 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-credential-store.txt
 create mode 100644 credential-store.c
diff --git a/.gitignore b/.gitignore
index a6b0bd4..2b7a3f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@
 /git-count-objects
 /git-credential-cache
 /git-credential-cache--daemon
+/git-credential-store
 /git-cvsexportcommit
 /git-cvsimport
 /git-cvsserver
diff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt
new file mode 100644
index 0000000..9fc0f76
--- /dev/null
+++ b/Documentation/git-credential-store.txt
@@ -0,0 +1,69 @@
+git-credential-store(1)
+=======================
+
+NAME
+----
+git-credential-store - helper to store credentials on disk
+
+SYNOPSIS
+--------
+-------------------
+git config credential.helper 'store [options]'
+-------------------
+
+DESCRIPTION
+-----------
+
+NOTE: Using this helper will store your passwords unencrypted on disk,
+protected only by filesystem permissions. If this is not an acceptable
+security tradeoff, try linkgit:git-credential-cache[1], or find a helper
+that integrates with secure storage provided by your operating system.
+
+This command requests credentials from the user and stores them
+indefinitely on disk for use by future git programs.
+
+You probably don't want to invoke this command directly; it is meant to
+be used as a credential helper by other parts of git. See
+linkgit:gitcredentials[7] or `EXAMPLES` below.
+
+OPTIONS
+-------
+
+--store=<path>::
+
+	Use `<path>` to store credentials. The file will have its
+	filesystem permissions set to prevent other users on the system
+	from reading it, but will not be encrypted or otherwise
+	protected.
+
+--chain <helper>::
+
+	Specify an external helper to use for retrieving credentials
+	from the user, instead of the default method. The resulting
+	credentials are then stored as normal. This option can be
+	given multiple times; each chained helper will be tried until
+	credentials are received.
+
+Git may provide other options to the program when it is called as a
+credential helper; see linkgit:gitcredentials[7].
+
+EXAMPLES
+--------
+
+The point of this helper is to reduce the number of times you must type
+your username or password. For example:
+
+------------------------------------
+$ git config credential.helper store
+$ git push http://example.com/repo.git
+Username: <type your username>
+Password: <type your password>
+
+[several days later]
+$ git push http://example.com/repo.git
+[your credentials are used automatically]
+------------------------------------
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt
index bd1a3b6..33ea56c 100644
--- a/Documentation/gitcredentials.txt
+++ b/Documentation/gitcredentials.txt
@@ -88,6 +88,11 @@ cache::
 	Cache credentials in memory for a short period of time. See
 	linkgit:git-credential-cache[1] for details.
 
+store::
+
+	Store credentials indefinitely on disk. See
+	linkgit:git-credential-store[1] for details.
+
 You may may also have third-party helpers installed; search for
 `credential-*` in the output of `git help -a`, and consult the
 documentation of individual helpers.  Once you have selected a helper,
diff --git a/Makefile b/Makefile
index 2ecfd90..e818257 100644
--- a/Makefile
+++ b/Makefile
@@ -419,6 +419,7 @@ PROGRAM_OBJS += http-backend.o
 PROGRAM_OBJS += sh-i18n--envsubst.o
 PROGRAM_OBJS += credential-cache.o
 PROGRAM_OBJS += credential-cache--daemon.o
+PROGRAM_OBJS += credential-store.o
 
 PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 
diff --git a/credential-store.c b/credential-store.c
new file mode 100644
index 0000000..8ab8582
--- /dev/null
+++ b/credential-store.c
@@ -0,0 +1,87 @@
+#include "cache.h"
+#include "credential.h"
+#include "string-list.h"
+#include "parse-options.h"
+
+static int lookup_credential(const char *fn, struct credential *c)
+{
+	config_exclusive_filename = fn;
+	credential_from_config(c);
+	return c->username && c->password;
+}
+
+static void store_item(const char *fn, const char *unique,
+		       const char *item, const char *value)
+{
+	struct strbuf key = STRBUF_INIT;
+
+	if (!unique)
+		return;
+
+	config_exclusive_filename = fn;
+	umask(077);
+
+	strbuf_addf(&key, "credential.%s.%s", unique, item);
+	git_config_set(key.buf, value);
+	strbuf_release(&key);
+}
+
+static void store_credential(const char *fn, struct credential *c)
+{
+	store_item(fn, c->unique, "username", c->username);
+	store_item(fn, c->unique, "password", c->password);
+}
+
+static void remove_credential(const char *fn, struct credential *c)
+{
+	store_item(fn, c->unique, "username", NULL);
+	store_item(fn, c->unique, "password", NULL);
+}
+
+int main(int argc, const char **argv)
+{
+	const char * const usage[] = {
+		"git credential-store [options]",
+		NULL
+	};
+	struct credential c = { NULL };
+	struct string_list chain = STRING_LIST_INIT_NODUP;
+	char *store = NULL;
+	int reject = 0;
+	struct option options[] = {
+		OPT_STRING_LIST(0, "store", &store, "file",
+				"fetch and store credentials in <file>"),
+		OPT_STRING_LIST(0, "chain", &chain, "helper",
+				"use <helper> to get non-cached credentials"),
+		OPT_BOOLEAN(0, "reject", &reject,
+			    "reject a stored credential"),
+		OPT_STRING(0, "username", &c.username, "name",
+			   "an existing username"),
+		OPT_STRING(0, "description", &c.description, "desc",
+			   "human-readable description of the credential"),
+		OPT_STRING(0, "unique", &c.unique, "token",
+			   "a unique context for the credential"),
+		OPT_END()
+	};
+
+	argc = parse_options(argc, argv, NULL, options, usage, 0);
+	if (argc)
+		usage_with_options(usage, options);
+
+	if (!store)
+		store = expand_user_path("~/.git-credentials");
+	if (!store)
+		die("unable to set up default store; use --store");
+
+	if (reject)
+		remove_credential(store, &c);
+	else {
+		if (!lookup_credential(store, &c)) {
+			credential_fill(&c, &chain);
+			store_credential(store, &c);
+		}
+		printf("username=%s\n", c.username);
+		printf("password=%s\n", c.password);
+	}
+	return 0;
+}
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
index 994a0aa..5d54976 100755
--- a/t/t0300-credentials.sh
+++ b/t/t0300-credentials.sh
@@ -273,4 +273,59 @@ test_expect_success 'credential-cache removes rejected credentials' '
 	EOF
 '
 
+test_expect_success 'credential-store stores password' '
+	test_when_finished "rm -f .git-credentials" &&
+	check --unique=host store <<-\EOF &&
+	username=askpass-result
+	password=askpass-result
+	--
+	askpass: Username:
+	askpass: Password:
+	EOF
+	check --unique=host store <<-\EOF
+	username=askpass-result
+	password=askpass-result
+	--
+	EOF
+'
+
+test_expect_success 'credential-store requires matching unique token' '
+	test_when_finished "rm -f .git-credentials" &&
+	check --unique=host store <<-\EOF &&
+	username=askpass-result
+	password=askpass-result
+	--
+	askpass: Username:
+	askpass: Password:
+	EOF
+	check --unique=host2 store <<-\EOF
+	username=askpass-result
+	password=askpass-result
+	--
+	askpass: Username:
+	askpass: Password:
+	EOF
+'
+
+test_expect_success 'credential-store removes rejected credentials' '
+	test_when_finished "rm -f .git-credentials" &&
+	check --unique=host store <<-\EOF &&
+	username=askpass-result
+	password=askpass-result
+	--
+	askpass: Username:
+	askpass: Password:
+	EOF
+	check --reject --unique=host --username=askpass-result store <<-\EOF &&
+	--
+	EOF
+	check --unique=host store <<-\EOF
+	username=askpass-result
+	password=askpass-result
+	--
+	askpass: Username:
+	askpass: Password:
+	EOF
+'
+
 test_done
-- 
1.7.6.rc1.12.g65e2
Previous: Jeff KingNext: Jeff King
Message 31 of 33 in “less annoying http authentication”
  1. 0/14 less annoying http authenticationJeff King, Jul 18, 2011
  2. 01/14 parse-options: add OPT_STRING_LIST helperJeff King, Jul 18, 2011
  3. 02/14 url: decode buffers that are not NUL-terminatedJeff King, Jul 18, 2011
  4. Junio C HamanoJul 20, 2011
  5. 03/14 improve httpd auth testsJeff King, Jul 18, 2011
  6. 04/14 remote-curl: don't retry auth failures with dumb protocolJeff King, Jul 18, 2011
  7. 05/14 http: retry authentication failures for all http requestsJeff King, Jul 18, 2011
  8. 06/14 introduce credentials APIJeff King, Jul 18, 2011
  9. Junio C HamanoJul 20, 2011
  10. Jeff KingJul 22, 2011
  11. Junio C HamanoJul 22, 2011
  12. Jeff KingJul 22, 2011
  13. Junio C HamanoJul 21, 2011
  14. Jeff KingJul 22, 2011
  15. 07/14 http: use credential API to get passwordsJeff King, Jul 18, 2011
  16. 08/14 look for credentials in config before promptingJeff King, Jul 18, 2011
  17. 09/14 allow the user to configure credential helpersJeff King, Jul 18, 2011
  18. 10/14 http: use hostname in credential descriptionJeff King, Jul 18, 2011
  19. Junio C HamanoJul 20, 2011
  20. Jeff KingJul 22, 2011
  21. Junio C HamanoJul 22, 2011
  22. Jeff KingJul 22, 2011
  23. Ted ZlatanovAug 8, 2011
  24. Junio C HamanoAug 8, 2011
  25. Ted ZlatanovAug 19, 2011
  26. Jeff KingAug 25, 2011
  27. Ted ZlatanovAug 26, 2011
  28. 11/14 docs: end-user documentation for the credential subsystemJeff King, Jul 18, 2011
  29. Junio C HamanoJul 20, 2011
  30. 12/14 credentials: add "cache" helperJeff King, Jul 18, 2011
  31. 13/14 credentials: add "store" helperJeff King, Jul 18, 2011
  32. 14/14 credentials: add "getpass" helperJeff King, Jul 18, 2011
  33. Jeff KingJul 18, 2011

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.