mirror of
git://git.gnupg.org/gnupg.git
synced 2024-11-13 22:08:52 +01:00
2639033890
error state as send by current gpg-agents.
1252 lines
32 KiB
C
1252 lines
32 KiB
C
/* passphrase.c - Get a passphrase
|
|
* Copyright (C) 1998,1999,2000,2001,2002,2003 Free Software Foundation, Inc.
|
|
*
|
|
* This file is part of GnuPG.
|
|
*
|
|
* GnuPG is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* GnuPG is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
#if !defined(HAVE_DOSISH_SYSTEM) && !defined(__riscos__)
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#endif
|
|
#if defined (_WIN32) || defined (__CYGWIN32__)
|
|
# include <windows.h>
|
|
#endif
|
|
#include <errno.h>
|
|
#ifdef HAVE_LOCALE_H
|
|
#include <locale.h>
|
|
#endif
|
|
#ifdef HAVE_LANGINFO_CODESET
|
|
#include <langinfo.h>
|
|
#endif
|
|
|
|
#include "util.h"
|
|
#include "memory.h"
|
|
#include "options.h"
|
|
#include "ttyio.h"
|
|
#include "cipher.h"
|
|
#include "keydb.h"
|
|
#include "main.h"
|
|
#include "i18n.h"
|
|
#include "status.h"
|
|
|
|
|
|
enum gpga_protocol_codes {
|
|
/* Request codes */
|
|
GPGA_PROT_GET_VERSION = 1,
|
|
GPGA_PROT_GET_PASSPHRASE = 2,
|
|
GPGA_PROT_CLEAR_PASSPHRASE= 3,
|
|
GPGA_PROT_SHUTDOWN = 4,
|
|
GPGA_PROT_FLUSH = 5,
|
|
|
|
/* Reply codes */
|
|
GPGA_PROT_REPLY_BASE = 0x10000,
|
|
GPGA_PROT_OKAY = 0x10001,
|
|
GPGA_PROT_GOT_PASSPHRASE = 0x10002,
|
|
|
|
/* Error codes */
|
|
GPGA_PROT_ERROR_BASE = 0x20000,
|
|
GPGA_PROT_PROTOCOL_ERROR = 0x20001,
|
|
GPGA_PROT_INVALID_REQUEST= 0x20002,
|
|
GPGA_PROT_CANCELED = 0x20003,
|
|
GPGA_PROT_NO_PASSPHRASE = 0x20004,
|
|
GPGA_PROT_BAD_PASSPHRASE = 0x20005,
|
|
GPGA_PROT_INVALID_DATA = 0x20006,
|
|
GPGA_PROT_NOT_IMPLEMENTED= 0x20007,
|
|
GPGA_PROT_UI_PROBLEM = 0x20008
|
|
};
|
|
|
|
|
|
#define buftou32( p ) ((*(byte*)(p) << 24) | (*((byte*)(p)+1)<< 16) | \
|
|
(*((byte*)(p)+2) << 8) | (*((byte*)(p)+3)))
|
|
#define u32tobuf( p, a ) do { \
|
|
((byte*)p)[0] = (byte)((a) >> 24); \
|
|
((byte*)p)[1] = (byte)((a) >> 16); \
|
|
((byte*)p)[2] = (byte)((a) >> 8); \
|
|
((byte*)p)[3] = (byte)((a) ); \
|
|
} while(0)
|
|
|
|
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
|
|
#define hexdigitp(a) (digitp (a) \
|
|
|| (*(a) >= 'A' && *(a) <= 'F') \
|
|
|| (*(a) >= 'a' && *(a) <= 'f'))
|
|
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
|
|
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
|
|
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
|
|
|
|
|
|
|
|
static char *fd_passwd = NULL;
|
|
static char *next_pw = NULL;
|
|
static char *last_pw = NULL;
|
|
|
|
#if defined (_WIN32)
|
|
static int read_fd = 0;
|
|
static int write_fd = 0;
|
|
#endif
|
|
|
|
static void hash_passphrase( DEK *dek, char *pw, STRING2KEY *s2k, int create );
|
|
|
|
int
|
|
have_static_passphrase()
|
|
{
|
|
if ( opt.use_agent )
|
|
return 0;
|
|
return !!fd_passwd;
|
|
}
|
|
|
|
/****************
|
|
* Set the passphrase to be used for the next query and only for the next
|
|
* one.
|
|
*/
|
|
void
|
|
set_next_passphrase( const char *s )
|
|
{
|
|
m_free(next_pw);
|
|
next_pw = NULL;
|
|
if( s ) {
|
|
next_pw = m_alloc_secure( strlen(s)+1 );
|
|
strcpy(next_pw, s );
|
|
}
|
|
}
|
|
|
|
/****************
|
|
* Get the last passphrase used in passphrase_to_dek.
|
|
* Note: This removes the passphrase from this modules and
|
|
* the caller must free the result. May return NULL:
|
|
*/
|
|
char *
|
|
get_last_passphrase()
|
|
{
|
|
char *p = last_pw;
|
|
last_pw = NULL;
|
|
return p;
|
|
}
|
|
|
|
|
|
void
|
|
read_passphrase_from_fd( int fd )
|
|
{
|
|
int i, len;
|
|
char *pw;
|
|
|
|
if (opt.use_agent)
|
|
{ /* Not used but we have to do a dummy read, so that it won't end
|
|
up at the begin of the message if the quite usual trick to
|
|
prepend the passphtrase to the message is used. */
|
|
char buf[1];
|
|
|
|
while (!(read (fd, buf, 1) != 1 || *buf == '\n' ))
|
|
;
|
|
*buf = 0;
|
|
return;
|
|
}
|
|
|
|
if( !opt.batch )
|
|
tty_printf("Reading passphrase from file descriptor %d ...", fd );
|
|
for (pw = NULL, i = len = 100; ; i++ )
|
|
{
|
|
if( i >= len-1 ) {
|
|
char *pw2 = pw;
|
|
len += 100;
|
|
pw = m_alloc_secure( len );
|
|
if( pw2 )
|
|
memcpy(pw, pw2, i );
|
|
else
|
|
i=0;
|
|
}
|
|
if (read( fd, pw+i, 1) != 1 || pw[i] == '\n' )
|
|
break;
|
|
}
|
|
pw[i] = 0;
|
|
if (!opt.batch)
|
|
tty_printf ("\b\b\b \n" );
|
|
|
|
m_free( fd_passwd );
|
|
fd_passwd = pw;
|
|
}
|
|
|
|
static int
|
|
writen ( int fd, const void *buf, size_t nbytes )
|
|
{
|
|
#if defined (_WIN32)
|
|
DWORD nwritten, nleft = nbytes;
|
|
|
|
while (nleft > 0) {
|
|
if ( !WriteFile( (HANDLE)write_fd, buf, nleft, &nwritten, NULL) ) {
|
|
log_error("write failed: ec=%d\n", (int)GetLastError());
|
|
return -1;
|
|
}
|
|
/*log_info("** WriteFile fd=%d nytes=%d nwritten=%d\n",
|
|
write_fd, nbytes, (int)nwritten);*/
|
|
Sleep(100);
|
|
|
|
nleft -= nwritten;
|
|
buf = (const BYTE *)buf + nwritten;
|
|
}
|
|
#elif defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
|
|
/* not implemented */
|
|
#else
|
|
size_t nleft = nbytes;
|
|
int nwritten;
|
|
|
|
while( nleft > 0 ) {
|
|
nwritten = write( fd, buf, nleft );
|
|
if( nwritten < 0 ) {
|
|
if ( errno == EINTR )
|
|
nwritten = 0;
|
|
else {
|
|
log_error ( "write() failed: %s\n", strerror (errno) );
|
|
return -1;
|
|
}
|
|
}
|
|
nleft -= nwritten;
|
|
buf = (const char*)buf + nwritten;
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
readn ( int fd, void *buf, size_t buflen, size_t *ret_nread )
|
|
{
|
|
#if defined (_WIN32)
|
|
DWORD nread, nleft = buflen;
|
|
|
|
while (nleft > 0) {
|
|
if ( !ReadFile( (HANDLE)read_fd, buf, nleft, &nread, NULL) ) {
|
|
log_error("read() error: ec=%d\n", (int)GetLastError());
|
|
return -1;
|
|
}
|
|
if (!nread || GetLastError() == ERROR_BROKEN_PIPE)
|
|
break;
|
|
/*log_info("** ReadFile fd=%d buflen=%d nread=%d\n",
|
|
read_fd, buflen, (int)nread);*/
|
|
Sleep(100);
|
|
|
|
nleft -= nread;
|
|
buf = (BYTE *)buf + nread;
|
|
}
|
|
if (ret_nread)
|
|
*ret_nread = buflen - nleft;
|
|
|
|
#elif defined(HAVE_DOSISH_SYSTEM) || defined(__riscos__)
|
|
/* not implemented */
|
|
#else
|
|
size_t nleft = buflen;
|
|
int nread;
|
|
char *p;
|
|
|
|
p = buf;
|
|
while( nleft > 0 ) {
|
|
nread = read ( fd, buf, nleft );
|
|
if( nread < 0 ) {
|
|
if (nread == EINTR)
|
|
nread = 0;
|
|
else {
|
|
log_error ( "read() error: %s\n", strerror (errno) );
|
|
return -1;
|
|
}
|
|
}
|
|
else if( !nread )
|
|
break; /* EOF */
|
|
nleft -= nread;
|
|
buf = (char*)buf + nread;
|
|
}
|
|
if( ret_nread )
|
|
*ret_nread = buflen - nleft;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* read an entire line */
|
|
static int
|
|
readline (int fd, char *buf, size_t buflen)
|
|
{
|
|
size_t nleft = buflen;
|
|
char *p;
|
|
int nread = 0;
|
|
|
|
while (nleft > 0)
|
|
{
|
|
int n = read (fd, buf, nleft);
|
|
if (n < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
return -1; /* read error */
|
|
}
|
|
else if (!n)
|
|
{
|
|
return -1; /* incomplete line */
|
|
}
|
|
p = buf;
|
|
nleft -= n;
|
|
buf += n;
|
|
nread += n;
|
|
|
|
for (; n && *p != '\n'; n--, p++)
|
|
;
|
|
if (n)
|
|
{
|
|
break; /* at least one full line available - that's enough.
|
|
This function is just a temporary hack until we use
|
|
the assuna lib in gpg. So it is okay to forget
|
|
about pending bytes */
|
|
}
|
|
}
|
|
|
|
return nread;
|
|
}
|
|
|
|
|
|
|
|
#if !defined (__riscos__)
|
|
|
|
#if !defined (_WIN32)
|
|
/* For the new Assuan protocol we may have to send options */
|
|
static int
|
|
agent_send_option (int fd, const char *name, const char *value)
|
|
{
|
|
char buf[200];
|
|
int nread;
|
|
char *line;
|
|
int i;
|
|
|
|
line = m_alloc (7 + strlen (name) + 1 + strlen (value) + 2);
|
|
strcpy (stpcpy (stpcpy (stpcpy (
|
|
stpcpy (line, "OPTION "), name), "="), value), "\n");
|
|
i = writen (fd, line, strlen (line));
|
|
m_free (line);
|
|
if (i)
|
|
return -1;
|
|
|
|
/* get response */
|
|
nread = readline (fd, buf, DIM(buf)-1);
|
|
if (nread < 3)
|
|
return -1;
|
|
|
|
if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n'))
|
|
return 0; /* okay */
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
agent_send_all_options (int fd)
|
|
{
|
|
char *dft_display = NULL;
|
|
const char *dft_ttyname = NULL;
|
|
char *dft_ttytype = NULL;
|
|
char *old_lc = NULL;
|
|
char *dft_lc = NULL;
|
|
int rc = 0;
|
|
|
|
dft_display = getenv ("DISPLAY");
|
|
if (opt.display || dft_display)
|
|
{
|
|
if (agent_send_option (fd, "display",
|
|
opt.display ? opt.display : dft_display))
|
|
return -1;
|
|
}
|
|
|
|
if (!opt.ttyname)
|
|
{
|
|
dft_ttyname = getenv ("GPG_TTY");
|
|
if ((!dft_ttyname || !*dft_ttyname) && tty_get_ttyname ())
|
|
dft_ttyname = tty_get_ttyname ();
|
|
}
|
|
if (opt.ttyname || dft_ttyname)
|
|
{
|
|
if (agent_send_option (fd, "ttyname",
|
|
opt.ttyname ? opt.ttyname : dft_ttyname))
|
|
return -1;
|
|
}
|
|
|
|
dft_ttytype = getenv ("TERM");
|
|
if (opt.ttytype || (dft_ttyname && dft_ttytype))
|
|
{
|
|
if (agent_send_option (fd, "ttytype",
|
|
opt.ttyname ? opt.ttytype : dft_ttytype))
|
|
return -1;
|
|
}
|
|
|
|
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
|
|
old_lc = setlocale (LC_CTYPE, NULL);
|
|
if (old_lc)
|
|
old_lc = m_strdup (old_lc);
|
|
dft_lc = setlocale (LC_CTYPE, "");
|
|
#endif
|
|
if (opt.lc_ctype || (dft_ttyname && dft_lc))
|
|
{
|
|
rc = agent_send_option (fd, "lc-ctype",
|
|
opt.lc_ctype ? opt.lc_ctype : dft_lc);
|
|
}
|
|
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
|
|
if (old_lc)
|
|
{
|
|
setlocale (LC_CTYPE, old_lc);
|
|
m_free (old_lc);
|
|
}
|
|
#endif
|
|
if (rc)
|
|
return rc;
|
|
|
|
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
|
|
old_lc = setlocale (LC_MESSAGES, NULL);
|
|
if (old_lc)
|
|
old_lc = m_strdup (old_lc);
|
|
dft_lc = setlocale (LC_MESSAGES, "");
|
|
#endif
|
|
if (opt.lc_messages || (dft_ttyname && dft_lc))
|
|
{
|
|
rc = agent_send_option (fd, "lc-messages",
|
|
opt.lc_messages ? opt.lc_messages : dft_lc);
|
|
}
|
|
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
|
|
if (old_lc)
|
|
{
|
|
setlocale (LC_MESSAGES, old_lc);
|
|
m_free (old_lc);
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
#endif /*!_WIN32*/
|
|
|
|
|
|
/*
|
|
* Open a connection to the agent and send the magic string
|
|
* Returns: -1 on error or an filedescriptor for urther processing
|
|
*/
|
|
|
|
static int
|
|
agent_open (int *ret_prot)
|
|
{
|
|
#if defined (_WIN32)
|
|
int fd;
|
|
char *infostr, *p;
|
|
HANDLE h;
|
|
char pidstr[128];
|
|
|
|
*ret_prot = 0;
|
|
if ( !(infostr = read_w32_registry_string(NULL, "Software\\GNU\\GnuPG",
|
|
"agentPID"))
|
|
|| *infostr == '0') {
|
|
log_error( _("gpg-agent is not available in this session\n"));
|
|
return -1;
|
|
}
|
|
free(infostr);
|
|
|
|
sprintf(pidstr, "%u", (unsigned int)GetCurrentProcessId());
|
|
if (write_w32_registry_string(NULL, "Software\\GNU\\GnuPG",
|
|
"agentCID", pidstr)) {
|
|
log_error( _("can't set client pid for the agent\n") );
|
|
return -1;
|
|
}
|
|
h = OpenEvent(EVENT_ALL_ACCESS, FALSE, "gpg_agent");
|
|
SetEvent(h);
|
|
Sleep(50); /* some time for the server */
|
|
if ( !(p = read_w32_registry_string(NULL, "Software\\GNU\\GnuPG",
|
|
"agentReadFD")) ) {
|
|
log_error( _("can't get server read FD for the agent\n") );
|
|
return -1;
|
|
}
|
|
read_fd = atol(p);
|
|
free(p);
|
|
if ( !(p = read_w32_registry_string(NULL, "Software\\GNU\\GnuPG",
|
|
"agentWriteFD")) ) {
|
|
log_error ( _("can't get server write FD for the agent\n") );
|
|
return -1;
|
|
}
|
|
write_fd = atol(p);
|
|
free(p);
|
|
fd = 0;
|
|
|
|
if ( writen ( fd, "GPGA\0\0\0\x01", 8 ) ) {
|
|
fd = -1;
|
|
}
|
|
#else /* Posix */
|
|
|
|
int fd;
|
|
char *infostr, *p;
|
|
struct sockaddr_un client_addr;
|
|
size_t len;
|
|
int prot;
|
|
|
|
if (opt.gpg_agent_info)
|
|
infostr = m_strdup (opt.gpg_agent_info);
|
|
else
|
|
{
|
|
infostr = getenv ( "GPG_AGENT_INFO" );
|
|
if ( !infostr ) {
|
|
log_error (_("gpg-agent is not available in this session\n"));
|
|
opt.use_agent = 0;
|
|
return -1;
|
|
}
|
|
infostr = m_strdup ( infostr );
|
|
}
|
|
|
|
if ( !(p = strchr ( infostr, ':')) || p == infostr
|
|
|| (p-infostr)+1 >= sizeof client_addr.sun_path ) {
|
|
log_error( _("malformed GPG_AGENT_INFO environment variable\n"));
|
|
m_free (infostr );
|
|
opt.use_agent = 0;
|
|
return -1;
|
|
}
|
|
*p++ = 0;
|
|
/* See whether this is the new gpg-agent using the Assuna protocl.
|
|
This agent identifies itself by have an info string with a
|
|
version number in the 3rd field. */
|
|
while (*p && *p != ':')
|
|
p++;
|
|
prot = *p? atoi (p+1) : 0;
|
|
if ( prot < 0 || prot > 1) {
|
|
log_error (_("gpg-agent protocol version %d is not supported\n"),prot);
|
|
m_free (infostr );
|
|
opt.use_agent = 0;
|
|
return -1;
|
|
}
|
|
*ret_prot = prot;
|
|
|
|
if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ) {
|
|
log_error ("can't create socket: %s\n", strerror(errno) );
|
|
m_free (infostr );
|
|
opt.use_agent = 0;
|
|
return -1;
|
|
}
|
|
|
|
memset( &client_addr, 0, sizeof client_addr );
|
|
client_addr.sun_family = AF_UNIX;
|
|
strcpy( client_addr.sun_path, infostr );
|
|
len = offsetof (struct sockaddr_un, sun_path)
|
|
+ strlen(client_addr.sun_path) + 1;
|
|
|
|
if( connect( fd, (struct sockaddr*)&client_addr, len ) == -1 ) {
|
|
log_error ( _("can't connect to `%s': %s\n"),
|
|
infostr, strerror (errno) );
|
|
m_free (infostr );
|
|
close (fd );
|
|
opt.use_agent = 0;
|
|
return -1;
|
|
}
|
|
m_free (infostr);
|
|
|
|
if (!prot) {
|
|
if ( writen ( fd, "GPGA\0\0\0\x01", 8 ) ) {
|
|
close (fd);
|
|
fd = -1;
|
|
}
|
|
}
|
|
else { /* assuan based gpg-agent */
|
|
char line[200];
|
|
int nread;
|
|
|
|
nread = readline (fd, line, DIM(line));
|
|
if (nread < 3 || !(line[0] == 'O' && line[1] == 'K'
|
|
&& (line[2] == '\n' || line[2] == ' ')) ) {
|
|
log_error ( _("communication problem with gpg-agent\n"));
|
|
close (fd );
|
|
opt.use_agent = 0;
|
|
return -1;
|
|
}
|
|
|
|
if (agent_send_all_options (fd)) {
|
|
log_error (_("problem with the agent - disabling agent use\n"));
|
|
close (fd);
|
|
opt.use_agent = 0;
|
|
return -1;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
|
|
return fd;
|
|
}
|
|
|
|
|
|
static void
|
|
agent_close ( int fd )
|
|
{
|
|
#if defined (_WIN32)
|
|
HANDLE h = OpenEvent(EVENT_ALL_ACCESS, FALSE, "gpg_agent");
|
|
ResetEvent(h);
|
|
#else
|
|
close (fd);
|
|
#endif
|
|
}
|
|
#endif /* !__riscos__ */
|
|
|
|
|
|
|
|
/*
|
|
* Ask the GPG Agent for the passphrase.
|
|
* Mode 0: Allow cached passphrase
|
|
* 1: No cached passphrase FIXME: Not really implemented
|
|
* 2: Ditto, but change the text to "repeat entry"
|
|
*
|
|
* Note that TRYAGAIN_TEXT must not be translated. If canceled is not
|
|
* NULL, the function does set it to 1 if the user canceled the
|
|
* operation.
|
|
*/
|
|
static char *
|
|
agent_get_passphrase ( u32 *keyid, int mode, const char *tryagain_text,
|
|
int *canceled )
|
|
{
|
|
#if defined(__riscos__)
|
|
return NULL;
|
|
#else
|
|
size_t n;
|
|
char *atext = NULL;
|
|
char buf[50];
|
|
int fd = -1;
|
|
int nread;
|
|
u32 reply;
|
|
char *pw = NULL;
|
|
PKT_public_key *pk = m_alloc_clear( sizeof *pk );
|
|
byte fpr[MAX_FINGERPRINT_LEN];
|
|
int have_fpr = 0;
|
|
int prot;
|
|
char *orig_codeset = NULL;
|
|
|
|
if (canceled)
|
|
*canceled = 0;
|
|
|
|
#if MAX_FINGERPRINT_LEN < 20
|
|
#error agent needs a 20 byte fingerprint
|
|
#endif
|
|
|
|
memset (fpr, 0, MAX_FINGERPRINT_LEN );
|
|
if( keyid && get_pubkey( pk, keyid ) )
|
|
{
|
|
free_public_key( pk );
|
|
pk = NULL; /* oops: no key for some reason */
|
|
}
|
|
|
|
|
|
#ifdef ENABLE_NLS
|
|
/* The Assuan agent protol requires us to trasnmit utf-8 strings */
|
|
orig_codeset = bind_textdomain_codeset (PACKAGE, NULL);
|
|
#ifdef HAVE_LANGINFO_CODESET
|
|
if (!orig_codeset)
|
|
orig_codeset = nl_langinfo (CODESET);
|
|
#endif
|
|
if (orig_codeset)
|
|
{ /* We only switch when we are able to restore the codeset later. */
|
|
orig_codeset = m_strdup (orig_codeset);
|
|
if (!bind_textdomain_codeset (PACKAGE, "utf-8"))
|
|
orig_codeset = NULL;
|
|
}
|
|
#endif
|
|
|
|
if ( (fd = agent_open (&prot)) == -1 )
|
|
goto failure;
|
|
|
|
if ( !mode && pk && keyid )
|
|
{
|
|
char *uid;
|
|
size_t uidlen;
|
|
const char *algo_name = pubkey_algo_to_string ( pk->pubkey_algo );
|
|
const char *timestr;
|
|
char *maink;
|
|
const char *fmtstr;
|
|
|
|
if ( !algo_name )
|
|
algo_name = "?";
|
|
|
|
fmtstr = _(" (main key ID %08lX)");
|
|
maink = m_alloc ( strlen (fmtstr) + 20 );
|
|
if( keyid[2] && keyid[3] && keyid[0] != keyid[2]
|
|
&& keyid[1] != keyid[3] )
|
|
sprintf( maink, fmtstr, (ulong)keyid[3] );
|
|
else
|
|
*maink = 0;
|
|
|
|
uid = get_user_id ( keyid, &uidlen );
|
|
timestr = strtimestamp (pk->timestamp);
|
|
fmtstr = _("You need a passphrase to unlock the"
|
|
" secret key for user:\n"
|
|
"\"%.*s\"\n"
|
|
"%u-bit %s key, ID %08lX, created %s%s\n" );
|
|
atext = m_alloc ( 100 + strlen (fmtstr)
|
|
+ uidlen + 15 + strlen(algo_name) + 8
|
|
+ strlen (timestr) + strlen (maink) );
|
|
sprintf (atext, fmtstr,
|
|
uidlen, uid,
|
|
nbits_from_pk (pk), algo_name, (ulong)keyid[1], timestr,
|
|
maink );
|
|
m_free (uid);
|
|
m_free (maink);
|
|
|
|
{
|
|
size_t dummy;
|
|
fingerprint_from_pk( pk, fpr, &dummy );
|
|
have_fpr = 1;
|
|
}
|
|
|
|
}
|
|
else if (mode == 2 )
|
|
atext = m_strdup ( _("Repeat passphrase\n") );
|
|
else
|
|
atext = m_strdup ( _("Enter passphrase\n") );
|
|
|
|
if (!prot)
|
|
{ /* old style protocol */
|
|
n = 4 + 20 + strlen (atext);
|
|
u32tobuf (buf, n );
|
|
u32tobuf (buf+4, GPGA_PROT_GET_PASSPHRASE );
|
|
memcpy (buf+8, fpr, 20 );
|
|
if ( writen ( fd, buf, 28 ) || writen ( fd, atext, strlen (atext) ) )
|
|
goto failure;
|
|
m_free (atext); atext = NULL;
|
|
|
|
/* get response */
|
|
if ( readn ( fd, buf, 12, &nread ) )
|
|
goto failure;
|
|
|
|
if ( nread < 8 )
|
|
{
|
|
log_error ( "response from agent too short\n" );
|
|
goto failure;
|
|
}
|
|
n = buftou32 ( buf );
|
|
reply = buftou32 ( buf + 4 );
|
|
if ( reply == GPGA_PROT_GOT_PASSPHRASE )
|
|
{
|
|
size_t pwlen;
|
|
size_t nn;
|
|
|
|
if ( nread < 12 || n < 8 )
|
|
{
|
|
log_error ( "response from agent too short\n" );
|
|
goto failure;
|
|
}
|
|
pwlen = buftou32 ( buf + 8 );
|
|
nread -= 12;
|
|
n -= 8;
|
|
if ( pwlen > n || n > 1000 )
|
|
{
|
|
log_error (_("passphrase too long\n"));
|
|
/* or protocol error */
|
|
goto failure;
|
|
}
|
|
/* we read the whole block in one chunk to give no hints
|
|
* on how long the passhrase actually is - this wastes some bytes
|
|
* but because we already have this padding we should not loosen
|
|
* this by issuing 2 read calls */
|
|
pw = m_alloc_secure ( n+1 );
|
|
if ( readn ( fd, pw, n, &nn ) )
|
|
goto failure;
|
|
if ( n != nn )
|
|
{
|
|
log_error (_("invalid response from agent\n"));
|
|
goto failure;
|
|
}
|
|
pw[pwlen] = 0; /* make a C String */
|
|
agent_close (fd);
|
|
free_public_key( pk );
|
|
#ifdef ENABLE_NLS
|
|
if (orig_codeset)
|
|
bind_textdomain_codeset (PACKAGE, orig_codeset);
|
|
#endif
|
|
m_free (orig_codeset);
|
|
return pw;
|
|
}
|
|
else if ( reply == GPGA_PROT_CANCELED )
|
|
{
|
|
log_info ( _("cancelled by user\n") );
|
|
if (canceled)
|
|
*canceled = 1;
|
|
}
|
|
else
|
|
log_error ( _("problem with the agent: agent returns 0x%lx\n"),
|
|
(ulong)reply );
|
|
}
|
|
else
|
|
{ /* The new Assuan protocol */
|
|
char *line, *p;
|
|
const unsigned char *s;
|
|
int i;
|
|
|
|
if (!tryagain_text)
|
|
tryagain_text = "X";
|
|
else
|
|
tryagain_text = _(tryagain_text);
|
|
|
|
/* We allocate 2 time the needed space for atext so that there
|
|
is enough space for escaping */
|
|
line = m_alloc (15 + 46
|
|
+ 3*strlen (tryagain_text) + 3*strlen (atext) + 2);
|
|
strcpy (line, "GET_PASSPHRASE ");
|
|
p = line+15;
|
|
if (!mode && have_fpr)
|
|
{
|
|
for (i=0; i < 20; i++, p +=2 )
|
|
sprintf (p, "%02X", fpr[i]);
|
|
}
|
|
else
|
|
*p++ = 'X'; /* no caching */
|
|
*p++ = ' ';
|
|
for (i=0, s=tryagain_text; *s; s++)
|
|
{
|
|
if (*s < ' ' || *s == '+')
|
|
{
|
|
sprintf (p, "%%%02X", *s);
|
|
p += 3;
|
|
}
|
|
else if (*s == ' ')
|
|
*p++ = '+';
|
|
else
|
|
*p++ = *s;
|
|
}
|
|
*p++ = ' ';
|
|
*p++ = 'X'; /* Use the standard prompt */
|
|
*p++ = ' ';
|
|
/* copy description */
|
|
for (i=0, s= atext; *s; s++)
|
|
{
|
|
if (*s < ' ' || *s == '+')
|
|
{
|
|
sprintf (p, "%%%02X", *s);
|
|
p += 3;
|
|
}
|
|
else if (*s == ' ')
|
|
*p++ = '+';
|
|
else
|
|
*p++ = *s;
|
|
}
|
|
*p++ = '\n';
|
|
i = writen (fd, line, p - line);
|
|
m_free (line);
|
|
if (i)
|
|
goto failure;
|
|
m_free (atext); atext = NULL;
|
|
|
|
/* get response */
|
|
pw = m_alloc_secure (500);
|
|
nread = readline (fd, pw, 499);
|
|
if (nread < 3)
|
|
goto failure;
|
|
|
|
if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ')
|
|
{ /* we got a passphrase - convert it back from hex */
|
|
size_t pwlen = 0;
|
|
|
|
for (i=3; i < nread && hexdigitp (pw+i); i+=2)
|
|
pw[pwlen++] = xtoi_2 (pw+i);
|
|
pw[pwlen] = 0; /* make a C String */
|
|
agent_close (fd);
|
|
free_public_key( pk );
|
|
#ifdef ENABLE_NLS
|
|
if (orig_codeset)
|
|
bind_textdomain_codeset (PACKAGE, orig_codeset);
|
|
#endif
|
|
m_free (orig_codeset);
|
|
return pw;
|
|
}
|
|
else if ((nread > 7 && !memcmp (pw, "ERR 111", 7)
|
|
&& (pw[7] == ' ' || pw[7] == '\n'))
|
|
|| ((nread > 4 && !memcmp (pw, "ERR ", 4)
|
|
&& (strtoul (pw+4, NULL, 0) & 0xffff) == 99)) )
|
|
{
|
|
/* 111 is the old Assuan code for canceled which might still
|
|
be in use by old installations. 99 is GPG_ERR_CANCELED as
|
|
used by modern gpg-agents; 0xfff is used to mask out the
|
|
error source. */
|
|
log_info (_("cancelled by user\n") );
|
|
if (canceled)
|
|
*canceled = 1;
|
|
}
|
|
else
|
|
{
|
|
log_error (_("problem with the agent - disabling agent use\n"));
|
|
opt.use_agent = 0;
|
|
}
|
|
}
|
|
|
|
|
|
failure:
|
|
#ifdef ENABLE_NLS
|
|
if (orig_codeset)
|
|
bind_textdomain_codeset (PACKAGE, orig_codeset);
|
|
#endif
|
|
m_free (atext);
|
|
if ( fd != -1 )
|
|
agent_close (fd);
|
|
m_free (pw );
|
|
free_public_key( pk );
|
|
m_free (orig_codeset);
|
|
|
|
return NULL;
|
|
#endif /* Posix or W32 */
|
|
}
|
|
|
|
/*
|
|
* Clear the cached passphrase
|
|
*/
|
|
void
|
|
passphrase_clear_cache ( u32 *keyid, int algo )
|
|
{
|
|
#if defined(__riscos__)
|
|
return ;
|
|
#else
|
|
size_t n;
|
|
char buf[200];
|
|
int fd = -1;
|
|
size_t nread;
|
|
u32 reply;
|
|
PKT_public_key *pk;
|
|
byte fpr[MAX_FINGERPRINT_LEN];
|
|
int prot;
|
|
|
|
#if MAX_FINGERPRINT_LEN < 20
|
|
#error agent needs a 20 byte fingerprint
|
|
#endif
|
|
|
|
if (!opt.use_agent)
|
|
return;
|
|
|
|
pk = m_alloc_clear ( sizeof *pk );
|
|
memset (fpr, 0, MAX_FINGERPRINT_LEN );
|
|
if( !keyid || get_pubkey( pk, keyid ) )
|
|
{
|
|
log_debug ("oops, no key in passphrase_clear_cache\n");
|
|
goto failure; /* oops: no key for some reason */
|
|
}
|
|
|
|
{
|
|
size_t dummy;
|
|
fingerprint_from_pk( pk, fpr, &dummy );
|
|
}
|
|
|
|
if ( (fd = agent_open (&prot)) == -1 )
|
|
goto failure;
|
|
|
|
if (!prot)
|
|
{
|
|
n = 4 + 20;
|
|
u32tobuf (buf, n );
|
|
u32tobuf (buf+4, GPGA_PROT_CLEAR_PASSPHRASE );
|
|
memcpy (buf+8, fpr, 20 );
|
|
if ( writen ( fd, buf, 28 ) )
|
|
goto failure;
|
|
|
|
/* get response */
|
|
if ( readn ( fd, buf, 8, &nread ) )
|
|
goto failure;
|
|
|
|
if ( nread < 8 ) {
|
|
log_error ( "response from agent too short\n" );
|
|
goto failure;
|
|
}
|
|
|
|
reply = buftou32 ( buf + 4 );
|
|
if ( reply != GPGA_PROT_OKAY && reply != GPGA_PROT_NO_PASSPHRASE )
|
|
{
|
|
log_error ( _("problem with the agent: agent returns 0x%lx\n"),
|
|
(ulong)reply );
|
|
}
|
|
}
|
|
else
|
|
{ /* The assuan protocol */
|
|
char *line, *p;
|
|
int i;
|
|
|
|
line = m_alloc (17 + 40 + 2);
|
|
strcpy (line, "CLEAR_PASSPHRASE ");
|
|
p = line+17;
|
|
for (i=0; i < 20; i++, p +=2 )
|
|
sprintf (p, "%02X", fpr[i]);
|
|
*p++ = '\n';
|
|
i = writen (fd, line, p - line);
|
|
m_free (line);
|
|
if (i)
|
|
goto failure;
|
|
|
|
/* get response */
|
|
nread = readline (fd, buf, DIM(buf)-1);
|
|
if (nread < 3)
|
|
goto failure;
|
|
|
|
if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n'))
|
|
;
|
|
else
|
|
{
|
|
log_error (_("problem with the agent - disabling agent use\n"));
|
|
opt.use_agent = 0;
|
|
}
|
|
}
|
|
|
|
failure:
|
|
if (fd != -1)
|
|
agent_close (fd);
|
|
free_public_key( pk );
|
|
#endif /* Posix or W32 */
|
|
}
|
|
|
|
|
|
|
|
|
|
/****************
|
|
* Get a passphrase for the secret key with KEYID, display TEXT
|
|
* if the user needs to enter the passphrase.
|
|
* mode 0 = standard, 2 = create new passphrase
|
|
* Returns: a DEK with a session key; caller must free
|
|
* or NULL if the passphrase was not correctly repeated.
|
|
* (only for mode 2)
|
|
* a dek->keylen of 0 means: no passphrase entered.
|
|
* (only for mode 2)
|
|
*
|
|
* pubkey_algo is only informational. Note that TRYAGAIN_TEXT must
|
|
* not be translated as this is done within this function (required to
|
|
* switch to utf-8 when the agent is in use). If CANCELED is not
|
|
* NULL, it is set to 1 if the user choosed to cancel the operation,
|
|
* otherwise it will be set to 0.
|
|
*/
|
|
DEK *
|
|
passphrase_to_dek( u32 *keyid, int pubkey_algo,
|
|
int cipher_algo, STRING2KEY *s2k, int mode,
|
|
const char *tryagain_text, int *canceled)
|
|
{
|
|
char *pw = NULL;
|
|
DEK *dek;
|
|
STRING2KEY help_s2k;
|
|
|
|
if (canceled)
|
|
*canceled = 0;
|
|
|
|
if( !s2k ) {
|
|
/* This is used for the old rfc1991 mode
|
|
* Note: This must match the code in encode.c with opt.rfc1991 set */
|
|
int algo = opt.def_digest_algo ? opt.def_digest_algo
|
|
: opt.s2k_digest_algo;
|
|
|
|
s2k = &help_s2k;
|
|
s2k->mode = 0;
|
|
s2k->hash_algo = algo;
|
|
}
|
|
|
|
if( !next_pw && is_status_enabled() ) {
|
|
char buf[50];
|
|
|
|
if( keyid ) {
|
|
u32 used_kid[2];
|
|
char *us;
|
|
|
|
if( keyid[2] && keyid[3] ) {
|
|
used_kid[0] = keyid[2];
|
|
used_kid[1] = keyid[3];
|
|
}
|
|
else {
|
|
used_kid[0] = keyid[0];
|
|
used_kid[1] = keyid[1];
|
|
}
|
|
|
|
us = get_long_user_id_string( keyid );
|
|
write_status_text( STATUS_USERID_HINT, us );
|
|
m_free(us);
|
|
|
|
sprintf( buf, "%08lX%08lX %08lX%08lX %d 0",
|
|
(ulong)keyid[0], (ulong)keyid[1],
|
|
(ulong)used_kid[0], (ulong)used_kid[1],
|
|
pubkey_algo );
|
|
|
|
write_status_text( STATUS_NEED_PASSPHRASE, buf );
|
|
}
|
|
else {
|
|
sprintf( buf, "%d %d %d", cipher_algo, s2k->mode, s2k->hash_algo );
|
|
write_status_text( STATUS_NEED_PASSPHRASE_SYM, buf );
|
|
}
|
|
}
|
|
|
|
if( keyid && !opt.batch && !next_pw ) {
|
|
PKT_public_key *pk = m_alloc_clear( sizeof *pk );
|
|
size_t n;
|
|
char *p;
|
|
|
|
tty_printf(_("\nYou need a passphrase to unlock the secret key for\n"
|
|
"user: \"") );
|
|
p = get_user_id( keyid, &n );
|
|
tty_print_utf8_string( p, n );
|
|
m_free(p);
|
|
tty_printf("\"\n");
|
|
|
|
if( !get_pubkey( pk, keyid ) ) {
|
|
const char *s = pubkey_algo_to_string( pk->pubkey_algo );
|
|
tty_printf( _("%u-bit %s key, ID %08lX, created %s"),
|
|
nbits_from_pk( pk ), s?s:"?", (ulong)keyid[1],
|
|
strtimestamp(pk->timestamp) );
|
|
if( keyid[2] && keyid[3] && keyid[0] != keyid[2]
|
|
&& keyid[1] != keyid[3] )
|
|
tty_printf( _(" (main key ID %08lX)"), (ulong)keyid[3] );
|
|
tty_printf("\n");
|
|
}
|
|
|
|
tty_printf("\n");
|
|
free_public_key( pk );
|
|
}
|
|
|
|
agent_died:
|
|
if( next_pw ) {
|
|
pw = next_pw;
|
|
next_pw = NULL;
|
|
}
|
|
else if ( opt.use_agent ) {
|
|
pw = agent_get_passphrase ( keyid, mode == 2? 1: 0,
|
|
tryagain_text, canceled);
|
|
if (!pw)
|
|
{
|
|
if (!opt.use_agent)
|
|
goto agent_died;
|
|
pw = m_strdup ("");
|
|
}
|
|
if( *pw && mode == 2 ) {
|
|
char *pw2 = agent_get_passphrase ( keyid, 2, NULL, canceled);
|
|
if (!pw2)
|
|
{
|
|
if (!opt.use_agent)
|
|
{
|
|
m_free (pw);
|
|
pw = NULL;
|
|
goto agent_died;
|
|
}
|
|
pw2 = m_strdup ("");
|
|
}
|
|
if( strcmp(pw, pw2) ) {
|
|
m_free(pw2);
|
|
m_free(pw);
|
|
return NULL;
|
|
}
|
|
m_free(pw2);
|
|
}
|
|
}
|
|
else if( fd_passwd ) {
|
|
pw = m_alloc_secure( strlen(fd_passwd)+1 );
|
|
strcpy( pw, fd_passwd );
|
|
}
|
|
else if( opt.batch ) {
|
|
log_error(_("can't query password in batchmode\n"));
|
|
pw = m_strdup( "" ); /* return an empty passphrase */
|
|
}
|
|
else {
|
|
pw = cpr_get_hidden("passphrase.enter", _("Enter passphrase: ") );
|
|
tty_kill_prompt();
|
|
if( mode == 2 && !cpr_enabled() ) {
|
|
char *pw2 = cpr_get_hidden("passphrase.repeat",
|
|
_("Repeat passphrase: ") );
|
|
tty_kill_prompt();
|
|
if( strcmp(pw, pw2) ) {
|
|
m_free(pw2);
|
|
m_free(pw);
|
|
return NULL;
|
|
}
|
|
m_free(pw2);
|
|
}
|
|
}
|
|
|
|
if( !pw || !*pw )
|
|
write_status( STATUS_MISSING_PASSPHRASE );
|
|
|
|
dek = m_alloc_secure_clear ( sizeof *dek );
|
|
dek->algo = cipher_algo;
|
|
if( !*pw && mode == 2 )
|
|
dek->keylen = 0;
|
|
else
|
|
hash_passphrase( dek, pw, s2k, mode==2 );
|
|
m_free(last_pw);
|
|
last_pw = pw;
|
|
return dek;
|
|
}
|
|
|
|
|
|
/****************
|
|
* Hash a passphrase using the supplied s2k. If create is true, create
|
|
* a new salt or what else must be filled into the s2k for a new key.
|
|
* always needs: dek->algo, s2k->mode, s2k->hash_algo.
|
|
*/
|
|
static void
|
|
hash_passphrase( DEK *dek, char *pw, STRING2KEY *s2k, int create )
|
|
{
|
|
MD_HANDLE md;
|
|
int pass, i;
|
|
int used = 0;
|
|
int pwlen = strlen(pw);
|
|
|
|
assert( s2k->hash_algo );
|
|
dek->keylen = cipher_get_keylen( dek->algo ) / 8;
|
|
if( !(dek->keylen > 0 && dek->keylen <= DIM(dek->key)) )
|
|
BUG();
|
|
|
|
md = md_open( s2k->hash_algo, 1);
|
|
for(pass=0; used < dek->keylen ; pass++ ) {
|
|
if( pass ) {
|
|
md_reset(md);
|
|
for(i=0; i < pass; i++ ) /* preset the hash context */
|
|
md_putc(md, 0 );
|
|
}
|
|
|
|
if( s2k->mode == 1 || s2k->mode == 3 ) {
|
|
int len2 = pwlen + 8;
|
|
ulong count = len2;
|
|
|
|
if( create && !pass ) {
|
|
randomize_buffer(s2k->salt, 8, 1);
|
|
if( s2k->mode == 3 )
|
|
s2k->count = 96; /* 65536 iterations */
|
|
}
|
|
|
|
if( s2k->mode == 3 ) {
|
|
count = (16ul + (s2k->count & 15)) << ((s2k->count >> 4) + 6);
|
|
if( count < len2 )
|
|
count = len2;
|
|
}
|
|
/* a little bit complicated because we need a ulong for count */
|
|
while( count > len2 ) { /* maybe iterated+salted */
|
|
md_write( md, s2k->salt, 8 );
|
|
md_write( md, pw, pwlen );
|
|
count -= len2;
|
|
}
|
|
if( count < 8 )
|
|
md_write( md, s2k->salt, count );
|
|
else {
|
|
md_write( md, s2k->salt, 8 );
|
|
count -= 8;
|
|
md_write( md, pw, count );
|
|
}
|
|
}
|
|
else
|
|
md_write( md, pw, pwlen );
|
|
md_final( md );
|
|
i = md_digest_length( s2k->hash_algo );
|
|
if( i > dek->keylen - used )
|
|
i = dek->keylen - used;
|
|
memcpy( dek->key+used, md_read(md, s2k->hash_algo), i );
|
|
used += i;
|
|
}
|
|
md_close(md);
|
|
}
|
|
|