mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
28aa689058
* agent/agent.h (server_control_s): Add field 'client_uid'. * agent/call-pinentry.c (start_pinentry): Add uid field to assuan option "owner" sent to pinentry. * agent/command-ssh.c (peer_info_s): New static struct. (get_client_pid): Rename to... (get_client_info): Here, and extract uid in addition to pid. (start_command_handler_ssh): Use get_client_info() instead of get_client_pid(). * agent/command.c (start_command_handler): Try assuan_get_peercred, and only fall back to assuan_get_pid when assuan_get_peercred fails. -- This also requires an update to pinentry to handle the new uid field. Distributing the uid as well as the pid makes it harder for a different user on the same machine to take advantage of any race conditions between when a requesting process might ask for something that needs pinentry, and when pinentry gets around to inspecting the state of that process. We put the uid before the nodename because the uid is guaranteed to be a integer (represented in decimal), which makes it much simpler to parse past than the potentially arbitrarily structured nodename. Use a / instead of whitespace to delimit pid/uid at Werner's request. If we were willing to depend on the nodename being whitespace-delimited (as the current, unreleased pinentry code does), then we could add the uid after the nodename. But since no released pinentry depends on this option anyway, i think we should make the more conservative, easily-parseable choice and put the user ID first. Signed-off-by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
3857 lines
95 KiB
C
3857 lines
95 KiB
C
/* command-ssh.c - gpg-agent's implementation of the ssh-agent protocol.
|
||
* Copyright (C) 2004-2006, 2009, 2012 Free Software Foundation, Inc.
|
||
* Copyright (C) 2004-2006, 2009, 2012-2014 Werner Koch
|
||
*
|
||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
/* Only v2 of the ssh-agent protocol is implemented. Relevant RFCs
|
||
are:
|
||
|
||
RFC-4250 - Protocol Assigned Numbers
|
||
RFC-4251 - Protocol Architecture
|
||
RFC-4252 - Authentication Protocol
|
||
RFC-4253 - Transport Layer Protocol
|
||
RFC-5656 - ECC support
|
||
|
||
The protocol for the agent is defined in OpenSSH's PROTOCL.agent
|
||
file.
|
||
*/
|
||
|
||
#include <config.h>
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <sys/types.h>
|
||
#include <sys/stat.h>
|
||
#include <assert.h>
|
||
#ifndef HAVE_W32_SYSTEM
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
#endif /*!HAVE_W32_SYSTEM*/
|
||
#ifdef HAVE_UCRED_H
|
||
#include <ucred.h>
|
||
#endif
|
||
|
||
#include "agent.h"
|
||
|
||
#include "../common/i18n.h"
|
||
#include "../common/util.h"
|
||
#include "../common/ssh-utils.h"
|
||
|
||
|
||
|
||
|
||
/* Request types. */
|
||
#define SSH_REQUEST_REQUEST_IDENTITIES 11
|
||
#define SSH_REQUEST_SIGN_REQUEST 13
|
||
#define SSH_REQUEST_ADD_IDENTITY 17
|
||
#define SSH_REQUEST_REMOVE_IDENTITY 18
|
||
#define SSH_REQUEST_REMOVE_ALL_IDENTITIES 19
|
||
#define SSH_REQUEST_LOCK 22
|
||
#define SSH_REQUEST_UNLOCK 23
|
||
#define SSH_REQUEST_ADD_ID_CONSTRAINED 25
|
||
|
||
/* Options. */
|
||
#define SSH_OPT_CONSTRAIN_LIFETIME 1
|
||
#define SSH_OPT_CONSTRAIN_CONFIRM 2
|
||
|
||
/* Response types. */
|
||
#define SSH_RESPONSE_SUCCESS 6
|
||
#define SSH_RESPONSE_FAILURE 5
|
||
#define SSH_RESPONSE_IDENTITIES_ANSWER 12
|
||
#define SSH_RESPONSE_SIGN_RESPONSE 14
|
||
|
||
/* Other constants. */
|
||
#define SSH_DSA_SIGNATURE_PADDING 20
|
||
#define SSH_DSA_SIGNATURE_ELEMS 2
|
||
#define SPEC_FLAG_USE_PKCS1V2 (1 << 0)
|
||
#define SPEC_FLAG_IS_ECDSA (1 << 1)
|
||
#define SPEC_FLAG_IS_EdDSA (1 << 2) /*(lowercase 'd' on purpose.)*/
|
||
#define SPEC_FLAG_WITH_CERT (1 << 7)
|
||
|
||
/* The name of the control file. */
|
||
#define SSH_CONTROL_FILE_NAME "sshcontrol"
|
||
|
||
/* The blurb we put into the header of a newly created control file. */
|
||
static const char sshcontrolblurb[] =
|
||
"# List of allowed ssh keys. Only keys present in this file are used\n"
|
||
"# in the SSH protocol. The ssh-add tool may add new entries to this\n"
|
||
"# file to enable them; you may also add them manually. Comment\n"
|
||
"# lines, like this one, as well as empty lines are ignored. Lines do\n"
|
||
"# have a certain length limit but this is not serious limitation as\n"
|
||
"# the format of the entries is fixed and checked by gpg-agent. A\n"
|
||
"# non-comment line starts with optional white spaces, followed by the\n"
|
||
"# keygrip of the key given as 40 hex digits, optionally followed by a\n"
|
||
"# caching TTL in seconds, and another optional field for arbitrary\n"
|
||
"# flags. Prepend the keygrip with an '!' mark to disable it.\n"
|
||
"\n";
|
||
|
||
|
||
/* Macros. */
|
||
|
||
/* Return a new uint32 with b0 being the most significant byte and b3
|
||
being the least significant byte. */
|
||
#define uint32_construct(b0, b1, b2, b3) \
|
||
((b0 << 24) | (b1 << 16) | (b2 << 8) | b3)
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Basic types.
|
||
*/
|
||
|
||
/* Type for a request handler. */
|
||
typedef gpg_error_t (*ssh_request_handler_t) (ctrl_t ctrl,
|
||
estream_t request,
|
||
estream_t response);
|
||
|
||
|
||
struct ssh_key_type_spec;
|
||
typedef struct ssh_key_type_spec ssh_key_type_spec_t;
|
||
|
||
/* Type, which is used for associating request handlers with the
|
||
appropriate request IDs. */
|
||
typedef struct ssh_request_spec
|
||
{
|
||
unsigned char type;
|
||
ssh_request_handler_t handler;
|
||
const char *identifier;
|
||
unsigned int secret_input;
|
||
} ssh_request_spec_t;
|
||
|
||
/* Type for "key modifier functions", which are necessary since
|
||
OpenSSH and GnuPG treat key material slightly different. A key
|
||
modifier is called right after a new key identity has been received
|
||
in order to "sanitize" the material. */
|
||
typedef gpg_error_t (*ssh_key_modifier_t) (const char *elems,
|
||
gcry_mpi_t *mpis);
|
||
|
||
/* The encoding of a generated signature is dependent on the
|
||
algorithm; therefore algorithm specific signature encoding
|
||
functions are necessary. */
|
||
typedef gpg_error_t (*ssh_signature_encoder_t) (ssh_key_type_spec_t *spec,
|
||
estream_t signature_blob,
|
||
gcry_sexp_t sig);
|
||
|
||
/* Type, which is used for boundling all the algorithm specific
|
||
information together in a single object. */
|
||
struct ssh_key_type_spec
|
||
{
|
||
/* Algorithm identifier as used by OpenSSH. */
|
||
const char *ssh_identifier;
|
||
|
||
/* Human readable name of the algorithm. */
|
||
const char *name;
|
||
|
||
/* Algorithm identifier as used by GnuPG. */
|
||
int algo;
|
||
|
||
/* List of MPI names for secret keys; order matches the one of the
|
||
agent protocol. */
|
||
const char *elems_key_secret;
|
||
|
||
/* List of MPI names for public keys; order matches the one of the
|
||
agent protocol. */
|
||
const char *elems_key_public;
|
||
|
||
/* List of MPI names for signature data. */
|
||
const char *elems_signature;
|
||
|
||
/* List of MPI names for secret keys; order matches the one, which
|
||
is required by gpg-agent's key access layer. */
|
||
const char *elems_sexp_order;
|
||
|
||
/* Key modifier function. Key modifier functions are necessary in
|
||
order to fix any inconsistencies between the representation of
|
||
keys on the SSH and on the GnuPG side. */
|
||
ssh_key_modifier_t key_modifier;
|
||
|
||
/* Signature encoder function. Signature encoder functions are
|
||
necessary since the encoding of signatures depends on the used
|
||
algorithm. */
|
||
ssh_signature_encoder_t signature_encoder;
|
||
|
||
/* The name of the ECC curve or NULL. */
|
||
const char *curve_name;
|
||
|
||
/* The hash algorithm to be used with this key. 0 for using the
|
||
default. */
|
||
int hash_algo;
|
||
|
||
/* Misc flags. */
|
||
unsigned int flags;
|
||
};
|
||
|
||
|
||
/* Definition of an object to access the sshcontrol file. */
|
||
struct ssh_control_file_s
|
||
{
|
||
char *fname; /* Name of the file. */
|
||
FILE *fp; /* This is never NULL. */
|
||
int lnr; /* The current line number. */
|
||
struct {
|
||
int valid; /* True if the data of this structure is valid. */
|
||
int disabled; /* The item is disabled. */
|
||
int ttl; /* The TTL of the item. */
|
||
int confirm; /* The confirm flag is set. */
|
||
char hexgrip[40+1]; /* The hexgrip of the item (uppercase). */
|
||
} item;
|
||
};
|
||
|
||
|
||
/* Prototypes. */
|
||
static gpg_error_t ssh_handler_request_identities (ctrl_t ctrl,
|
||
estream_t request,
|
||
estream_t response);
|
||
static gpg_error_t ssh_handler_sign_request (ctrl_t ctrl,
|
||
estream_t request,
|
||
estream_t response);
|
||
static gpg_error_t ssh_handler_add_identity (ctrl_t ctrl,
|
||
estream_t request,
|
||
estream_t response);
|
||
static gpg_error_t ssh_handler_remove_identity (ctrl_t ctrl,
|
||
estream_t request,
|
||
estream_t response);
|
||
static gpg_error_t ssh_handler_remove_all_identities (ctrl_t ctrl,
|
||
estream_t request,
|
||
estream_t response);
|
||
static gpg_error_t ssh_handler_lock (ctrl_t ctrl,
|
||
estream_t request,
|
||
estream_t response);
|
||
static gpg_error_t ssh_handler_unlock (ctrl_t ctrl,
|
||
estream_t request,
|
||
estream_t response);
|
||
|
||
static gpg_error_t ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis);
|
||
static gpg_error_t ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
|
||
estream_t signature_blob,
|
||
gcry_sexp_t signature);
|
||
static gpg_error_t ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
|
||
estream_t signature_blob,
|
||
gcry_sexp_t signature);
|
||
static gpg_error_t ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
|
||
estream_t signature_blob,
|
||
gcry_sexp_t signature);
|
||
static gpg_error_t ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
|
||
estream_t signature_blob,
|
||
gcry_sexp_t signature);
|
||
static gpg_error_t ssh_key_extract_comment (gcry_sexp_t key, char **comment);
|
||
|
||
|
||
struct peer_info_s
|
||
{
|
||
unsigned long pid;
|
||
int uid;
|
||
};
|
||
|
||
/* Global variables. */
|
||
|
||
|
||
/* Associating request types with the corresponding request
|
||
handlers. */
|
||
|
||
static const ssh_request_spec_t request_specs[] =
|
||
{
|
||
#define REQUEST_SPEC_DEFINE(id, name, secret_input) \
|
||
{ SSH_REQUEST_##id, ssh_handler_##name, #name, secret_input }
|
||
|
||
REQUEST_SPEC_DEFINE (REQUEST_IDENTITIES, request_identities, 1),
|
||
REQUEST_SPEC_DEFINE (SIGN_REQUEST, sign_request, 0),
|
||
REQUEST_SPEC_DEFINE (ADD_IDENTITY, add_identity, 1),
|
||
REQUEST_SPEC_DEFINE (ADD_ID_CONSTRAINED, add_identity, 1),
|
||
REQUEST_SPEC_DEFINE (REMOVE_IDENTITY, remove_identity, 0),
|
||
REQUEST_SPEC_DEFINE (REMOVE_ALL_IDENTITIES, remove_all_identities, 0),
|
||
REQUEST_SPEC_DEFINE (LOCK, lock, 0),
|
||
REQUEST_SPEC_DEFINE (UNLOCK, unlock, 0)
|
||
#undef REQUEST_SPEC_DEFINE
|
||
};
|
||
|
||
|
||
/* Table holding key type specifications. */
|
||
static const ssh_key_type_spec_t ssh_key_types[] =
|
||
{
|
||
{
|
||
"ssh-ed25519", "Ed25519", GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
|
||
NULL, ssh_signature_encoder_eddsa,
|
||
"Ed25519", 0, SPEC_FLAG_IS_EdDSA
|
||
},
|
||
{
|
||
"ssh-rsa", "RSA", GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
|
||
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
|
||
NULL, 0, SPEC_FLAG_USE_PKCS1V2
|
||
},
|
||
{
|
||
"ssh-dss", "DSA", GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
|
||
NULL, ssh_signature_encoder_dsa,
|
||
NULL, 0, 0
|
||
},
|
||
{
|
||
"ecdsa-sha2-nistp256", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
|
||
NULL, ssh_signature_encoder_ecdsa,
|
||
"nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA
|
||
},
|
||
{
|
||
"ecdsa-sha2-nistp384", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
|
||
NULL, ssh_signature_encoder_ecdsa,
|
||
"nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA
|
||
},
|
||
{
|
||
"ecdsa-sha2-nistp521", "ECDSA", GCRY_PK_ECC, "qd", "q", "rs", "qd",
|
||
NULL, ssh_signature_encoder_ecdsa,
|
||
"nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA
|
||
},
|
||
{
|
||
"ssh-ed25519-cert-v01@openssh.com", "Ed25519",
|
||
GCRY_PK_EDDSA, "qd", "q", "rs", "qd",
|
||
NULL, ssh_signature_encoder_eddsa,
|
||
"Ed25519", 0, SPEC_FLAG_IS_EdDSA | SPEC_FLAG_WITH_CERT
|
||
},
|
||
{
|
||
"ssh-rsa-cert-v01@openssh.com", "RSA",
|
||
GCRY_PK_RSA, "nedupq", "en", "s", "nedpqu",
|
||
ssh_key_modifier_rsa, ssh_signature_encoder_rsa,
|
||
NULL, 0, SPEC_FLAG_USE_PKCS1V2 | SPEC_FLAG_WITH_CERT
|
||
},
|
||
{
|
||
"ssh-dss-cert-v01@openssh.com", "DSA",
|
||
GCRY_PK_DSA, "pqgyx", "pqgy", "rs", "pqgyx",
|
||
NULL, ssh_signature_encoder_dsa,
|
||
NULL, 0, SPEC_FLAG_WITH_CERT | SPEC_FLAG_WITH_CERT
|
||
},
|
||
{
|
||
"ecdsa-sha2-nistp256-cert-v01@openssh.com", "ECDSA",
|
||
GCRY_PK_ECC, "qd", "q", "rs", "qd",
|
||
NULL, ssh_signature_encoder_ecdsa,
|
||
"nistp256", GCRY_MD_SHA256, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
|
||
},
|
||
{
|
||
"ecdsa-sha2-nistp384-cert-v01@openssh.com", "ECDSA",
|
||
GCRY_PK_ECC, "qd", "q", "rs", "qd",
|
||
NULL, ssh_signature_encoder_ecdsa,
|
||
"nistp384", GCRY_MD_SHA384, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
|
||
},
|
||
{
|
||
"ecdsa-sha2-nistp521-cert-v01@openssh.com", "ECDSA",
|
||
GCRY_PK_ECC, "qd", "q", "rs", "qd",
|
||
NULL, ssh_signature_encoder_ecdsa,
|
||
"nistp521", GCRY_MD_SHA512, SPEC_FLAG_IS_ECDSA | SPEC_FLAG_WITH_CERT
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
|
||
/*
|
||
General utility functions.
|
||
*/
|
||
|
||
/* A secure realloc, i.e. it makes sure to allocate secure memory if A
|
||
is NULL. This is required because the standard gcry_realloc does
|
||
not know whether to allocate secure or normal if NULL is passed as
|
||
existing buffer. */
|
||
static void *
|
||
realloc_secure (void *a, size_t n)
|
||
{
|
||
void *p;
|
||
|
||
if (a)
|
||
p = gcry_realloc (a, n);
|
||
else
|
||
p = gcry_malloc_secure (n);
|
||
|
||
return p;
|
||
}
|
||
|
||
|
||
/* Lookup the ssh-identifier for the ECC curve CURVE_NAME. Returns
|
||
NULL if not found. */
|
||
static const char *
|
||
ssh_identifier_from_curve_name (const char *curve_name)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < DIM (ssh_key_types); i++)
|
||
if (ssh_key_types[i].curve_name
|
||
&& !strcmp (ssh_key_types[i].curve_name, curve_name))
|
||
return ssh_key_types[i].ssh_identifier;
|
||
|
||
return NULL;
|
||
}
|
||
|
||
|
||
/*
|
||
Primitive I/O functions.
|
||
*/
|
||
|
||
|
||
/* Read a byte from STREAM, store it in B. */
|
||
static gpg_error_t
|
||
stream_read_byte (estream_t stream, unsigned char *b)
|
||
{
|
||
gpg_error_t err;
|
||
int ret;
|
||
|
||
ret = es_fgetc (stream);
|
||
if (ret == EOF)
|
||
{
|
||
if (es_ferror (stream))
|
||
err = gpg_error_from_syserror ();
|
||
else
|
||
err = gpg_error (GPG_ERR_EOF);
|
||
*b = 0;
|
||
}
|
||
else
|
||
{
|
||
*b = ret & 0xFF;
|
||
err = 0;
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Write the byte contained in B to STREAM. */
|
||
static gpg_error_t
|
||
stream_write_byte (estream_t stream, unsigned char b)
|
||
{
|
||
gpg_error_t err;
|
||
int ret;
|
||
|
||
ret = es_fputc (b, stream);
|
||
if (ret == EOF)
|
||
err = gpg_error_from_syserror ();
|
||
else
|
||
err = 0;
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Read a uint32 from STREAM, store it in UINT32. */
|
||
static gpg_error_t
|
||
stream_read_uint32 (estream_t stream, u32 *uint32)
|
||
{
|
||
unsigned char buffer[4];
|
||
size_t bytes_read;
|
||
gpg_error_t err;
|
||
int ret;
|
||
|
||
ret = es_read (stream, buffer, sizeof (buffer), &bytes_read);
|
||
if (ret)
|
||
err = gpg_error_from_syserror ();
|
||
else
|
||
{
|
||
if (bytes_read != sizeof (buffer))
|
||
err = gpg_error (GPG_ERR_EOF);
|
||
else
|
||
{
|
||
u32 n;
|
||
|
||
n = uint32_construct (buffer[0], buffer[1], buffer[2], buffer[3]);
|
||
*uint32 = n;
|
||
err = 0;
|
||
}
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Write the uint32 contained in UINT32 to STREAM. */
|
||
static gpg_error_t
|
||
stream_write_uint32 (estream_t stream, u32 uint32)
|
||
{
|
||
unsigned char buffer[4];
|
||
gpg_error_t err;
|
||
int ret;
|
||
|
||
buffer[0] = uint32 >> 24;
|
||
buffer[1] = uint32 >> 16;
|
||
buffer[2] = uint32 >> 8;
|
||
buffer[3] = uint32 >> 0;
|
||
|
||
ret = es_write (stream, buffer, sizeof (buffer), NULL);
|
||
if (ret)
|
||
err = gpg_error_from_syserror ();
|
||
else
|
||
err = 0;
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Read SIZE bytes from STREAM into BUFFER. */
|
||
static gpg_error_t
|
||
stream_read_data (estream_t stream, unsigned char *buffer, size_t size)
|
||
{
|
||
gpg_error_t err;
|
||
size_t bytes_read;
|
||
int ret;
|
||
|
||
ret = es_read (stream, buffer, size, &bytes_read);
|
||
if (ret)
|
||
err = gpg_error_from_syserror ();
|
||
else
|
||
{
|
||
if (bytes_read != size)
|
||
err = gpg_error (GPG_ERR_EOF);
|
||
else
|
||
err = 0;
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Skip over SIZE bytes from STREAM. */
|
||
static gpg_error_t
|
||
stream_read_skip (estream_t stream, size_t size)
|
||
{
|
||
char buffer[128];
|
||
size_t bytes_to_read, bytes_read;
|
||
int ret;
|
||
|
||
do
|
||
{
|
||
bytes_to_read = size;
|
||
if (bytes_to_read > sizeof buffer)
|
||
bytes_to_read = sizeof buffer;
|
||
|
||
ret = es_read (stream, buffer, bytes_to_read, &bytes_read);
|
||
if (ret)
|
||
return gpg_error_from_syserror ();
|
||
else if (bytes_read != bytes_to_read)
|
||
return gpg_error (GPG_ERR_EOF);
|
||
else
|
||
size -= bytes_to_read;
|
||
}
|
||
while (size);
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Write SIZE bytes from BUFFER to STREAM. */
|
||
static gpg_error_t
|
||
stream_write_data (estream_t stream, const unsigned char *buffer, size_t size)
|
||
{
|
||
gpg_error_t err;
|
||
int ret;
|
||
|
||
ret = es_write (stream, buffer, size, NULL);
|
||
if (ret)
|
||
err = gpg_error_from_syserror ();
|
||
else
|
||
err = 0;
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Read a binary string from STREAM into STRING, store size of string
|
||
in STRING_SIZE. Append a hidden nul so that the result may
|
||
directly be used as a C string. Depending on SECURE use secure
|
||
memory for STRING. If STRING is NULL do only a dummy read. */
|
||
static gpg_error_t
|
||
stream_read_string (estream_t stream, unsigned int secure,
|
||
unsigned char **string, u32 *string_size)
|
||
{
|
||
gpg_error_t err;
|
||
unsigned char *buffer = NULL;
|
||
u32 length = 0;
|
||
|
||
if (string_size)
|
||
*string_size = 0;
|
||
|
||
/* Read string length. */
|
||
err = stream_read_uint32 (stream, &length);
|
||
if (err)
|
||
goto out;
|
||
|
||
if (string)
|
||
{
|
||
/* Allocate space. */
|
||
if (secure)
|
||
buffer = xtrymalloc_secure (length + 1);
|
||
else
|
||
buffer = xtrymalloc (length + 1);
|
||
if (! buffer)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
/* Read data. */
|
||
err = stream_read_data (stream, buffer, length);
|
||
if (err)
|
||
goto out;
|
||
|
||
/* Finalize string object. */
|
||
buffer[length] = 0;
|
||
*string = buffer;
|
||
}
|
||
else /* Dummy read requested. */
|
||
{
|
||
err = stream_read_skip (stream, length);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
if (string_size)
|
||
*string_size = length;
|
||
|
||
out:
|
||
|
||
if (err)
|
||
xfree (buffer);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Read a binary string from STREAM and store it as an opaque MPI at
|
||
R_MPI, adding 0x40 (this is the prefix for EdDSA key in OpenPGP).
|
||
Depending on SECURE use secure memory. If the string is too large
|
||
for key material return an error. */
|
||
static gpg_error_t
|
||
stream_read_blob (estream_t stream, unsigned int secure, gcry_mpi_t *r_mpi)
|
||
{
|
||
gpg_error_t err;
|
||
unsigned char *buffer = NULL;
|
||
u32 length = 0;
|
||
|
||
*r_mpi = NULL;
|
||
|
||
/* Read string length. */
|
||
err = stream_read_uint32 (stream, &length);
|
||
if (err)
|
||
goto leave;
|
||
|
||
/* To avoid excessive use of secure memory we check that an MPI is
|
||
not too large. */
|
||
if (length > (4096/8) + 8)
|
||
{
|
||
log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
|
||
err = GPG_ERR_TOO_LARGE;
|
||
goto leave;
|
||
}
|
||
|
||
/* Allocate space. */
|
||
if (secure)
|
||
buffer = xtrymalloc_secure (length+1);
|
||
else
|
||
buffer = xtrymalloc (length+1);
|
||
if (!buffer)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
|
||
/* Read data. */
|
||
err = stream_read_data (stream, buffer + 1, length);
|
||
if (err)
|
||
goto leave;
|
||
|
||
buffer[0] = 0x40;
|
||
*r_mpi = gcry_mpi_set_opaque (NULL, buffer, 8*(length+1));
|
||
buffer = NULL;
|
||
|
||
leave:
|
||
xfree (buffer);
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Read a C-string from STREAM, store copy in STRING. */
|
||
static gpg_error_t
|
||
stream_read_cstring (estream_t stream, char **string)
|
||
{
|
||
return stream_read_string (stream, 0, (unsigned char **)string, NULL);
|
||
}
|
||
|
||
|
||
/* Write a binary string from STRING of size STRING_N to STREAM. */
|
||
static gpg_error_t
|
||
stream_write_string (estream_t stream,
|
||
const unsigned char *string, u32 string_n)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
err = stream_write_uint32 (stream, string_n);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = stream_write_data (stream, string, string_n);
|
||
|
||
out:
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Write a C-string from STRING to STREAM. */
|
||
static gpg_error_t
|
||
stream_write_cstring (estream_t stream, const char *string)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
err = stream_write_string (stream,
|
||
(const unsigned char *) string, strlen (string));
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Read an MPI from STREAM, store it in MPINT. Depending on SECURE
|
||
use secure memory. */
|
||
static gpg_error_t
|
||
stream_read_mpi (estream_t stream, unsigned int secure, gcry_mpi_t *mpint)
|
||
{
|
||
unsigned char *mpi_data;
|
||
u32 mpi_data_size;
|
||
gpg_error_t err;
|
||
gcry_mpi_t mpi;
|
||
|
||
mpi_data = NULL;
|
||
|
||
err = stream_read_string (stream, secure, &mpi_data, &mpi_data_size);
|
||
if (err)
|
||
goto out;
|
||
|
||
/* To avoid excessive use of secure memory we check that an MPI is
|
||
not too large. */
|
||
if (mpi_data_size > 520)
|
||
{
|
||
log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
|
||
err = GPG_ERR_TOO_LARGE;
|
||
goto out;
|
||
}
|
||
|
||
err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_STD, mpi_data, mpi_data_size, NULL);
|
||
if (err)
|
||
goto out;
|
||
|
||
*mpint = mpi;
|
||
|
||
out:
|
||
|
||
xfree (mpi_data);
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Write the MPI contained in MPINT to STREAM. */
|
||
static gpg_error_t
|
||
stream_write_mpi (estream_t stream, gcry_mpi_t mpint)
|
||
{
|
||
unsigned char *mpi_buffer;
|
||
size_t mpi_buffer_n;
|
||
gpg_error_t err;
|
||
|
||
mpi_buffer = NULL;
|
||
|
||
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &mpi_buffer, &mpi_buffer_n, mpint);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = stream_write_string (stream, mpi_buffer, mpi_buffer_n);
|
||
|
||
out:
|
||
|
||
xfree (mpi_buffer);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Copy data from SRC to DST until EOF is reached. */
|
||
static gpg_error_t
|
||
stream_copy (estream_t dst, estream_t src)
|
||
{
|
||
char buffer[BUFSIZ];
|
||
size_t bytes_read;
|
||
gpg_error_t err;
|
||
int ret;
|
||
|
||
err = 0;
|
||
while (1)
|
||
{
|
||
ret = es_read (src, buffer, sizeof (buffer), &bytes_read);
|
||
if (ret || (! bytes_read))
|
||
{
|
||
if (ret)
|
||
err = gpg_error_from_syserror ();
|
||
break;
|
||
}
|
||
ret = es_write (dst, buffer, bytes_read, NULL);
|
||
if (ret)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
break;
|
||
}
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Open the ssh control file and create it if not available. With
|
||
APPEND passed as true the file will be opened in append mode,
|
||
otherwise in read only mode. On success 0 is returned and a new
|
||
control file object stored at R_CF. On error an error code is
|
||
returned and NULL is stored at R_CF. */
|
||
static gpg_error_t
|
||
open_control_file (ssh_control_file_t *r_cf, int append)
|
||
{
|
||
gpg_error_t err;
|
||
ssh_control_file_t cf;
|
||
|
||
cf = xtrycalloc (1, sizeof *cf);
|
||
if (!cf)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
|
||
/* Note: As soon as we start to use non blocking functions here
|
||
(i.e. where Pth might switch threads) we need to employ a
|
||
mutex. */
|
||
cf->fname = make_filename_try (gnupg_homedir (), SSH_CONTROL_FILE_NAME, NULL);
|
||
if (!cf->fname)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
/* FIXME: With "a+" we are not able to check whether this will
|
||
be created and thus the blurb needs to be written first. */
|
||
cf->fp = fopen (cf->fname, append? "a+":"r");
|
||
if (!cf->fp && errno == ENOENT)
|
||
{
|
||
estream_t stream = es_fopen (cf->fname, "wx,mode=-rw-r");
|
||
if (!stream)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error (_("can't create '%s': %s\n"),
|
||
cf->fname, gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
es_fputs (sshcontrolblurb, stream);
|
||
es_fclose (stream);
|
||
cf->fp = fopen (cf->fname, append? "a+":"r");
|
||
}
|
||
|
||
if (!cf->fp)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error (_("can't open '%s': %s\n"),
|
||
cf->fname, gpg_strerror (err));
|
||
goto leave;
|
||
}
|
||
|
||
err = 0;
|
||
|
||
leave:
|
||
if (err && cf)
|
||
{
|
||
if (cf->fp)
|
||
fclose (cf->fp);
|
||
xfree (cf->fname);
|
||
xfree (cf);
|
||
}
|
||
else
|
||
*r_cf = cf;
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
static void
|
||
rewind_control_file (ssh_control_file_t cf)
|
||
{
|
||
fseek (cf->fp, 0, SEEK_SET);
|
||
cf->lnr = 0;
|
||
clearerr (cf->fp);
|
||
}
|
||
|
||
|
||
static void
|
||
close_control_file (ssh_control_file_t cf)
|
||
{
|
||
if (!cf)
|
||
return;
|
||
fclose (cf->fp);
|
||
xfree (cf->fname);
|
||
xfree (cf);
|
||
}
|
||
|
||
|
||
|
||
/* Read the next line from the control file and store the data in CF.
|
||
Returns 0 on success, GPG_ERR_EOF on EOF, or other error codes. */
|
||
static gpg_error_t
|
||
read_control_file_item (ssh_control_file_t cf)
|
||
{
|
||
int c, i, n;
|
||
char *p, *pend, line[256];
|
||
long ttl = 0;
|
||
|
||
cf->item.valid = 0;
|
||
clearerr (cf->fp);
|
||
|
||
do
|
||
{
|
||
if (!fgets (line, DIM(line)-1, cf->fp) )
|
||
{
|
||
if (feof (cf->fp))
|
||
return gpg_error (GPG_ERR_EOF);
|
||
return gpg_error_from_syserror ();
|
||
}
|
||
cf->lnr++;
|
||
|
||
if (!*line || line[strlen(line)-1] != '\n')
|
||
{
|
||
/* Eat until end of line */
|
||
while ( (c=getc (cf->fp)) != EOF && c != '\n')
|
||
;
|
||
return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
|
||
: GPG_ERR_INCOMPLETE_LINE);
|
||
}
|
||
|
||
/* Allow for empty lines and spaces */
|
||
for (p=line; spacep (p); p++)
|
||
;
|
||
}
|
||
while (!*p || *p == '\n' || *p == '#');
|
||
|
||
cf->item.disabled = 0;
|
||
if (*p == '!')
|
||
{
|
||
cf->item.disabled = 1;
|
||
for (p++; spacep (p); p++)
|
||
;
|
||
}
|
||
|
||
for (i=0; hexdigitp (p) && i < 40; p++, i++)
|
||
cf->item.hexgrip[i] = (*p >= 'a'? (*p & 0xdf): *p);
|
||
cf->item.hexgrip[i] = 0;
|
||
if (i != 40 || !(spacep (p) || *p == '\n'))
|
||
{
|
||
log_error ("%s:%d: invalid formatted line\n", cf->fname, cf->lnr);
|
||
return gpg_error (GPG_ERR_BAD_DATA);
|
||
}
|
||
|
||
ttl = strtol (p, &pend, 10);
|
||
p = pend;
|
||
if (!(spacep (p) || *p == '\n') || (int)ttl < -1)
|
||
{
|
||
log_error ("%s:%d: invalid TTL value; assuming 0\n", cf->fname, cf->lnr);
|
||
cf->item.ttl = 0;
|
||
}
|
||
cf->item.ttl = ttl;
|
||
|
||
/* Now check for key-value pairs of the form NAME[=VALUE]. */
|
||
cf->item.confirm = 0;
|
||
while (*p)
|
||
{
|
||
for (; spacep (p) && *p != '\n'; p++)
|
||
;
|
||
if (!*p || *p == '\n')
|
||
break;
|
||
n = strcspn (p, "= \t\n");
|
||
if (p[n] == '=')
|
||
{
|
||
log_error ("%s:%d: assigning a value to a flag is not yet supported; "
|
||
"flag ignored\n", cf->fname, cf->lnr);
|
||
p++;
|
||
}
|
||
else if (n == 7 && !memcmp (p, "confirm", 7))
|
||
{
|
||
cf->item.confirm = 1;
|
||
}
|
||
else
|
||
log_error ("%s:%d: invalid flag '%.*s'; ignored\n",
|
||
cf->fname, cf->lnr, n, p);
|
||
p += n;
|
||
}
|
||
|
||
/* log_debug ("%s:%d: grip=%s ttl=%d%s%s\n", */
|
||
/* cf->fname, cf->lnr, */
|
||
/* cf->item.hexgrip, cf->item.ttl, */
|
||
/* cf->item.disabled? " disabled":"", */
|
||
/* cf->item.confirm? " confirm":""); */
|
||
|
||
cf->item.valid = 1;
|
||
return 0; /* Okay: valid entry found. */
|
||
}
|
||
|
||
|
||
|
||
/* Search the control file CF from the beginning until a matching
|
||
HEXGRIP is found; return success in this case and store true at
|
||
DISABLED if the found key has been disabled. If R_TTL is not NULL
|
||
a specified TTL for that key is stored there. If R_CONFIRM is not
|
||
NULL it is set to 1 if the key has the confirm flag set. */
|
||
static gpg_error_t
|
||
search_control_file (ssh_control_file_t cf, const char *hexgrip,
|
||
int *r_disabled, int *r_ttl, int *r_confirm)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
assert (strlen (hexgrip) == 40 );
|
||
|
||
if (r_disabled)
|
||
*r_disabled = 0;
|
||
if (r_ttl)
|
||
*r_ttl = 0;
|
||
if (r_confirm)
|
||
*r_confirm = 0;
|
||
|
||
rewind_control_file (cf);
|
||
while (!(err=read_control_file_item (cf)))
|
||
{
|
||
if (!cf->item.valid)
|
||
continue; /* Should not happen. */
|
||
if (!strcmp (hexgrip, cf->item.hexgrip))
|
||
break;
|
||
}
|
||
if (!err)
|
||
{
|
||
if (r_disabled)
|
||
*r_disabled = cf->item.disabled;
|
||
if (r_ttl)
|
||
*r_ttl = cf->item.ttl;
|
||
if (r_confirm)
|
||
*r_confirm = cf->item.confirm;
|
||
}
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
/* Add an entry to the control file to mark the key with the keygrip
|
||
HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks
|
||
for it. FMTFPR is the fingerprint string. This function is in
|
||
general used to add a key received through the ssh-add function.
|
||
We can assume that the user wants to allow ssh using this key. */
|
||
static gpg_error_t
|
||
add_control_entry (ctrl_t ctrl, ssh_key_type_spec_t *spec,
|
||
const char *hexgrip, gcry_sexp_t key,
|
||
int ttl, int confirm)
|
||
{
|
||
gpg_error_t err;
|
||
ssh_control_file_t cf;
|
||
int disabled;
|
||
char *fpr_md5 = NULL;
|
||
char *fpr_sha256 = NULL;
|
||
|
||
(void)ctrl;
|
||
|
||
err = open_control_file (&cf, 1);
|
||
if (err)
|
||
return err;
|
||
|
||
err = search_control_file (cf, hexgrip, &disabled, NULL, NULL);
|
||
if (err && gpg_err_code(err) == GPG_ERR_EOF)
|
||
{
|
||
struct tm *tp;
|
||
time_t atime = time (NULL);
|
||
|
||
err = ssh_get_fingerprint_string (key, GCRY_MD_MD5, &fpr_md5);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = ssh_get_fingerprint_string (key, GCRY_MD_SHA256, &fpr_sha256);
|
||
if (err)
|
||
goto out;
|
||
|
||
/* Not yet in the file - add it. Because the file has been
|
||
opened in append mode, we simply need to write to it. */
|
||
tp = localtime (&atime);
|
||
fprintf (cf->fp,
|
||
("# %s key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
|
||
"# Fingerprints: %s\n"
|
||
"# %s\n"
|
||
"%s %d%s\n"),
|
||
spec->name,
|
||
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
|
||
tp->tm_hour, tp->tm_min, tp->tm_sec,
|
||
fpr_md5, fpr_sha256, hexgrip, ttl, confirm? " confirm":"");
|
||
|
||
}
|
||
out:
|
||
xfree (fpr_md5);
|
||
xfree (fpr_sha256);
|
||
close_control_file (cf);
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Scan the sshcontrol file and return the TTL. */
|
||
static int
|
||
ttl_from_sshcontrol (const char *hexgrip)
|
||
{
|
||
ssh_control_file_t cf;
|
||
int disabled, ttl;
|
||
|
||
if (!hexgrip || strlen (hexgrip) != 40)
|
||
return 0; /* Wrong input: Use global default. */
|
||
|
||
if (open_control_file (&cf, 0))
|
||
return 0; /* Error: Use the global default TTL. */
|
||
|
||
if (search_control_file (cf, hexgrip, &disabled, &ttl, NULL)
|
||
|| disabled)
|
||
ttl = 0; /* Use the global default if not found or disabled. */
|
||
|
||
close_control_file (cf);
|
||
|
||
return ttl;
|
||
}
|
||
|
||
|
||
/* Scan the sshcontrol file and return the confirm flag. */
|
||
static int
|
||
confirm_flag_from_sshcontrol (const char *hexgrip)
|
||
{
|
||
ssh_control_file_t cf;
|
||
int disabled, confirm;
|
||
|
||
if (!hexgrip || strlen (hexgrip) != 40)
|
||
return 1; /* Wrong input: Better ask for confirmation. */
|
||
|
||
if (open_control_file (&cf, 0))
|
||
return 1; /* Error: Better ask for confirmation. */
|
||
|
||
if (search_control_file (cf, hexgrip, &disabled, NULL, &confirm)
|
||
|| disabled)
|
||
confirm = 0; /* If not found or disabled, there is no reason to
|
||
ask for confirmation. */
|
||
|
||
close_control_file (cf);
|
||
|
||
return confirm;
|
||
}
|
||
|
||
|
||
|
||
|
||
/* Open the ssh control file for reading. This is a public version of
|
||
open_control_file. The caller must use ssh_close_control_file to
|
||
release the returned handle. */
|
||
ssh_control_file_t
|
||
ssh_open_control_file (void)
|
||
{
|
||
ssh_control_file_t cf;
|
||
|
||
/* Then look at all the registered and non-disabled keys. */
|
||
if (open_control_file (&cf, 0))
|
||
return NULL;
|
||
return cf;
|
||
}
|
||
|
||
/* Close an ssh control file handle. This is the public version of
|
||
close_control_file. CF may be NULL. */
|
||
void
|
||
ssh_close_control_file (ssh_control_file_t cf)
|
||
{
|
||
close_control_file (cf);
|
||
}
|
||
|
||
/* Read the next item from the ssh control file. The function returns
|
||
0 if a item was read, GPG_ERR_EOF on eof or another error value.
|
||
R_HEXGRIP shall either be null or a BUFFER of at least 41 byte.
|
||
R_DISABLED, R_TTLm and R_CONFIRM return flags from the control
|
||
file; they are only set on success. */
|
||
gpg_error_t
|
||
ssh_read_control_file (ssh_control_file_t cf,
|
||
char *r_hexgrip,
|
||
int *r_disabled, int *r_ttl, int *r_confirm)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
do
|
||
err = read_control_file_item (cf);
|
||
while (!err && !cf->item.valid);
|
||
if (!err)
|
||
{
|
||
if (r_hexgrip)
|
||
strcpy (r_hexgrip, cf->item.hexgrip);
|
||
if (r_disabled)
|
||
*r_disabled = cf->item.disabled;
|
||
if (r_ttl)
|
||
*r_ttl = cf->item.ttl;
|
||
if (r_confirm)
|
||
*r_confirm = cf->item.confirm;
|
||
}
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Search for a key with HEXGRIP in sshcontrol and return all
|
||
info. */
|
||
gpg_error_t
|
||
ssh_search_control_file (ssh_control_file_t cf,
|
||
const char *hexgrip,
|
||
int *r_disabled, int *r_ttl, int *r_confirm)
|
||
{
|
||
gpg_error_t err;
|
||
int i;
|
||
const char *s;
|
||
char uphexgrip[41];
|
||
|
||
/* We need to make sure that HEXGRIP is all uppercase. The easiest
|
||
way to do this and also check its length is by copying to a
|
||
second buffer. */
|
||
for (i=0, s=hexgrip; i < 40 && *s; s++, i++)
|
||
uphexgrip[i] = *s >= 'a'? (*s & 0xdf): *s;
|
||
uphexgrip[i] = 0;
|
||
if (i != 40)
|
||
err = gpg_error (GPG_ERR_INV_LENGTH);
|
||
else
|
||
err = search_control_file (cf, uphexgrip, r_disabled, r_ttl, r_confirm);
|
||
if (gpg_err_code (err) == GPG_ERR_EOF)
|
||
err = gpg_error (GPG_ERR_NOT_FOUND);
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
|
||
/*
|
||
|
||
MPI lists.
|
||
|
||
*/
|
||
|
||
/* Free the list of MPIs MPI_LIST. */
|
||
static void
|
||
mpint_list_free (gcry_mpi_t *mpi_list)
|
||
{
|
||
if (mpi_list)
|
||
{
|
||
unsigned int i;
|
||
|
||
for (i = 0; mpi_list[i]; i++)
|
||
gcry_mpi_release (mpi_list[i]);
|
||
xfree (mpi_list);
|
||
}
|
||
}
|
||
|
||
/* Receive key material MPIs from STREAM according to KEY_SPEC;
|
||
depending on SECRET expect a public key or secret key. CERT is the
|
||
certificate blob used if KEY_SPEC indicates the certificate format;
|
||
it needs to be positioned to the end of the nonce. The newly
|
||
allocated list of MPIs is stored in MPI_LIST. Returns usual error
|
||
code. */
|
||
static gpg_error_t
|
||
ssh_receive_mpint_list (estream_t stream, int secret,
|
||
ssh_key_type_spec_t *spec, estream_t cert,
|
||
gcry_mpi_t **mpi_list)
|
||
{
|
||
const char *elems_public;
|
||
unsigned int elems_n;
|
||
const char *elems;
|
||
int elem_is_secret;
|
||
gcry_mpi_t *mpis = NULL;
|
||
gpg_error_t err = 0;
|
||
unsigned int i;
|
||
|
||
if (secret)
|
||
elems = spec->elems_key_secret;
|
||
else
|
||
elems = spec->elems_key_public;
|
||
elems_n = strlen (elems);
|
||
elems_public = spec->elems_key_public;
|
||
|
||
/* Check that either both, CERT and the WITH_CERT flag, are given or
|
||
none of them. */
|
||
if (!(!!(spec->flags & SPEC_FLAG_WITH_CERT) ^ !cert))
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
|
||
goto out;
|
||
}
|
||
|
||
mpis = xtrycalloc (elems_n + 1, sizeof *mpis );
|
||
if (!mpis)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
elem_is_secret = 0;
|
||
for (i = 0; i < elems_n; i++)
|
||
{
|
||
if (secret)
|
||
elem_is_secret = !strchr (elems_public, elems[i]);
|
||
|
||
if (cert && !elem_is_secret)
|
||
err = stream_read_mpi (cert, elem_is_secret, &mpis[i]);
|
||
else
|
||
err = stream_read_mpi (stream, elem_is_secret, &mpis[i]);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
*mpi_list = mpis;
|
||
mpis = NULL;
|
||
|
||
out:
|
||
if (err)
|
||
mpint_list_free (mpis);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
/* Key modifier function for RSA. */
|
||
static gpg_error_t
|
||
ssh_key_modifier_rsa (const char *elems, gcry_mpi_t *mpis)
|
||
{
|
||
gcry_mpi_t p;
|
||
gcry_mpi_t q;
|
||
gcry_mpi_t u;
|
||
|
||
if (strcmp (elems, "nedupq"))
|
||
/* Modifying only necessary for secret keys. */
|
||
goto out;
|
||
|
||
u = mpis[3];
|
||
p = mpis[4];
|
||
q = mpis[5];
|
||
|
||
if (gcry_mpi_cmp (p, q) > 0)
|
||
{
|
||
/* P shall be smaller then Q! Swap primes. iqmp becomes u. */
|
||
gcry_mpi_t tmp;
|
||
|
||
tmp = mpis[4];
|
||
mpis[4] = mpis[5];
|
||
mpis[5] = tmp;
|
||
}
|
||
else
|
||
/* U needs to be recomputed. */
|
||
gcry_mpi_invm (u, p, q);
|
||
|
||
out:
|
||
|
||
return 0;
|
||
}
|
||
|
||
/* Signature encoder function for RSA. */
|
||
static gpg_error_t
|
||
ssh_signature_encoder_rsa (ssh_key_type_spec_t *spec,
|
||
estream_t signature_blob,
|
||
gcry_sexp_t s_signature)
|
||
{
|
||
gpg_error_t err = 0;
|
||
gcry_sexp_t valuelist = NULL;
|
||
gcry_sexp_t sublist = NULL;
|
||
gcry_mpi_t sig_value = NULL;
|
||
gcry_mpi_t *mpis = NULL;
|
||
const char *elems;
|
||
size_t elems_n;
|
||
int i;
|
||
|
||
unsigned char *data;
|
||
size_t data_n;
|
||
gcry_mpi_t s;
|
||
|
||
valuelist = gcry_sexp_nth (s_signature, 1);
|
||
if (!valuelist)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
|
||
elems = spec->elems_signature;
|
||
elems_n = strlen (elems);
|
||
|
||
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
|
||
if (!mpis)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
for (i = 0; i < elems_n; i++)
|
||
{
|
||
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
|
||
if (!sublist)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
break;
|
||
}
|
||
|
||
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
|
||
if (!sig_value)
|
||
{
|
||
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
|
||
break;
|
||
}
|
||
gcry_sexp_release (sublist);
|
||
sublist = NULL;
|
||
|
||
mpis[i] = sig_value;
|
||
}
|
||
if (err)
|
||
goto out;
|
||
|
||
/* RSA specific */
|
||
s = mpis[0];
|
||
|
||
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, s);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = stream_write_string (signature_blob, data, data_n);
|
||
xfree (data);
|
||
|
||
out:
|
||
gcry_sexp_release (valuelist);
|
||
gcry_sexp_release (sublist);
|
||
mpint_list_free (mpis);
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Signature encoder function for DSA. */
|
||
static gpg_error_t
|
||
ssh_signature_encoder_dsa (ssh_key_type_spec_t *spec,
|
||
estream_t signature_blob,
|
||
gcry_sexp_t s_signature)
|
||
{
|
||
gpg_error_t err = 0;
|
||
gcry_sexp_t valuelist = NULL;
|
||
gcry_sexp_t sublist = NULL;
|
||
gcry_mpi_t sig_value = NULL;
|
||
gcry_mpi_t *mpis = NULL;
|
||
const char *elems;
|
||
size_t elems_n;
|
||
int i;
|
||
|
||
unsigned char buffer[SSH_DSA_SIGNATURE_PADDING * SSH_DSA_SIGNATURE_ELEMS];
|
||
unsigned char *data = NULL;
|
||
size_t data_n;
|
||
|
||
valuelist = gcry_sexp_nth (s_signature, 1);
|
||
if (!valuelist)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
|
||
elems = spec->elems_signature;
|
||
elems_n = strlen (elems);
|
||
|
||
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
|
||
if (!mpis)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
for (i = 0; i < elems_n; i++)
|
||
{
|
||
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
|
||
if (!sublist)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
break;
|
||
}
|
||
|
||
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
|
||
if (!sig_value)
|
||
{
|
||
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
|
||
break;
|
||
}
|
||
gcry_sexp_release (sublist);
|
||
sublist = NULL;
|
||
|
||
mpis[i] = sig_value;
|
||
}
|
||
if (err)
|
||
goto out;
|
||
|
||
/* DSA specific code. */
|
||
|
||
/* FIXME: Why this complicated code? Why collecting boths mpis in a
|
||
buffer instead of writing them out one after the other? */
|
||
for (i = 0; i < 2; i++)
|
||
{
|
||
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &data, &data_n, mpis[i]);
|
||
if (err)
|
||
break;
|
||
|
||
if (data_n > SSH_DSA_SIGNATURE_PADDING)
|
||
{
|
||
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
|
||
break;
|
||
}
|
||
|
||
memset (buffer + (i * SSH_DSA_SIGNATURE_PADDING), 0,
|
||
SSH_DSA_SIGNATURE_PADDING - data_n);
|
||
memcpy (buffer + (i * SSH_DSA_SIGNATURE_PADDING)
|
||
+ (SSH_DSA_SIGNATURE_PADDING - data_n), data, data_n);
|
||
|
||
xfree (data);
|
||
data = NULL;
|
||
}
|
||
if (err)
|
||
goto out;
|
||
|
||
err = stream_write_string (signature_blob, buffer, sizeof (buffer));
|
||
|
||
out:
|
||
xfree (data);
|
||
gcry_sexp_release (valuelist);
|
||
gcry_sexp_release (sublist);
|
||
mpint_list_free (mpis);
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Signature encoder function for ECDSA. */
|
||
static gpg_error_t
|
||
ssh_signature_encoder_ecdsa (ssh_key_type_spec_t *spec,
|
||
estream_t stream, gcry_sexp_t s_signature)
|
||
{
|
||
gpg_error_t err = 0;
|
||
gcry_sexp_t valuelist = NULL;
|
||
gcry_sexp_t sublist = NULL;
|
||
gcry_mpi_t sig_value = NULL;
|
||
gcry_mpi_t *mpis = NULL;
|
||
const char *elems;
|
||
size_t elems_n;
|
||
int i;
|
||
|
||
unsigned char *data[2] = {NULL, NULL};
|
||
size_t data_n[2];
|
||
size_t innerlen;
|
||
|
||
valuelist = gcry_sexp_nth (s_signature, 1);
|
||
if (!valuelist)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
|
||
elems = spec->elems_signature;
|
||
elems_n = strlen (elems);
|
||
|
||
mpis = xtrycalloc (elems_n + 1, sizeof *mpis);
|
||
if (!mpis)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
for (i = 0; i < elems_n; i++)
|
||
{
|
||
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
|
||
if (!sublist)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
break;
|
||
}
|
||
|
||
sig_value = gcry_sexp_nth_mpi (sublist, 1, GCRYMPI_FMT_USG);
|
||
if (!sig_value)
|
||
{
|
||
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
|
||
break;
|
||
}
|
||
gcry_sexp_release (sublist);
|
||
sublist = NULL;
|
||
|
||
mpis[i] = sig_value;
|
||
}
|
||
if (err)
|
||
goto out;
|
||
|
||
/* ECDSA specific */
|
||
|
||
innerlen = 0;
|
||
for (i = 0; i < DIM(data); i++)
|
||
{
|
||
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &data[i], &data_n[i], mpis[i]);
|
||
if (err)
|
||
goto out;
|
||
innerlen += 4 + data_n[i];
|
||
}
|
||
|
||
err = stream_write_uint32 (stream, innerlen);
|
||
if (err)
|
||
goto out;
|
||
|
||
for (i = 0; i < DIM(data); i++)
|
||
{
|
||
err = stream_write_string (stream, data[i], data_n[i]);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
out:
|
||
for (i = 0; i < DIM(data); i++)
|
||
xfree (data[i]);
|
||
gcry_sexp_release (valuelist);
|
||
gcry_sexp_release (sublist);
|
||
mpint_list_free (mpis);
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Signature encoder function for EdDSA. */
|
||
static gpg_error_t
|
||
ssh_signature_encoder_eddsa (ssh_key_type_spec_t *spec,
|
||
estream_t stream, gcry_sexp_t s_signature)
|
||
{
|
||
gpg_error_t err = 0;
|
||
gcry_sexp_t valuelist = NULL;
|
||
gcry_sexp_t sublist = NULL;
|
||
const char *elems;
|
||
size_t elems_n;
|
||
int i;
|
||
|
||
unsigned char *data[2] = {NULL, NULL};
|
||
size_t data_n[2];
|
||
size_t totallen = 0;
|
||
|
||
valuelist = gcry_sexp_nth (s_signature, 1);
|
||
if (!valuelist)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
|
||
elems = spec->elems_signature;
|
||
elems_n = strlen (elems);
|
||
|
||
if (elems_n != DIM(data))
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
|
||
for (i = 0; i < DIM(data); i++)
|
||
{
|
||
sublist = gcry_sexp_find_token (valuelist, spec->elems_signature + i, 1);
|
||
if (!sublist)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
break;
|
||
}
|
||
|
||
data[i] = gcry_sexp_nth_buffer (sublist, 1, &data_n[i]);
|
||
if (!data[i])
|
||
{
|
||
err = gpg_error (GPG_ERR_INTERNAL); /* FIXME? */
|
||
break;
|
||
}
|
||
totallen += data_n[i];
|
||
gcry_sexp_release (sublist);
|
||
sublist = NULL;
|
||
}
|
||
if (err)
|
||
goto out;
|
||
|
||
err = stream_write_uint32 (stream, totallen);
|
||
if (err)
|
||
goto out;
|
||
|
||
for (i = 0; i < DIM(data); i++)
|
||
{
|
||
err = stream_write_data (stream, data[i], data_n[i]);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
out:
|
||
for (i = 0; i < DIM(data); i++)
|
||
xfree (data[i]);
|
||
gcry_sexp_release (valuelist);
|
||
gcry_sexp_release (sublist);
|
||
return err;
|
||
}
|
||
|
||
|
||
/*
|
||
S-Expressions.
|
||
*/
|
||
|
||
|
||
/* This function constructs a new S-Expression for the key identified
|
||
by the KEY_SPEC, SECRET, CURVE_NAME, MPIS, and COMMENT, which is to
|
||
be stored at R_SEXP. Returns an error code. */
|
||
static gpg_error_t
|
||
sexp_key_construct (gcry_sexp_t *r_sexp,
|
||
ssh_key_type_spec_t key_spec, int secret,
|
||
const char *curve_name, gcry_mpi_t *mpis,
|
||
const char *comment)
|
||
{
|
||
gpg_error_t err;
|
||
gcry_sexp_t sexp_new = NULL;
|
||
void *formatbuf = NULL;
|
||
void **arg_list = NULL;
|
||
estream_t format = NULL;
|
||
char *algo_name = NULL;
|
||
|
||
if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
|
||
{
|
||
/* It is much easier and more readable to use a separate code
|
||
path for EdDSA. */
|
||
if (!curve_name)
|
||
err = gpg_error (GPG_ERR_INV_CURVE);
|
||
else if (!mpis[0] || !gcry_mpi_get_flag (mpis[0], GCRYMPI_FLAG_OPAQUE))
|
||
err = gpg_error (GPG_ERR_BAD_PUBKEY);
|
||
else if (secret
|
||
&& (!mpis[1]
|
||
|| !gcry_mpi_get_flag (mpis[1], GCRYMPI_FLAG_OPAQUE)))
|
||
err = gpg_error (GPG_ERR_BAD_SECKEY);
|
||
else if (secret)
|
||
err = gcry_sexp_build (&sexp_new, NULL,
|
||
"(private-key(ecc(curve %s)"
|
||
"(flags eddsa)(q %m)(d %m))"
|
||
"(comment%s))",
|
||
curve_name,
|
||
mpis[0], mpis[1],
|
||
comment? comment:"");
|
||
else
|
||
err = gcry_sexp_build (&sexp_new, NULL,
|
||
"(public-key(ecc(curve %s)"
|
||
"(flags eddsa)(q %m))"
|
||
"(comment%s))",
|
||
curve_name,
|
||
mpis[0],
|
||
comment? comment:"");
|
||
}
|
||
else
|
||
{
|
||
const char *key_identifier[] = { "public-key", "private-key" };
|
||
int arg_idx;
|
||
const char *elems;
|
||
size_t elems_n;
|
||
unsigned int i, j;
|
||
|
||
if (secret)
|
||
elems = key_spec.elems_sexp_order;
|
||
else
|
||
elems = key_spec.elems_key_public;
|
||
elems_n = strlen (elems);
|
||
|
||
format = es_fopenmem (0, "a+b");
|
||
if (!format)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
/* Key identifier, algorithm identifier, mpis, comment, and a NULL
|
||
as a safeguard. */
|
||
arg_list = xtrymalloc (sizeof (*arg_list) * (2 + 1 + elems_n + 1 + 1));
|
||
if (!arg_list)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
arg_idx = 0;
|
||
|
||
es_fputs ("(%s(%s", format);
|
||
arg_list[arg_idx++] = &key_identifier[secret];
|
||
algo_name = xtrystrdup (gcry_pk_algo_name (key_spec.algo));
|
||
if (!algo_name)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
strlwr (algo_name);
|
||
arg_list[arg_idx++] = &algo_name;
|
||
if (curve_name)
|
||
{
|
||
es_fputs ("(curve%s)", format);
|
||
arg_list[arg_idx++] = &curve_name;
|
||
}
|
||
|
||
for (i = 0; i < elems_n; i++)
|
||
{
|
||
es_fprintf (format, "(%c%%m)", elems[i]);
|
||
if (secret)
|
||
{
|
||
for (j = 0; j < elems_n; j++)
|
||
if (key_spec.elems_key_secret[j] == elems[i])
|
||
break;
|
||
}
|
||
else
|
||
j = i;
|
||
arg_list[arg_idx++] = &mpis[j];
|
||
}
|
||
es_fputs (")(comment%s))", format);
|
||
arg_list[arg_idx++] = &comment;
|
||
arg_list[arg_idx] = NULL;
|
||
|
||
es_putc (0, format);
|
||
if (es_ferror (format))
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
if (es_fclose_snatch (format, &formatbuf, NULL))
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
format = NULL;
|
||
|
||
err = gcry_sexp_build_array (&sexp_new, NULL, formatbuf, arg_list);
|
||
}
|
||
|
||
if (!err)
|
||
*r_sexp = sexp_new;
|
||
|
||
out:
|
||
es_fclose (format);
|
||
xfree (arg_list);
|
||
xfree (formatbuf);
|
||
xfree (algo_name);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* This function extracts the key from the s-expression SEXP according
|
||
to KEY_SPEC and stores it in ssh format at (R_BLOB, R_BLOBLEN). If
|
||
WITH_SECRET is true, the secret key parts are also extracted if
|
||
possible. Returns 0 on success or an error code. Note that data
|
||
stored at R_BLOB must be freed using es_free! */
|
||
static gpg_error_t
|
||
ssh_key_to_blob (gcry_sexp_t sexp, int with_secret,
|
||
ssh_key_type_spec_t key_spec,
|
||
void **r_blob, size_t *r_blob_size)
|
||
{
|
||
gpg_error_t err = 0;
|
||
gcry_sexp_t value_list = NULL;
|
||
gcry_sexp_t value_pair = NULL;
|
||
char *curve_name = NULL;
|
||
estream_t stream = NULL;
|
||
void *blob = NULL;
|
||
size_t blob_size;
|
||
const char *elems, *p_elems;
|
||
const char *data;
|
||
size_t datalen;
|
||
|
||
*r_blob = NULL;
|
||
*r_blob_size = 0;
|
||
|
||
stream = es_fopenmem (0, "r+b");
|
||
if (!stream)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
/* Get the type of the key extpression. */
|
||
data = gcry_sexp_nth_data (sexp, 0, &datalen);
|
||
if (!data)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
|
||
if ((datalen == 10 && !strncmp (data, "public-key", 10))
|
||
|| (datalen == 21 && !strncmp (data, "protected-private-key", 21))
|
||
|| (datalen == 20 && !strncmp (data, "shadowed-private-key", 20)))
|
||
elems = key_spec.elems_key_public;
|
||
else if (datalen == 11 && !strncmp (data, "private-key", 11))
|
||
elems = with_secret? key_spec.elems_key_secret : key_spec.elems_key_public;
|
||
else
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
|
||
/* Get key value list. */
|
||
value_list = gcry_sexp_cadr (sexp);
|
||
if (!value_list)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
|
||
/* Write the ssh algorithm identifier. */
|
||
if ((key_spec.flags & SPEC_FLAG_IS_ECDSA))
|
||
{
|
||
/* Parse the "curve" parameter. We currently expect the curve
|
||
name for ECC and not the parameters of the curve. This can
|
||
easily be changed but then we need to find the curve name
|
||
from the parameters using gcry_pk_get_curve. */
|
||
const char *mapped;
|
||
const char *sshname;
|
||
|
||
gcry_sexp_release (value_pair);
|
||
value_pair = gcry_sexp_find_token (value_list, "curve", 5);
|
||
if (!value_pair)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_CURVE);
|
||
goto out;
|
||
}
|
||
curve_name = gcry_sexp_nth_string (value_pair, 1);
|
||
if (!curve_name)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_CURVE); /* (Or out of core.) */
|
||
goto out;
|
||
}
|
||
|
||
/* Fixme: The mapping should be done by using gcry_pk_get_curve
|
||
et al to iterate over all name aliases. */
|
||
if (!strcmp (curve_name, "NIST P-256"))
|
||
mapped = "nistp256";
|
||
else if (!strcmp (curve_name, "NIST P-384"))
|
||
mapped = "nistp384";
|
||
else if (!strcmp (curve_name, "NIST P-521"))
|
||
mapped = "nistp521";
|
||
else
|
||
mapped = NULL;
|
||
if (mapped)
|
||
{
|
||
xfree (curve_name);
|
||
curve_name = xtrystrdup (mapped);
|
||
if (!curve_name)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
sshname = ssh_identifier_from_curve_name (curve_name);
|
||
if (!sshname)
|
||
{
|
||
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
|
||
goto out;
|
||
}
|
||
err = stream_write_cstring (stream, sshname);
|
||
if (err)
|
||
goto out;
|
||
err = stream_write_cstring (stream, curve_name);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
/* Note: This is also used for EdDSA. */
|
||
err = stream_write_cstring (stream, key_spec.ssh_identifier);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
/* Write the parameters. */
|
||
for (p_elems = elems; *p_elems; p_elems++)
|
||
{
|
||
gcry_sexp_release (value_pair);
|
||
value_pair = gcry_sexp_find_token (value_list, p_elems, 1);
|
||
if (!value_pair)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
if ((key_spec.flags & SPEC_FLAG_IS_EdDSA))
|
||
{
|
||
|
||
data = gcry_sexp_nth_data (value_pair, 1, &datalen);
|
||
if (!data)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
if (*p_elems == 'q' && datalen)
|
||
{ /* Remove the prefix 0x40. */
|
||
data++;
|
||
datalen--;
|
||
}
|
||
err = stream_write_string (stream, data, datalen);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
gcry_mpi_t mpi;
|
||
|
||
/* Note that we need to use STD format; i.e. prepend a 0x00
|
||
to indicate a positive number if the high bit is set. */
|
||
mpi = gcry_sexp_nth_mpi (value_pair, 1, GCRYMPI_FMT_STD);
|
||
if (!mpi)
|
||
{
|
||
err = gpg_error (GPG_ERR_INV_SEXP);
|
||
goto out;
|
||
}
|
||
err = stream_write_mpi (stream, mpi);
|
||
gcry_mpi_release (mpi);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
if (es_fclose_snatch (stream, &blob, &blob_size))
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
stream = NULL;
|
||
|
||
*r_blob = blob;
|
||
blob = NULL;
|
||
*r_blob_size = blob_size;
|
||
|
||
out:
|
||
gcry_sexp_release (value_list);
|
||
gcry_sexp_release (value_pair);
|
||
xfree (curve_name);
|
||
es_fclose (stream);
|
||
es_free (blob);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/*
|
||
|
||
Key I/O.
|
||
|
||
*/
|
||
|
||
/* Search for a key specification entry. If SSH_NAME is not NULL,
|
||
search for an entry whose "ssh_name" is equal to SSH_NAME;
|
||
otherwise, search for an entry whose algorithm is equal to ALGO.
|
||
Store found entry in SPEC on success, return error otherwise. */
|
||
static gpg_error_t
|
||
ssh_key_type_lookup (const char *ssh_name, int algo,
|
||
ssh_key_type_spec_t *spec)
|
||
{
|
||
gpg_error_t err;
|
||
unsigned int i;
|
||
|
||
for (i = 0; i < DIM (ssh_key_types); i++)
|
||
if ((ssh_name && (! strcmp (ssh_name, ssh_key_types[i].ssh_identifier)))
|
||
|| algo == ssh_key_types[i].algo)
|
||
break;
|
||
|
||
if (i == DIM (ssh_key_types))
|
||
err = gpg_error (GPG_ERR_NOT_FOUND);
|
||
else
|
||
{
|
||
*spec = ssh_key_types[i];
|
||
err = 0;
|
||
}
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Receive a key from STREAM, according to the key specification given
|
||
as KEY_SPEC. Depending on SECRET, receive a secret or a public
|
||
key. If READ_COMMENT is true, receive a comment string as well.
|
||
Constructs a new S-Expression from received data and stores it in
|
||
KEY_NEW. Returns zero on success or an error code. */
|
||
static gpg_error_t
|
||
ssh_receive_key (estream_t stream, gcry_sexp_t *key_new, int secret,
|
||
int read_comment, ssh_key_type_spec_t *key_spec)
|
||
{
|
||
gpg_error_t err;
|
||
char *key_type = NULL;
|
||
char *comment = NULL;
|
||
estream_t cert = NULL;
|
||
gcry_sexp_t key = NULL;
|
||
ssh_key_type_spec_t spec;
|
||
gcry_mpi_t *mpi_list = NULL;
|
||
const char *elems;
|
||
char *curve_name = NULL;
|
||
|
||
|
||
err = stream_read_cstring (stream, &key_type);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = ssh_key_type_lookup (key_type, 0, &spec);
|
||
if (err)
|
||
goto out;
|
||
|
||
if ((spec.flags & SPEC_FLAG_WITH_CERT))
|
||
{
|
||
/* This is an OpenSSH certificate+private key. The certificate
|
||
is an SSH string and which we store in an estream object. */
|
||
unsigned char *buffer;
|
||
u32 buflen;
|
||
char *cert_key_type;
|
||
|
||
err = stream_read_string (stream, 0, &buffer, &buflen);
|
||
if (err)
|
||
goto out;
|
||
cert = es_fopenmem_init (0, "rb", buffer, buflen);
|
||
xfree (buffer);
|
||
if (!cert)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
/* Check that the key type matches. */
|
||
err = stream_read_cstring (cert, &cert_key_type);
|
||
if (err)
|
||
goto out;
|
||
if (strcmp (cert_key_type, key_type) )
|
||
{
|
||
xfree (cert_key_type);
|
||
log_error ("key types in received ssh certificate do not match\n");
|
||
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
|
||
goto out;
|
||
}
|
||
xfree (cert_key_type);
|
||
|
||
/* Skip the nonce. */
|
||
err = stream_read_string (cert, 0, NULL, NULL);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
|
||
{
|
||
/* The format of an EdDSA key is:
|
||
* string key_type ("ssh-ed25519")
|
||
* string public_key
|
||
* string private_key
|
||
*
|
||
* Note that the private key is the concatenation of the private
|
||
* key with the public key. Thus there's are 64 bytes; however
|
||
* we only want the real 32 byte private key - Libgcrypt expects
|
||
* this.
|
||
*/
|
||
mpi_list = xtrycalloc (3, sizeof *mpi_list);
|
||
if (!mpi_list)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
err = stream_read_blob (cert? cert : stream, 0, &mpi_list[0]);
|
||
if (err)
|
||
goto out;
|
||
if (secret)
|
||
{
|
||
u32 len = 0;
|
||
unsigned char *buffer;
|
||
|
||
/* Read string length. */
|
||
err = stream_read_uint32 (stream, &len);
|
||
if (err)
|
||
goto out;
|
||
if (len != 32 && len != 64)
|
||
{
|
||
err = gpg_error (GPG_ERR_BAD_SECKEY);
|
||
goto out;
|
||
}
|
||
buffer = xtrymalloc_secure (32);
|
||
if (!buffer)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
err = stream_read_data (stream, buffer, 32);
|
||
if (err)
|
||
{
|
||
xfree (buffer);
|
||
goto out;
|
||
}
|
||
mpi_list[1] = gcry_mpi_set_opaque (NULL, buffer, 8*32);
|
||
buffer = NULL;
|
||
if (len == 64)
|
||
{
|
||
err = stream_read_skip (stream, 32);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
else if ((spec.flags & SPEC_FLAG_IS_ECDSA))
|
||
{
|
||
/* The format of an ECDSA key is:
|
||
* string key_type ("ecdsa-sha2-nistp256" |
|
||
* "ecdsa-sha2-nistp384" |
|
||
* "ecdsa-sha2-nistp521" )
|
||
* string ecdsa_curve_name
|
||
* string ecdsa_public_key
|
||
* mpint ecdsa_private
|
||
*
|
||
* Note that we use the mpint reader instead of the string
|
||
* reader for ecsa_public_key. For the certificate variante
|
||
* ecdsa_curve_name+ecdsa_public_key are replaced by the
|
||
* certificate.
|
||
*/
|
||
unsigned char *buffer;
|
||
const char *mapped;
|
||
|
||
err = stream_read_string (cert? cert : stream, 0, &buffer, NULL);
|
||
if (err)
|
||
goto out;
|
||
curve_name = buffer;
|
||
/* Fixme: Check that curve_name matches the keytype. */
|
||
/* Because Libgcrypt < 1.6 has no support for the "nistpNNN"
|
||
curve names, we need to translate them here to Libgcrypt's
|
||
native names. */
|
||
if (!strcmp (curve_name, "nistp256"))
|
||
mapped = "NIST P-256";
|
||
else if (!strcmp (curve_name, "nistp384"))
|
||
mapped = "NIST P-384";
|
||
else if (!strcmp (curve_name, "nistp521"))
|
||
mapped = "NIST P-521";
|
||
else
|
||
mapped = NULL;
|
||
if (mapped)
|
||
{
|
||
xfree (curve_name);
|
||
curve_name = xtrystrdup (mapped);
|
||
if (!curve_name)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
err = ssh_receive_mpint_list (stream, secret, &spec, cert, &mpi_list);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
if (read_comment)
|
||
{
|
||
err = stream_read_cstring (stream, &comment);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
if (secret)
|
||
elems = spec.elems_key_secret;
|
||
else
|
||
elems = spec.elems_key_public;
|
||
|
||
if (spec.key_modifier)
|
||
{
|
||
err = (*spec.key_modifier) (elems, mpi_list);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
|
||
{
|
||
if (secret)
|
||
{
|
||
err = gcry_sexp_build (&key, NULL,
|
||
"(private-key(ecc(curve \"Ed25519\")"
|
||
"(flags eddsa)(q %m)(d %m))"
|
||
"(comment%s))",
|
||
mpi_list[0], mpi_list[1],
|
||
comment? comment:"");
|
||
}
|
||
else
|
||
{
|
||
err = gcry_sexp_build (&key, NULL,
|
||
"(public-key(ecc(curve \"Ed25519\")"
|
||
"(flags eddsa)(q %m))"
|
||
"(comment%s))",
|
||
mpi_list[0],
|
||
comment? comment:"");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
err = sexp_key_construct (&key, spec, secret, curve_name, mpi_list,
|
||
comment? comment:"");
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
if (key_spec)
|
||
*key_spec = spec;
|
||
*key_new = key;
|
||
|
||
out:
|
||
es_fclose (cert);
|
||
mpint_list_free (mpi_list);
|
||
xfree (curve_name);
|
||
xfree (key_type);
|
||
xfree (comment);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Write the public key from KEY to STREAM in SSH key format. If
|
||
OVERRIDE_COMMENT is not NULL, it will be used instead of the
|
||
comment stored in the key. */
|
||
static gpg_error_t
|
||
ssh_send_key_public (estream_t stream, gcry_sexp_t key,
|
||
const char *override_comment)
|
||
{
|
||
ssh_key_type_spec_t spec;
|
||
int algo;
|
||
char *comment = NULL;
|
||
void *blob = NULL;
|
||
size_t bloblen;
|
||
gpg_error_t err = 0;
|
||
|
||
algo = get_pk_algo_from_key (key);
|
||
if (algo == 0)
|
||
goto out;
|
||
|
||
err = ssh_key_type_lookup (NULL, algo, &spec);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = ssh_key_to_blob (key, 0, spec, &blob, &bloblen);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = stream_write_string (stream, blob, bloblen);
|
||
if (err)
|
||
goto out;
|
||
|
||
if (override_comment)
|
||
err = stream_write_cstring (stream, override_comment);
|
||
else
|
||
{
|
||
err = ssh_key_extract_comment (key, &comment);
|
||
if (err)
|
||
err = stream_write_cstring (stream, "(none)");
|
||
else
|
||
err = stream_write_cstring (stream, comment);
|
||
}
|
||
if (err)
|
||
goto out;
|
||
|
||
out:
|
||
xfree (comment);
|
||
es_free (blob);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Read a public key out of BLOB/BLOB_SIZE according to the key
|
||
specification given as KEY_SPEC, storing the new key in KEY_PUBLIC.
|
||
Returns zero on success or an error code. */
|
||
static gpg_error_t
|
||
ssh_read_key_public_from_blob (unsigned char *blob, size_t blob_size,
|
||
gcry_sexp_t *key_public,
|
||
ssh_key_type_spec_t *key_spec)
|
||
{
|
||
gpg_error_t err;
|
||
estream_t blob_stream;
|
||
|
||
blob_stream = es_fopenmem (0, "r+b");
|
||
if (!blob_stream)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
err = stream_write_data (blob_stream, blob, blob_size);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = es_fseek (blob_stream, 0, SEEK_SET);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = ssh_receive_key (blob_stream, key_public, 0, 0, key_spec);
|
||
|
||
out:
|
||
es_fclose (blob_stream);
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
/* This function calculates the key grip for the key contained in the
|
||
S-Expression KEY and writes it to BUFFER, which must be large
|
||
enough to hold it. Returns usual error code. */
|
||
static gpg_error_t
|
||
ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
|
||
{
|
||
if (!gcry_pk_get_keygrip (key, buffer))
|
||
{
|
||
gpg_error_t err = gcry_pk_testkey (key);
|
||
return err? err : gpg_error (GPG_ERR_INTERNAL);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
static gpg_error_t
|
||
card_key_list (ctrl_t ctrl, char **r_serialno, strlist_t *result)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
*r_serialno = NULL;
|
||
*result = NULL;
|
||
|
||
err = agent_card_serialno (ctrl, r_serialno, NULL);
|
||
if (err)
|
||
{
|
||
if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose)
|
||
log_info (_("error getting serial number of card: %s\n"),
|
||
gpg_strerror (err));
|
||
|
||
/* Nothing available. */
|
||
return 0;
|
||
}
|
||
|
||
err = agent_card_cardlist (ctrl, result);
|
||
if (err)
|
||
{
|
||
xfree (*r_serialno);
|
||
*r_serialno = NULL;
|
||
}
|
||
return err;
|
||
}
|
||
|
||
/* Check whether a smartcard is available and whether it has a usable
|
||
key. Store a copy of that key at R_PK and return 0. If no key is
|
||
available store NULL at R_PK and return an error code. If CARDSN
|
||
is not NULL, a string with the serial number of the card will be
|
||
a malloced and stored there. */
|
||
static gpg_error_t
|
||
card_key_available (ctrl_t ctrl, gcry_sexp_t *r_pk, char **cardsn)
|
||
{
|
||
gpg_error_t err;
|
||
char *authkeyid;
|
||
char *serialno = NULL;
|
||
unsigned char *pkbuf;
|
||
size_t pkbuflen;
|
||
gcry_sexp_t s_pk;
|
||
unsigned char grip[20];
|
||
|
||
*r_pk = NULL;
|
||
if (cardsn)
|
||
*cardsn = NULL;
|
||
|
||
/* First see whether a card is available and whether the application
|
||
is supported. */
|
||
err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid);
|
||
if ( gpg_err_code (err) == GPG_ERR_CARD_REMOVED )
|
||
{
|
||
/* Ask for the serial number to reset the card. */
|
||
err = agent_card_serialno (ctrl, &serialno, NULL);
|
||
if (err)
|
||
{
|
||
if (opt.verbose)
|
||
log_info (_("error getting serial number of card: %s\n"),
|
||
gpg_strerror (err));
|
||
return err;
|
||
}
|
||
log_info (_("detected card with S/N: %s\n"), serialno);
|
||
err = agent_card_getattr (ctrl, "$AUTHKEYID", &authkeyid);
|
||
}
|
||
if (err)
|
||
{
|
||
log_error (_("no authentication key for ssh on card: %s\n"),
|
||
gpg_strerror (err));
|
||
xfree (serialno);
|
||
return err;
|
||
}
|
||
|
||
/* Get the S/N if we don't have it yet. Use the fast getattr method. */
|
||
if (!serialno && (err = agent_card_getattr (ctrl, "SERIALNO", &serialno)) )
|
||
{
|
||
log_error (_("error getting serial number of card: %s\n"),
|
||
gpg_strerror (err));
|
||
xfree (authkeyid);
|
||
return err;
|
||
}
|
||
|
||
/* Read the public key. */
|
||
err = agent_card_readkey (ctrl, authkeyid, &pkbuf);
|
||
if (err)
|
||
{
|
||
if (opt.verbose)
|
||
log_info (_("no suitable card key found: %s\n"), gpg_strerror (err));
|
||
xfree (serialno);
|
||
xfree (authkeyid);
|
||
return err;
|
||
}
|
||
|
||
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
|
||
err = gcry_sexp_sscan (&s_pk, NULL, (char*)pkbuf, pkbuflen);
|
||
if (err)
|
||
{
|
||
log_error ("failed to build S-Exp from received card key: %s\n",
|
||
gpg_strerror (err));
|
||
xfree (pkbuf);
|
||
xfree (serialno);
|
||
xfree (authkeyid);
|
||
return err;
|
||
}
|
||
|
||
err = ssh_key_grip (s_pk, grip);
|
||
if (err)
|
||
{
|
||
log_debug ("error computing keygrip from received card key: %s\n",
|
||
gcry_strerror (err));
|
||
xfree (pkbuf);
|
||
gcry_sexp_release (s_pk);
|
||
xfree (serialno);
|
||
xfree (authkeyid);
|
||
return err;
|
||
}
|
||
|
||
if ( agent_key_available (grip) )
|
||
{
|
||
/* (Shadow)-key is not available in our key storage. */
|
||
err = agent_write_shadow_key (grip, serialno, authkeyid, pkbuf, 0);
|
||
if (err)
|
||
{
|
||
xfree (pkbuf);
|
||
gcry_sexp_release (s_pk);
|
||
xfree (serialno);
|
||
xfree (authkeyid);
|
||
return err;
|
||
}
|
||
}
|
||
|
||
if (cardsn)
|
||
{
|
||
char *dispsn;
|
||
|
||
/* If the card handler is able to return a short serialnumber,
|
||
use that one, else use the complete serialno. */
|
||
if (!agent_card_getattr (ctrl, "$DISPSERIALNO", &dispsn))
|
||
{
|
||
*cardsn = xtryasprintf ("cardno:%s", dispsn);
|
||
xfree (dispsn);
|
||
}
|
||
else
|
||
*cardsn = xtryasprintf ("cardno:%s", serialno);
|
||
if (!*cardsn)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
xfree (pkbuf);
|
||
gcry_sexp_release (s_pk);
|
||
xfree (serialno);
|
||
xfree (authkeyid);
|
||
return err;
|
||
}
|
||
}
|
||
|
||
xfree (pkbuf);
|
||
xfree (serialno);
|
||
xfree (authkeyid);
|
||
*r_pk = s_pk;
|
||
return 0;
|
||
}
|
||
|
||
|
||
|
||
|
||
/*
|
||
|
||
Request handler. Each handler is provided with a CTRL context, a
|
||
REQUEST object and a RESPONSE object. The actual request is to be
|
||
read from REQUEST, the response needs to be written to RESPONSE.
|
||
|
||
*/
|
||
|
||
|
||
/* Handler for the "request_identities" command. */
|
||
static gpg_error_t
|
||
ssh_handler_request_identities (ctrl_t ctrl,
|
||
estream_t request, estream_t response)
|
||
{
|
||
u32 key_counter;
|
||
estream_t key_blobs;
|
||
gcry_sexp_t key_public;
|
||
gpg_error_t err;
|
||
int ret;
|
||
ssh_control_file_t cf = NULL;
|
||
gpg_error_t ret_err;
|
||
|
||
(void)request;
|
||
|
||
/* Prepare buffer stream. */
|
||
|
||
key_public = NULL;
|
||
key_counter = 0;
|
||
|
||
key_blobs = es_fopenmem (0, "r+b");
|
||
if (! key_blobs)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
/* First check whether a key is currently available in the card
|
||
reader - this should be allowed even without being listed in
|
||
sshcontrol. */
|
||
|
||
if (!opt.disable_scdaemon)
|
||
{
|
||
char *serialno;
|
||
strlist_t card_list, sl;
|
||
|
||
err = card_key_list (ctrl, &serialno, &card_list);
|
||
if (err)
|
||
{
|
||
if (opt.verbose)
|
||
log_info (_("error getting list of cards: %s\n"),
|
||
gpg_strerror (err));
|
||
goto scd_out;
|
||
}
|
||
|
||
for (sl = card_list; sl; sl = sl->next)
|
||
{
|
||
char *serialno0;
|
||
char *cardsn;
|
||
|
||
err = agent_card_serialno (ctrl, &serialno0, sl->d);
|
||
if (err)
|
||
{
|
||
if (opt.verbose)
|
||
log_info (_("error getting serial number of card: %s\n"),
|
||
gpg_strerror (err));
|
||
continue;
|
||
}
|
||
|
||
xfree (serialno0);
|
||
if (card_key_available (ctrl, &key_public, &cardsn))
|
||
continue;
|
||
|
||
err = ssh_send_key_public (key_blobs, key_public, cardsn);
|
||
gcry_sexp_release (key_public);
|
||
key_public = NULL;
|
||
xfree (cardsn);
|
||
if (err)
|
||
{
|
||
xfree (serialno);
|
||
free_strlist (card_list);
|
||
goto out;
|
||
}
|
||
|
||
key_counter++;
|
||
}
|
||
|
||
xfree (serialno);
|
||
free_strlist (card_list);
|
||
}
|
||
|
||
scd_out:
|
||
/* Then look at all the registered and non-disabled keys. */
|
||
err = open_control_file (&cf, 0);
|
||
if (err)
|
||
goto out;
|
||
|
||
while (!read_control_file_item (cf))
|
||
{
|
||
unsigned char grip[20];
|
||
|
||
if (!cf->item.valid)
|
||
continue; /* Should not happen. */
|
||
if (cf->item.disabled)
|
||
continue;
|
||
assert (strlen (cf->item.hexgrip) == 40);
|
||
hex2bin (cf->item.hexgrip, grip, sizeof (grip));
|
||
|
||
err = agent_public_key_from_file (ctrl, grip, &key_public);
|
||
if (err)
|
||
{
|
||
log_error ("%s:%d: key '%s' skipped: %s\n",
|
||
cf->fname, cf->lnr, cf->item.hexgrip,
|
||
gpg_strerror (err));
|
||
continue;
|
||
}
|
||
|
||
err = ssh_send_key_public (key_blobs, key_public, NULL);
|
||
if (err)
|
||
goto out;
|
||
gcry_sexp_release (key_public);
|
||
key_public = NULL;
|
||
|
||
key_counter++;
|
||
}
|
||
err = 0;
|
||
|
||
ret = es_fseek (key_blobs, 0, SEEK_SET);
|
||
if (ret)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
out:
|
||
/* Send response. */
|
||
|
||
gcry_sexp_release (key_public);
|
||
|
||
if (!err)
|
||
{
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_IDENTITIES_ANSWER);
|
||
if (!ret_err)
|
||
ret_err = stream_write_uint32 (response, key_counter);
|
||
if (!ret_err)
|
||
ret_err = stream_copy (response, key_blobs);
|
||
}
|
||
else
|
||
{
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
|
||
}
|
||
|
||
es_fclose (key_blobs);
|
||
close_control_file (cf);
|
||
|
||
return ret_err;
|
||
}
|
||
|
||
|
||
/* This function hashes the data contained in DATA of size DATA_N
|
||
according to the message digest algorithm specified by MD_ALGORITHM
|
||
and writes the message digest to HASH, which needs to large enough
|
||
for the digest. */
|
||
static gpg_error_t
|
||
data_hash (unsigned char *data, size_t data_n,
|
||
int md_algorithm, unsigned char *hash)
|
||
{
|
||
gcry_md_hash_buffer (md_algorithm, hash, data, data_n);
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* This function signs the data described by CTRL. If HASH is not
|
||
NULL, (HASH,HASHLEN) overrides the hash stored in CTRL. This is to
|
||
allow the use of signature algorithms that implement the hashing
|
||
internally (e.g. Ed25519). On success the created signature is
|
||
stored in ssh format at R_SIG and it's size at R_SIGLEN; the caller
|
||
must use es_free to releaase this memory. */
|
||
static gpg_error_t
|
||
data_sign (ctrl_t ctrl, ssh_key_type_spec_t *spec,
|
||
const void *hash, size_t hashlen,
|
||
unsigned char **r_sig, size_t *r_siglen)
|
||
{
|
||
gpg_error_t err;
|
||
gcry_sexp_t signature_sexp = NULL;
|
||
estream_t stream = NULL;
|
||
void *blob = NULL;
|
||
size_t bloblen;
|
||
char hexgrip[40+1];
|
||
|
||
*r_sig = NULL;
|
||
*r_siglen = 0;
|
||
|
||
/* Quick check to see whether we have a valid keygrip and convert it
|
||
to hex. */
|
||
if (!ctrl->have_keygrip)
|
||
{
|
||
err = gpg_error (GPG_ERR_NO_SECKEY);
|
||
goto out;
|
||
}
|
||
bin2hex (ctrl->keygrip, 20, hexgrip);
|
||
|
||
/* Ask for confirmation if needed. */
|
||
if (confirm_flag_from_sshcontrol (hexgrip))
|
||
{
|
||
gcry_sexp_t key;
|
||
char *fpr, *prompt;
|
||
char *comment = NULL;
|
||
|
||
err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key);
|
||
if (err)
|
||
goto out;
|
||
err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &fpr);
|
||
if (!err)
|
||
{
|
||
gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0);
|
||
if (tmpsxp)
|
||
comment = gcry_sexp_nth_string (tmpsxp, 1);
|
||
gcry_sexp_release (tmpsxp);
|
||
}
|
||
gcry_sexp_release (key);
|
||
if (err)
|
||
goto out;
|
||
prompt = xtryasprintf (L_("An ssh process requested the use of key%%0A"
|
||
" %s%%0A"
|
||
" (%s)%%0A"
|
||
"Do you want to allow this?"),
|
||
fpr, comment? comment:"");
|
||
xfree (fpr);
|
||
gcry_free (comment);
|
||
err = agent_get_confirmation (ctrl, prompt, L_("Allow"), L_("Deny"), 0);
|
||
xfree (prompt);
|
||
if (err)
|
||
goto out;
|
||
}
|
||
|
||
/* Create signature. */
|
||
ctrl->use_auth_call = 1;
|
||
err = agent_pksign_do (ctrl, NULL,
|
||
L_("Please enter the passphrase "
|
||
"for the ssh key%%0A %F%%0A (%c)"),
|
||
&signature_sexp,
|
||
CACHE_MODE_SSH, ttl_from_sshcontrol,
|
||
hash, hashlen);
|
||
ctrl->use_auth_call = 0;
|
||
if (err)
|
||
goto out;
|
||
|
||
stream = es_fopenmem (0, "r+b");
|
||
if (!stream)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
err = stream_write_cstring (stream, spec->ssh_identifier);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = spec->signature_encoder (spec, stream, signature_sexp);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = es_fclose_snatch (stream, &blob, &bloblen);
|
||
if (err)
|
||
goto out;
|
||
stream = NULL;
|
||
|
||
*r_sig = blob; blob = NULL;
|
||
*r_siglen = bloblen;
|
||
|
||
out:
|
||
xfree (blob);
|
||
es_fclose (stream);
|
||
gcry_sexp_release (signature_sexp);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Handler for the "sign_request" command. */
|
||
static gpg_error_t
|
||
ssh_handler_sign_request (ctrl_t ctrl, estream_t request, estream_t response)
|
||
{
|
||
gcry_sexp_t key = NULL;
|
||
ssh_key_type_spec_t spec;
|
||
unsigned char hash[MAX_DIGEST_LEN];
|
||
unsigned int hash_n;
|
||
unsigned char key_grip[20];
|
||
unsigned char *key_blob = NULL;
|
||
u32 key_blob_size;
|
||
unsigned char *data = NULL;
|
||
unsigned char *sig = NULL;
|
||
size_t sig_n;
|
||
u32 data_size;
|
||
u32 flags;
|
||
gpg_error_t err;
|
||
gpg_error_t ret_err;
|
||
int hash_algo;
|
||
|
||
/* Receive key. */
|
||
|
||
err = stream_read_string (request, 0, &key_blob, &key_blob_size);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, &spec);
|
||
if (err)
|
||
goto out;
|
||
|
||
/* Receive data to sign. */
|
||
err = stream_read_string (request, 0, &data, &data_size);
|
||
if (err)
|
||
goto out;
|
||
|
||
/* FIXME? */
|
||
err = stream_read_uint32 (request, &flags);
|
||
if (err)
|
||
goto out;
|
||
|
||
hash_algo = spec.hash_algo;
|
||
if (!hash_algo)
|
||
hash_algo = GCRY_MD_SHA1; /* Use the default. */
|
||
ctrl->digest.algo = hash_algo;
|
||
if ((spec.flags & SPEC_FLAG_USE_PKCS1V2))
|
||
ctrl->digest.raw_value = 0;
|
||
else
|
||
ctrl->digest.raw_value = 1;
|
||
|
||
/* Calculate key grip. */
|
||
err = ssh_key_grip (key, key_grip);
|
||
if (err)
|
||
goto out;
|
||
ctrl->have_keygrip = 1;
|
||
memcpy (ctrl->keygrip, key_grip, 20);
|
||
|
||
/* Hash data unless we use EdDSA. */
|
||
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
|
||
{
|
||
ctrl->digest.valuelen = 0;
|
||
}
|
||
else
|
||
{
|
||
hash_n = gcry_md_get_algo_dlen (hash_algo);
|
||
if (!hash_n)
|
||
{
|
||
err = gpg_error (GPG_ERR_INTERNAL);
|
||
goto out;
|
||
}
|
||
err = data_hash (data, data_size, hash_algo, hash);
|
||
if (err)
|
||
goto out;
|
||
memcpy (ctrl->digest.value, hash, hash_n);
|
||
ctrl->digest.valuelen = hash_n;
|
||
}
|
||
|
||
/* Sign data. */
|
||
if ((spec.flags & SPEC_FLAG_IS_EdDSA))
|
||
err = data_sign (ctrl, &spec, data, data_size, &sig, &sig_n);
|
||
else
|
||
err = data_sign (ctrl, &spec, NULL, 0, &sig, &sig_n);
|
||
|
||
out:
|
||
/* Done. */
|
||
if (!err)
|
||
{
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_SIGN_RESPONSE);
|
||
if (ret_err)
|
||
goto leave;
|
||
ret_err = stream_write_string (response, sig, sig_n);
|
||
if (ret_err)
|
||
goto leave;
|
||
}
|
||
else
|
||
{
|
||
log_error ("ssh sign request failed: %s <%s>\n",
|
||
gpg_strerror (err), gpg_strsource (err));
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
|
||
if (ret_err)
|
||
goto leave;
|
||
}
|
||
|
||
leave:
|
||
|
||
gcry_sexp_release (key);
|
||
xfree (key_blob);
|
||
xfree (data);
|
||
es_free (sig);
|
||
|
||
return ret_err;
|
||
}
|
||
|
||
|
||
/* This function extracts the comment contained in the key
|
||
s-expression KEY and stores a copy in COMMENT. Returns usual error
|
||
code. */
|
||
static gpg_error_t
|
||
ssh_key_extract_comment (gcry_sexp_t key, char **r_comment)
|
||
{
|
||
gcry_sexp_t comment_list;
|
||
|
||
*r_comment = NULL;
|
||
|
||
comment_list = gcry_sexp_find_token (key, "comment", 0);
|
||
if (!comment_list)
|
||
return gpg_error (GPG_ERR_INV_SEXP);
|
||
|
||
*r_comment = gcry_sexp_nth_string (comment_list, 1);
|
||
gcry_sexp_release (comment_list);
|
||
if (!*r_comment)
|
||
return gpg_error (GPG_ERR_INV_SEXP);
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* This function converts the key contained in the S-Expression KEY
|
||
into a buffer, which is protected by the passphrase PASSPHRASE.
|
||
If PASSPHRASE is the empty passphrase, the key is not protected.
|
||
Returns usual error code. */
|
||
static gpg_error_t
|
||
ssh_key_to_protected_buffer (gcry_sexp_t key, const char *passphrase,
|
||
unsigned char **buffer, size_t *buffer_n)
|
||
{
|
||
unsigned char *buffer_new;
|
||
unsigned int buffer_new_n;
|
||
gpg_error_t err;
|
||
|
||
buffer_new_n = gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, NULL, 0);
|
||
buffer_new = xtrymalloc_secure (buffer_new_n);
|
||
if (! buffer_new)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
gcry_sexp_sprint (key, GCRYSEXP_FMT_CANON, buffer_new, buffer_new_n);
|
||
/* FIXME: guarantee? */
|
||
|
||
if (*passphrase)
|
||
err = agent_protect (buffer_new, passphrase, buffer, buffer_n, 0, -1);
|
||
else
|
||
{
|
||
/* The key derivation function does not support zero length
|
||
* strings. Store key unprotected if the user wishes so. */
|
||
*buffer = buffer_new;
|
||
*buffer_n = buffer_new_n;
|
||
buffer_new = NULL;
|
||
err = 0;
|
||
}
|
||
|
||
out:
|
||
|
||
xfree (buffer_new);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
/* Callback function to compare the first entered PIN with the one
|
||
currently being entered. */
|
||
static gpg_error_t
|
||
reenter_compare_cb (struct pin_entry_info_s *pi)
|
||
{
|
||
const char *pin1 = pi->check_cb_arg;
|
||
|
||
if (!strcmp (pin1, pi->pin))
|
||
return 0; /* okay */
|
||
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
|
||
}
|
||
|
||
|
||
/* Store the ssh KEY into our local key storage and protect it after
|
||
asking for a passphrase. Cache that passphrase. TTL is the
|
||
maximum caching time for that key. If the key already exists in
|
||
our key storage, don't do anything. When entering a key also add
|
||
an entry to the sshcontrol file. */
|
||
static gpg_error_t
|
||
ssh_identity_register (ctrl_t ctrl, ssh_key_type_spec_t *spec,
|
||
gcry_sexp_t key, int ttl, int confirm)
|
||
{
|
||
gpg_error_t err;
|
||
unsigned char key_grip_raw[20];
|
||
char key_grip[41];
|
||
unsigned char *buffer = NULL;
|
||
size_t buffer_n;
|
||
char *description = NULL;
|
||
const char *description2 = L_("Please re-enter this passphrase");
|
||
char *comment = NULL;
|
||
char *key_fpr = NULL;
|
||
const char *initial_errtext = NULL;
|
||
struct pin_entry_info_s *pi = NULL;
|
||
struct pin_entry_info_s *pi2 = NULL;
|
||
|
||
err = ssh_key_grip (key, key_grip_raw);
|
||
if (err)
|
||
goto out;
|
||
|
||
bin2hex (key_grip_raw, 20, key_grip);
|
||
|
||
err = ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest, &key_fpr);
|
||
if (err)
|
||
goto out;
|
||
|
||
/* Check whether the key is already in our key storage. Don't do
|
||
anything then besides (re-)adding it to sshcontrol. */
|
||
if ( !agent_key_available (key_grip_raw) )
|
||
goto key_exists; /* Yes, key is available. */
|
||
|
||
err = ssh_key_extract_comment (key, &comment);
|
||
if (err)
|
||
goto out;
|
||
|
||
if ( asprintf (&description,
|
||
L_("Please enter a passphrase to protect"
|
||
" the received secret key%%0A"
|
||
" %s%%0A"
|
||
" %s%%0A"
|
||
"within gpg-agent's key storage"),
|
||
key_fpr, comment ? comment : "") < 0)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
|
||
if (!pi)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
|
||
if (!pi2)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
pi->max_length = MAX_PASSPHRASE_LEN + 1;
|
||
pi->max_tries = 1;
|
||
pi->with_repeat = 1;
|
||
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
|
||
pi2->max_tries = 1;
|
||
pi2->check_cb = reenter_compare_cb;
|
||
pi2->check_cb_arg = pi->pin;
|
||
|
||
next_try:
|
||
err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0);
|
||
initial_errtext = NULL;
|
||
if (err)
|
||
goto out;
|
||
|
||
/* Unless the passphrase is empty or the pinentry told us that
|
||
it already did the repetition check, ask to confirm it. */
|
||
if (*pi->pin && !pi->repeat_okay)
|
||
{
|
||
err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0);
|
||
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
|
||
{ /* The re-entered one did not match and the user did not
|
||
hit cancel. */
|
||
initial_errtext = L_("does not match - try again");
|
||
goto next_try;
|
||
}
|
||
}
|
||
|
||
err = ssh_key_to_protected_buffer (key, pi->pin, &buffer, &buffer_n);
|
||
if (err)
|
||
goto out;
|
||
|
||
/* Store this key to our key storage. */
|
||
err = agent_write_private_key (key_grip_raw, buffer, buffer_n, 0);
|
||
if (err)
|
||
goto out;
|
||
|
||
/* Cache this passphrase. */
|
||
err = agent_put_cache (key_grip, CACHE_MODE_SSH, pi->pin, ttl);
|
||
if (err)
|
||
goto out;
|
||
|
||
key_exists:
|
||
/* And add an entry to the sshcontrol file. */
|
||
err = add_control_entry (ctrl, spec, key_grip, key, ttl, confirm);
|
||
|
||
|
||
out:
|
||
if (pi2 && pi2->max_length)
|
||
wipememory (pi2->pin, pi2->max_length);
|
||
xfree (pi2);
|
||
if (pi && pi->max_length)
|
||
wipememory (pi->pin, pi->max_length);
|
||
xfree (pi);
|
||
xfree (buffer);
|
||
xfree (comment);
|
||
xfree (key_fpr);
|
||
xfree (description);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* This function removes the key contained in the S-Expression KEY
|
||
from the local key storage, in case it exists there. Returns usual
|
||
error code. FIXME: this function is a stub. */
|
||
static gpg_error_t
|
||
ssh_identity_drop (gcry_sexp_t key)
|
||
{
|
||
unsigned char key_grip[21] = { 0 };
|
||
gpg_error_t err;
|
||
|
||
err = ssh_key_grip (key, key_grip);
|
||
if (err)
|
||
goto out;
|
||
|
||
key_grip[sizeof (key_grip) - 1] = 0;
|
||
|
||
/* FIXME: What to do here - forgetting the passphrase or deleting
|
||
the key from key cache? */
|
||
|
||
out:
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Handler for the "add_identity" command. */
|
||
static gpg_error_t
|
||
ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
|
||
{
|
||
gpg_error_t ret_err;
|
||
ssh_key_type_spec_t spec;
|
||
gpg_error_t err;
|
||
gcry_sexp_t key;
|
||
unsigned char b;
|
||
int confirm;
|
||
int ttl;
|
||
|
||
confirm = 0;
|
||
key = NULL;
|
||
ttl = 0;
|
||
|
||
/* FIXME? */
|
||
err = ssh_receive_key (request, &key, 1, 1, &spec);
|
||
if (err)
|
||
goto out;
|
||
|
||
while (1)
|
||
{
|
||
err = stream_read_byte (request, &b);
|
||
if (gpg_err_code (err) == GPG_ERR_EOF)
|
||
{
|
||
err = 0;
|
||
break;
|
||
}
|
||
|
||
switch (b)
|
||
{
|
||
case SSH_OPT_CONSTRAIN_LIFETIME:
|
||
{
|
||
u32 n = 0;
|
||
|
||
err = stream_read_uint32 (request, &n);
|
||
if (! err)
|
||
ttl = n;
|
||
break;
|
||
}
|
||
|
||
case SSH_OPT_CONSTRAIN_CONFIRM:
|
||
{
|
||
confirm = 1;
|
||
break;
|
||
}
|
||
|
||
default:
|
||
/* FIXME: log/bad? */
|
||
break;
|
||
}
|
||
}
|
||
if (err)
|
||
goto out;
|
||
|
||
err = ssh_identity_register (ctrl, &spec, key, ttl, confirm);
|
||
|
||
out:
|
||
|
||
gcry_sexp_release (key);
|
||
|
||
if (! err)
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
|
||
else
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
|
||
|
||
return ret_err;
|
||
}
|
||
|
||
/* Handler for the "remove_identity" command. */
|
||
static gpg_error_t
|
||
ssh_handler_remove_identity (ctrl_t ctrl,
|
||
estream_t request, estream_t response)
|
||
{
|
||
unsigned char *key_blob;
|
||
u32 key_blob_size;
|
||
gcry_sexp_t key;
|
||
gpg_error_t ret_err;
|
||
gpg_error_t err;
|
||
|
||
(void)ctrl;
|
||
|
||
/* Receive key. */
|
||
|
||
key_blob = NULL;
|
||
key = NULL;
|
||
|
||
err = stream_read_string (request, 0, &key_blob, &key_blob_size);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = ssh_read_key_public_from_blob (key_blob, key_blob_size, &key, NULL);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = ssh_identity_drop (key);
|
||
|
||
out:
|
||
|
||
xfree (key_blob);
|
||
gcry_sexp_release (key);
|
||
|
||
if (! err)
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
|
||
else
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
|
||
|
||
return ret_err;
|
||
}
|
||
|
||
/* FIXME: stub function. Actually useful? */
|
||
static gpg_error_t
|
||
ssh_identities_remove_all (void)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
err = 0;
|
||
|
||
/* FIXME: shall we remove _all_ cache entries or only those
|
||
registered through the ssh-agent protocol? */
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Handler for the "remove_all_identities" command. */
|
||
static gpg_error_t
|
||
ssh_handler_remove_all_identities (ctrl_t ctrl,
|
||
estream_t request, estream_t response)
|
||
{
|
||
gpg_error_t ret_err;
|
||
gpg_error_t err;
|
||
|
||
(void)ctrl;
|
||
(void)request;
|
||
|
||
err = ssh_identities_remove_all ();
|
||
|
||
if (! err)
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
|
||
else
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
|
||
|
||
return ret_err;
|
||
}
|
||
|
||
/* Lock agent? FIXME: stub function. */
|
||
static gpg_error_t
|
||
ssh_lock (void)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
/* FIXME */
|
||
log_error ("ssh-agent's lock command is not implemented\n");
|
||
err = 0;
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Unock agent? FIXME: stub function. */
|
||
static gpg_error_t
|
||
ssh_unlock (void)
|
||
{
|
||
gpg_error_t err;
|
||
|
||
log_error ("ssh-agent's unlock command is not implemented\n");
|
||
err = 0;
|
||
|
||
return err;
|
||
}
|
||
|
||
/* Handler for the "lock" command. */
|
||
static gpg_error_t
|
||
ssh_handler_lock (ctrl_t ctrl, estream_t request, estream_t response)
|
||
{
|
||
gpg_error_t ret_err;
|
||
gpg_error_t err;
|
||
|
||
(void)ctrl;
|
||
(void)request;
|
||
|
||
err = ssh_lock ();
|
||
|
||
if (! err)
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
|
||
else
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
|
||
|
||
return ret_err;
|
||
}
|
||
|
||
/* Handler for the "unlock" command. */
|
||
static gpg_error_t
|
||
ssh_handler_unlock (ctrl_t ctrl, estream_t request, estream_t response)
|
||
{
|
||
gpg_error_t ret_err;
|
||
gpg_error_t err;
|
||
|
||
(void)ctrl;
|
||
(void)request;
|
||
|
||
err = ssh_unlock ();
|
||
|
||
if (! err)
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_SUCCESS);
|
||
else
|
||
ret_err = stream_write_byte (response, SSH_RESPONSE_FAILURE);
|
||
|
||
return ret_err;
|
||
}
|
||
|
||
|
||
|
||
/* Return the request specification for the request identified by TYPE
|
||
or NULL in case the requested request specification could not be
|
||
found. */
|
||
static const ssh_request_spec_t *
|
||
request_spec_lookup (int type)
|
||
{
|
||
const ssh_request_spec_t *spec;
|
||
unsigned int i;
|
||
|
||
for (i = 0; i < DIM (request_specs); i++)
|
||
if (request_specs[i].type == type)
|
||
break;
|
||
if (i == DIM (request_specs))
|
||
{
|
||
if (opt.verbose)
|
||
log_info ("ssh request %u is not supported\n", type);
|
||
spec = NULL;
|
||
}
|
||
else
|
||
spec = request_specs + i;
|
||
|
||
return spec;
|
||
}
|
||
|
||
/* Process a single request. The request is read from and the
|
||
response is written to STREAM_SOCK. Uses CTRL as context. Returns
|
||
zero in case of success, non zero in case of failure. */
|
||
static int
|
||
ssh_request_process (ctrl_t ctrl, estream_t stream_sock)
|
||
{
|
||
const ssh_request_spec_t *spec;
|
||
estream_t response = NULL;
|
||
estream_t request = NULL;
|
||
unsigned char request_type;
|
||
gpg_error_t err;
|
||
int send_err = 0;
|
||
int ret;
|
||
unsigned char *request_data = NULL;
|
||
u32 request_data_size;
|
||
u32 response_size;
|
||
|
||
/* Create memory streams for request/response data. The entire
|
||
request will be stored in secure memory, since it might contain
|
||
secret key material. The response does not have to be stored in
|
||
secure memory, since we never give out secret keys.
|
||
|
||
Note: we only have little secure memory, but there is NO
|
||
possibility of DoS here; only trusted clients are allowed to
|
||
connect to the agent. What could happen is that the agent
|
||
returns out-of-secure-memory errors on requests in case the
|
||
agent's owner floods his own agent with many large messages.
|
||
-moritz */
|
||
|
||
/* Retrieve request. */
|
||
err = stream_read_string (stream_sock, 1, &request_data, &request_data_size);
|
||
if (err)
|
||
goto out;
|
||
|
||
if (opt.verbose > 1)
|
||
log_info ("received ssh request of length %u\n",
|
||
(unsigned int)request_data_size);
|
||
|
||
if (! request_data_size)
|
||
{
|
||
send_err = 1;
|
||
goto out;
|
||
/* Broken request; FIXME. */
|
||
}
|
||
|
||
request_type = request_data[0];
|
||
spec = request_spec_lookup (request_type);
|
||
if (! spec)
|
||
{
|
||
send_err = 1;
|
||
goto out;
|
||
/* Unknown request; FIXME. */
|
||
}
|
||
|
||
if (spec->secret_input)
|
||
request = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+b");
|
||
else
|
||
request = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+b");
|
||
if (! request)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
ret = es_setvbuf (request, NULL, _IONBF, 0);
|
||
if (ret)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
err = stream_write_data (request, request_data + 1, request_data_size - 1);
|
||
if (err)
|
||
goto out;
|
||
es_rewind (request);
|
||
|
||
response = es_fopenmem (0, "r+b");
|
||
if (! response)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto out;
|
||
}
|
||
|
||
if (opt.verbose)
|
||
log_info ("ssh request handler for %s (%u) started\n",
|
||
spec->identifier, spec->type);
|
||
|
||
err = (*spec->handler) (ctrl, request, response);
|
||
|
||
if (opt.verbose)
|
||
{
|
||
if (err)
|
||
log_info ("ssh request handler for %s (%u) failed: %s\n",
|
||
spec->identifier, spec->type, gpg_strerror (err));
|
||
else
|
||
log_info ("ssh request handler for %s (%u) ready\n",
|
||
spec->identifier, spec->type);
|
||
}
|
||
|
||
if (err)
|
||
{
|
||
send_err = 1;
|
||
goto out;
|
||
}
|
||
|
||
response_size = es_ftell (response);
|
||
if (opt.verbose > 1)
|
||
log_info ("sending ssh response of length %u\n",
|
||
(unsigned int)response_size);
|
||
|
||
err = es_fseek (response, 0, SEEK_SET);
|
||
if (err)
|
||
{
|
||
send_err = 1;
|
||
goto out;
|
||
}
|
||
|
||
err = stream_write_uint32 (stream_sock, response_size);
|
||
if (err)
|
||
{
|
||
send_err = 1;
|
||
goto out;
|
||
}
|
||
|
||
err = stream_copy (stream_sock, response);
|
||
if (err)
|
||
goto out;
|
||
|
||
err = es_fflush (stream_sock);
|
||
if (err)
|
||
goto out;
|
||
|
||
out:
|
||
|
||
if (err && es_feof (stream_sock))
|
||
log_error ("error occurred while processing request: %s\n",
|
||
gpg_strerror (err));
|
||
|
||
if (send_err)
|
||
{
|
||
if (opt.verbose > 1)
|
||
log_info ("sending ssh error response\n");
|
||
err = stream_write_uint32 (stream_sock, 1);
|
||
if (err)
|
||
goto leave;
|
||
err = stream_write_byte (stream_sock, SSH_RESPONSE_FAILURE);
|
||
if (err)
|
||
goto leave;
|
||
}
|
||
|
||
leave:
|
||
|
||
es_fclose (request);
|
||
es_fclose (response);
|
||
xfree (request_data);
|
||
|
||
return !!err;
|
||
}
|
||
|
||
|
||
/* Return the peer's pid. */
|
||
static void
|
||
get_client_info (int fd, struct peer_info_s *out)
|
||
{
|
||
pid_t client_pid = (pid_t)(-1);
|
||
uid_t client_uid = (uid_t)-1;
|
||
|
||
#ifdef SO_PEERCRED
|
||
{
|
||
#ifdef HAVE_STRUCT_SOCKPEERCRED_PID
|
||
struct sockpeercred cr;
|
||
#else
|
||
struct ucred cr;
|
||
#endif
|
||
socklen_t cl = sizeof cr;
|
||
|
||
if (!getsockopt (fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl))
|
||
{
|
||
#if defined (HAVE_STRUCT_SOCKPEERCRED_PID) || defined (HAVE_STRUCT_UCRED_PID)
|
||
client_pid = cr.pid;
|
||
client_uid = cr.uid;
|
||
#elif defined (HAVE_STRUCT_UCRED_CR_PID)
|
||
client_pid = cr.cr_pid;
|
||
client_pid = cr.cr_uid;
|
||
#else
|
||
#error "Unknown SO_PEERCRED struct"
|
||
#endif
|
||
}
|
||
}
|
||
#elif defined (LOCAL_PEERPID)
|
||
{
|
||
socklen_t len = sizeof (pid_t);
|
||
|
||
getsockopt (fd, SOL_LOCAL, LOCAL_PEERPID, &client_pid, &len);
|
||
getsockopt (fd, SOL_LOCAL, LOCAL_PEERUID, &client_uid, &len);
|
||
}
|
||
#elif defined (LOCAL_PEEREID)
|
||
{
|
||
struct unpcbid unp;
|
||
socklen_t unpl = sizeof unp;
|
||
|
||
if (getsockopt (fd, 0, LOCAL_PEEREID, &unp, &unpl) != -1)
|
||
client_pid = unp.unp_pid;
|
||
client_uid = unp.unp_euid;
|
||
}
|
||
#elif defined (HAVE_GETPEERUCRED)
|
||
{
|
||
ucred_t *ucred = NULL;
|
||
|
||
if (getpeerucred (fd, &ucred) != -1)
|
||
{
|
||
client_pid = ucred_getpid (ucred);
|
||
client_uid = ucred_geteuid (ucred);
|
||
ucred_free (ucred);
|
||
}
|
||
}
|
||
#else
|
||
(void)fd;
|
||
#endif
|
||
|
||
out->pid = (client_pid == (pid_t)(-1)? 0 : (unsigned long)client_pid);
|
||
out->uid = (int)client_uid;
|
||
}
|
||
|
||
|
||
/* Start serving client on SOCK_CLIENT. */
|
||
void
|
||
start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client)
|
||
{
|
||
estream_t stream_sock = NULL;
|
||
gpg_error_t err;
|
||
int ret;
|
||
struct peer_info_s peer_info;
|
||
|
||
err = agent_copy_startup_env (ctrl);
|
||
if (err)
|
||
goto out;
|
||
|
||
get_client_info (FD2INT(sock_client), &peer_info);
|
||
ctrl->client_pid = peer_info.pid;
|
||
ctrl->client_uid = peer_info.uid;
|
||
|
||
/* Create stream from socket. */
|
||
stream_sock = es_fdopen (FD2INT(sock_client), "r+");
|
||
if (!stream_sock)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error (_("failed to create stream from socket: %s\n"),
|
||
gpg_strerror (err));
|
||
goto out;
|
||
}
|
||
/* We have to disable the estream buffering, because the estream
|
||
core doesn't know about secure memory. */
|
||
ret = es_setvbuf (stream_sock, NULL, _IONBF, 0);
|
||
if (ret)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
log_error ("failed to disable buffering "
|
||
"on socket stream: %s\n", gpg_strerror (err));
|
||
goto out;
|
||
}
|
||
|
||
/* Main processing loop. */
|
||
while ( !ssh_request_process (ctrl, stream_sock) )
|
||
{
|
||
/* Check whether we have reached EOF before trying to read
|
||
another request. */
|
||
int c;
|
||
|
||
c = es_fgetc (stream_sock);
|
||
if (c == EOF)
|
||
break;
|
||
es_ungetc (c, stream_sock);
|
||
}
|
||
|
||
/* Reset the SCD in case it has been used. */
|
||
agent_reset_scd (ctrl);
|
||
|
||
|
||
out:
|
||
if (stream_sock)
|
||
es_fclose (stream_sock);
|
||
}
|
||
|
||
|
||
#ifdef HAVE_W32_SYSTEM
|
||
/* Serve one ssh-agent request. This is used for the Putty support.
|
||
REQUEST is the mmapped memory which may be accessed up to a
|
||
length of MAXREQLEN. Returns 0 on success which also indicates
|
||
that a valid SSH response message is now in REQUEST. */
|
||
int
|
||
serve_mmapped_ssh_request (ctrl_t ctrl,
|
||
unsigned char *request, size_t maxreqlen)
|
||
{
|
||
gpg_error_t err;
|
||
int send_err = 0;
|
||
int valid_response = 0;
|
||
const ssh_request_spec_t *spec;
|
||
u32 msglen;
|
||
estream_t request_stream, response_stream;
|
||
|
||
if (agent_copy_startup_env (ctrl))
|
||
goto leave; /* Error setting up the environment. */
|
||
|
||
if (maxreqlen < 5)
|
||
goto leave; /* Caller error. */
|
||
|
||
msglen = uint32_construct (request[0], request[1], request[2], request[3]);
|
||
if (msglen < 1 || msglen > maxreqlen - 4)
|
||
{
|
||
log_error ("ssh message len (%u) out of range", (unsigned int)msglen);
|
||
goto leave;
|
||
}
|
||
|
||
spec = request_spec_lookup (request[4]);
|
||
if (!spec)
|
||
{
|
||
send_err = 1; /* Unknown request type. */
|
||
goto leave;
|
||
}
|
||
|
||
/* Create a stream object with the data part of the request. */
|
||
if (spec->secret_input)
|
||
request_stream = es_mopen (NULL, 0, 0, 1, realloc_secure, gcry_free, "r+");
|
||
else
|
||
request_stream = es_mopen (NULL, 0, 0, 1, gcry_realloc, gcry_free, "r+");
|
||
if (!request_stream)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
/* We have to disable the estream buffering, because the estream
|
||
core doesn't know about secure memory. */
|
||
if (es_setvbuf (request_stream, NULL, _IONBF, 0))
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
/* Copy the request to the stream but omit the request type. */
|
||
err = stream_write_data (request_stream, request + 5, msglen - 1);
|
||
if (err)
|
||
goto leave;
|
||
es_rewind (request_stream);
|
||
|
||
response_stream = es_fopenmem (0, "r+b");
|
||
if (!response_stream)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
|
||
if (opt.verbose)
|
||
log_info ("ssh request handler for %s (%u) started\n",
|
||
spec->identifier, spec->type);
|
||
|
||
err = (*spec->handler) (ctrl, request_stream, response_stream);
|
||
|
||
if (opt.verbose)
|
||
{
|
||
if (err)
|
||
log_info ("ssh request handler for %s (%u) failed: %s\n",
|
||
spec->identifier, spec->type, gpg_strerror (err));
|
||
else
|
||
log_info ("ssh request handler for %s (%u) ready\n",
|
||
spec->identifier, spec->type);
|
||
}
|
||
|
||
es_fclose (request_stream);
|
||
request_stream = NULL;
|
||
|
||
if (err)
|
||
{
|
||
send_err = 1;
|
||
goto leave;
|
||
}
|
||
|
||
/* Put the response back into the mmapped buffer. */
|
||
{
|
||
void *response_data;
|
||
size_t response_size;
|
||
|
||
/* NB: In contrast to the request-stream, the response stream
|
||
includes the message type byte. */
|
||
if (es_fclose_snatch (response_stream, &response_data, &response_size))
|
||
{
|
||
log_error ("snatching ssh response failed: %s",
|
||
gpg_strerror (gpg_error_from_syserror ()));
|
||
send_err = 1; /* Ooops. */
|
||
goto leave;
|
||
}
|
||
|
||
if (opt.verbose > 1)
|
||
log_info ("sending ssh response of length %u\n",
|
||
(unsigned int)response_size);
|
||
if (response_size > maxreqlen - 4)
|
||
{
|
||
log_error ("invalid length of the ssh response: %s",
|
||
gpg_strerror (GPG_ERR_INTERNAL));
|
||
es_free (response_data);
|
||
send_err = 1;
|
||
goto leave;
|
||
}
|
||
|
||
request[0] = response_size >> 24;
|
||
request[1] = response_size >> 16;
|
||
request[2] = response_size >> 8;
|
||
request[3] = response_size >> 0;
|
||
memcpy (request+4, response_data, response_size);
|
||
es_free (response_data);
|
||
valid_response = 1;
|
||
}
|
||
|
||
leave:
|
||
if (send_err)
|
||
{
|
||
request[0] = 0;
|
||
request[1] = 0;
|
||
request[2] = 0;
|
||
request[3] = 1;
|
||
request[4] = SSH_RESPONSE_FAILURE;
|
||
valid_response = 1;
|
||
}
|
||
|
||
/* Reset the SCD in case it has been used. */
|
||
agent_reset_scd (ctrl);
|
||
|
||
return valid_response? 0 : -1;
|
||
}
|
||
#endif /*HAVE_W32_SYSTEM*/
|