1/* 2 * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_* 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26/* This auth provider stores a plaintext password in memory managed by 27 * a running gpg-agent. In contrast to other password store providers 28 * it does not save the password to disk. 29 * 30 * Prompting is performed by the gpg-agent using a "pinentry" program 31 * which needs to be installed separately. There are several pinentry 32 * implementations with different front-ends (e.g. qt, gtk, ncurses). 33 * 34 * The gpg-agent will let the password time out after a while, 35 * or immediately when it receives the SIGHUP signal. 36 * When the password has timed out it will automatically prompt the 37 * user for the password again. This is transparent to Subversion. 38 * 39 * SECURITY CONSIDERATIONS: 40 * 41 * Communication to the agent happens over a UNIX socket, which is located 42 * in a directory which only the user running Subversion can access. 43 * However, any program the user runs could access this socket and get 44 * the Subversion password if the program knows the "cache ID" Subversion 45 * uses for the password. 46 * The cache ID is very easy to obtain for programs running as the same user. 47 * Subversion uses the MD5 of the realmstring as cache ID, and these checksums 48 * are also used as filenames within ~/.subversion/auth/svn.simple. 49 * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for 50 * permission if another program attempts to access the password. 51 * 52 * Therefore, while the gpg-agent is running and has the password cached, 53 * this provider is no more secure than a file storing the password in 54 * plaintext. 55 */ 56 57 58/*** Includes. ***/ 59 60#ifndef WIN32 61 62#include <unistd.h> 63 64#include <sys/socket.h> 65#include <sys/un.h> 66 67#include <apr_pools.h> 68#include "svn_auth.h" 69#include "svn_config.h" 70#include "svn_error.h" 71#include "svn_pools.h" 72#include "svn_cmdline.h" 73#include "svn_checksum.h" 74#include "svn_string.h" 75#include "svn_hash.h" 76#include "svn_user.h" 77#include "svn_dirent_uri.h" 78 79#include "auth.h" 80#include "private/svn_auth_private.h" 81 82#include "svn_private_config.h" 83 84#ifdef SVN_HAVE_GPG_AGENT 85 86#define BUFFER_SIZE 1024 87#define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt" 88 89/* Modify STR in-place such that blanks are escaped as required by the 90 * gpg-agent protocol. Return a pointer to STR. */ 91static char * 92escape_blanks(char *str) 93{ 94 char *s = str; 95 96 while (*s) 97 { 98 if (*s == ' ') 99 *s = '+'; 100 s++; 101 } 102 103 return str; 104} 105 106#define is_hex(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F')) 107#define hex_to_int(c) ((c) < '9' ? (c) - '0' : (c) - 'A' + 10) 108 109/* Modify STR in-place. '%', CR and LF are always percent escaped, 110 other characters may be percent escaped, always using uppercase 111 hex, see https://www.gnupg.org/documentation/manuals/assuan.pdf */ 112static char * 113unescape_assuan(char *str) 114{ 115 char *s = str; 116 117 while (s[0]) 118 { 119 if (s[0] == '%' && is_hex(s[1]) && is_hex(s[2])) 120 { 121 char *s2 = s; 122 char val = hex_to_int(s[1]) * 16 + hex_to_int(s[2]); 123 124 s2[0] = val; 125 ++s2; 126 127 while (s2[2]) 128 { 129 s2[0] = s2[2]; 130 ++s2; 131 } 132 s2[0] = '\0'; 133 } 134 ++s; 135 } 136 137 return str; 138} 139 140/* Generate the string CACHE_ID_P based on the REALMSTRING allocated in 141 * RESULT_POOL using SCRATCH_POOL for temporary allocations. This is similar 142 * to other password caching mechanisms. */ 143static svn_error_t * 144get_cache_id(const char **cache_id_p, const char *realmstring, 145 apr_pool_t *result_pool, apr_pool_t *scratch_pool) 146{ 147 const char *cache_id = NULL; 148 svn_checksum_t *digest = NULL; 149 150 SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring, 151 strlen(realmstring), scratch_pool)); 152 cache_id = svn_checksum_to_cstring(digest, result_pool); 153 *cache_id_p = cache_id; 154 155 return SVN_NO_ERROR; 156} 157 158/* Attempt to read a gpg-agent response message from the socket SD into 159 * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response 160 * message could be read that fits into the buffer. Else return FALSE. 161 * If a message could be read it will always be NUL-terminated and the 162 * trailing newline is retained. */ 163static svn_boolean_t 164receive_from_gpg_agent(int sd, char *buf, size_t n) 165{ 166 int i = 0; 167 size_t recvd; 168 char c; 169 170 /* Clear existing buffer content before reading response. */ 171 if (n > 0) 172 *buf = '\0'; 173 174 /* Require the message to fit into the buffer and be terminated 175 * with a newline. */ 176 while (i < n) 177 { 178 recvd = read(sd, &c, 1); 179 if (recvd == -1) 180 return FALSE; 181 buf[i] = c; 182 i++; 183 if (i < n && c == '\n') 184 { 185 buf[i] = '\0'; 186 return TRUE; 187 } 188 } 189 190 return FALSE; 191} 192 193/* Using socket SD, send the option OPTION with the specified VALUE 194 * to the gpg agent. Store the response in BUF, assumed to be N bytes 195 * in size, and evaluate the response. Return TRUE if the agent liked 196 * the smell of the option, if there is such a thing, and doesn't feel 197 * saturated by it. Else return FALSE. 198 * Do temporary allocations in scratch_pool. */ 199static svn_boolean_t 200send_option(int sd, char *buf, size_t n, const char *option, const char *value, 201 apr_pool_t *scratch_pool) 202{ 203 const char *request; 204 205 request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value); 206 207 if (write(sd, request, strlen(request)) == -1) 208 return FALSE; 209 210 if (!receive_from_gpg_agent(sd, buf, n)) 211 return FALSE; 212 213 return (strncmp(buf, "OK", 2) == 0); 214} 215 216/* Send the BYE command and disconnect from the gpg-agent. Doing this avoids 217 * gpg-agent emitting a "Connection reset by peer" log message with some 218 * versions of gpg-agent. */ 219static void 220bye_gpg_agent(int sd) 221{ 222 /* don't bother to check the result of the write, it either worked or it 223 * didn't, but either way we're closing. */ 224 write(sd, "BYE\n", 4); 225 close(sd); 226} 227 228/* Locate a running GPG Agent, and return an open file descriptor 229 * for communication with the agent in *NEW_SD. If no running agent 230 * can be found, set *NEW_SD to -1. */ 231static svn_error_t * 232find_running_gpg_agent(int *new_sd, apr_pool_t *pool) 233{ 234 char *buffer; 235 char *gpg_agent_info = NULL; 236 char *gnupghome = NULL; 237 const char *socket_name = NULL; 238 const char *request = NULL; 239 const char *p = NULL; 240 char *ep = NULL; 241 int sd; 242 243 *new_sd = -1; 244 245 /* This implements the method of finding the socket as described in 246 * the gpg-agent man page under the --use-standard-socket option. 247 * The manage page says the standard socket is "named 'S.gpg-agent' located 248 * in the home directory." GPG's home directory is either the directory 249 * specified by $GNUPGHOME or ~/.gnupg. */ 250 gpg_agent_info = getenv("GPG_AGENT_INFO"); 251 if (gpg_agent_info != NULL) 252 { 253 apr_array_header_t *socket_details; 254 255 /* For reference GPG_AGENT_INFO consists of 3 : separated fields. 256 * The path to the socket, the pid of the gpg-agent process and 257 * finally the version of the protocol the agent talks. */ 258 socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE, 259 pool); 260 socket_name = APR_ARRAY_IDX(socket_details, 0, const char *); 261 } 262 else if ((gnupghome = getenv("GNUPGHOME")) != NULL) 263 { 264 const char *homedir = svn_dirent_canonicalize(gnupghome, pool); 265 socket_name = svn_dirent_join(homedir, "S.gpg-agent", pool); 266 } 267 else 268 { 269 const char *homedir = svn_user_get_homedir(pool); 270 271 if (!homedir) 272 return SVN_NO_ERROR; 273 274 homedir = svn_dirent_canonicalize(homedir, pool); 275 socket_name = svn_dirent_join_many(pool, homedir, ".gnupg", 276 "S.gpg-agent", SVN_VA_NULL); 277 } 278 279 if (socket_name != NULL) 280 { 281 struct sockaddr_un addr; 282 283 addr.sun_family = AF_UNIX; 284 strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1); 285 addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; 286 287 sd = socket(AF_UNIX, SOCK_STREAM, 0); 288 if (sd == -1) 289 return SVN_NO_ERROR; 290 291 if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) 292 { 293 close(sd); 294 return SVN_NO_ERROR; 295 } 296 } 297 else 298 return SVN_NO_ERROR; 299 300 /* Receive the connection status from the gpg-agent daemon. */ 301 buffer = apr_palloc(pool, BUFFER_SIZE); 302 if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 303 { 304 bye_gpg_agent(sd); 305 return SVN_NO_ERROR; 306 } 307 308 if (strncmp(buffer, "OK", 2) != 0) 309 { 310 bye_gpg_agent(sd); 311 return SVN_NO_ERROR; 312 } 313 314 /* The GPG-Agent documentation says: 315 * "Clients should deny to access an agent with a socket name which does 316 * not match its own configuration". */ 317 request = "GETINFO socket_name\n"; 318 if (write(sd, request, strlen(request)) == -1) 319 { 320 bye_gpg_agent(sd); 321 return SVN_NO_ERROR; 322 } 323 if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 324 { 325 bye_gpg_agent(sd); 326 return SVN_NO_ERROR; 327 } 328 if (strncmp(buffer, "D", 1) == 0) 329 p = &buffer[2]; 330 if (!p) 331 { 332 bye_gpg_agent(sd); 333 return SVN_NO_ERROR; 334 } 335 ep = strchr(p, '\n'); 336 if (ep != NULL) 337 *ep = '\0'; 338 if (strcmp(socket_name, p) != 0) 339 { 340 bye_gpg_agent(sd); 341 return SVN_NO_ERROR; 342 } 343 /* The agent will terminate its response with "OK". */ 344 if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 345 { 346 bye_gpg_agent(sd); 347 return SVN_NO_ERROR; 348 } 349 if (strncmp(buffer, "OK", 2) != 0) 350 { 351 bye_gpg_agent(sd); 352 return SVN_NO_ERROR; 353 } 354 355 *new_sd = sd; 356 return SVN_NO_ERROR; 357} 358 359static svn_boolean_t 360send_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool) 361{ 362 const char *tty_name; 363 const char *tty_type; 364 const char *lc_ctype; 365 const char *display; 366 367 /* Send TTY_NAME to the gpg-agent daemon. */ 368 tty_name = getenv("GPG_TTY"); 369 if (tty_name != NULL) 370 { 371 if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool)) 372 return FALSE; 373 } 374 375 /* Send TTY_TYPE to the gpg-agent daemon. */ 376 tty_type = getenv("TERM"); 377 if (tty_type != NULL) 378 { 379 if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool)) 380 return FALSE; 381 } 382 383 /* Compute LC_CTYPE. */ 384 lc_ctype = getenv("LC_ALL"); 385 if (lc_ctype == NULL) 386 lc_ctype = getenv("LC_CTYPE"); 387 if (lc_ctype == NULL) 388 lc_ctype = getenv("LANG"); 389 390 /* Send LC_CTYPE to the gpg-agent daemon. */ 391 if (lc_ctype != NULL) 392 { 393 if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool)) 394 return FALSE; 395 } 396 397 /* Send DISPLAY to the gpg-agent daemon. */ 398 display = getenv("DISPLAY"); 399 if (display != NULL) 400 { 401 if (!send_option(sd, buf, n, "display", display, scratch_pool)) 402 return FALSE; 403 } 404 405 return TRUE; 406} 407 408/* Implementation of svn_auth__password_get_t that retrieves the password 409 from gpg-agent */ 410static svn_error_t * 411password_get_gpg_agent(svn_boolean_t *done, 412 const char **password, 413 apr_hash_t *creds, 414 const char *realmstring, 415 const char *username, 416 apr_hash_t *parameters, 417 svn_boolean_t non_interactive, 418 apr_pool_t *pool) 419{ 420 int sd; 421 char *p = NULL; 422 char *ep = NULL; 423 char *buffer; 424 const char *request = NULL; 425 const char *cache_id = NULL; 426 char *password_prompt; 427 char *realm_prompt; 428 char *error_prompt; 429 int *attempt; 430 431 *done = FALSE; 432 433 attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER); 434 435 SVN_ERR(find_running_gpg_agent(&sd, pool)); 436 if (sd == -1) 437 return SVN_NO_ERROR; 438 439 buffer = apr_palloc(pool, BUFFER_SIZE); 440 441 if (!send_options(sd, buffer, BUFFER_SIZE, pool)) 442 { 443 bye_gpg_agent(sd); 444 return SVN_NO_ERROR; 445 } 446 447 SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool)); 448 449 password_prompt = apr_psprintf(pool, _("Password for '%s': "), username); 450 realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"), 451 realmstring); 452 if (*attempt == 1) 453 /* X means no error to the gpg-agent protocol */ 454 error_prompt = apr_pstrdup(pool, "X"); 455 else 456 error_prompt = apr_pstrdup(pool, _("Authentication failed")); 457 458 request = apr_psprintf(pool, 459 "GET_PASSPHRASE --data %s" 460 "%s %s %s %s\n", 461 non_interactive ? "--no-ask " : "", 462 cache_id, 463 escape_blanks(error_prompt), 464 escape_blanks(password_prompt), 465 escape_blanks(realm_prompt)); 466 467 if (write(sd, request, strlen(request)) == -1) 468 { 469 bye_gpg_agent(sd); 470 return SVN_NO_ERROR; 471 } 472 if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 473 { 474 bye_gpg_agent(sd); 475 return SVN_NO_ERROR; 476 } 477 478 bye_gpg_agent(sd); 479 480 if (strncmp(buffer, "ERR", 3) == 0) 481 return SVN_NO_ERROR; 482 483 p = NULL; 484 if (strncmp(buffer, "D", 1) == 0) 485 p = &buffer[2]; 486 487 if (!p) 488 return SVN_NO_ERROR; 489 490 ep = strchr(p, '\n'); 491 if (ep != NULL) 492 *ep = '\0'; 493 494 *password = unescape_assuan(p); 495 496 *done = TRUE; 497 return SVN_NO_ERROR; 498} 499 500 501/* Implementation of svn_auth__password_set_t that would store the 502 password in GPG Agent if that's how this particular integration 503 worked. But it isn't. GPG Agent stores the password provided by 504 the user via the pinentry program immediately upon its provision 505 (and regardless of its accuracy as passwords go), so we just need 506 to check if a running GPG Agent exists. */ 507static svn_error_t * 508password_set_gpg_agent(svn_boolean_t *done, 509 apr_hash_t *creds, 510 const char *realmstring, 511 const char *username, 512 const char *password, 513 apr_hash_t *parameters, 514 svn_boolean_t non_interactive, 515 apr_pool_t *pool) 516{ 517 int sd; 518 519 *done = FALSE; 520 521 SVN_ERR(find_running_gpg_agent(&sd, pool)); 522 if (sd == -1) 523 return SVN_NO_ERROR; 524 525 bye_gpg_agent(sd); 526 *done = TRUE; 527 528 return SVN_NO_ERROR; 529} 530 531 532/* An implementation of svn_auth_provider_t::first_credentials() */ 533static svn_error_t * 534simple_gpg_agent_first_creds(void **credentials, 535 void **iter_baton, 536 void *provider_baton, 537 apr_hash_t *parameters, 538 const char *realmstring, 539 apr_pool_t *pool) 540{ 541 svn_error_t *err; 542 int *attempt = apr_palloc(pool, sizeof(*attempt)); 543 544 *attempt = 1; 545 svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt); 546 err = svn_auth__simple_creds_cache_get(credentials, iter_baton, 547 provider_baton, parameters, 548 realmstring, password_get_gpg_agent, 549 SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 550 pool); 551 *iter_baton = attempt; 552 553 return err; 554} 555 556/* An implementation of svn_auth_provider_t::next_credentials() */ 557static svn_error_t * 558simple_gpg_agent_next_creds(void **credentials, 559 void *iter_baton, 560 void *provider_baton, 561 apr_hash_t *parameters, 562 const char *realmstring, 563 apr_pool_t *pool) 564{ 565 int *attempt = (int *)iter_baton; 566 int sd; 567 char *buffer; 568 const char *cache_id = NULL; 569 const char *request = NULL; 570 571 *credentials = NULL; 572 573 /* The users previous credentials failed so first remove the cached entry, 574 * before trying to retrieve them again. Because gpg-agent stores cached 575 * credentials immediately upon retrieving them, this gives us the 576 * opportunity to remove the invalid credentials and prompt the 577 * user again. While it's possible that server side issues could trigger 578 * this, this cache is ephemeral so at worst we're just speeding up 579 * when the user would need to re-enter their password. */ 580 581 if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE)) 582 { 583 /* In this case since we're running non-interactively we do not 584 * want to clear the cache since the user was never prompted by 585 * gpg-agent to set a password. */ 586 return SVN_NO_ERROR; 587 } 588 589 *attempt = *attempt + 1; 590 591 SVN_ERR(find_running_gpg_agent(&sd, pool)); 592 if (sd == -1) 593 return SVN_NO_ERROR; 594 595 buffer = apr_palloc(pool, BUFFER_SIZE); 596 597 if (!send_options(sd, buffer, BUFFER_SIZE, pool)) 598 { 599 bye_gpg_agent(sd); 600 return SVN_NO_ERROR; 601 } 602 603 SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool)); 604 605 request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id); 606 607 if (write(sd, request, strlen(request)) == -1) 608 { 609 bye_gpg_agent(sd); 610 return SVN_NO_ERROR; 611 } 612 613 if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 614 { 615 bye_gpg_agent(sd); 616 return SVN_NO_ERROR; 617 } 618 619 bye_gpg_agent(sd); 620 621 if (strncmp(buffer, "OK\n", 3) != 0) 622 return SVN_NO_ERROR; 623 624 /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries) 625 * which matches svn command line client's retry_limit as set in 626 * svn_cmdline_create_auth_baton(). It would be nice to have that 627 * limit reflected here but that violates the boundry between the 628 * prompt provider and the cache provider. gpg-agent is acting as 629 * both here due to the peculiarties of their design so we'll have to 630 * live with this for now. Note that when these failures get exceeded 631 * it'll eventually fall back on the retry limits of whatever prompt 632 * provider is in effect, so this effectively doubles the limit. */ 633 if (*attempt < 4) 634 return svn_auth__simple_creds_cache_get(credentials, &iter_baton, 635 provider_baton, parameters, 636 realmstring, 637 password_get_gpg_agent, 638 SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 639 pool); 640 641 return SVN_NO_ERROR; 642} 643 644 645/* An implementation of svn_auth_provider_t::save_credentials() */ 646static svn_error_t * 647simple_gpg_agent_save_creds(svn_boolean_t *saved, 648 void *credentials, 649 void *provider_baton, 650 apr_hash_t *parameters, 651 const char *realmstring, 652 apr_pool_t *pool) 653{ 654 return svn_auth__simple_creds_cache_set(saved, credentials, 655 provider_baton, parameters, 656 realmstring, password_set_gpg_agent, 657 SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 658 pool); 659} 660 661 662static const svn_auth_provider_t gpg_agent_simple_provider = { 663 SVN_AUTH_CRED_SIMPLE, 664 simple_gpg_agent_first_creds, 665 simple_gpg_agent_next_creds, 666 simple_gpg_agent_save_creds 667}; 668 669 670/* Public API */ 671void 672svn_auth__get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider, 673 apr_pool_t *pool) 674{ 675 svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); 676 677 po->vtable = &gpg_agent_simple_provider; 678 *provider = po; 679} 680 681#endif /* SVN_HAVE_GPG_AGENT */ 682#endif /* !WIN32 */ 683