Add a new flag --signed-if-possible to push and send-pack that sends a
push certificate if and only if the server advertised a push cert
nonce. If not, at least warn the user that their push may not be as
secure as they thought.

Signed-off-by: Dave Borowitz <dborow...@google.com>
---
 Documentation/git-push.txt      |  9 +++++++--
 Documentation/git-send-pack.txt |  9 +++++++--
 builtin/push.c                  |  4 +++-
 builtin/send-pack.c             |  6 +++++-
 remote-curl.c                   | 14 ++++++++++----
 send-pack.c                     | 18 +++++++++++++++---
 send-pack.h                     |  8 +++++++-
 transport-helper.c              | 34 +++++++++++++++++-----------------
 transport.c                     |  8 +++++++-
 transport.h                     |  5 +++--
 10 files changed, 81 insertions(+), 34 deletions(-)

diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index f8b8b8b..fcfdf73 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -11,7 +11,7 @@ SYNOPSIS
 [verse]
 'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | 
--dry-run] [--receive-pack=<git-receive-pack>]
           [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose]
-          [-u | --set-upstream] [--signed]
+          [-u | --set-upstream] [--signed] [--signed-if-possible]
           [--force-with-lease[=<refname>[:<expect>]]]
           [--no-verify] [<repository> [<refspec>...]]
 
@@ -139,7 +139,12 @@ already exists on the remote side.
        logged.  See linkgit:git-receive-pack[1] for the details
        on the receiving end.  If the `gpg` executable is not available,
        or if the server does not support signed pushes, the push will
-       fail.
+       fail. Takes precedence over --signed-if-possible.
+
+--signed-if-possible::
+       Like --signed, but only if the server supports signed pushes. If
+       the server supports signed pushes but the `gpg` is not available,
+       the push will fail.
 
 --[no-]atomic::
        Use an atomic transaction on the remote side if available.
diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index dde13b0..5789208 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git send-pack' [--all] [--dry-run] [--force] 
[--receive-pack=<git-receive-pack>]
-               [--verbose] [--thin] [--atomic] [--signed]
+               [--verbose] [--thin] [--atomic] [--signed] 
[--signed-if-possible]
                [<host>:]<directory> [<ref>...]
 
 DESCRIPTION
@@ -75,7 +75,12 @@ be in a separate packet, and the list must end with a flush 
packet.
        logged.  See linkgit:git-receive-pack[1] for the details
        on the receiving end.  If the `gpg` executable is not available,
        or if the server does not support signed pushes, the push will
-       fail.
+       fail. Takes precedence over --signed-if-possible.
+
+--signed-if-possible::
+       Like --signed, but only if the server supports signed pushes. If
+       the server supports signed pushes but the `gpg` is not available,
+       the push will fail.
 
 <host>::
        A remote host to house the repository.  When this
diff --git a/builtin/push.c b/builtin/push.c
index 57c138b..95a67c5 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -526,7 +526,9 @@ int cmd_push(int argc, const char **argv, const char 
*prefix)
                OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), 
TRANSPORT_PUSH_NO_HOOK),
                OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant 
tags"),
                        TRANSPORT_PUSH_FOLLOW_TAGS),
