From patchwork Wed Apr 17 00:02:25 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632770 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 841052F2E for ; Wed, 17 Apr 2024 00:03:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312184; cv=none; b=svdMKXiRAAyinJEh80WnXbA1jzY41kDs7UyD+DROhqJya3iIgFIosJ53eS+VNVDOtJ9sdTQu7Nvnqt6a1NlrzXy3ZVf+xskTSR/0IQ1yeYPJt2SCaTXKvJSlXkjXFvrZGomB4Hz3QijDyBTf8ShAjMXcQG3uX9AJN2UdX/8rUgI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312184; c=relaxed/simple; bh=3Rn148Tb27U7Koze/W8bg0Ri3gJwwh7RJc898Sg4wXc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ej6ByzP+GNqPCIU2eqzBQYJH09HNCDym44a4gYl5DkPwp7nkAiQeCW+JGtxEK9cNYixNUNfL4JiMnjdMnWhOCIDmLXUtQJL7/t6Jd+wpKk2GhUf6JdgpIY+6j2DrXKDbAbJaB8DvmPL2XZwYa5UmJNwrHhI+PpcGZgAKV43pkN0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=xvZhutbU; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="xvZhutbU" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id EA9625D4E2; Wed, 17 Apr 2024 00:02:56 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312176; bh=3Rn148Tb27U7Koze/W8bg0Ri3gJwwh7RJc898Sg4wXc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=xvZhutbUJXDOr5bc1tXfeFBdge+ST21eyP7C7sULp+RY7TEDg+dh1wbwpNpRGSRAr dUyyVbCY8L/QMoE/ZlGPww5wYfpiWy1FUB1BExBnPTqhURZQhHJgkDFr9Xhxj15HhJ 26+NEY1GNJ+ZheHt6X5Hddj78K4Izqxaz/6maRsoyi6usNBIy0hNxJxFirDIXNUD47 jXgJI8VblZkRmefYAWrUsOh62pJRe3ipGEonbk/kis35USLCX/MJA6i5WWvB6XEvN0 lecVzt4Z1iuaYju3334zpaXcLEFGExBOGgvj25TQKlJOItzU66XKL6m6O8AK8Fualg VYtPCauuKXvGMRxaug2vYbfxX0P/X1bLJzvBWdrA/0tyidL0syNhi/f6pIEWo+38Mv JkVMwC91IpFtXH9F8JY8nUoIS+BzUVdDqPPmIDlcfH2keAsUS1uSKfdfkmbqdUuPD/ erzpzrbsYBLqa9jjn7oeqbpNF/dnE8mHL/KyP7dvdCI7qpIFkVI From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 01/16] credential: add an authtype field Date: Wed, 17 Apr 2024 00:02:25 +0000 Message-ID: <20240417000240.3611948-2-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 When Git makes an HTTP request, it can negotiate the type of authentication to use with the server provided the authentication scheme is one of a few well-known types (Basic, Digest, NTLM, or Negotiate). However, some servers wish to use other types of authentication, such as the Bearer type from OAuth2. Since libcurl doesn't natively support this type, it isn't possible to use it, and the user is forced to specify the Authorization header using the http.extraheader setting. However, storing a plaintext token in the repository configuration is not very secure, especially if a repository can be shared by multiple parties. We already have support for many types of secure credential storage by using credential helpers, so let's teach credential helpers how to produce credentials for an arbitrary scheme. If the credential helper specifies an authtype field, then it specifies an authentication scheme (e.g., Bearer) and the password field specifies the raw authentication token, with any encoding already specified. We reuse the password field for this because some credential helpers store the metadata without encryption even though the password is encrypted, and we'd like to avoid insecure storage if an older version of the credential helper gets ahold of the data. The username is not used in this case, but it is still preserved for the purpose of finding the right credential if the user has multiple accounts. If the authtype field is not specified, then the password behaves as normal and it is passed along with the username to libcurl. Signed-off-by: brian m. carlson --- credential.c | 5 +++++ credential.h | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/credential.c b/credential.c index 18098bd35e..3dec433df5 100644 --- a/credential.c +++ b/credential.c @@ -26,6 +26,7 @@ void credential_clear(struct credential *c) free(c->username); free(c->password); free(c->oauth_refresh_token); + free(c->authtype); string_list_clear(&c->helpers, 0); strvec_clear(&c->wwwauth_headers); @@ -252,6 +253,9 @@ int credential_read(struct credential *c, FILE *fp) } else if (!strcmp(key, "oauth_refresh_token")) { free(c->oauth_refresh_token); c->oauth_refresh_token = xstrdup(value); + } else if (!strcmp(key, "authtype")) { + free(c->authtype); + c->authtype = xstrdup(value); } else if (!strcmp(key, "url")) { credential_from_url(c, value); } else if (!strcmp(key, "quit")) { @@ -295,6 +299,7 @@ void credential_write(const struct credential *c, FILE *fp) } for (size_t i = 0; i < c->wwwauth_headers.nr; i++) credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); + credential_write_item(fp, "authtype", c->authtype, 0); } static int run_credential_helper(struct credential *c, diff --git a/credential.h b/credential.h index acc41adf54..dc96ca0318 100644 --- a/credential.h +++ b/credential.h @@ -143,6 +143,12 @@ struct credential { char *path; char *oauth_refresh_token; timestamp_t password_expiry_utc; + + /** + * The authorization scheme to use. If this is NULL, libcurl is free to + * negotiate any scheme it likes. + */ + char *authtype; }; #define CREDENTIAL_INIT { \ From patchwork Wed Apr 17 00:02:26 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632766 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 840992913 for ; Wed, 17 Apr 2024 00:03:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312183; cv=none; b=qEBAa7HLVFn8qZ/OxIlDw87guNy29ilb2NQSf0phr11AbIL3cb12nm6FdyKvTtgPEHHDqyVUkXFFVfEEUud42oZMIojZIRbeLU6OhCuU1fQL0dqgltyXk24U5J69KvkQBUFhBwSAMcLy0U+AZLYIR+1ariDxXrVFk7EZ0k9jhAc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312183; c=relaxed/simple; bh=/9gJP+E+JQoaZgFAJNZO8fswMIkX3MIP1Cv0Zg/zORY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ttl0eL21+9FHmsm8aHfj6Aa/KqInZqP8C+t8Pw/RJagSydh1Lvs0VQEqkl3CkM4w+lm31PCr5N9+Nfnb2NzkfwzAqciuz4cuSfC+f8ALbFn+y9go7y9HwwnmCuEVsix42KXDmt1kYtBoeOGNVkI7ODf6Hwo8HSHwnxK4HR3Ah4U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=YJVkmU4L; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="YJVkmU4L" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 03B875D4E3; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=/9gJP+E+JQoaZgFAJNZO8fswMIkX3MIP1Cv0Zg/zORY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=YJVkmU4LZaOYLHlvlkvSAPTsyxqtyfH24pM+wOMF1qtCEV0mnF6jRyMMDqTudavls 8awTMsDlBzkfECbTsHjCj75wWVbqzi69mMrtnkP7/JbMQkDvuw1vxGWy11+kNUqI3u TbxmbCrBn+Aho0Zng2S/Vs9Q8dLXFf87CWcNr1kI515DIzK5u9uwEsO0qUJ2RQ53ID ONe6U2+pbWOBOinNIOuuLbcnTyy6o+7Ckvpbhn9uctQyZFJZJtYEMB3ec+4BBUwvtv 8IpeuavN557jmlOUk82TjDt8j29vgA9k54my9PG8Q+t/JTOAC91Uk7GuM3vcqcYvEC g6BjZ1ALDaWcw3MMnR7FrxXhgnJc3WXJ4tNubsDZkO096QInIzZbRc87qgvUnI/3R0 kjdXs/SFL3ns9VZEUq3le2evsGXzbwRYN8c4tS1ihNMuTAFhy4GwVRQl+J1FH6wzyO K1NWRYUByC2UdVaSZMJNk5HuaySjx1Q1G4f0Xgk5VvECBief3dt From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 02/16] remote-curl: reset headers on new request Date: Wed, 17 Apr 2024 00:02:26 +0000 Message-ID: <20240417000240.3611948-3-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 When we retry a post_rpc request, we currently reuse the same headers as before. In the future, we'd like to be able to modify them based on the result we get back, so let's reset them on each retry so we can avoid sending potentially duplicate headers if the values change. Signed-off-by: brian m. carlson --- remote-curl.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/remote-curl.c b/remote-curl.c index 1161dc7fed..e37eaa17b7 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -893,7 +893,7 @@ static curl_off_t xcurl_off_t(size_t len) static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_received) { struct active_request_slot *slot; - struct curl_slist *headers = http_copy_default_headers(); + struct curl_slist *headers = NULL; int use_gzip = rpc->gzip_request; char *gzip_body = NULL; size_t gzip_size = 0; @@ -935,6 +935,8 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece needs_100_continue = 1; } +retry: + headers = http_copy_default_headers(); headers = curl_slist_append(headers, rpc->hdr_content_type); headers = curl_slist_append(headers, rpc->hdr_accept); headers = curl_slist_append(headers, needs_100_continue ? @@ -948,7 +950,6 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece if (rpc->protocol_header) headers = curl_slist_append(headers, rpc->protocol_header); -retry: slot = get_active_slot(); curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); @@ -1044,6 +1045,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece err = run_slot(slot, NULL); if (err == HTTP_REAUTH && !large_request) { credential_fill(&http_auth); + curl_slist_free_all(headers); goto retry; } if (err != HTTP_OK) From patchwork Wed Apr 17 00:02:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632767 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 840722904 for ; Wed, 17 Apr 2024 00:03:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312183; cv=none; b=b0HIEXGqMq+rroOXE3l4DAZTDaj9KryEeGt5eAIPSjhZGOrt9GX5YtlsjOISHwP78medKfFPkOB3gWKKdepZTS0n/0M5TyjE9GqYDYvwlF2Qh6sgaCqR12H1HjEXvZHt/Ju4KhAunMQFTj/S3kBMF2VeNW/hvdNkH3M4IMfT4i0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312183; c=relaxed/simple; bh=r2cEH9gJw6DRewoR5EcFUlQwCcobEMlk5fkACL07Djs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=FntNZHy23KbMFeBvQn/J5WsGiwCzKmLyhM1BhXLZ1n315NGTumyJA3hmdXLGQZPwScOK5Lhwv+TBoSkR8DLRibYYlzWuUMj6muPr1GAXldvSK4gxY6Uj/3VZJhmSjBKEWewjE5KMQ9OswWskGkQakz6ysczHdzbxSenTCsygr84= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=UiikIxwv; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="UiikIxwv" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 10C1A5D4E4; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=r2cEH9gJw6DRewoR5EcFUlQwCcobEMlk5fkACL07Djs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=UiikIxwvQ4Svf4iFkD+7CvQLxSQ2CDPB8nppNr4ypuUZRYBd6vWW6eOc/XtBGqHQQ k6l6+3KgxwSYlgAlHCWg9DbBsdXHYooZ6YM7oJbnU6P/bfCHW7IR3NEScNSUOrarkE fNhHHGFenGsvMDhFp7HBURTzLTZcvoxRyUqZiA60cml5J4Q/jAemqD/NYk722MQKhZ 94Ep8em9olDcGEuyqhM+XgaYjxpneMlIFdxCTIIt4gLJZJY/EVsgPRYZcPM/sBHH3c CtEG3vCe7kb2Hn6bXvaKEXVWl0G9eIDehsNpDJIlOX9qTFJwoBjX0bJdpCieP+tmHj SQroaHHdUxvXWq3DND8/SNcXGXMNfT8V663dTx+/5FDHPv2/W+EBj4A2VhASrunufR RfthogZf5Nzc4NA/06ZGKr2JIJNUOqAMdA+hK3hV5Xpg+S3m3K/3S8UDS8XGVFosFx Cb3kbCKDrS4hBY6X+QefH8t7cZ9NkXXpB32vncFRIbZXDd8FqKU From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 03/16] http: use new headers for each object request Date: Wed, 17 Apr 2024 00:02:27 +0000 Message-ID: <20240417000240.3611948-4-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Currently we create one set of headers for all object requests and reuse it. However, we'll need to adjust the headers for authentication purposes in the future, so let's create a new set for each request so that we can adjust them if the authentication changes. Note that the cost of allocation here is tiny compared to the fact that we're making a network call, not to mention probably a full TLS connection, so this shouldn't have a significant impact on performance. Moreover, nobody who cares about performance is using the dumb HTTP protocol anyway, since it often makes huge numbers of requests compared to the smart protocol. Signed-off-by: brian m. carlson --- http.c | 20 +++++++++++--------- http.h | 2 ++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/http.c b/http.c index e73b136e58..22639b0974 100644 --- a/http.c +++ b/http.c @@ -128,7 +128,6 @@ static unsigned long empty_auth_useless = | CURLAUTH_DIGEST; static struct curl_slist *pragma_header; -static struct curl_slist *no_pragma_header; static struct string_list extra_http_headers = STRING_LIST_INIT_DUP; static struct curl_slist *host_resolutions; @@ -299,6 +298,11 @@ size_t fwrite_null(char *ptr UNUSED, size_t eltsize UNUSED, size_t nmemb, return nmemb; } +static struct curl_slist *object_request_headers(void) +{ + return curl_slist_append(http_copy_default_headers(), "Pragma:"); +} + static void closedown_active_slot(struct active_request_slot *slot) { active_requests--; @@ -1275,8 +1279,6 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) pragma_header = curl_slist_append(http_copy_default_headers(), "Pragma: no-cache"); - no_pragma_header = curl_slist_append(http_copy_default_headers(), - "Pragma:"); { char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS"); @@ -1360,9 +1362,6 @@ void http_cleanup(void) curl_slist_free_all(pragma_header); pragma_header = NULL; - curl_slist_free_all(no_pragma_header); - no_pragma_header = NULL; - curl_slist_free_all(host_resolutions); host_resolutions = NULL; @@ -2370,6 +2369,7 @@ void release_http_pack_request(struct http_pack_request *preq) } preq->slot = NULL; strbuf_release(&preq->tmpfile); + curl_slist_free_all(preq->headers); free(preq->url); free(preq); } @@ -2454,11 +2454,11 @@ struct http_pack_request *new_direct_http_pack_request( } preq->slot = get_active_slot(); + preq->headers = object_request_headers(); curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEDATA, preq->packfile); curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite); curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url); - curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, - no_pragma_header); + curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, preq->headers); /* * If there is data present from a previous transfer attempt, @@ -2624,13 +2624,14 @@ struct http_object_request *new_http_object_request(const char *base_url, } freq->slot = get_active_slot(); + freq->headers = object_request_headers(); curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEDATA, freq); curl_easy_setopt(freq->slot->curl, CURLOPT_FAILONERROR, 0); curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr); curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url); - curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, freq->headers); /* * If we have successfully processed data from a previous fetch @@ -2718,5 +2719,6 @@ void release_http_object_request(struct http_object_request *freq) release_active_slot(freq->slot); freq->slot = NULL; } + curl_slist_free_all(freq->headers); strbuf_release(&freq->tmpfile); } diff --git a/http.h b/http.h index 3af19a8bf5..c5f8cc4620 100644 --- a/http.h +++ b/http.h @@ -196,6 +196,7 @@ struct http_pack_request { FILE *packfile; struct strbuf tmpfile; struct active_request_slot *slot; + struct curl_slist *headers; }; struct http_pack_request *new_http_pack_request( @@ -229,6 +230,7 @@ struct http_object_request { int zret; int rename; struct active_request_slot *slot; + struct curl_slist *headers; }; struct http_object_request *new_http_object_request( From patchwork Wed Apr 17 00:02:28 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632769 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8403C28F4 for ; Wed, 17 Apr 2024 00:03:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312184; cv=none; b=QXTePtOPbOifgTRBwAH1Ph5kGgPAnRAZYPsjvMWnBMBjbMniWKziVfstIC+1bDxXwP9AVh7X8xOQ8noWb6ZmgDpooYOabEq1st1DlDE5eU3qHTAVbW4IOanw01YqIIYHaXrnjjkZDCkpuwjhPQrXTknFz/2E5VJ7y+jb9DhAINU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312184; c=relaxed/simple; bh=8TxfuBxsWtpSLU/4F9w2T/UCfwNMMCASchJnroJ1l/o=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=oigOh2GWOLsa/DGuiY/dA37ip0hU6az/bjw+rQR/ImRfL7jbiuzYoW5+JP0Z57774emvj5KsUyGy6maBuyk1Nt/XRBjGNJOkbcsxMJrv10xXai+LRLWJGXC9fF10Xfob9EJDGm+ktMTkAmQBh1zfNtS9w8WIlnfGezESKUFuytw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=O2b4DC9J; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="O2b4DC9J" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 200A35D4E5; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=8TxfuBxsWtpSLU/4F9w2T/UCfwNMMCASchJnroJ1l/o=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=O2b4DC9JGeMPttLe61/YJAt8meWaSIycQykVCII/8vqVGtIIKYNkF1LAzz13QM6EG F3YubSTztbwDIb3NlqCtRx9yHCwWWNiLURRDSZ0EqAkAqTi6Jxgzoutz4fYaWpPbQa OCxVap+PhPiGE8aX3n+h1HYi0HPZojHs/VeCeHYkQnSDb/4wwUH6Xy7b3DsuIsgIEG c4zclt+fkjf3sULUFHpOCJHUltZoJQdm9L66CS5H0PoUwV2bDn6Tg8fU7MnzpJF9O2 uULmIVvoUXFDWT42RjrXLYhtHewO8ZOV18Nvy7xSejxOaY3fIZ9JLs6isioullfqUQ 7BziKwxjxmnO4EFdFUP+ecMEhJTfU74c5JnQzjRgg9Wl2kGztvp5adDqagZLTiDLZ8 Asiuytep61s/bywtshRAPj76E002WBnscyV+BAyODSNfF5mjTul8voKxXhLtHBvMYr mRe2GBEvNaeSvtAiezNnbk4+VFAJAGldrhRdpwpzXFP/SU1Pv/R From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 04/16] credential: add a field for pre-encoded credentials Date: Wed, 17 Apr 2024 00:02:28 +0000 Message-ID: <20240417000240.3611948-5-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 At the moment, our credential code wants to find a username and password for access, which, for HTTP, it will pass to libcurl to encode and process. However, many users want to use authentication schemes that libcurl doesn't support, such as Bearer authentication. In these schemes, the secret is not a username and password pair, but some sort of token that meets the production for authentication data in the RFC. In fact, in general, it's useful to allow our credential helper to have knowledge about what specifically to put in the protocol header. Thus, add a field, credential, which contains data that's preencoded to be suitable for the protocol in question. If we have such data, we need neither a username nor a password, so make that adjustment as well. It is in theory possible to reuse the password field for this. However, if we do so, we must know whether the credential helper supports our new scheme before sending it data, which necessitates some sort of capability inquiry, because otherwise an uninformed credential helper would store our preencoded data as a password, which would fail the next time we attempted to connect to the remote server. This design is substantially simpler, and we can hint to the credential helper that we support this approach with a simple new field instead of needing to query it first. Signed-off-by: brian m. carlson --- credential.c | 14 ++++++++++---- credential.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/credential.c b/credential.c index 3dec433df5..c521822e5a 100644 --- a/credential.c +++ b/credential.c @@ -25,6 +25,7 @@ void credential_clear(struct credential *c) free(c->path); free(c->username); free(c->password); + free(c->credential); free(c->oauth_refresh_token); free(c->authtype); string_list_clear(&c->helpers, 0); @@ -234,6 +235,9 @@ int credential_read(struct credential *c, FILE *fp) } else if (!strcmp(key, "password")) { free(c->password); c->password = xstrdup(value); + } else if (!strcmp(key, "credential")) { + free(c->credential); + c->credential = xstrdup(value); } else if (!strcmp(key, "protocol")) { free(c->protocol); c->protocol = xstrdup(value); @@ -291,6 +295,7 @@ void credential_write(const struct credential *c, FILE *fp) credential_write_item(fp, "path", c->path, 0); credential_write_item(fp, "username", c->username, 0); credential_write_item(fp, "password", c->password, 0); + credential_write_item(fp, "credential", c->credential, 0); credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0); if (c->password_expiry_utc != TIME_MAX) { char *s = xstrfmt("%"PRItime, c->password_expiry_utc); @@ -366,7 +371,7 @@ void credential_fill(struct credential *c) { int i; - if (c->username && c->password) + if ((c->username && c->password) || c->credential) return; credential_apply_config(c); @@ -379,7 +384,7 @@ void credential_fill(struct credential *c) /* Reset expiry to maintain consistency */ c->password_expiry_utc = TIME_MAX; } - if (c->username && c->password) + if ((c->username && c->password) || c->credential) return; if (c->quit) die("credential helper '%s' told us to quit", @@ -387,7 +392,7 @@ void credential_fill(struct credential *c) } credential_getpass(c); - if (!c->username && !c->password) + if (!c->username && !c->password && !c->credential) die("unable to get password from user"); } @@ -397,7 +402,7 @@ void credential_approve(struct credential *c) if (c->approved) return; - if (!c->username || !c->password || c->password_expiry_utc < time(NULL)) + if (((!c->username || !c->password) && !c->credential) || c->password_expiry_utc < time(NULL)) return; credential_apply_config(c); @@ -418,6 +423,7 @@ void credential_reject(struct credential *c) FREE_AND_NULL(c->username); FREE_AND_NULL(c->password); + FREE_AND_NULL(c->credential); FREE_AND_NULL(c->oauth_refresh_token); c->password_expiry_utc = TIME_MAX; c->approved = 0; diff --git a/credential.h b/credential.h index dc96ca0318..9db892cf4d 100644 --- a/credential.h +++ b/credential.h @@ -138,6 +138,7 @@ struct credential { char *username; char *password; + char *credential; char *protocol; char *host; char *path; From patchwork Wed Apr 17 00:02:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632773 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 00CCE3C0C for ; Wed, 17 Apr 2024 00:03:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; cv=none; b=AS8QettXzPl0n0Wv9ix1W4zqwYljROiBxq1T2Y9KSeyKhVHy4M/U0s66OjJT1pYYnEk0PpPM2XP61IFtPEcRLWGY9oqex4GiRcRDvt0zzMsDwpbY4a8JW4mqqKvZ29oiOPOxuU7Qv9CCPYo8xTm3+kM7KxfVxXaymO49m6iyiQs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; c=relaxed/simple; bh=Gqw/5HfQVTmwET94RT7DHa69H1GmF45FqzD5hTWUWjo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ZuCUXeDwF5Qb91WLS6/Vvny0EwPEISM5ITupgHq1VThbSSz48i4+kJuKZFVip/1XBCM3Cf787LbNclLQ+gVAHcSEXV2T57oooLyoGTI03EfdU9rcNnhDBtAOtd8MTpkF9jAhiYMDGqPCLkxLetOIOijuwaGWSKXJ6uhNp65AQFE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=mMBGWMmf; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="mMBGWMmf" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 2ED305D4E6; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=Gqw/5HfQVTmwET94RT7DHa69H1GmF45FqzD5hTWUWjo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=mMBGWMmfk0h7rTf7o6Xmso6I7WnNVY3xUKXnM/Fh1PCQZmaH7MWuDD9Ro4tpx8U28 ysAQpUUPLuaelobjMMz8dvW+nxqyl25i54FVb+KeF1mYFEtd9SC48OLMFzCf+U0vyM 2qJe2RznZemkgdMcG5U3jOdUbbJJHPfBjFr/VKxUiqufUXMWJNvB0lt0HEkpcG3JCq 3itXtLX9AbaQ556askzzzQtlmA/pjs2ya9v/wT3QyRAslbO5glgvFjrbQk0znhtwzb QOKfKZkFTfomfZqRLDCvYxoLEgHZRvKX0jkMsxzjRCJEBHUC7kYmejegT0kujm7YOL fnSeAkNwI7UgijGufy61ajRv8ZQpTbeqA/Ct7rfxQEIzzlttOPOOZbSJcIM6ZYrHo7 mClH+uEe/4N+me5VE3+YXb9oWdMq5+2mvXXBkKxck5RGVPmiff6U0AqeK82FftTR8A 5Kw5OJcuvf35Abq+O7SxqZlCle0j5GOfQbVPuRfSEl3tzw7G70h From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 05/16] credential: gate new fields on capability Date: Wed, 17 Apr 2024 00:02:29 +0000 Message-ID: <20240417000240.3611948-6-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 We support the new credential and authtype fields, but we lack a way to indicate to a credential helper that we'd like them to be used. Without some sort of indication, the credential helper doesn't know if it should try to provide us a username and password, or a pre-encoded credential. For example, the helper might prefer a more restricted Bearer token if pre-encoded credentials are possible, but might have to fall back to more general username and password if not. Let's provide a simple way to indicate whether Git (or, for that matter, the helper) is capable of understanding the authtype and credential fields. We send this capability when we generate a request, and the other side may reply to indicate to us that it does, too. For now, don't enable sending capabilities for the HTTP code. In a future commit, we'll introduce appropriate handling for that code, which requires more in-depth work. The logic for determining whether a capability is supported may seem complex, but it is not. At each stage, we emit the capability to the following stage if all preceding stages have declared it. Thus, if the caller to git credential fill didn't declare it, then we won't send it to the helper, and if fill's caller did send but the helper doesn't understand it, then we won't send it on in the response. If we're an internal user, then we know about all capabilities and will request them. For "git credential approve" and "git credential reject", we set the helper capability before calling the helper, since we assume that the input we're getting from the external program comes from a previous call to "git credential fill", and thus we'll invoke send a capability to the helper if and only if we got one from the standard input, which is the correct behavior. Signed-off-by: brian m. carlson --- builtin/credential-cache--daemon.c | 2 +- builtin/credential-store.c | 2 +- builtin/credential.c | 8 ++- credential.c | 64 +++++++++++++++-- credential.h | 40 ++++++++++- http.c | 10 +-- imap-send.c | 2 +- remote-curl.c | 4 +- t/t0300-credentials.sh | 107 ++++++++++++++++++++++++++++- 9 files changed, 215 insertions(+), 24 deletions(-) diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c index 3a6a750a8e..ccbcf99ac1 100644 --- a/builtin/credential-cache--daemon.c +++ b/builtin/credential-cache--daemon.c @@ -115,7 +115,7 @@ static int read_request(FILE *fh, struct credential *c, return error("client sent bogus timeout line: %s", item.buf); *timeout = atoi(p); - if (credential_read(c, fh) < 0) + if (credential_read(c, fh, CREDENTIAL_OP_HELPER) < 0) return -1; return 0; } diff --git a/builtin/credential-store.c b/builtin/credential-store.c index 4a492411bb..494c809332 100644 --- a/builtin/credential-store.c +++ b/builtin/credential-store.c @@ -205,7 +205,7 @@ int cmd_credential_store(int argc, const char **argv, const char *prefix) if (!fns.nr) die("unable to set up default path; use --file"); - if (credential_read(&c, stdin) < 0) + if (credential_read(&c, stdin, CREDENTIAL_OP_HELPER) < 0) die("unable to read credential"); if (!strcmp(op, "get")) diff --git a/builtin/credential.c b/builtin/credential.c index 7010752987..643bf0b5e4 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -17,15 +17,17 @@ int cmd_credential(int argc, const char **argv, const char *prefix UNUSED) usage(usage_msg); op = argv[1]; - if (credential_read(&c, stdin) < 0) + if (credential_read(&c, stdin, CREDENTIAL_OP_INITIAL) < 0) die("unable to read credential from stdin"); if (!strcmp(op, "fill")) { - credential_fill(&c); - credential_write(&c, stdout); + credential_fill(&c, 0); + credential_write(&c, stdout, CREDENTIAL_OP_RESPONSE); } else if (!strcmp(op, "approve")) { + credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER); credential_approve(&c); } else if (!strcmp(op, "reject")) { + credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER); credential_reject(&c); } else { usage(usage_msg); diff --git a/credential.c b/credential.c index c521822e5a..f5396629df 100644 --- a/credential.c +++ b/credential.c @@ -34,6 +34,29 @@ void credential_clear(struct credential *c) credential_init(c); } +static void credential_set_capability(struct credential_capability *capa, + enum credential_op_type op_type) +{ + switch (op_type) { + case CREDENTIAL_OP_INITIAL: + capa->request_initial = 1; + break; + case CREDENTIAL_OP_HELPER: + capa->request_helper = 1; + break; + case CREDENTIAL_OP_RESPONSE: + capa->response = 1; + break; + } +} + + +void credential_set_all_capabilities(struct credential *c, + enum credential_op_type op_type) +{ + credential_set_capability(&c->capa_authtype, op_type); +} + int credential_match(const struct credential *want, const struct credential *have, int match_password) { @@ -210,7 +233,26 @@ static void credential_getpass(struct credential *c) PROMPT_ASKPASS); } -int credential_read(struct credential *c, FILE *fp) +static int credential_has_capability(const struct credential_capability *capa, + enum credential_op_type op_type) +{ + /* + * We're checking here if each previous step indicated that we had the + * capability. If it did, then we want to pass it along; conversely, if + * it did not, we don't want to report that to our caller. + */ + switch (op_type) { + case CREDENTIAL_OP_HELPER: + return capa->request_initial; + case CREDENTIAL_OP_RESPONSE: + return capa->request_initial && capa->request_helper; + default: + return 0; + } +} + +int credential_read(struct credential *c, FILE *fp, + enum credential_op_type op_type) { struct strbuf line = STRBUF_INIT; @@ -249,6 +291,8 @@ int credential_read(struct credential *c, FILE *fp) c->path = xstrdup(value); } else if (!strcmp(key, "wwwauth[]")) { strvec_push(&c->wwwauth_headers, value); + } else if (!strcmp(key, "capability[]") && !strcmp(value, "authtype")) { + credential_set_capability(&c->capa_authtype, op_type); } else if (!strcmp(key, "password_expiry_utc")) { errno = 0; c->password_expiry_utc = parse_timestamp(value, NULL, 10); @@ -288,14 +332,19 @@ static void credential_write_item(FILE *fp, const char *key, const char *value, fprintf(fp, "%s=%s\n", key, value); } -void credential_write(const struct credential *c, FILE *fp) +void credential_write(const struct credential *c, FILE *fp, + enum credential_op_type op_type) { + if (credential_has_capability(&c->capa_authtype, op_type)) { + credential_write_item(fp, "capability[]", "authtype", 0); + credential_write_item(fp, "authtype", c->authtype, 0); + credential_write_item(fp, "credential", c->credential, 0); + } credential_write_item(fp, "protocol", c->protocol, 1); credential_write_item(fp, "host", c->host, 1); credential_write_item(fp, "path", c->path, 0); credential_write_item(fp, "username", c->username, 0); credential_write_item(fp, "password", c->password, 0); - credential_write_item(fp, "credential", c->credential, 0); credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0); if (c->password_expiry_utc != TIME_MAX) { char *s = xstrfmt("%"PRItime, c->password_expiry_utc); @@ -304,7 +353,6 @@ void credential_write(const struct credential *c, FILE *fp) } for (size_t i = 0; i < c->wwwauth_headers.nr; i++) credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); - credential_write_item(fp, "authtype", c->authtype, 0); } static int run_credential_helper(struct credential *c, @@ -327,14 +375,14 @@ static int run_credential_helper(struct credential *c, fp = xfdopen(helper.in, "w"); sigchain_push(SIGPIPE, SIG_IGN); - credential_write(c, fp); + credential_write(c, fp, want_output ? CREDENTIAL_OP_HELPER : CREDENTIAL_OP_RESPONSE); fclose(fp); sigchain_pop(SIGPIPE); if (want_output) { int r; fp = xfdopen(helper.out, "r"); - r = credential_read(c, fp); + r = credential_read(c, fp, CREDENTIAL_OP_HELPER); fclose(fp); if (r < 0) { finish_command(&helper); @@ -367,7 +415,7 @@ static int credential_do(struct credential *c, const char *helper, return r; } -void credential_fill(struct credential *c) +void credential_fill(struct credential *c, int all_capabilities) { int i; @@ -375,6 +423,8 @@ void credential_fill(struct credential *c) return; credential_apply_config(c); + if (all_capabilities) + credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL); for (i = 0; i < c->helpers.nr; i++) { credential_do(c, c->helpers.items[i].string, "get"); diff --git a/credential.h b/credential.h index 9db892cf4d..b524fdba59 100644 --- a/credential.h +++ b/credential.h @@ -93,6 +93,27 @@ * ----------------------------------------------------------------------- */ +/* + * These values define the kind of operation we're performing and the + * capabilities at each stage. The first is either an external request (via git + * credential fill) or an internal request (e.g., via the HTTP) code. The + * second is the call to the credential helper, and the third is the response + * we're providing. + * + * At each stage, we will emit the capability only if the previous stage + * supported it. + */ +enum credential_op_type { + CREDENTIAL_OP_INITIAL = 1, + CREDENTIAL_OP_HELPER = 2, + CREDENTIAL_OP_RESPONSE = 3, +}; + +struct credential_capability { + unsigned request_initial:1, + request_helper:1, + response:1; +}; /** * This struct represents a single username/password combination @@ -136,6 +157,8 @@ struct credential { use_http_path:1, username_from_proto:1; + struct credential_capability capa_authtype; + char *username; char *password; char *credential; @@ -174,8 +197,11 @@ void credential_clear(struct credential *); * returns, the username and password fields of the credential are * guaranteed to be non-NULL. If an error occurs, the function will * die(). + * + * If all_capabilities is set, this is an internal user that is prepared + * to deal with all known capabilities, and we should advertise that fact. */ -void credential_fill(struct credential *); +void credential_fill(struct credential *, int all_capabilities); /** * Inform the credential subsystem that the provided credentials @@ -198,8 +224,16 @@ void credential_approve(struct credential *); */ void credential_reject(struct credential *); -int credential_read(struct credential *, FILE *); -void credential_write(const struct credential *, FILE *); +/** + * Enable all of the supported credential flags in this credential. + */ +void credential_set_all_capabilities(struct credential *c, + enum credential_op_type op_type); + +int credential_read(struct credential *, FILE *, + enum credential_op_type); +void credential_write(const struct credential *, FILE *, + enum credential_op_type); /* * Parse a url into a credential struct, replacing any existing contents. diff --git a/http.c b/http.c index 22639b0974..54ddff03fb 100644 --- a/http.c +++ b/http.c @@ -569,7 +569,7 @@ static void init_curl_http_auth(CURL *result) return; } - credential_fill(&http_auth); + credential_fill(&http_auth, 0); curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username); curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password); @@ -596,7 +596,7 @@ static void init_curl_proxy_auth(CURL *result) { if (proxy_auth.username) { if (!proxy_auth.password) - credential_fill(&proxy_auth); + credential_fill(&proxy_auth, 0); set_proxyauth_name_password(result); } @@ -630,7 +630,7 @@ static int has_cert_password(void) cert_auth.host = xstrdup(""); cert_auth.username = xstrdup(""); cert_auth.path = xstrdup(ssl_cert); - credential_fill(&cert_auth); + credential_fill(&cert_auth, 0); } return 1; } @@ -645,7 +645,7 @@ static int has_proxy_cert_password(void) proxy_cert_auth.host = xstrdup(""); proxy_cert_auth.username = xstrdup(""); proxy_cert_auth.path = xstrdup(http_proxy_ssl_cert); - credential_fill(&proxy_cert_auth); + credential_fill(&proxy_cert_auth, 0); } return 1; } @@ -2190,7 +2190,7 @@ static int http_request_reauth(const char *url, BUG("Unknown http_request target"); } - credential_fill(&http_auth); + credential_fill(&http_auth, 0); return http_request(url, result, target, options); } diff --git a/imap-send.c b/imap-send.c index f2e1947e63..8c89e866b6 100644 --- a/imap-send.c +++ b/imap-send.c @@ -944,7 +944,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent cred->username = xstrdup_or_null(srvc->user); cred->password = xstrdup_or_null(srvc->pass); - credential_fill(cred); + credential_fill(cred, 1); if (!srvc->user) srvc->user = xstrdup(cred->username); diff --git a/remote-curl.c b/remote-curl.c index e37eaa17b7..f96bda2431 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -926,7 +926,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece do { err = probe_rpc(rpc, &results); if (err == HTTP_REAUTH) - credential_fill(&http_auth); + credential_fill(&http_auth, 0); } while (err == HTTP_REAUTH); if (err != HTTP_OK) return -1; @@ -1044,7 +1044,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece rpc->any_written = 0; err = run_slot(slot, NULL); if (err == HTTP_REAUTH && !large_request) { - credential_fill(&http_auth); + credential_fill(&http_auth, 0); curl_slist_free_all(headers); goto retry; } diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 400f6bdbca..daf330ddd8 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -12,7 +12,13 @@ test_expect_success 'setup helper scripts' ' IFS== while read key value; do echo >&2 "$whoami: $key=$value" - eval "$key=$value" + if test -z "${key%%*\[\]}" + then + key=${key%%\[\]} + eval "$key=\"\$$key $value\"" + else + eval "$key=$value" + fi done IFS=$OIFS EOF @@ -35,6 +41,16 @@ test_expect_success 'setup helper scripts' ' test -z "$pass" || echo password=$pass EOF + write_script git-credential-verbatim-cred <<-\EOF && + authtype=$1; shift + credential=$1; shift + . ./dump + echo capability[]=authtype + test -z "${capability##*authtype*}" || exit 0 + test -z "$authtype" || echo authtype=$authtype + test -z "$credential" || echo credential=$credential + EOF + write_script git-credential-verbatim-with-expiry <<-\EOF && user=$1; shift pass=$1; shift @@ -64,6 +80,26 @@ test_expect_success 'credential_fill invokes helper' ' EOF ' +test_expect_success 'credential_fill invokes helper with credential' ' + check fill "verbatim-cred Bearer token" <<-\EOF + capability[]=authtype + protocol=http + host=example.com + -- + capability[]=authtype + authtype=Bearer + credential=token + protocol=http + host=example.com + -- + verbatim-cred: get + verbatim-cred: capability[]=authtype + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + + test_expect_success 'credential_fill invokes multiple helpers' ' check fill useless "verbatim foo bar" <<-\EOF protocol=http @@ -83,6 +119,42 @@ test_expect_success 'credential_fill invokes multiple helpers' ' EOF ' +test_expect_success 'credential_fill response does not get capabilities when helpers are incapable' ' + check fill useless "verbatim foo bar" <<-\EOF + capability[]=authtype + protocol=http + host=example.com + -- + protocol=http + host=example.com + username=foo + password=bar + -- + useless: get + useless: capability[]=authtype + useless: protocol=http + useless: host=example.com + verbatim: get + verbatim: capability[]=authtype + verbatim: protocol=http + verbatim: host=example.com + EOF +' + +test_expect_success 'credential_fill response does not get capabilities when caller is incapable' ' + check fill "verbatim-cred Bearer token" <<-\EOF + protocol=http + host=example.com + -- + protocol=http + host=example.com + -- + verbatim-cred: get + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + test_expect_success 'credential_fill stops when we get a full response' ' check fill "verbatim one two" "verbatim three four" <<-\EOF protocol=http @@ -99,6 +171,25 @@ test_expect_success 'credential_fill stops when we get a full response' ' EOF ' +test_expect_success 'credential_fill thinks a credential is a full response' ' + check fill "verbatim-cred Bearer token" "verbatim three four" <<-\EOF + capability[]=authtype + protocol=http + host=example.com + -- + capability[]=authtype + authtype=Bearer + credential=token + protocol=http + host=example.com + -- + verbatim-cred: get + verbatim-cred: capability[]=authtype + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + test_expect_success 'credential_fill continues through partial response' ' check fill "verbatim one \"\"" "verbatim two three" <<-\EOF protocol=http @@ -175,6 +266,20 @@ test_expect_success 'credential_fill passes along metadata' ' EOF ' +test_expect_success 'credential_fill produces no credential without capability' ' + check fill "verbatim-cred Bearer token" <<-\EOF + protocol=http + host=example.com + -- + protocol=http + host=example.com + -- + verbatim-cred: get + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + test_expect_success 'credential_approve calls all helpers' ' check approve useless "verbatim one two" <<-\EOF protocol=http From patchwork Wed Apr 17 00:02:30 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632771 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 02C8D3C2F for ; Wed, 17 Apr 2024 00:03:03 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312185; cv=none; b=kPeN20Ece5bg1aVsQZWJF2LuSyMq2qbF1XY7WiOAz8I2wJF1Z+NK6UHWF6npaNrDjJ1sYbwwRI4Y+rpAYbOV42BfMgIcIi0Yy24w4OSTUm15q8EW5N5Ssn9RVh5L6XpZrRYp0xB7e+AoF96cn2AzH/uFZEy2VU/owE+jYoiBVAg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312185; c=relaxed/simple; bh=HA0AIlaLL+FWmt3nsi1CR7e7of0upxixtRcsanYODQY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aACRE0iu/gAXsIsR85LkkXamrxRFIM2vO0ibcFByiGYQjJQy2PDMFjZM2zTjLS7M2H37dOXXMSTuPu2T6KlisGtqSNWBS0Vz3n6vDqOC9FC9XM1brD5YDY0vvz2Jx/jvaVCLtnrirYCWwswIu/I73u6xlWFZbODBxuda0GHWijM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=tb7Bbddy; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="tb7Bbddy" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 468795D4E7; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=HA0AIlaLL+FWmt3nsi1CR7e7of0upxixtRcsanYODQY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=tb7BbddyZ/jBGvqDWn4bBrVIhpnFuo33AsSfhBNXhnQ3/TtgTCMwD8jsMONkyTOx2 EFFKr0dQ/C67tEwg68VhAD8YPQ7R9XazTRM5EBE64AHMv1Q7dEHuY+X1M+n4mBXSNE GkO/5O7u6IIn51TCO7uHUByfCA/z/W0r1bmYsogp7nrpKm5Icr7M08+elawBqvxPjb BTvDAt5lPKEBSzZFLPm+VQjprHne/csFPrFdNlKarRByhKfA5eUA8xpigetAEbE9WM 5y/8IDQjHgWiQLHDbWwKjkCsOaJGtQUt1vPfdYzmTrIHvIUXbKOQXoJTgRXZ0Cae3w Nv01fbqd6Ye31eBjwtk3VgGDcQ2Bty/bhSwRzlozb5G9SBcJje/pAuJMBirJ+PZhxw NQ1XNsq8HFfon8m8vH/Rd+2qZOK+jRhVbS7+9vCHV4+/IXO+qWyW+jUGP6D3g1jwHF bUv2Kp1ro2yvRuCpnGnOb4MKhjjsHyGJ9oGzhaA7y6hhQtszWB/ From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 06/16] credential: add a field called "ephemeral" Date: Wed, 17 Apr 2024 00:02:30 +0000 Message-ID: <20240417000240.3611948-7-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Now that we have support for a wide variety of types of authentication, it's important to indicate to other credential helpers whether they should store credentials, since not every credential helper may intuitively understand all possible values of the authtype field. Do so with a boolean field called "ephemeral", to indicate whether the credential is expected to be temporary. For example, in HTTP Digest authentication, the Authorization header value is based off a nonce. It isn't useful to store this value for later use because reusing the credential long term will not result in successful authentication due to the nonce necessarily differing. An additional case is potentially short-lived credentials, which may last only a few hours. It similarly wouldn't be helper for other credential helpers to attempt to provide these much later. We do still pass the value to "git credential store" or "git credential erase", since it may be helpful to the original helper to know whether the operation was successful. Signed-off-by: brian m. carlson --- credential.c | 4 ++++ credential.h | 1 + t/t0300-credentials.sh | 30 ++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/credential.c b/credential.c index f5396629df..3531d74346 100644 --- a/credential.c +++ b/credential.c @@ -289,6 +289,8 @@ int credential_read(struct credential *c, FILE *fp, } else if (!strcmp(key, "path")) { free(c->path); c->path = xstrdup(value); + } else if (!strcmp(key, "ephemeral")) { + c->ephemeral = !!git_config_bool("ephemeral", value); } else if (!strcmp(key, "wwwauth[]")) { strvec_push(&c->wwwauth_headers, value); } else if (!strcmp(key, "capability[]") && !strcmp(value, "authtype")) { @@ -339,6 +341,8 @@ void credential_write(const struct credential *c, FILE *fp, credential_write_item(fp, "capability[]", "authtype", 0); credential_write_item(fp, "authtype", c->authtype, 0); credential_write_item(fp, "credential", c->credential, 0); + if (c->ephemeral) + credential_write_item(fp, "ephemeral", "1", 0); } credential_write_item(fp, "protocol", c->protocol, 1); credential_write_item(fp, "host", c->host, 1); diff --git a/credential.h b/credential.h index b524fdba59..da2a4802b7 100644 --- a/credential.h +++ b/credential.h @@ -152,6 +152,7 @@ struct credential { unsigned header_is_last_match:1; unsigned approved:1, + ephemeral:1, configured:1, quit:1, use_http_path:1, diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index daf330ddd8..eceb6bbfbe 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -51,6 +51,17 @@ test_expect_success 'setup helper scripts' ' test -z "$credential" || echo credential=$credential EOF + write_script git-credential-verbatim-ephemeral <<-\EOF && + authtype=$1; shift + credential=$1; shift + . ./dump + echo capability[]=authtype + test -z "${capability##*authtype*}" || exit 0 + test -z "$authtype" || echo authtype=$authtype + test -z "$credential" || echo credential=$credential + echo "ephemeral=1" + EOF + write_script git-credential-verbatim-with-expiry <<-\EOF && user=$1; shift pass=$1; shift @@ -99,6 +110,25 @@ test_expect_success 'credential_fill invokes helper with credential' ' EOF ' +test_expect_success 'credential_fill invokes helper with ephemeral credential' ' + check fill "verbatim-ephemeral Bearer token" <<-\EOF + capability[]=authtype + protocol=http + host=example.com + -- + capability[]=authtype + authtype=Bearer + credential=token + ephemeral=1 + protocol=http + host=example.com + -- + verbatim-ephemeral: get + verbatim-ephemeral: capability[]=authtype + verbatim-ephemeral: protocol=http + verbatim-ephemeral: host=example.com + EOF +' test_expect_success 'credential_fill invokes multiple helpers' ' check fill useless "verbatim foo bar" <<-\EOF From patchwork Wed Apr 17 00:02:31 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632772 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7206E4A3F for ; Wed, 17 Apr 2024 00:03:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; cv=none; b=dULpPtdKtK9F1cJvCLzv0fchRcXZEkYbQAQJ0RrNYx6H0kUJKPzArziOOClWVnL+d23aogqh07aWhDcFJDcAQbk5UEjgJYtlh8zV3Y7sJGlkoVnaBl8jD5wxCPsKRk2fnFLC6v9A2vYrSKhIXmwFArK9AB4gC+DaylR9Qz8RaxY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; c=relaxed/simple; bh=R0thGV7MOwPRpTMsHyprK50fo88d3hwIsjIEQgif06U=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pLxuveA0cWN7rcJRFu2msusdxetz2RuBdwulMzSXH3DNKZhPr2O+12ym6fRcnQi7838mGzNNc55bGUER32V9lij7z67BqmCSFPhcq8nU9iD5+tQYPzIez17pqb+w0vUvtQvX3/5PIjxUTuYkDXhBcrvWIE6vgJdTZM7kH95tdy4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=djDhDumo; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="djDhDumo" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 541605D4E8; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=R0thGV7MOwPRpTMsHyprK50fo88d3hwIsjIEQgif06U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=djDhDumoVPnKifU3i+Vu4WstPvz2g2lllFjRMPhbDx++8LxaNG6uFv820ZsunD+U6 TeNE+/id4apLMSAerJ9oYFIU+JKR3l80JFK3IzYHo4nnznvlMD6I8DLN9symDoqZnu 8TAJtqstvPXbDtOohH9QLLAlCw7aPt56m2mnmJdKiwhv1Qul4PfMk4RAAk9MeHny+b zEtC8ubHLEqwh3F0/3UpCFfhC6BM4IpUuITjnGAkYoZSI50rlB46ycuUbLxKoVl+WH 9wOnaK1yDCg7+tt+UwhapwDCX7gOe7bW+KCdOL4DKLeRpJbec/EkdxHZRyg6iJFtm5 5cYUH8XXxvrU9d34JUs85t0QA5j3fHog/HrVSS44js32Fye/aCrUmmxB/MWf3q5Evl pDW9iPmN29yOdeF94m9ITdcJjCql6Ze1L5+B9q/z3iXsBhtxw/pcYbrcfdYmJVPXGG c13UF9k5sTQbpfnVwoeJY0Uy2t24IQfRyfW2DiXP8JEe+pVQjeB From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 07/16] docs: indicate new credential protocol fields Date: Wed, 17 Apr 2024 00:02:31 +0000 Message-ID: <20240417000240.3611948-8-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Now that we have new fields (authtype and credential), let's document them for users and credential helper implementers. Indicate specifically what common values of authtype are and what values are allowed. Note that, while common, digest and NTLM authentication are insecure because they require unsalted, uniterated password hashes to be stored. Tell users that they can continue to use a username and password even if the new capability is supported. Signed-off-by: brian m. carlson --- Documentation/git-credential.txt | 49 +++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt index 918a0aa42b..230ac4c2c3 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -178,6 +178,39 @@ empty string. Components which are missing from the URL (e.g., there is no username in the example above) will be left unset. +`authtype`:: + This indicates that the authentication scheme in question should be used. + Common values for HTTP and HTTPS include `basic`, `bearer`, and `digest`, + although the latter is insecure and should not be used. If `credential` + is used, this may be set to an arbitrary string suitable for the protocol in + question (usually HTTP). ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. + +`credential`:: + The pre-encoded credential, suitable for the protocol in question (usually + HTTP). If this key is sent, `authtype` is mandatory, and `username` and + `password` are not used. For HTTP, Git concatenates the `authtype` value and + this value with a single space to determine the `Authorization` header. ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. + +`ephemeral`:: + This boolean value indicates, if true, that the value in the `credential` + field should not be saved by the credential helper because its usefulness is + limited in time. For example, an HTTP Digest `credential` value is computed + using a nonce and reusing it will not result in successful authentication. + This may also be used for situations with short duration (e.g., 24-hour) + credentials. The default value is false. ++ +The credential helper will still be invoked with `store` or `erase` so that it +can determine whether the operation was successful. ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. + `wwwauth[]`:: When an HTTP response is received by Git that includes one or more @@ -189,7 +222,21 @@ attribute 'wwwauth[]', where the order of the attributes is the same as they appear in the HTTP response. This attribute is 'one-way' from Git to pass additional information to credential helpers. -Unrecognised attributes are silently discarded. +`capability[]`:: + This signals that the caller supports the capability in question. + This can be used to provide better, more specific data as part of the + protocol. ++ +The only capability currently supported is `authtype`, which indicates that the +`authtype`, `credential`, and `ephemeral` values are understood. It is not +obligatory to use these values in such a case, but they should not be provided +without this capability. ++ +Callers of `git credential` and credential helpers should emit the +capabilities they support unconditionally, and Git will gracefully +handle passing them on. + +Unrecognised attributes and capabilities are silently discarded. GIT --- From patchwork Wed Apr 17 00:02:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632781 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7472F4C63 for ; Wed, 17 Apr 2024 00:03:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312188; cv=none; b=VDU2N3HrnNDe5IL/q7JaJHBkq32Vk1rVP8EnMcnBUzTi60BhdJj9xIj1xq/cKf3sunwoRlP+SUPU/HJ/Bw1RBTw7V0mkxG7R+Bvkgu0KlB/lKJebG52SiZNTRZgRh4utxt3aysbgrZ1lHmG0kfz1fVSYuWmgMsmJyNUJVEsitMQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312188; c=relaxed/simple; bh=KHEH0dnvmeBAdLmi/cMjMGXv5JwiDbeWgW+U2tIRcfs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SHhjY45E9Jyu4A+CFvuTU2yZbruWt170H2lygdGWCl5OU346PNRYt5CmvGWEWUIOijCXF0lIGTKeILeRNZYgF9QL6tWCaEdsZg3N6nJWjFCzs0XkCvzliLXB4Ci5pYx49030CA6SeGBewBoun2umB+CeyZVbPL2MZ5edeJ74sA4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=gNyPCEAH; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="gNyPCEAH" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 60ABC5D4E9; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=KHEH0dnvmeBAdLmi/cMjMGXv5JwiDbeWgW+U2tIRcfs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=gNyPCEAHDFLSzADVxJ+OwcMS5iZw5roJaN2oClK7COeSkUM/WjSx+TzBOys2uZN8Z saZUd0vU978ARld9qcy5KQroJM0sVTSy+xbAg9UzD4eQ6R/xVmT8riTVTqLnfdcH2z wx2tkRbCCdtH+GFZsxJmWlabSYQgoNk5yga7tANXE7RpLPX8Jc7FUYjp12XsD7xUzi u8wQDWPptc0rq6PulGZwREmcqMQZ2xegDUttNM2ezPzo7dFaJZqZa26ppbDVXol/6p Cz3SUmRN0P7cA8GM3gAcBk5qffZYtI1ZSQSE5qqkGlVA3WrlC+hAEvQZ9f+y/CJ/eb ylBktPcZ3wlOp6wfqolNyvYwaWrU90o2kyw6zy1IwPLCUiLPw9QsZB0Hst/s0K2AjT Rh2JnzWFnJfTHw+9QEG86TLSXsJYuLgut2A0tcho0nMgOv5RgzoBPqmPZqo5gdX1zl oCU7q/QXPhMm1TJ7Bcpdj/xTQxDTbCW+SAYUuKGq8hyFYSJEYsd From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 08/16] http: add support for authtype and credential Date: Wed, 17 Apr 2024 00:02:32 +0000 Message-ID: <20240417000240.3611948-9-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Now that we have the credential helper code set up to handle arbitrary authentications schemes, let's add support for this in the HTTP code, where we really want to use it. If we're using this new functionality, don't set a username and password, and instead set a header wherever we'd normally do so, including for proxy authentication. Since we can now handle this case, ask the credential helper to enable the appropriate capabilities. Finally, if we're using the authtype value, set "Expect: 100-continue". Any type of authentication that requires multiple rounds (such as NTLM or Kerberos) requires a 100 Continue (if we're larger than http.postBuffer) because otherwise we send the pack data before we're authenticated, the push gets a 401 response, and we can't rewind the stream. We don't know for certain what other custom schemes might require this, the HTTP/1.1 standard has required handling this since 1999, the broken HTTP server for which we disabled this (Google's) is now fixed and has been for some time, and libcurl has a 1-second fallback in case the HTTP server is still broken. In addition, it is not unreasonable to require compliance with a 25-year old standard to use new Git features. For all of these reasons, do so here. Signed-off-by: brian m. carlson --- http.c | 48 ++++++++++--- http.h | 3 + remote-curl.c | 4 +- t/t5563-simple-http-auth.sh | 133 ++++++++++++++++++++++++++++++++++++ 4 files changed, 176 insertions(+), 12 deletions(-) diff --git a/http.c b/http.c index 54ddff03fb..906eb098c8 100644 --- a/http.c +++ b/http.c @@ -561,18 +561,34 @@ static int curl_empty_auth_enabled(void) return 0; } +struct curl_slist *http_append_auth_header(const struct credential *c, + struct curl_slist *headers) +{ + if (c->authtype && c->credential) { + struct strbuf auth = STRBUF_INIT; + strbuf_addf(&auth, "Authorization: %s %s", + c->authtype, c->credential); + headers = curl_slist_append(headers, auth.buf); + strbuf_release(&auth); + } + return headers; +} + static void init_curl_http_auth(CURL *result) { - if (!http_auth.username || !*http_auth.username) { + if ((!http_auth.username || !*http_auth.username) && + (!http_auth.credential || !*http_auth.credential)) { if (curl_empty_auth_enabled()) curl_easy_setopt(result, CURLOPT_USERPWD, ":"); return; } - credential_fill(&http_auth, 0); + credential_fill(&http_auth, 1); - curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username); - curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password); + if (http_auth.password) { + curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username); + curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password); + } } /* *var must be free-able */ @@ -586,17 +602,22 @@ static void var_override(const char **var, char *value) static void set_proxyauth_name_password(CURL *result) { + if (proxy_auth.password) { curl_easy_setopt(result, CURLOPT_PROXYUSERNAME, proxy_auth.username); curl_easy_setopt(result, CURLOPT_PROXYPASSWORD, proxy_auth.password); + } else if (proxy_auth.authtype && proxy_auth.credential) { + curl_easy_setopt(result, CURLOPT_PROXYHEADER, + http_append_auth_header(&proxy_auth, NULL)); + } } static void init_curl_proxy_auth(CURL *result) { if (proxy_auth.username) { - if (!proxy_auth.password) - credential_fill(&proxy_auth, 0); + if (!proxy_auth.password && !proxy_auth.credential) + credential_fill(&proxy_auth, 1); set_proxyauth_name_password(result); } @@ -1468,7 +1489,7 @@ struct active_request_slot *get_active_slot(void) curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve); curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods); - if (http_auth.password || curl_empty_auth_enabled()) + if (http_auth.password || http_auth.credential || curl_empty_auth_enabled()) init_curl_http_auth(slot->curl); return slot; @@ -1757,7 +1778,8 @@ static int handle_curl_result(struct slot_results *results) } else if (missing_target(results)) return HTTP_MISSING_TARGET; else if (results->http_code == 401) { - if (http_auth.username && http_auth.password) { + if ((http_auth.username && http_auth.password) ||\ + (http_auth.authtype && http_auth.credential)) { credential_reject(&http_auth); return HTTP_NOAUTH; } else { @@ -2065,11 +2087,15 @@ static int http_request(const char *url, /* Add additional headers here */ if (options && options->extra_headers) { const struct string_list_item *item; - for_each_string_list_item(item, options->extra_headers) { - headers = curl_slist_append(headers, item->string); + if (options && options->extra_headers) { + for_each_string_list_item(item, options->extra_headers) { + headers = curl_slist_append(headers, item->string); + } } } + headers = http_append_auth_header(&http_auth, headers); + curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(slot->curl, CURLOPT_ENCODING, ""); @@ -2190,7 +2216,7 @@ static int http_request_reauth(const char *url, BUG("Unknown http_request target"); } - credential_fill(&http_auth, 0); + credential_fill(&http_auth, 1); return http_request(url, result, target, options); } diff --git a/http.h b/http.h index c5f8cc4620..a516ca4a9a 100644 --- a/http.h +++ b/http.h @@ -175,6 +175,9 @@ int http_get_file(const char *url, const char *filename, int http_fetch_ref(const char *base, struct ref *ref); +struct curl_slist *http_append_auth_header(const struct credential *c, + struct curl_slist *headers); + /* Helpers for fetching packs */ int http_get_info_packs(const char *base_url, struct packed_git **packs_head); diff --git a/remote-curl.c b/remote-curl.c index f96bda2431..1c5416812a 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -931,7 +931,7 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece if (err != HTTP_OK) return -1; - if (results.auth_avail & CURLAUTH_GSSNEGOTIATE) + if (results.auth_avail & CURLAUTH_GSSNEGOTIATE || http_auth.authtype) needs_100_continue = 1; } @@ -942,6 +942,8 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece headers = curl_slist_append(headers, needs_100_continue ? "Expect: 100-continue" : "Expect:"); + headers = http_append_auth_header(&http_auth, headers); + /* Add Accept-Language header */ if (rpc->hdr_accept_language) headers = curl_slist_append(headers, rpc->hdr_accept_language); diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index ab8a721ccc..b3ed0d9fc2 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -74,6 +74,7 @@ test_expect_success 'access using basic auth' ' git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype protocol=http host=$HTTPD_DEST wwwauth[]=Basic realm="example.com" @@ -87,6 +88,43 @@ test_expect_success 'access using basic auth' ' EOF ' +test_expect_success 'access using basic auth via authtype' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + authtype=Basic + credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA== + EOF + + # Basic base64(alice:secret-passwd) + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + GIT_CURL_VERBOSE=1 git ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + protocol=http + host=$HTTPD_DEST + wwwauth[]=Basic realm="example.com" + EOF + + expect_credential_query store <<-EOF + capability[]=authtype + authtype=Basic + credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA== + protocol=http + host=$HTTPD_DEST + EOF +' + test_expect_success 'access using basic auth invalid credentials' ' test_when_finished "per_test_cleanup" && @@ -108,6 +146,7 @@ test_expect_success 'access using basic auth invalid credentials' ' test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype protocol=http host=$HTTPD_DEST wwwauth[]=Basic realm="example.com" @@ -145,6 +184,7 @@ test_expect_success 'access using basic auth with extra challenges' ' git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -183,6 +223,7 @@ test_expect_success 'access using basic auth mixed-case wwwauth header name' ' git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype protocol=http host=$HTTPD_DEST wwwauth[]=foobar param1="value1" param2="value2" @@ -226,6 +267,7 @@ test_expect_success 'access using basic auth with wwwauth header continuations' git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -271,6 +313,7 @@ test_expect_success 'access using basic auth with wwwauth header empty continuat git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -312,6 +355,7 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -326,4 +370,93 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi EOF ' +test_expect_success 'access using bearer auth' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + authtype=Bearer + credential=YS1naXQtdG9rZW4= + EOF + + # Basic base64(a-git-token) + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + Bearer YS1naXQtdG9rZW4= + EOF + + CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + WWW-Authenticate: FooBar param1="value1" param2="value2" + WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + git ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + protocol=http + host=$HTTPD_DEST + wwwauth[]=FooBar param1="value1" param2="value2" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + wwwauth[]=Basic realm="example.com" + EOF + + expect_credential_query store <<-EOF + capability[]=authtype + authtype=Bearer + credential=YS1naXQtdG9rZW4= + protocol=http + host=$HTTPD_DEST + EOF +' + +test_expect_success 'access using bearer auth with invalid credentials' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + authtype=Bearer + credential=incorrect-token + EOF + + # Basic base64(a-git-token) + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + Bearer YS1naXQtdG9rZW4= + EOF + + CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + WWW-Authenticate: FooBar param1="value1" param2="value2" + WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + protocol=http + host=$HTTPD_DEST + wwwauth[]=FooBar param1="value1" param2="value2" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + wwwauth[]=Basic realm="example.com" + EOF + + expect_credential_query erase <<-EOF + capability[]=authtype + authtype=Bearer + credential=incorrect-token + protocol=http + host=$HTTPD_DEST + wwwauth[]=FooBar param1="value1" param2="value2" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + wwwauth[]=Basic realm="example.com" + EOF +' + test_done From patchwork Wed Apr 17 00:02:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632775 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 90E5B5680 for ; Wed, 17 Apr 2024 00:03:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; cv=none; b=K1AEqqzbBM8h6h585OT5iD/EPNWKW41EPM62mYJ5rbfE8TTOqhN8L2+wsByPhDr4JOtCRH1fkk6SInZonuke4HlLfhUuZroRKUeHKmW3yZ/HZKv3MzIMLWzq848+UIDfTstNbYK9HB3V0rVdO8AGit/nIzOc3BIwkAxW6+QMDIw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; c=relaxed/simple; bh=8quWGpTsBQIRksQuP6cG61ynLNaTJvPBYRl5ekZay/c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=TUZt2AoUP88Pn/nf74Lnt48IXTspr8RD/+8R47ad/eRkAitS6Ts9ozLLiZIqCm7zg22rHfqrH+E6KvFCG1rIgJnBBJT3LDgzFEBVzxesdoejy4UNZwVrejgTm7Rqu7nHXYl7K3pmkZDcBp1N3kJSZc87jI+3IRm7eV7TTyA7n/c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=q1Ise9mX; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="q1Ise9mX" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 6F5005D4EA; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=8quWGpTsBQIRksQuP6cG61ynLNaTJvPBYRl5ekZay/c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=q1Ise9mXzPw6OvkTP2fX/kUVzqU+I+q7SLIIUeVG0B/+7DlwSAJWJZsIHa3FH1Ke+ STlBmQfZUPP1b2m9cDMom+HiXh9xpEuL23D/Ni+/+HOMGIMASyHNc5l2tTqsHWCpG+ 4qwFwi6n+/boRFMOD0L5Cdq7eDiaTYcLMB703Ty//rfXBjVRirOPh5fLQvStSvDRWb 4gaxTLzdRl8pyVFJua5BMGdDRZgl9SC8X26OA9IprB50XZOrE1jJ/HR/cl022P9muT /7KNMzM/FAhZmLfAe0qG4uxWN01ULm1HAbbPUzmDcYuL7EYC/EQewDARmqXmdn067x iYos7T2ntuOj+0i63AfSTlJbMNhjZA92CsuAxG/ISuMLIrzhpvAq9YyMJI/2u2l1HW cL/AYamoV4d8UfqCuXRe6F/5CGqZN//omDaxHTA83Zww+jpjrUXFesohMoUfhP5eCh pznuN/7IRzXzQ2NXZns+uylDFJqQcg6iqR6rOQrssUi6no6a6SF From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 09/16] credential: add an argument to keep state Date: Wed, 17 Apr 2024 00:02:33 +0000 Message-ID: <20240417000240.3611948-10-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Until now, our credential code has mostly deal with usernames and passwords and we've let libcurl deal with the variant of authentication to be used. However, now that we have the credential value, the credential helper can take control of the authentication, so the value provided might be something that's generated, such as a Digest hash value. In such a case, it would be helpful for a credential helper that gets an erase or store command to be able to keep track of an identifier for the original secret that went into the computation. Furthermore, some types of authentication, such as NTLM and Kerberos, actually need two round trips to authenticate, which will require that the credential helper keep some state. In order to allow for these use cases and others, allow storing state in a field called "state[]". This value is passed back to the credential helper that created it, which avoids confusion caused by parsing values from different helpers. Signed-off-by: brian m. carlson --- Documentation/git-credential.txt | 28 +++++++++++++++++++--------- credential.c | 20 +++++++++++++++++--- credential.h | 7 +++++++ t/t0300-credentials.sh | 28 ++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt index 230ac4c2c3..f63a8e0458 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -211,6 +211,15 @@ can determine whether the operation was successful. This value should not be sent unless the appropriate capability (see below) is provided on input. +`state[]`:: + This value provides an opaque state that will be passed back to this helper + if it is called again. Each different credential helper may specify this + once. The value should include a prefix unique to the credential helper and + should ignore values that don't match its prefix. ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. + `wwwauth[]`:: When an HTTP response is received by Git that includes one or more @@ -223,18 +232,19 @@ they appear in the HTTP response. This attribute is 'one-way' from Git to pass additional information to credential helpers. `capability[]`:: - This signals that the caller supports the capability in question. - This can be used to provide better, more specific data as part of the + This signals that Git, or the helper, as appropriate, supports the capability + in question. This can be used to provide better, more specific data as part + of the protocol. A `capability[]` directive must precede any value depending + on it and these directives _should_ be the first item announced in the protocol. + -The only capability currently supported is `authtype`, which indicates that the -`authtype`, `credential`, and `ephemeral` values are understood. It is not -obligatory to use these values in such a case, but they should not be provided -without this capability. +There are two currently supported capabilities. The first is `authtype`, which +indicates that the `authtype`, `credential`, and `ephemeral` values are +understood. The second is `state`, which indicates that the `state[]` and +`continue` values are understood. + -Callers of `git credential` and credential helpers should emit the -capabilities they support unconditionally, and Git will gracefully -handle passing them on. +It is not obligatory to use the additional features just because the capability +is supported, but they should not be provided without the capability. Unrecognised attributes and capabilities are silently discarded. diff --git a/credential.c b/credential.c index 3531d74346..48826fb5a2 100644 --- a/credential.c +++ b/credential.c @@ -30,6 +30,7 @@ void credential_clear(struct credential *c) free(c->authtype); string_list_clear(&c->helpers, 0); strvec_clear(&c->wwwauth_headers); + strvec_clear(&c->state_headers); credential_init(c); } @@ -293,8 +294,13 @@ int credential_read(struct credential *c, FILE *fp, c->ephemeral = !!git_config_bool("ephemeral", value); } else if (!strcmp(key, "wwwauth[]")) { strvec_push(&c->wwwauth_headers, value); - } else if (!strcmp(key, "capability[]") && !strcmp(value, "authtype")) { - credential_set_capability(&c->capa_authtype, op_type); + } else if (!strcmp(key, "state[]")) { + strvec_push(&c->state_headers, value); + } else if (!strcmp(key, "capability[]")) { + if (!strcmp(value, "authtype")) + credential_set_capability(&c->capa_authtype, op_type); + else if (!strcmp(value, "state")) + credential_set_capability(&c->capa_state, op_type); } else if (!strcmp(key, "password_expiry_utc")) { errno = 0; c->password_expiry_utc = parse_timestamp(value, NULL, 10); @@ -337,8 +343,12 @@ static void credential_write_item(FILE *fp, const char *key, const char *value, void credential_write(const struct credential *c, FILE *fp, enum credential_op_type op_type) { - if (credential_has_capability(&c->capa_authtype, op_type)) { + if (credential_has_capability(&c->capa_authtype, op_type)) credential_write_item(fp, "capability[]", "authtype", 0); + if (credential_has_capability(&c->capa_state, op_type)) + credential_write_item(fp, "capability[]", "state", 0); + + if (credential_has_capability(&c->capa_authtype, op_type)) { credential_write_item(fp, "authtype", c->authtype, 0); credential_write_item(fp, "credential", c->credential, 0); if (c->ephemeral) @@ -357,6 +367,10 @@ void credential_write(const struct credential *c, FILE *fp, } for (size_t i = 0; i < c->wwwauth_headers.nr; i++) credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); + if (credential_has_capability(&c->capa_state, op_type)) { + for (size_t i = 0; i < c->state_headers.nr; i++) + credential_write_item(fp, "state[]", c->state_headers.v[i], 0); + } } static int run_credential_helper(struct credential *c, diff --git a/credential.h b/credential.h index da2a4802b7..c307300d12 100644 --- a/credential.h +++ b/credential.h @@ -144,6 +144,11 @@ struct credential { */ struct strvec wwwauth_headers; + /** + * A `strvec` of state headers from credential helpers. + */ + struct strvec state_headers; + /** * Internal use only. Keeps track of if we previously matched against a * WWW-Authenticate header line in order to re-fold future continuation @@ -159,6 +164,7 @@ struct credential { username_from_proto:1; struct credential_capability capa_authtype; + struct credential_capability capa_state; char *username; char *password; @@ -180,6 +186,7 @@ struct credential { .helpers = STRING_LIST_INIT_DUP, \ .password_expiry_utc = TIME_MAX, \ .wwwauth_headers = STRVEC_INIT, \ + .state_headers = STRVEC_INIT, \ } /* Initialize a credential structure, setting all fields to empty. */ diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index eceb6bbfbe..432f029d48 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -46,9 +46,12 @@ test_expect_success 'setup helper scripts' ' credential=$1; shift . ./dump echo capability[]=authtype + echo capability[]=state test -z "${capability##*authtype*}" || exit 0 test -z "$authtype" || echo authtype=$authtype test -z "$credential" || echo credential=$credential + test -z "${capability##*state*}" || exit 0 + echo state[]=verbatim-cred:foo EOF write_script git-credential-verbatim-ephemeral <<-\EOF && @@ -129,6 +132,28 @@ test_expect_success 'credential_fill invokes helper with ephemeral credential' ' verbatim-ephemeral: host=example.com EOF ' +test_expect_success 'credential_fill invokes helper with credential and state' ' + check fill "verbatim-cred Bearer token" <<-\EOF + capability[]=authtype + capability[]=state + protocol=http + host=example.com + -- + capability[]=authtype + capability[]=state + authtype=Bearer + credential=token + protocol=http + host=example.com + state[]=verbatim-cred:foo + -- + verbatim-cred: get + verbatim-cred: capability[]=authtype + verbatim-cred: capability[]=state + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' test_expect_success 'credential_fill invokes multiple helpers' ' check fill useless "verbatim foo bar" <<-\EOF @@ -152,6 +177,7 @@ test_expect_success 'credential_fill invokes multiple helpers' ' test_expect_success 'credential_fill response does not get capabilities when helpers are incapable' ' check fill useless "verbatim foo bar" <<-\EOF capability[]=authtype + capability[]=state protocol=http host=example.com -- @@ -162,10 +188,12 @@ test_expect_success 'credential_fill response does not get capabilities when hel -- useless: get useless: capability[]=authtype + useless: capability[]=state useless: protocol=http useless: host=example.com verbatim: get verbatim: capability[]=authtype + verbatim: capability[]=state verbatim: protocol=http verbatim: host=example.com EOF From patchwork Wed Apr 17 00:02:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632776 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 927CD5CAC for ; Wed, 17 Apr 2024 00:03:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; cv=none; b=NKIQti0tJ6JpABfGLDMEKfrwm9u6yhZI+Aow/3dQM3UyZvlvwUPF2ml3XkDnau/4vGWGT0ud/KeIP4Fo9+CeGfM9HvnZqnU/Va1ijWHSNxwKkPsj1qTDcZ/4PaSEV/Aw6JosXu3kHsVKnyIYNwaxC7o2UGq6SsrJO4aPT0yheeA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; c=relaxed/simple; bh=EeEPRneNl6VOvGHk/XXoZ2Trj3rBaOJciYEUHGKIzxI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HQQuw0EAZg8v3zOIYP0zIkIxdSj8O/Lq8h/TdXgtmBoyStRqvl4fikF+WbTUb57jwr0qDeZwbFCX5vZt5NfM7hlHq6E73ONUiJ3wScJYwb2NUtYQVMczztE41zVMbyn7GDb2y9CKq5H77sm/jjJtCdpj3CWQQUN2d1TkFu48U08= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=WGlrrZba; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="WGlrrZba" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 7ED505D4EB; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=EeEPRneNl6VOvGHk/XXoZ2Trj3rBaOJciYEUHGKIzxI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=WGlrrZbaYNxo0O2ZUBYlU4WtYGNX+a30rx+XEj4cYj2aPGoTEBM9+J09Lj6L9Rtt/ g1fLLYNmHmdAKvLmdjwLsIV1I3v85veNXBW9cL7HdtKucqD4y8i2LAxGaYrAdJIVRI EAV6YaBEHTw/R+EJEWTsVtBr+jdsmLBdYgeMqV17ZCkzDt1bWmltiiPUa+PRHZhmQs V9oZBp839IVqw2fkH7SAi8vr0Ghst0g3Vt9Pjm33ZSWCAuXGO4i86sLl6viqonHHTC tmc+gEs9cdDfMSrZurJbCSOjfv+DO5jUVYVHDDHbuN0F2g0wqINA7gWr3Xsi7Hyqxn tGKcokwMp60Ce9FjCvjXpPL3CMkVL7FU8Wffa8sZiCqszg6EEr0/EwJAENfmOgn0ls Vof/Ib3DzJOzfkEqljpZyaJElz+hQ1UvShF9nvz8JpkUEeDrdW4AQPwizWwxfiFEH6 DW5mXzJFAn6kdCNve1nevVZLhvuqj4yBW24a0oQdNduhk/UjveL From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 10/16] credential: enable state capability Date: Wed, 17 Apr 2024 00:02:34 +0000 Message-ID: <20240417000240.3611948-11-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Now that we've implemented the state capability, let's send it along by default when filling credentials so we can make use of it. Signed-off-by: brian m. carlson --- credential.c | 1 + t/t5563-simple-http-auth.sh | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/credential.c b/credential.c index 48826fb5a2..c93de92f65 100644 --- a/credential.c +++ b/credential.c @@ -56,6 +56,7 @@ void credential_set_all_capabilities(struct credential *c, enum credential_op_type op_type) { credential_set_capability(&c->capa_authtype, op_type); + credential_set_capability(&c->capa_state, op_type); } int credential_match(const struct credential *want, diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index b3ed0d9fc2..b098cd0fdf 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -75,6 +75,7 @@ test_expect_success 'access using basic auth' ' expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=Basic realm="example.com" @@ -111,6 +112,7 @@ test_expect_success 'access using basic auth via authtype' ' expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=Basic realm="example.com" @@ -147,6 +149,7 @@ test_expect_success 'access using basic auth invalid credentials' ' expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=Basic realm="example.com" @@ -185,6 +188,7 @@ test_expect_success 'access using basic auth with extra challenges' ' expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -224,6 +228,7 @@ test_expect_success 'access using basic auth mixed-case wwwauth header name' ' expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=foobar param1="value1" param2="value2" @@ -268,6 +273,7 @@ test_expect_success 'access using basic auth with wwwauth header continuations' expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -314,6 +320,7 @@ test_expect_success 'access using basic auth with wwwauth header empty continuat expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -356,6 +363,7 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -397,6 +405,7 @@ test_expect_success 'access using bearer auth' ' expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -440,6 +449,7 @@ test_expect_success 'access using bearer auth with invalid credentials' ' expect_credential_query get <<-EOF && capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" From patchwork Wed Apr 17 00:02:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632774 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CAFA86FC3 for ; Wed, 17 Apr 2024 00:03:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; cv=none; b=YTQUfaBVjsHbZ0kWa2zTnfsz7rta3DGcXy0pGpHVnmGJ/Zd803xvNggZrDw83yw2bzhvajGQQpHZE/J7nRY1rqypR+T3RBgO3zReaWcQ/xloetlzSp7zEDid70751E2Lj9Gt/xsYAZBKF1MmqViMJ7QBlutfsD/lerRLF49Oo38= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; c=relaxed/simple; bh=313XSdoETwMJzxe6bb11uCkgEh4q9LEGQOsZd64pYQE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Fl97Ky94/5xNKKi8DyC9Hh+XPTXYV4wfivUoguMobS0ifVZhsnnWrZnW37+7SsxFO+BL6xFMYqUCILvcu2J4ISlPVMz/JZ8nVNDIm4tQDlerIGAyxXe7dW5RRhGMCgfvGPDDDvS663OMnHUvp/1+3tFXoqJAnBcgeoGyR+fYDUU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=NLDlJIix; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="NLDlJIix" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 8C3755D4EC; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=313XSdoETwMJzxe6bb11uCkgEh4q9LEGQOsZd64pYQE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=NLDlJIixDyr+HdGV+9fIjbC50NIlxO/+8PZRU5mKJ2Dago1L5tsWsTMX9n7o0FrM+ 57jDLqsgWHGjDRCrl+wgx7VXI5YFIGdQ1HYNp5Jzh/YkW8D3WqQwGBrKeagoWx4BAQ aHmrkXweTmhtZi74nydfmiI10PyNUU00GHbu9r26Dz+IlrJmEU9n59JMjC1iZMFfeK g21btcOop7hFs8UdyaIr557QKeF3pbCf6YMnIGCApJD8bg1X9fSFPxdHwEPK9foJHr O5eYYqMSMeqn/Kc+Tl0iQevlclIimkBkHX8ylV61z1voZgc2O8lDzb2AmslDg9rHvE Q6BT9K72FTmcAbiox1ZXvV55r4zzHPlEFHorP4dIhccRZ/YKR+ydsVK0XskT6MBPIz RHm5pAtFalFIuwNxpR2PdhxES07HFRBV5HPXse2LTMg1MaAFZb/2B1n22X16Ppri3s mwCxUJhJdXLvsHZxBRTFi968EYVd5aXTPXnS6Ga9+chc2tvrWK+ From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 11/16] docs: set a limit on credential line length Date: Wed, 17 Apr 2024 00:02:35 +0000 Message-ID: <20240417000240.3611948-12-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 We recently introduced a way for credential helpers to add arbitrary state as part of the protocol. Set some limits on line length to avoid helpers passing extremely large amounts of data. While Git doesn't have a fixed parsing length, there are other tools which support this protocol and it's kind to allow them to use a reasonable fixed-size buffer for parsing. In addition, we would like to be moderate in our memory usage and imposing reasonable limits is helpful for that purpose. In the event a credential helper is incapable of storing its serialized state in 64 KiB, it can feel free to serialize it on disk and store a reference instead. Signed-off-by: brian m. carlson --- Documentation/git-credential.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt index f63a8e0458..4bbf2db9ca 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -111,7 +111,9 @@ attribute per line. Each attribute is specified by a key-value pair, separated by an `=` (equals) sign, followed by a newline. The key may contain any bytes except `=`, newline, or NUL. The value may -contain any bytes except newline or NUL. +contain any bytes except newline or NUL. A line, including the trailing +newline, may not exceed 65535 bytes in order to allow implementations to +parse efficiently. Attributes with keys that end with C-style array brackets `[]` can have multiple values. Each instance of a multi-valued attribute forms an From patchwork Wed Apr 17 00:02:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632777 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 06A3879D8 for ; Wed, 17 Apr 2024 00:03:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; cv=none; b=g7RKEbkl57oG5hA8zuJ7w3K57TDKUNqX1EwFJQqVzYCNsZu5cpBCjdW1ymfhe/ODoGCfKtGIBIT1TI/hPudKnN+z3hEWplUwlR6vwOWBuFwCUjVTwDlnv+KtEqpIjCUKiN2lvZ7WPqpaB+IZDuktdHh18xEj/Lp0zAmiieCeFFc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312186; c=relaxed/simple; bh=OYUDDWVtX0rJQqZYjpAXqg4Jc5rZmv2VJh935Jl/Ws0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UGtgFgLUnUIFMNqZcSjKoCxatvzxNWMqvlgHYs1biV3bhaJDgRMhvt24+IGLoGFwtuzlGEW7XI4DmuxkcJY+/g1nMpWwsqNwEV0j+7AokPJHCoQNSh0VRLPgm7RAPLIjD1C6bSDrWjQ5OIm35YD0imUt2tgLTiG3q+PL5ivVjSo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=Mxp/lyMk; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="Mxp/lyMk" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id 9855C5D6CB; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=OYUDDWVtX0rJQqZYjpAXqg4Jc5rZmv2VJh935Jl/Ws0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=Mxp/lyMkGJKi/s4+cw9PVRfubIoVb0FrpSKbIb4qJY0bzB91uxHs/1GyZQOKmDrxb TgGf0II1YRzZOgPsuBkIaavZtNA+xp0tc5UaUni5y6lBQYBYcTcvTWQnH4IBWbagaN ROXnU8xREN2bFPhYS8oMI7BftCvYr1LjoL1bsuE1/m1S2W+xbb9M07cj60DqhF94Bw RTzGnTdoFftOoihWeEwdidu/6ve4QhwnEa/lu2mMyR/ynzV9exvqpYCxxjpe5Bdy8i MJ+rsLKv/srCXKCGZynoYIig75TGxrXQQn0gArrF4YNPxYe85F0pFH/zbs7KEijJWo t0A0GXWrihd/BCDj9+4aSyvdlpw/iEgOOf5MLoCSSYN7CdI0FetG0bkrouj0PVmh6p a00kEa/gqHB71QIbIfsRy8wkG+ipJyW20HjOzygI2Jpcwt3FdEPL7qc7fKelgjWyXg 8X+RKw0UDpoE1rnR9kcvp76lhqRk9dTBP0sIbs4wUb46Fc1BPyW From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 12/16] t5563: refactor for multi-stage authentication Date: Wed, 17 Apr 2024 00:02:36 +0000 Message-ID: <20240417000240.3611948-13-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Some HTTP authentication schemes, such as NTLM- and Kerberos-based options, require more than one round trip to authenticate. Currently, these can only be supported in libcurl, since Git does not have support for this in the credential helper protocol. However, in a future commit, we'll add support for this functionality into the credential helper protocol and Git itself. Because we don't really want to implement either NTLM or Kerberos, both of which are complex protocols, we'll want to test this using a fake credential authentication scheme. In order to do so, update t5563 and its backend to allow us to accept multiple sets of credentials and respond with different behavior in each case. Since we can now provide any number of possible status codes, provide a non-specific reason phrase so we don't have to generate a more specific one based on the response. The reason phrase is mandatory according to the status-line production in RFC 7230, but clients SHOULD ignore it, and curl does (except to print it). Each entry in the authorization and challenge fields contains an ID, which indicates a corresponding credential and response. If the response is a 200 status, then we continue to execute git-http-backend. Otherwise, we print the corresponding status and response. If no ID is matched, we use the default response with a status of 401. Note that there is an implicit order to the parameters. The ID is always first and the creds or response value is always last, and therefore may contain spaces, equals signs, or other arbitrary data. Signed-off-by: brian m. carlson --- t/lib-httpd/nph-custom-auth.sh | 17 ++++-- t/t5563-simple-http-auth.sh | 96 +++++++++++++++++++--------------- 2 files changed, 66 insertions(+), 47 deletions(-) diff --git a/t/lib-httpd/nph-custom-auth.sh b/t/lib-httpd/nph-custom-auth.sh index f5345e775e..d408d2caad 100644 --- a/t/lib-httpd/nph-custom-auth.sh +++ b/t/lib-httpd/nph-custom-auth.sh @@ -19,21 +19,30 @@ CHALLENGE_FILE=custom-auth.challenge # if test -n "$HTTP_AUTHORIZATION" && \ - grep -Fqsx "${HTTP_AUTHORIZATION}" "$VALID_CREDS_FILE" + grep -Fqs "creds=${HTTP_AUTHORIZATION}" "$VALID_CREDS_FILE" then + idno=$(grep -F "creds=${HTTP_AUTHORIZATION}" "$VALID_CREDS_FILE" | sed -e 's/^id=\([a-z0-9-][a-z0-9-]*\) .*$/\1/') + status=$(sed -ne "s/^id=$idno.*status=\\([0-9][0-9][0-9]\\).*\$/\\1/p" "$CHALLENGE_FILE" | head -n1) # Note that although git-http-backend returns a status line, it # does so using a CGI 'Status' header. Because this script is an # No Parsed Headers (NPH) script, we must return a real HTTP # status line. # This is only a test script, so we don't bother to check for # the actual status from git-http-backend and always return 200. - echo 'HTTP/1.1 200 OK' - exec "$GIT_EXEC_PATH"/git-http-backend + echo "HTTP/1.1 $status Nonspecific Reason Phrase" + if test "$status" -eq 200 + then + exec "$GIT_EXEC_PATH"/git-http-backend + else + sed -ne "s/^id=$idno.*response=//p" "$CHALLENGE_FILE" + echo + exit + fi fi echo 'HTTP/1.1 401 Authorization Required' if test -f "$CHALLENGE_FILE" then - cat "$CHALLENGE_FILE" + sed -ne 's/^id=default.*response=//p' "$CHALLENGE_FILE" fi echo diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index b098cd0fdf..515185ae00 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -63,11 +63,12 @@ test_expect_success 'access using basic auth' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && @@ -100,11 +101,12 @@ test_expect_success 'access using basic auth via authtype' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && @@ -137,11 +139,12 @@ test_expect_success 'access using basic auth invalid credentials' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && @@ -174,13 +177,14 @@ test_expect_success 'access using basic auth with extra challenges' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: FooBar param1="value1" param2="value2" - WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && @@ -214,13 +218,14 @@ test_expect_success 'access using basic auth mixed-case wwwauth header name' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - www-authenticate: foobar param1="value1" param2="value2" - WWW-AUTHENTICATE: BEARER authorize_uri="id.example.com" p=1 q=0 - WwW-aUtHeNtIcAtE: baSiC realm="example.com" + id=1 status=200 + id=default response=www-authenticate: foobar param1="value1" param2="value2" + id=default response=WWW-AUTHENTICATE: BEARER authorize_uri="id.example.com" p=1 q=0 + id=default response=WwW-aUtHeNtIcAtE: baSiC realm="example.com" EOF test_config_global credential.helper test-helper && @@ -254,18 +259,19 @@ test_expect_success 'access using basic auth with wwwauth header continuations' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF # Note that leading and trailing whitespace is important to correctly # simulate a continuation/folded header. cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: FooBar param1="value1" - param2="value2" - WWW-Authenticate: Bearer authorize_uri="id.example.com" - p=1 - q=0 - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" + id=default response= param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" + id=default response= p=1 + id=default response= q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && @@ -299,21 +305,22 @@ test_expect_success 'access using basic auth with wwwauth header empty continuat # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && # Note that leading and trailing whitespace is important to correctly # simulate a continuation/folded header. - printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >"$CHALLENGE" && - printf " \r\n" >>"$CHALLENGE" && - printf " param2=\"value2\"\r\n" >>"$CHALLENGE" && - printf "WWW-Authenticate: Bearer authorize_uri=\"id.example.com\"\r\n" >>"$CHALLENGE" && - printf " p=1\r\n" >>"$CHALLENGE" && - printf " \r\n" >>"$CHALLENGE" && - printf " q=0\r\n" >>"$CHALLENGE" && - printf "WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>"$CHALLENGE" && + printf "id=1 status=200\n" >"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: FooBar param1=\"value1\"\r\n" >>"$CHALLENGE" && + printf "id=default response= \r\n" >>"$CHALLENGE" && + printf "id=default response= param2=\"value2\"\r\n" >>"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: Bearer authorize_uri=\"id.example.com\"\r\n" >>"$CHALLENGE" && + printf "id=default response= p=1\r\n" >>"$CHALLENGE" && + printf "id=default response= \r\n" >>"$CHALLENGE" && + printf "id=default response= q=0\r\n" >>"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>"$CHALLENGE" && test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && @@ -346,17 +353,18 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && # Note that leading and trailing whitespace is important to correctly # simulate a continuation/folded header. - printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >"$CHALLENGE" && - printf " \r\n" >>"$CHALLENGE" && - printf "\tparam2=\"value2\"\r\n" >>"$CHALLENGE" && - printf "WWW-Authenticate: Basic realm=\"example.com\"" >>"$CHALLENGE" && + printf "id=1 status=200\n" >"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: FooBar param1=\"value1\"\r\n" >>"$CHALLENGE" && + printf "id=default response= \r\n" >>"$CHALLENGE" && + printf "id=default response=\tparam2=\"value2\"\r\n" >>"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"" >>"$CHALLENGE" && test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && @@ -389,15 +397,16 @@ test_expect_success 'access using bearer auth' ' # Basic base64(a-git-token) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Bearer YS1naXQtdG9rZW4= + id=1 creds=Bearer YS1naXQtdG9rZW4= EOF CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: FooBar param1="value1" param2="value2" - WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && @@ -433,15 +442,16 @@ test_expect_success 'access using bearer auth with invalid credentials' ' # Basic base64(a-git-token) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Bearer YS1naXQtdG9rZW4= + id=1 creds=Bearer YS1naXQtdG9rZW4= EOF CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: FooBar param1="value1" param2="value2" - WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && From patchwork Wed Apr 17 00:02:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632779 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 527768BFC for ; Wed, 17 Apr 2024 00:03:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312187; cv=none; b=fRx6oeWlgBWFeswTUE0haK4jPNXLQyS55vlY0aX9GKMFTw23aoYL8ZYMqiVScJd1yyiZhZuwacWwTdLgAHzyEGFTGCA695T6dYVPQXqlTLKbeGXouo0JSwUQRxfvOEMmNmsXRhKeaFpeHPMWnZ8MnSw6P/oHxunbZG9C8/s+HwY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312187; c=relaxed/simple; bh=6K+hBGtum5nkXPRLV+XM6cyIIm7H6ZxheBQLzTmiUHo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WPIXRiP3cENHP7hoFoXvUFhat4HcDyF11qXAXMF/05Ef1ikORGIR9wX+Lq0g/CrWvR1Ci4ldvCRhA6YnWYvbAjF9EzvZ+whQheSaO5qHuwxyeBltP6J1Eib1HFGYOmEM38cmpN8z7m+/t+TOre7M16azZy91EV2qJ8X27RS3yfE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=0fKUGlwa; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="0fKUGlwa" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id A83915D6CF; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=6K+hBGtum5nkXPRLV+XM6cyIIm7H6ZxheBQLzTmiUHo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=0fKUGlwaNop1ZnoYKcDkRx4RdboUUfdiPssjUJfyF+VDGqVpo3e5EzF+TCaloplSE UemAgAGMeHAgu9iS2/alWgukIT4+my1gOLM3SJ84HC3Hbfh3SSwU3XCPMejtODHfCt FtQCeVevoNtskeiBYXG2E0sVblQ6XRojiySNxuAiylW5ur+tyUDFIDlDNuOpb+YZmz xZPon7MXPOLg26pLJ0T5BJZY6wqei08ogohyjY0I/ACW2SOSqLO+MtaHAGlMiQU/XH 27lv0QgofVw7Ty9Ongh7PwQzdy8gckpw5we9GnC1Ax5xioaOcWMkbFIkGIlDJHw2Rp VWHZW/YIkjWJ5a4YgxiJ94DT9KEX0uGcDb+pJVbi9+YtoffiGMummmVKqR2eCyqIi3 nMDl1rfKkdnGur5hpv6IaARSoQWBYPJuhWDyEKQZ8q5euQL0QwkW7n2g008MBMOBXY ty5WOMHOXcEj89UlKpnfCMKEg6Z+ph/WejwvGkEE8Hrw9//oF2c From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 13/16] credential: add support for multistage credential rounds Date: Wed, 17 Apr 2024 00:02:37 +0000 Message-ID: <20240417000240.3611948-14-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Over HTTP, NTLM and Kerberos require two rounds of authentication on the client side. It's possible that there are custom authentication schemes that also implement this same approach. Since these are tricky schemes to implement and the HTTP library in use may not always handle them gracefully on all systems, it would be helpful to allow the credential helper to implement them instead for increased portability and robustness. To allow this to happen, add a boolean flag, continue, that indicates that instead of failing when we get a 401, we should retry another round of authentication. However, this necessitates some changes in our current credential code so that we can make this work. Keep the state[] headers between iterations, but only use them to send to the helper and only consider the new ones we read from the credential helper to be valid on subsequent iterations. That avoids us passing stale data when we finally approve or reject the credential. Similarly, clear the multistage and wwwauth[] values appropriately so that we don't pass stale data or think we're trying a multiround response when we're not. Remove the credential values so that we can actually fill a second time with new responses. Limit the number of iterations of reauthentication we do to 3. This means that if there's a problem, we'll terminate with an error message instead of retrying indefinitely and not informing the user (and possibly conducting a DoS on the server). In our tests, handle creating multiple response output files from our helper so we can verify that each of the messages sent is correct. Signed-off-by: brian m. carlson --- Documentation/git-credential.txt | 13 +++++ builtin/credential.c | 1 + credential.c | 32 ++++++++++-- credential.h | 27 +++++++++- http.c | 59 +++++++++++---------- t/t5563-simple-http-auth.sh | 89 ++++++++++++++++++++++++++++++-- 6 files changed, 185 insertions(+), 36 deletions(-) diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt index 4bbf2db9ca..3d3accc273 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -222,6 +222,19 @@ provided on input. This value should not be sent unless the appropriate capability (see below) is provided on input. +`continue`:: + This is a boolean value, which, if enabled, indicates that this + authentication is a non-final part of a multistage authentication step. This + is common in protocols such as NTLM and Kerberos, where two rounds of client + authentication are required, and setting this flag allows the credential + helper to implement the multistage authentication step. This flag should + only be sent if a further stage is required; that is, if another round of + authentication is expected. ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. This attribute is 'one-way' from a credential helper to +pass information to Git (or other programs invoking `git credential`). + `wwwauth[]`:: When an HTTP response is received by Git that includes one or more diff --git a/builtin/credential.c b/builtin/credential.c index 643bf0b5e4..3568e57025 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -22,6 +22,7 @@ int cmd_credential(int argc, const char **argv, const char *prefix UNUSED) if (!strcmp(op, "fill")) { credential_fill(&c, 0); + credential_next_state(&c); credential_write(&c, stdout, CREDENTIAL_OP_RESPONSE); } else if (!strcmp(op, "approve")) { credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER); diff --git a/credential.c b/credential.c index c93de92f65..98b040cf11 100644 --- a/credential.c +++ b/credential.c @@ -31,10 +31,23 @@ void credential_clear(struct credential *c) string_list_clear(&c->helpers, 0); strvec_clear(&c->wwwauth_headers); strvec_clear(&c->state_headers); + strvec_clear(&c->state_headers_to_send); credential_init(c); } +void credential_next_state(struct credential *c) +{ + strvec_clear(&c->state_headers_to_send); + SWAP(c->state_headers, c->state_headers_to_send); +} + +void credential_clear_secrets(struct credential *c) +{ + FREE_AND_NULL(c->password); + FREE_AND_NULL(c->credential); +} + static void credential_set_capability(struct credential_capability *capa, enum credential_op_type op_type) { @@ -302,6 +315,8 @@ int credential_read(struct credential *c, FILE *fp, credential_set_capability(&c->capa_authtype, op_type); else if (!strcmp(value, "state")) credential_set_capability(&c->capa_state, op_type); + } else if (!strcmp(key, "continue")) { + c->multistage = !!git_config_bool("continue", value); } else if (!strcmp(key, "password_expiry_utc")) { errno = 0; c->password_expiry_utc = parse_timestamp(value, NULL, 10); @@ -369,8 +384,10 @@ void credential_write(const struct credential *c, FILE *fp, for (size_t i = 0; i < c->wwwauth_headers.nr; i++) credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); if (credential_has_capability(&c->capa_state, op_type)) { - for (size_t i = 0; i < c->state_headers.nr; i++) - credential_write_item(fp, "state[]", c->state_headers.v[i], 0); + if (c->multistage) + credential_write_item(fp, "continue", "1", 0); + for (size_t i = 0; i < c->state_headers_to_send.nr; i++) + credential_write_item(fp, "state[]", c->state_headers_to_send.v[i], 0); } } @@ -441,6 +458,9 @@ void credential_fill(struct credential *c, int all_capabilities) if ((c->username && c->password) || c->credential) return; + credential_next_state(c); + c->multistage = 0; + credential_apply_config(c); if (all_capabilities) credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL); @@ -453,8 +473,10 @@ void credential_fill(struct credential *c, int all_capabilities) /* Reset expiry to maintain consistency */ c->password_expiry_utc = TIME_MAX; } - if ((c->username && c->password) || c->credential) + if ((c->username && c->password) || c->credential) { + strvec_clear(&c->wwwauth_headers); return; + } if (c->quit) die("credential helper '%s' told us to quit", c->helpers.items[i].string); @@ -474,6 +496,8 @@ void credential_approve(struct credential *c) if (((!c->username || !c->password) && !c->credential) || c->password_expiry_utc < time(NULL)) return; + credential_next_state(c); + credential_apply_config(c); for (i = 0; i < c->helpers.nr; i++) @@ -485,6 +509,8 @@ void credential_reject(struct credential *c) { int i; + credential_next_state(c); + credential_apply_config(c); for (i = 0; i < c->helpers.nr; i++) diff --git a/credential.h b/credential.h index c307300d12..19163fc6a0 100644 --- a/credential.h +++ b/credential.h @@ -145,10 +145,15 @@ struct credential { struct strvec wwwauth_headers; /** - * A `strvec` of state headers from credential helpers. + * A `strvec` of state headers received from credential helpers. */ struct strvec state_headers; + /** + * A `strvec` of state headers to send to credential helpers. + */ + struct strvec state_headers_to_send; + /** * Internal use only. Keeps track of if we previously matched against a * WWW-Authenticate header line in order to re-fold future continuation @@ -159,6 +164,7 @@ struct credential { unsigned approved:1, ephemeral:1, configured:1, + multistage: 1, quit:1, use_http_path:1, username_from_proto:1; @@ -187,6 +193,7 @@ struct credential { .password_expiry_utc = TIME_MAX, \ .wwwauth_headers = STRVEC_INIT, \ .state_headers = STRVEC_INIT, \ + .state_headers_to_send = STRVEC_INIT, \ } /* Initialize a credential structure, setting all fields to empty. */ @@ -238,6 +245,24 @@ void credential_reject(struct credential *); void credential_set_all_capabilities(struct credential *c, enum credential_op_type op_type); +/** + * Clear the secrets in this credential, but leave other data intact. + * + * This is useful for resetting credentials in preparation for a subsequent + * stage of filling. + */ +void credential_clear_secrets(struct credential *c); + +/** + * Prepares the credential for the next iteration of the helper protocol by + * updating the state headers to send with the ones read by the last iteration + * of the protocol. + * + * Except for internal callers, this should be called exactly once between + * reading credentials with `credential_fill` and writing them. + */ +void credential_next_state(struct credential *c); + int credential_read(struct credential *, FILE *, enum credential_op_type); void credential_write(const struct credential *, FILE *, diff --git a/http.c b/http.c index 906eb098c8..9a514404d0 100644 --- a/http.c +++ b/http.c @@ -1780,6 +1780,10 @@ static int handle_curl_result(struct slot_results *results) else if (results->http_code == 401) { if ((http_auth.username && http_auth.password) ||\ (http_auth.authtype && http_auth.credential)) { + if (http_auth.multistage) { + credential_clear_secrets(&http_auth); + return HTTP_REAUTH; + } credential_reject(&http_auth); return HTTP_NOAUTH; } else { @@ -2177,6 +2181,7 @@ static int http_request_reauth(const char *url, void *result, int target, struct http_get_options *options) { + int i = 3; int ret = http_request(url, result, target, options); if (ret != HTTP_OK && ret != HTTP_REAUTH) @@ -2190,35 +2195,35 @@ static int http_request_reauth(const char *url, } } - if (ret != HTTP_REAUTH) - return ret; + while (ret == HTTP_REAUTH && --i) { + /* + * The previous request may have put cruft into our output stream; we + * should clear it out before making our next request. + */ + switch (target) { + case HTTP_REQUEST_STRBUF: + strbuf_reset(result); + break; + case HTTP_REQUEST_FILE: + if (fflush(result)) { + error_errno("unable to flush a file"); + return HTTP_START_FAILED; + } + rewind(result); + if (ftruncate(fileno(result), 0) < 0) { + error_errno("unable to truncate a file"); + return HTTP_START_FAILED; + } + break; + default: + BUG("Unknown http_request target"); + } - /* - * The previous request may have put cruft into our output stream; we - * should clear it out before making our next request. - */ - switch (target) { - case HTTP_REQUEST_STRBUF: - strbuf_reset(result); - break; - case HTTP_REQUEST_FILE: - if (fflush(result)) { - error_errno("unable to flush a file"); - return HTTP_START_FAILED; - } - rewind(result); - if (ftruncate(fileno(result), 0) < 0) { - error_errno("unable to truncate a file"); - return HTTP_START_FAILED; - } - break; - default: - BUG("Unknown http_request target"); + credential_fill(&http_auth, 1); + + ret = http_request(url, result, target, options); } - - credential_fill(&http_auth, 1); - - return http_request(url, result, target, options); + return ret; } int http_get_strbuf(const char *url, diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index 515185ae00..5d5caa3f58 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -21,9 +21,17 @@ test_expect_success 'setup_credential_helper' ' CREDENTIAL_HELPER="$TRASH_DIRECTORY/bin/git-credential-test-helper" && write_script "$CREDENTIAL_HELPER" <<-\EOF cmd=$1 - teefile=$cmd-query.cred + teefile=$cmd-query-temp.cred catfile=$cmd-reply.cred sed -n -e "/^$/q" -e "p" >>$teefile + state=$(sed -ne "s/^state\[\]=helper://p" "$teefile") + if test -z "$state" + then + mv "$teefile" "$cmd-query.cred" + else + mv "$teefile" "$cmd-query-$state.cred" + catfile="$cmd-reply-$state.cred" + fi if test "$cmd" = "get" then cat $catfile @@ -32,13 +40,15 @@ test_expect_success 'setup_credential_helper' ' ' set_credential_reply () { - cat >"$TRASH_DIRECTORY/$1-reply.cred" + local suffix="$(test -n "$2" && echo "-$2")" + cat >"$TRASH_DIRECTORY/$1-reply$suffix.cred" } expect_credential_query () { - cat >"$TRASH_DIRECTORY/$1-expect.cred" && - test_cmp "$TRASH_DIRECTORY/$1-expect.cred" \ - "$TRASH_DIRECTORY/$1-query.cred" + local suffix="$(test -n "$2" && echo "-$2")" + cat >"$TRASH_DIRECTORY/$1-expect$suffix.cred" && + test_cmp "$TRASH_DIRECTORY/$1-expect$suffix.cred" \ + "$TRASH_DIRECTORY/$1-query$suffix.cred" } per_test_cleanup () { @@ -479,4 +489,73 @@ test_expect_success 'access using bearer auth with invalid credentials' ' EOF ' +test_expect_success 'access using three-legged auth' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + capability[]=state + authtype=Multistage + credential=YS1naXQtdG9rZW4= + state[]=helper:foobar + continue=1 + EOF + + set_credential_reply get foobar <<-EOF && + capability[]=authtype + capability[]=state + authtype=Multistage + credential=YW5vdGhlci10b2tlbg== + state[]=helper:bazquux + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Multistage YS1naXQtdG9rZW4= + id=2 creds=Multistage YW5vdGhlci10b2tlbg== + EOF + + CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=401 response=WWW-Authenticate: Multistage challenge="456" + id=1 status=401 response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=2 status=200 + id=default response=WWW-Authenticate: Multistage challenge="123" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + EOF + + test_config_global credential.helper test-helper && + git ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state + protocol=http + host=$HTTPD_DEST + wwwauth[]=Multistage challenge="123" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + EOF + + expect_credential_query get foobar <<-EOF && + capability[]=authtype + capability[]=state + authtype=Multistage + protocol=http + host=$HTTPD_DEST + wwwauth[]=Multistage challenge="456" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + state[]=helper:foobar + EOF + + expect_credential_query store bazquux <<-EOF + capability[]=authtype + capability[]=state + authtype=Multistage + credential=YW5vdGhlci10b2tlbg== + protocol=http + host=$HTTPD_DEST + state[]=helper:bazquux + EOF +' + test_done From patchwork Wed Apr 17 00:02:38 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632778 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B87E0A95C for ; Wed, 17 Apr 2024 00:03:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312187; cv=none; b=Tll/ZukToT3RIweXwWau65reKAWArBlD+x3jPXtrVBR86ltvNCPirbIsrX0rMZvBAwad2LZ9WtRQJF44YP7ivkUVRRxqzh/fchZYp2Xy5gC09zNBeq63kANFnlnbpLhKksHdniVjL9PVwQnHOSsc+MmGekxxY8Q8ZE0lPvBYIAU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312187; c=relaxed/simple; bh=gH31h0+38/Wl1OH8f0S+Es8O4jnRi3WoLtXE8O1GFp4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=e6pzlwoGi5LXV2hus8Id7NH/QmhI9gU/7khoKRwGO/2xN5B9cTnzFpj8/e563/RH1tfRBvGiaHGIE87Ne5i4YNYphBbHZBouZVkhAmazZ2KI2BER8Qi9Aus5KChl/gMPgWK2sX66IEZvNZ3oCtFhJdwQbSOLiyo1nLiy6JYLoMM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=R7RuSMqC; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="R7RuSMqC" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id B90875D6D0; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=gH31h0+38/Wl1OH8f0S+Es8O4jnRi3WoLtXE8O1GFp4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=R7RuSMqCGmhFmzoonYgLh1EmjyM0iWTNFk9VSrdM5c1nLxw1KjnA9LBkuEW4386di 5RuHM5tV5u/HvKfAudZhzxXsZlOwZ/DMOczi6+S7c/mG9oahSFAAJYTPLsi4Q2UUEY i745f9PWPn4gzc/dD/R9t8ewZhQtY7f4jw1OwNZU65zL1QbRHJjTkio4s0gLIdY0XA L3g3ZN1Ae0Qz3iE/DHtA1b7siXtXeq/tX+mxaEaMKQBeVCIcEWZ0V+AldCfpkc9wGt akq3wsUv0cWgLad1VgBu3iIofjpWfPxt6jeA7VN+xg7pycdF9cnuv6LpL4V0mJnOFI mCsw3WSYM7fKzQjQ7gteCGPxtOwTeONvlSDqJNIoA5Hm9AZ5Yh0ql/ndo2H90ROj7g myP6cXa3oE/QWrwKW88tH1WlUK1hGNsCgUR5rbUk69jYWJyPyUV/VSRAHTmLPPhxxs gncs8AO2F0ZJzXgNpMazN7cnzsjt1c5h4Y15mT8hZ4N2a1NtaWG From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 14/16] t: add credential tests for authtype Date: Wed, 17 Apr 2024 00:02:38 +0000 Message-ID: <20240417000240.3611948-15-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 It's helpful to have some basic tests for credential helpers supporting the authtype and credential fields. Let's add some tests for this case so that we can make sure newly supported helpers work correctly. Note that we explicitly check that credential helpers can produce different sets of authtype and credential values based on the username. While the username is not used in the HTTP protocol with authtype and credential, it can still be specified in the URL and thus may be part of the protocol. Additionally, because it is common for users to have multiple accounts on one service (say, both personal and professional accounts), it's very helpful to be able to store different credentials for different accounts in the same helper, and that doesn't become less useful if one is using, say, Bearer authentication instead of Basic. Thus, credential helpers should be expected to support this functionality as basic functionality, so verify here that they do so. Signed-off-by: brian m. carlson --- t/lib-credential.sh | 123 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/t/lib-credential.sh b/t/lib-credential.sh index 44799c0d38..58b9c74060 100644 --- a/t/lib-credential.sh +++ b/t/lib-credential.sh @@ -538,6 +538,129 @@ helper_test_oauth_refresh_token() { ' } +helper_test_authtype() { + HELPER=$1 + + test_expect_success "helper ($HELPER) stores authtype and credential" ' + check approve $HELPER <<-\EOF + capability[]=authtype + authtype=Bearer + credential=random-token + protocol=https + host=git.example.com + EOF + ' + + test_expect_success "helper ($HELPER) gets authtype and credential" ' + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git.example.com + -- + capability[]=authtype + authtype=Bearer + credential=random-token + protocol=https + host=git.example.com + -- + EOF + ' + + test_expect_success "helper ($HELPER) stores authtype and credential with username" ' + check approve $HELPER <<-\EOF + capability[]=authtype + authtype=Bearer + credential=other-token + protocol=https + host=git.example.com + username=foobar + EOF + ' + + test_expect_success "helper ($HELPER) gets authtype and credential with username" ' + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git.example.com + username=foobar + -- + capability[]=authtype + authtype=Bearer + credential=other-token + protocol=https + host=git.example.com + username=foobar + -- + EOF + ' + + test_expect_success "helper ($HELPER) does not get authtype and credential with different username" ' + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git.example.com + username=barbaz + -- + protocol=https + host=git.example.com + username=barbaz + password=askpass-password + -- + askpass: Password for '\''https://barbaz@git.example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) does not store ephemeral authtype and credential" ' + check approve $HELPER <<-\EOF && + capability[]=authtype + authtype=Bearer + credential=git2-token + protocol=https + host=git2.example.com + ephemeral=1 + EOF + + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git2.example.com + -- + protocol=https + host=git2.example.com + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://git2.example.com'\'': + askpass: Password for '\''https://askpass-username@git2.example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) does not store ephemeral username and password" ' + check approve $HELPER <<-\EOF && + capability[]=authtype + protocol=https + host=git2.example.com + user=barbaz + password=secret + ephemeral=1 + EOF + + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git2.example.com + -- + protocol=https + host=git2.example.com + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://git2.example.com'\'': + askpass: Password for '\''https://askpass-username@git2.example.com'\'': + EOF + ' +} + write_script askpass <<\EOF echo >&2 askpass: $* what=$(echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z) From patchwork Wed Apr 17 00:02:39 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632780 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 12328AD4C for ; Wed, 17 Apr 2024 00:03:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312187; cv=none; b=KmD15S0Ul9SsskBaHPHq0Fcz9ZLNbesnGyyOgxhpG8a7Mtk6lrNudoXc6vPRiqVQTXQArX6GObYJmq7yakolZHErjSu7hc1pNFFjHu1GxXjF7J8vdXnhxjxgcR+D7peON0XNVxjGRu76MO3m1EMqp6NlTd4gEnsWi7FgSAptOqs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312187; c=relaxed/simple; bh=Hu9AMRoEfj8kJCz7oMU2qCqaJx7NMEVkkiMsL0Hq+c0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SIb5eiNGTR3tTfhbxYID32xn7+R0ZIZuO35Sf/ZXlO3T3zChsJZl0LO+6akSw5Y14ewuizU3k681sFq8VNUxYqmjB6qdELLG6dP6IliRfJ7CpL/3i7uOSOntK5rk73QIRsvxNVWIJXrKVqOAgZ0pcg1rbST+SwzyNKJsWiOTKvo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=IuxdMQ4A; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="IuxdMQ4A" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id C69FD5D6D1; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=Hu9AMRoEfj8kJCz7oMU2qCqaJx7NMEVkkiMsL0Hq+c0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=IuxdMQ4AnM2RschHxDh1evEmRsHBQMQDG6P7cyzW4BJSGbROHx1VDQi9p5BcUCgJZ 6Ka24DuA1XSMfT1hiill6sy7MS5dtgcCScTTJJ1KO5Oe9+fBz1b6K4+9CnV5k1LxCi geBGxN4xHcwhxCMYKxSSGcvN83OfBdokHWxyb4HyzZpVivRtuX+hKVwSI3cj8wSIJ4 IoBHEaDNTfB/ZRkCjmgz23SONk08JX0hZV/PiivjtOLn8soU+i5v7za0BktpJ6XL+o mrgnaJom7PeguTHy0sYyuhTb9dwV3GRRA811fGZNEatA4fu8hyRoyPvbSUfH3ag6Fi TJssnoMxz678Ryml/tJVAPDqdH6dxaKwzOuqJaiMsyAAOG+muGpx9J2EweK+bvfpFD O92ScNf3yX5nLcot//WY/7QcAmp/TNi35zURi4hItC6a+lTtgaWpkOSoqWkBCb5IR8 rHofskWJfJu5+v2CSekB1aTmopN8zT1EXKGSgNLhkTDfoeUilhK From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 15/16] credential-cache: implement authtype capability Date: Wed, 17 Apr 2024 00:02:39 +0000 Message-ID: <20240417000240.3611948-16-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Now that we have full support in Git for the authtype capability, let's add support to the cache credential helper. When parsing data, we always set the initial capabilities because we're the helper, and we need both the initial and helper capabilities to be set in order to have the helper capabilities take effect. When emitting data, always emit the supported capability and make sure we emit items only if we have them and they're supported by the caller. Since we may no longer have a username or password, be sure to emit those conditionally as well so we don't segfault on a NULL pointer. Similarly, when comparing credentials, consider both the password and credential fields when we're matching passwords. Adjust the partial credential detection code so that we can store credentials missing a username or password as long as they have an authtype and credential. Signed-off-by: brian m. carlson --- builtin/credential-cache--daemon.c | 20 +++++++++++++++++--- credential.c | 7 ++++--- credential.h | 6 ++++++ t/t0301-credential-cache.sh | 1 + 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c index ccbcf99ac1..6ffedcd17c 100644 --- a/builtin/credential-cache--daemon.c +++ b/builtin/credential-cache--daemon.c @@ -115,6 +115,8 @@ static int read_request(FILE *fh, struct credential *c, return error("client sent bogus timeout line: %s", item.buf); *timeout = atoi(p); + credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL); + if (credential_read(c, fh, CREDENTIAL_OP_HELPER) < 0) return -1; return 0; @@ -131,8 +133,18 @@ static void serve_one_client(FILE *in, FILE *out) else if (!strcmp(action.buf, "get")) { struct credential_cache_entry *e = lookup_credential(&c); if (e) { - fprintf(out, "username=%s\n", e->item.username); - fprintf(out, "password=%s\n", e->item.password); + e->item.capa_authtype.request_initial = 1; + e->item.capa_authtype.request_helper = 1; + + fprintf(out, "capability[]=authtype\n"); + if (e->item.username) + fprintf(out, "username=%s\n", e->item.username); + if (e->item.password) + fprintf(out, "password=%s\n", e->item.password); + if (credential_has_capability(&c.capa_authtype, CREDENTIAL_OP_HELPER) && e->item.authtype) + fprintf(out, "authtype=%s\n", e->item.authtype); + if (credential_has_capability(&c.capa_authtype, CREDENTIAL_OP_HELPER) && e->item.credential) + fprintf(out, "credential=%s\n", e->item.credential); if (e->item.password_expiry_utc != TIME_MAX) fprintf(out, "password_expiry_utc=%"PRItime"\n", e->item.password_expiry_utc); @@ -157,8 +169,10 @@ static void serve_one_client(FILE *in, FILE *out) else if (!strcmp(action.buf, "store")) { if (timeout < 0) warning("cache client didn't specify a timeout"); - else if (!c.username || !c.password) + else if ((!c.username || !c.password) && (!c.authtype && !c.credential)) warning("cache client gave us a partial credential"); + else if (c.ephemeral) + warning("not storing ephemeral credential"); else { remove_credential(&c, 0); cache_credential(&c, timeout); diff --git a/credential.c b/credential.c index 98b040cf11..ffaf31499e 100644 --- a/credential.c +++ b/credential.c @@ -80,7 +80,8 @@ int credential_match(const struct credential *want, CHECK(host) && CHECK(path) && CHECK(username) && - (!match_password || CHECK(password)); + (!match_password || CHECK(password)) && + (!match_password || CHECK(credential)); #undef CHECK } @@ -248,8 +249,8 @@ static void credential_getpass(struct credential *c) PROMPT_ASKPASS); } -static int credential_has_capability(const struct credential_capability *capa, - enum credential_op_type op_type) +int credential_has_capability(const struct credential_capability *capa, + enum credential_op_type op_type) { /* * We're checking here if each previous step indicated that we had the diff --git a/credential.h b/credential.h index 19163fc6a0..f8df10937c 100644 --- a/credential.h +++ b/credential.h @@ -263,6 +263,12 @@ void credential_clear_secrets(struct credential *c); */ void credential_next_state(struct credential *c); +/** + * Return true if the capability is enabled for an operation of op_type. + */ +int credential_has_capability(const struct credential_capability *capa, + enum credential_op_type op_type); + int credential_read(struct credential *, FILE *, enum credential_op_type); void credential_write(const struct credential *, FILE *, diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh index 8300faadea..3897353d55 100755 --- a/t/t0301-credential-cache.sh +++ b/t/t0301-credential-cache.sh @@ -31,6 +31,7 @@ test_atexit 'git credential-cache exit' helper_test cache helper_test_password_expiry_utc cache helper_test_oauth_refresh_token cache +helper_test_authtype cache test_expect_success 'socket defaults to ~/.cache/git/credential/socket' ' test_when_finished " From patchwork Wed Apr 17 00:02:40 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "brian m. carlson" X-Patchwork-Id: 13632782 Received: from ring.crustytoothpaste.net (ring.crustytoothpaste.net [172.105.110.227]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 847262209B for ; Wed, 17 Apr 2024 00:03:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=172.105.110.227 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312194; cv=none; b=S/agv4/7HCe2HULtaOLf3nSh1YAGnnIOklCyZVYUjLM/BHCf1oWSpBGrlGVcqZTpNga5u9p2pH0OpTeKW4q8hOB2N/3GF1bv6smvtEsuDAMffRLI8OEPjm4hmcYmwt7VeUUx4LiMXd246+334+ijXd/yCWGgY43oosnnOXYGMAs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713312194; c=relaxed/simple; bh=JI+2lBkvOdkbJ/WBo6KAr60AhaR7ZCB5mJCCUB4DR5k=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Pt4lGSovKQbBIVSZqsX1SNiw8+kVyXF/DD0YqHnmlB+YRCGI6yOq6t0oBH6WVH7Iiew5z09+eURtSY5N8Qgaiu5MWpzeIsijMCki8dA1OiPRUV6NjQOnBEAMO5Yab0Zy9nt7dVhAkc4YUYaro4tBqq5kxyehV2KbO6l+xqzD4YU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net; spf=pass smtp.mailfrom=crustytoothpaste.net; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b=KPmuY02s; arc=none smtp.client-ip=172.105.110.227 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=crustytoothpaste.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (3072-bit key) header.d=crustytoothpaste.net header.i=@crustytoothpaste.net header.b="KPmuY02s" Received: from tapette.tailf784c.ts.net (unknown [IPv6:2001:470:b056:101:e59a:3ed0:5f5c:31f3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (3072 bits) server-digest SHA256) (No client certificate requested) by ring.crustytoothpaste.net (Postfix) with ESMTPSA id D43BA5D6D2; Wed, 17 Apr 2024 00:02:57 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=crustytoothpaste.net; s=default; t=1713312177; bh=JI+2lBkvOdkbJ/WBo6KAr60AhaR7ZCB5mJCCUB4DR5k=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From:Reply-To: Subject:Date:To:CC:Resent-Date:Resent-From:Resent-To:Resent-Cc: In-Reply-To:References:Content-Type:Content-Disposition; b=KPmuY02so47UqVpx2fFQm2TdmH5SKm7FB7PuYcmUlMNgYYTZkuWH3pcp6o7UcoAFX D16mOwWhO0jrsL8NVpMmKsiuCDG/xJMI66gqe8RFM3zE20l+uKNGmug+SGG+TnD73r C4yw6SDxbAQNmuJComCFAC3Vspod06C/Oy/A5KrOwnNcmDjMHy85srpu0qmDEB522H yvBO3fDANCAedIkO41xAnBkUDsWaxPXnLJCRToQ/2OGr8zFKwyMDkzq6cUqJ1C281A MunIImz/R8MEC0vLCGVLpjPE+4qmrBHll9cIWizTKnODtBNQaYuWQIE5sW8JrHwfD/ dL4Xbc4Uaa2N4CY0KoOuFrAcdmnOU3TIUMo5V0aV0OiIvmPR6+PWiZRtnbBHFx7XSH skCfOZcMqb8MilE2y4ClLYinWq4MkjZiDj+VP40Szsrd+9J5LLboInejkUv4IkN4pi F9G5T5d92hL3tcbjTStrNxu8gPLajghpQasyLav68wXdbQJy4zI From: "brian m. carlson" To: Cc: Junio C Hamano , Matthew John Cheetham , M Hickford , Jeff King , Patrick Steinhardt Subject: [PATCH v2 16/16] credential: add method for querying capabilities Date: Wed, 17 Apr 2024 00:02:40 +0000 Message-ID: <20240417000240.3611948-17-sandals@crustytoothpaste.net> X-Mailer: git-send-email 2.43.0.381.gb435a96ce8 In-Reply-To: <20240417000240.3611948-1-sandals@crustytoothpaste.net> References: <20240324011301.1553072-1-sandals@crustytoothpaste.net> <20240417000240.3611948-1-sandals@crustytoothpaste.net> Precedence: bulk X-Mailing-List: git@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Right now, there's no specific way to determine whether a credential helper or git credential itself supports a given set of capabilities. It would be helpful to have such a way, so let's let credential helpers and git credential take an argument, "capability", which has it list the capabilities and a version number on standard output. Specifically choose a format that is slightly different from regular credential output and assume that no capabilities are supported if a non-zero exit status occurs or the data deviates from the format. It is common for users to write small shell scripts as the argument to credential.helper, which will almost never be designed to emit capabilities. We want callers to gracefully handle this case by assuming that they are not capable of extended support because that is almost certainly the case, and specifying the error behavior up front does this and preserves backwards compatibility in a graceful way. Signed-off-by: brian m. carlson --- Documentation/git-credential.txt | 28 +++++++++++++++++++++++++++- builtin/credential-cache.c | 10 ++++++++++ builtin/credential.c | 6 ++++++ credential.c | 11 +++++++++++ credential.h | 6 ++++++ 5 files changed, 60 insertions(+), 1 deletion(-) diff --git a/Documentation/git-credential.txt b/Documentation/git-credential.txt index 3d3accc273..e41493292f 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -8,7 +8,7 @@ git-credential - Retrieve and store user credentials SYNOPSIS -------- ------------------ -'git credential' (fill|approve|reject) +'git credential' (fill|approve|reject|capability) ------------------ DESCRIPTION @@ -41,6 +41,9 @@ If the action is `reject`, git-credential will send the description to any configured credential helpers, which may erase any stored credentials matching the description. +If the action is `capability`, git-credential will announce any capabilities +it supports to standard output. + If the action is `approve` or `reject`, no output should be emitted. TYPICAL USE OF GIT CREDENTIAL @@ -263,6 +266,29 @@ is supported, but they should not be provided without the capability. Unrecognised attributes and capabilities are silently discarded. +[[CAPA-IOFMT]] +CAPABILITY INPUT/OUTPUT FORMAT +------------------------------ + +For `git credential capability`, the format is slightly different. First, a +`version 0` announcement is made to indicate the current version of the +protocol, and then each capability is announced with a line like `capability +authtype`. Credential helpers may also implement this format, again with the +`capability` argument. Additional lines may be added in the future; callers +should ignore lines which they don't understand. + +Because this is a new part of the credential helper protocol, older versions of +Git, as well as some credential helpers, may not support it. If a non-zero +exit status is received, or if the first line doesn't start with the word +`version` and a space, callers should assume that no capabilities are supported. + +The intention of this format is to differentiate it from the credential output +in an unambiguous way. It is possible to use very simple credential helpers +(e.g., inline shell scripts) which always produce identical output. Using a +distinct format allows users to continue to use this syntax without having to +worry about correctly implementing capability advertisements or accidentally +confusing callers querying for capabilities. + GIT --- Part of the linkgit:git[1] suite diff --git a/builtin/credential-cache.c b/builtin/credential-cache.c index bba96d4ffd..f5c989e2b2 100644 --- a/builtin/credential-cache.c +++ b/builtin/credential-cache.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "credential.h" #include "gettext.h" #include "parse-options.h" #include "path.h" @@ -127,6 +128,13 @@ static char *get_socket_path(void) return socket; } +static void announce_capabilities(void) +{ + struct credential c = CREDENTIAL_INIT; + c.capa_authtype.request_initial = 1; + credential_announce_capabilities(&c, stdout); +} + int cmd_credential_cache(int argc, const char **argv, const char *prefix) { char *socket_path = NULL; @@ -160,6 +168,8 @@ int cmd_credential_cache(int argc, const char **argv, const char *prefix) do_cache(socket_path, op, timeout, FLAG_RELAY); else if (!strcmp(op, "store")) do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN); + else if (!strcmp(op, "capability")) + announce_capabilities(); else ; /* ignore unknown operation */ diff --git a/builtin/credential.c b/builtin/credential.c index 3568e57025..5100d441f2 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -17,6 +17,12 @@ int cmd_credential(int argc, const char **argv, const char *prefix UNUSED) usage(usage_msg); op = argv[1]; + if (!strcmp(op, "capability")) { + credential_set_all_capabilities(&c, CREDENTIAL_OP_INITIAL); + credential_announce_capabilities(&c, stdout); + return 0; + } + if (credential_read(&c, stdin, CREDENTIAL_OP_INITIAL) < 0) die("unable to read credential from stdin"); diff --git a/credential.c b/credential.c index ffaf31499e..758528b291 100644 --- a/credential.c +++ b/credential.c @@ -72,6 +72,17 @@ void credential_set_all_capabilities(struct credential *c, credential_set_capability(&c->capa_state, op_type); } +static void announce_one(struct credential_capability *cc, const char *name, FILE *fp) { + if (cc->request_initial) + fprintf(fp, "capability %s\n", name); +} + +void credential_announce_capabilities(struct credential *c, FILE *fp) { + fprintf(fp, "version 0\n"); + announce_one(&c->capa_authtype, "authtype", fp); + announce_one(&c->capa_state, "state", fp); +} + int credential_match(const struct credential *want, const struct credential *have, int match_password) { diff --git a/credential.h b/credential.h index f8df10937c..af8c287ff2 100644 --- a/credential.h +++ b/credential.h @@ -253,6 +253,12 @@ void credential_set_all_capabilities(struct credential *c, */ void credential_clear_secrets(struct credential *c); +/** + * Print a list of supported capabilities and version numbers to standard + * output. + */ +void credential_announce_capabilities(struct credential *c, FILE *fp); + /** * Prepares the credential for the next iteration of the helper protocol by * updating the state headers to send with the ones read by the last iteration