@@ -48,10 +48,17 @@ static void *head_name_to_free;
4848static int sent_capabilities ;
4949static int shallow_update ;
5050static const char * alt_shallow_file ;
51- static int accept_push_cert = 1 ;
5251static struct strbuf push_cert = STRBUF_INIT ;
5352static unsigned char push_cert_sha1 [20 ];
5453static struct signature_check sigcheck ;
54+ static const char * push_cert_nonce ;
55+ static const char * cert_nonce_seed ;
56+
57+ static const char * NONCE_UNSOLICITED = "UNSOLICITED" ;
58+ static const char * NONCE_BAD = "BAD" ;
59+ static const char * NONCE_MISSING = "MISSING" ;
60+ static const char * NONCE_OK = "OK" ;
61+ static const char * nonce_status ;
5562
5663static enum deny_action parse_deny_action (const char * var , const char * value )
5764{
@@ -135,10 +142,8 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
135142 return 0 ;
136143 }
137144
138- if (strcmp (var , "receive.acceptpushcert" ) == 0 ) {
139- accept_push_cert = git_config_bool (var , value );
140- return 0 ;
141- }
145+ if (strcmp (var , "receive.certnonceseed" ) == 0 )
146+ return git_config_string (& cert_nonce_seed , var , value );
142147
143148 return git_default_config (var , value , cb );
144149}
@@ -157,8 +162,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
157162 "report-status delete-refs side-band-64k quiet" );
158163 if (prefer_ofs_delta )
159164 strbuf_addstr (& cap , " ofs-delta" );
160- if (accept_push_cert )
161- strbuf_addstr (& cap , " push-cert" );
165+ if (push_cert_nonce )
166+ strbuf_addf (& cap , " push-cert=%s" , push_cert_nonce );
162167 strbuf_addf (& cap , " agent=%s" , git_user_agent_sanitized ());
163168 packet_write (1 , "%s %s%c%s\n" ,
164169 sha1_to_hex (sha1 ), path , 0 , cap .buf );
@@ -271,6 +276,110 @@ static int copy_to_sideband(int in, int out, void *arg)
271276 return 0 ;
272277}
273278
279+ #define HMAC_BLOCK_SIZE 64
280+
281+ static void hmac_sha1 (unsigned char out [20 ],
282+ const char * key_in , size_t key_len ,
283+ const char * text , size_t text_len )
284+ {
285+ unsigned char key [HMAC_BLOCK_SIZE ];
286+ unsigned char k_ipad [HMAC_BLOCK_SIZE ];
287+ unsigned char k_opad [HMAC_BLOCK_SIZE ];
288+ int i ;
289+ git_SHA_CTX ctx ;
290+
291+ /* RFC 2104 2. (1) */
292+ memset (key , '\0' , HMAC_BLOCK_SIZE );
293+ if (HMAC_BLOCK_SIZE < key_len ) {
294+ git_SHA1_Init (& ctx );
295+ git_SHA1_Update (& ctx , key_in , key_len );
296+ git_SHA1_Final (key , & ctx );
297+ } else {
298+ memcpy (key , key_in , key_len );
299+ }
300+
301+ /* RFC 2104 2. (2) & (5) */
302+ for (i = 0 ; i < sizeof (key ); i ++ ) {
303+ k_ipad [i ] = key [i ] ^ 0x36 ;
304+ k_opad [i ] = key [i ] ^ 0x5c ;
305+ }
306+
307+ /* RFC 2104 2. (3) & (4) */
308+ git_SHA1_Init (& ctx );
309+ git_SHA1_Update (& ctx , k_ipad , sizeof (k_ipad ));
310+ git_SHA1_Update (& ctx , text , text_len );
311+ git_SHA1_Final (out , & ctx );
312+
313+ /* RFC 2104 2. (6) & (7) */
314+ git_SHA1_Init (& ctx );
315+ git_SHA1_Update (& ctx , k_opad , sizeof (k_opad ));
316+ git_SHA1_Update (& ctx , out , sizeof (out ));
317+ git_SHA1_Final (out , & ctx );
318+ }
319+
320+ static char * prepare_push_cert_nonce (const char * path , unsigned long stamp )
321+ {
322+ struct strbuf buf = STRBUF_INIT ;
323+ unsigned char sha1 [20 ];
324+
325+ strbuf_addf (& buf , "%s:%lu" , path , stamp );
326+ hmac_sha1 (sha1 , buf .buf , buf .len , cert_nonce_seed , strlen (cert_nonce_seed ));;
327+ strbuf_release (& buf );
328+
329+ /* RFC 2104 5. HMAC-SHA1-80 */
330+ strbuf_addf (& buf , "%lu-%.*s" , stamp , 20 , sha1_to_hex (sha1 ));
331+ return strbuf_detach (& buf , NULL );
332+ }
333+
334+ /*
335+ * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing
336+ * after dropping "_commit" from its name and possibly moving it out
337+ * of commit.c
338+ */
339+ static char * find_header (const char * msg , size_t len , const char * key )
340+ {
341+ int key_len = strlen (key );
342+ const char * line = msg ;
343+
344+ while (line && line < msg + len ) {
345+ const char * eol = strchrnul (line , '\n' );
346+
347+ if ((msg + len <= eol ) || line == eol )
348+ return NULL ;
349+ if (line + key_len < eol &&
350+ !memcmp (line , key , key_len ) && line [key_len ] == ' ' ) {
351+ int offset = key_len + 1 ;
352+ return xmemdupz (line + offset , (eol - line ) - offset );
353+ }
354+ line = * eol ? eol + 1 : NULL ;
355+ }
356+ return NULL ;
357+ }
358+
359+ static const char * check_nonce (const char * buf , size_t len )
360+ {
361+ char * nonce = find_header (buf , len , "nonce" );
362+ const char * retval = NONCE_BAD ;
363+
364+ if (!nonce ) {
365+ retval = NONCE_MISSING ;
366+ goto leave ;
367+ } else if (!push_cert_nonce ) {
368+ retval = NONCE_UNSOLICITED ;
369+ goto leave ;
370+ } else if (!strcmp (push_cert_nonce , nonce )) {
371+ retval = NONCE_OK ;
372+ goto leave ;
373+ }
374+
375+ /* returned nonce MUST match what we gave out earlier */
376+ retval = NONCE_BAD ;
377+
378+ leave :
379+ free (nonce );
380+ return retval ;
381+ }
382+
274383static void prepare_push_cert_sha1 (struct child_process * proc )
275384{
276385 static int already_done ;
@@ -305,6 +414,7 @@ static void prepare_push_cert_sha1(struct child_process *proc)
305414
306415 strbuf_release (& gpg_output );
307416 strbuf_release (& gpg_status );
417+ nonce_status = check_nonce (push_cert .buf , bogs );
308418 }
309419 if (!is_null_sha1 (push_cert_sha1 )) {
310420 argv_array_pushf (& env , "GIT_PUSH_CERT=%s" , sha1_to_hex (push_cert_sha1 ));
@@ -313,7 +423,10 @@ static void prepare_push_cert_sha1(struct child_process *proc)
313423 argv_array_pushf (& env , "GIT_PUSH_CERT_KEY=%s" ,
314424 sigcheck .key ? sigcheck .key : "" );
315425 argv_array_pushf (& env , "GIT_PUSH_CERT_STATUS=%c" , sigcheck .result );
316-
426+ if (push_cert_nonce ) {
427+ argv_array_pushf (& env , "GIT_PUSH_CERT_NONCE=%s" , push_cert_nonce );
428+ argv_array_pushf (& env , "GIT_PUSH_CERT_NONCE_STATUS=%s" , nonce_status );
429+ }
317430 proc -> env = env .argv ;
318431 }
319432}
@@ -1296,6 +1409,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
12961409 die ("'%s' does not appear to be a git repository" , dir );
12971410
12981411 git_config (receive_pack_config , NULL );
1412+ if (cert_nonce_seed )
1413+ push_cert_nonce = prepare_push_cert_nonce (dir , time (NULL ));
12991414
13001415 if (0 <= transfer_unpack_limit )
13011416 unpack_limit = transfer_unpack_limit ;
@@ -1340,5 +1455,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
13401455 packet_flush (1 );
13411456 sha1_array_clear (& shallow );
13421457 sha1_array_clear (& ref );
1458+ free ((void * )push_cert_nonce );
13431459 return 0 ;
13441460}
0 commit comments