-               OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), 
TRANSPORT_PUSH_CERT),
+               OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), 
TRANSPORT_PUSH_CERT_ALWAYS),
+               OPT_BIT(0, "signed-if-possible", &flags, N_("GPG sign the push, 
if supported by the server"),
+                       TRANSPORT_PUSH_CERT_IF_POSSIBLE),
                OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on 
remote side"), TRANSPORT_PUSH_ATOMIC),
                OPT_END()
        };
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index 23b2962..8eebbf4 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -158,7 +158,11 @@ int cmd_send_pack(int argc, const char **argv, const char 
*prefix)
                                continue;
                        }
                        if (!strcmp(arg, "--signed")) {
-                               args.push_cert = 1;
+                               args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--signed-if-possible")) {
+                               args.push_cert = 
SEND_PACK_PUSH_CERT_IF_POSSIBLE;
                                continue;
                        }
                        if (!strcmp(arg, "--progress")) {
diff --git a/remote-curl.c b/remote-curl.c
index af7b678..f049de8 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -11,6 +11,7 @@
 #include "argv-array.h"
 #include "credential.h"
 #include "sha1-array.h"
+#include "send-pack.h"
 
 static struct remote *remote;
 /* always ends with a trailing slash */
@@ -26,7 +27,8 @@ struct options {
                followtags : 1,
                dry_run : 1,
                thin : 1,
-               push_cert : 1;
+               /* One of the SEND_PACK_PUSH_CERT_* constants. */
+               push_cert : 2;
 };
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
@@ -109,9 +111,11 @@ static int set_option(const char *name, const char *value)
                return 0;
        } else if (!strcmp(name, "pushcert")) {
                if (!strcmp(value, "true"))
-                       options.push_cert = 1;
+                       options.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
                else if (!strcmp(value, "false"))
-                       options.push_cert = 0;
+                       options.push_cert = SEND_PACK_PUSH_CERT_NEVER;
+               else if (!strcmp(value, "if-possible"))
+                       options.push_cert = SEND_PACK_PUSH_CERT_IF_POSSIBLE;
                else
                        return -1;
                return 0;
@@ -880,8 +884,10 @@ static int push_git(struct discovery *heads, int nr_spec, 
char **specs)
                argv_array_push(&args, "--thin");
        if (options.dry_run)
                argv_array_push(&args, "--dry-run");
-       if (options.push_cert)
+       if (options.push_cert == SEND_PACK_PUSH_CERT_ALWAYS)
                argv_array_push(&args, "--signed");
+       else if (options.push_cert == SEND_PACK_PUSH_CERT_IF_POSSIBLE)
+               argv_array_push(&args, "--signed-if-possible");
        if (options.verbosity == 0)
                argv_array_push(&args, "--quiet");
        else if (options.verbosity > 1)
diff --git a/send-pack.c b/send-pack.c
index 2a64fec..6ae9f45 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -370,7 +370,7 @@ int send_pack(struct send_pack_args *args,
                args->use_thin_pack = 0;
        if (server_supports("atomic"))
                atomic_supported = 1;
-       if (args->push_cert) {
+       if (args->push_cert == SEND_PACK_PUSH_CERT_ALWAYS) {
                int len;
 
                push_cert_nonce = server_feature_value("push-cert", &len);
@@ -379,6 +379,18 @@ int send_pack(struct send_pack_args *args,
                reject_invalid_nonce(push_cert_nonce, len);
                push_cert_nonce = xmemdupz(push_cert_nonce, len);
        }
+       if (args->push_cert == SEND_PACK_PUSH_CERT_IF_POSSIBLE) {
+               int len;
+
+               push_cert_nonce = server_feature_value("push-cert", &len);
+               if (push_cert_nonce) {
+                       reject_invalid_nonce(push_cert_nonce, len);
+                       push_cert_nonce = xmemdupz(push_cert_nonce, len);
+               } else
+                       warning(_("not sending a push certificate since the"
+                                 " receiving end does not support --signed"
+                                 " push"));
+       }
 
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing 
nothing.\n"
@@ -413,7 +425,7 @@ int send_pack(struct send_pack_args *args,
        if (!args->dry_run)
                advertise_shallow_grafts_buf(&req_buf);
 
-       if (!args->dry_run && args->push_cert)
+       if (!args->dry_run && push_cert_nonce)
                cmds_sent = generate_push_cert(&req_buf, remote_refs, args,
                                               cap_buf.buf, push_cert_nonce);
 
@@ -452,7 +464,7 @@ int send_pack(struct send_pack_args *args,
        for (ref = remote_refs; ref; ref = ref->next) {
                char *old_hex, *new_hex;
 
-               if (args->dry_run || args->push_cert)
+               if (args->dry_run || push_cert_nonce)
                        continue;
 
                if (check_to_send_update(ref, args) < 0)
diff --git a/send-pack.h b/send-pack.h
index b664648..5042b65 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -1,6 +1,11 @@
 #ifndef SEND_PACK_H
 #define SEND_PACK_H
 
+/* Possible values for push_cert field in send_pack_args. */
+#define SEND_PACK_PUSH_CERT_NEVER 0
+#define SEND_PACK_PUSH_CERT_IF_POSSIBLE 1
+#define SEND_PACK_PUSH_CERT_ALWAYS 2
+
 struct send_pack_args {
        const char *url;
        unsigned verbose:1,
@@ -12,7 +17,8 @@ struct send_pack_args {
                use_thin_pack:1,
                use_ofs_delta:1,
                dry_run:1,
-               push_cert:1,
+               /* One of the SEND_PACK_PUSH_CERT_* constants. */
+               push_cert:2,
                stateless_rpc:1,
                atomic:1;
 };
diff --git a/transport-helper.c b/transport-helper.c
index 5d99a6b..bf4dd22 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -257,7 +257,6 @@ static const char *boolean_options[] = {
        TRANS_OPT_THIN,
        TRANS_OPT_KEEP,
        TRANS_OPT_FOLLOWTAGS,
-       TRANS_OPT_PUSH_CERT
        };
 
 static int set_helper_option(struct transport *transport,
@@ -763,6 +762,21 @@ static int push_update_refs_status(struct helper_data 
*data,
        return ret;
 }
 
+static void set_common_push_options(struct transport *transport,
+                                  const char *name, int flags)
+{
+       if (flags & TRANSPORT_PUSH_DRY_RUN) {
+               if (set_helper_option(transport, "dry-run", "true") != 0)
+                       die("helper %s does not support dry-run", name);
+       } else if (flags & TRANSPORT_PUSH_CERT_ALWAYS) {
+               if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") 
!= 0)
+                       die("helper %s does not support --signed", name);
+       } else if (flags & TRANSPORT_PUSH_CERT_IF_POSSIBLE) {
+               if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, 
"if-possible") != 0)
+                       die("helper %s does not support --signed-if-possible", 
name);
+       }
+}
+
 static int push_refs_with_push(struct transport *transport,
                               struct ref *remote_refs, int flags)
 {
@@ -830,14 +844,7 @@ static int push_refs_with_push(struct transport *transport,
 
        for_each_string_list_item(cas_option, &cas_options)
                set_helper_option(transport, "cas", cas_option->string);
-
-       if (flags & TRANSPORT_PUSH_DRY_RUN) {
-               if (set_helper_option(transport, "dry-run", "true") != 0)
-                       die("helper %s does not support dry-run", data->name);
-       } else if (flags & TRANSPORT_PUSH_CERT) {
-               if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") 
!= 0)
-                       die("helper %s does not support --signed", data->name);
-       }
+       set_common_push_options(transport, data->name, flags);
 
        strbuf_addch(&buf, '\n');
        sendline(data, &buf);
@@ -858,14 +865,7 @@ static int push_refs_with_export(struct transport 
*transport,
        if (!data->refspecs)
                die("remote-helper doesn't support push; refspec needed");
 
-       if (flags & TRANSPORT_PUSH_DRY_RUN) {
-               if (set_helper_option(transport, "dry-run", "true") != 0)
-                       die("helper %s does not support dry-run", data->name);
-       } else if (flags & TRANSPORT_PUSH_CERT) {
-               if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") 
!= 0)
-                       die("helper %s does not support --signed", data->name);
-       }
-
+       set_common_push_options(transport, data->name, flags);
        if (flags & TRANSPORT_PUSH_FORCE) {
                if (set_helper_option(transport, "force", "true") != 0)
                        warning("helper %s does not support 'force'", 
data->name);
diff --git a/transport.c b/transport.c
index 3dd6e30..66cd2d3 100644
--- a/transport.c
+++ b/transport.c
@@ -826,10 +826,16 @@ static int git_transport_push(struct transport 
*transport, struct ref *remote_re
        args.progress = transport->progress;
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
        args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
-       args.push_cert = !!(flags & TRANSPORT_PUSH_CERT);
        args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
        args.url = transport->url;
 
+       if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
+               args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
+       else if (flags & TRANSPORT_PUSH_CERT_IF_POSSIBLE)
+               args.push_cert = SEND_PACK_PUSH_CERT_IF_POSSIBLE;
+       else
+               args.push_cert = SEND_PACK_PUSH_CERT_NEVER;
+
        ret = send_pack(&args, data->fd, data->conn, remote_refs,
                        &data->extra_have);
 
diff --git a/transport.h b/transport.h
index 79190df..49f3e44 100644
--- a/transport.h
+++ b/transport.h
@@ -123,8 +123,9 @@ struct transport {
 #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
 #define TRANSPORT_PUSH_NO_HOOK 512
 #define TRANSPORT_PUSH_FOLLOW_TAGS 1024
-#define TRANSPORT_PUSH_CERT 2048
-#define TRANSPORT_PUSH_ATOMIC 4096
+#define TRANSPORT_PUSH_CERT_ALWAYS 2048
+#define TRANSPORT_PUSH_CERT_IF_POSSIBLE 4096
+#define TRANSPORT_PUSH_ATOMIC 8192
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - 
gettext_width(x)), (x)
-- 
2.5.0.276.gf5e568e

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to