mirror of
https://github.com/freebsd/freebsd-src.git
synced 2026-11-07 04:36:52 -10:00
Full release notes are available at https://www.openssh.com/txt/release-10.3 Selected highlights from the release notes: * ssh(1), sshd(8): remove bug compatibility for implementations that don't support rekeying. If such an implementation tries to interoperate with OpenSSH, it will now eventually fail when the transport needs rekeying. * ssh(1), sshd(8): support IANA-assigned codepoints for SSH agent forwarding, as per draft-ietf-sshm-ssh-agent. Support for the new names is advertised via the EXT_INFO message. If a server offers support for the new names, then they are used preferentially. * ssh(1): add a ~I escape option that shows information about the current SSH connection. * sshd(8): add 'invaliduser' penalty to PerSourcePenalties, which is applied to login attempts for usernames that do not match real accounts. Defaults to 5s to match 'authfail' but allows administrators to block such attempts for longer if desired. * Support the ed25519 signature scheme via libcrypto. Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D56999
412 lines
10 KiB
C
412 lines
10 KiB
C
/* $OpenBSD: gss-serv.c,v 1.37 2026/02/11 16:57:38 dtucker Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2001-2003 Simon Wilkinson. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
|
|
#ifdef GSSAPI
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/param.h>
|
|
#include <sys/queue.h>
|
|
|
|
#include <netdb.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include "xmalloc.h"
|
|
#include "sshkey.h"
|
|
#include "hostfile.h"
|
|
#include "auth.h"
|
|
#include "log.h"
|
|
#include "channels.h"
|
|
#include "session.h"
|
|
#include "misc.h"
|
|
#include "servconf.h"
|
|
|
|
#include "ssh-gss.h"
|
|
|
|
extern ServerOptions options;
|
|
|
|
static ssh_gssapi_client gssapi_client =
|
|
{ GSS_C_EMPTY_BUFFER, GSS_C_EMPTY_BUFFER,
|
|
GSS_C_NO_CREDENTIAL, NULL, {NULL, NULL, NULL, NULL}};
|
|
|
|
ssh_gssapi_mech gssapi_null_mech =
|
|
{ NULL, NULL, {0, NULL}, NULL, NULL, NULL, NULL};
|
|
|
|
#ifdef KRB5
|
|
extern ssh_gssapi_mech gssapi_kerberos_mech;
|
|
#endif
|
|
|
|
ssh_gssapi_mech* supported_mechs[]= {
|
|
#ifdef KRB5
|
|
&gssapi_kerberos_mech,
|
|
#endif
|
|
&gssapi_null_mech,
|
|
};
|
|
|
|
/*
|
|
* ssh_gssapi_supported_oids() can cause sandbox violations, so prepare the
|
|
* list of supported mechanisms before privsep is set up.
|
|
*/
|
|
static gss_OID_set supported_oids;
|
|
|
|
void
|
|
ssh_gssapi_prepare_supported_oids(void)
|
|
{
|
|
ssh_gssapi_supported_oids(&supported_oids);
|
|
}
|
|
|
|
OM_uint32
|
|
ssh_gssapi_test_oid_supported(OM_uint32 *ms, gss_OID member, int *present)
|
|
{
|
|
if (supported_oids == NULL)
|
|
ssh_gssapi_prepare_supported_oids();
|
|
return gss_test_oid_set_member(ms, member, supported_oids, present);
|
|
}
|
|
|
|
/*
|
|
* Acquire credentials for a server running on the current host.
|
|
* Requires that the context structure contains a valid OID
|
|
*/
|
|
|
|
/* Returns a GSSAPI error code */
|
|
/* Privileged (called from ssh_gssapi_server_ctx) */
|
|
static OM_uint32
|
|
ssh_gssapi_acquire_cred(Gssctxt *ctx)
|
|
{
|
|
OM_uint32 status;
|
|
char lname[NI_MAXHOST];
|
|
gss_OID_set oidset;
|
|
|
|
if (options.gss_strict_acceptor) {
|
|
gss_create_empty_oid_set(&status, &oidset);
|
|
gss_add_oid_set_member(&status, ctx->oid, &oidset);
|
|
|
|
if (gethostname(lname, sizeof(lname))) {
|
|
gss_release_oid_set(&status, &oidset);
|
|
return (-1);
|
|
}
|
|
|
|
if (GSS_ERROR(ssh_gssapi_import_name(ctx, lname))) {
|
|
gss_release_oid_set(&status, &oidset);
|
|
return (ctx->major);
|
|
}
|
|
|
|
if ((ctx->major = gss_acquire_cred(&ctx->minor,
|
|
ctx->name, 0, oidset, GSS_C_ACCEPT, &ctx->creds,
|
|
NULL, NULL)))
|
|
ssh_gssapi_error(ctx);
|
|
|
|
gss_release_oid_set(&status, &oidset);
|
|
return (ctx->major);
|
|
} else {
|
|
ctx->name = GSS_C_NO_NAME;
|
|
ctx->creds = GSS_C_NO_CREDENTIAL;
|
|
}
|
|
return GSS_S_COMPLETE;
|
|
}
|
|
|
|
/* Privileged */
|
|
OM_uint32
|
|
ssh_gssapi_server_ctx(Gssctxt **ctx, gss_OID oid)
|
|
{
|
|
if (*ctx)
|
|
ssh_gssapi_delete_ctx(ctx);
|
|
ssh_gssapi_build_ctx(ctx);
|
|
ssh_gssapi_set_oid(*ctx, oid);
|
|
return (ssh_gssapi_acquire_cred(*ctx));
|
|
}
|
|
|
|
/* Unprivileged */
|
|
void
|
|
ssh_gssapi_supported_oids(gss_OID_set *oidset)
|
|
{
|
|
int i = 0;
|
|
OM_uint32 min_status;
|
|
int present;
|
|
gss_OID_set supported;
|
|
|
|
gss_create_empty_oid_set(&min_status, oidset);
|
|
gss_indicate_mechs(&min_status, &supported);
|
|
|
|
while (supported_mechs[i]->name != NULL) {
|
|
if (GSS_ERROR(gss_test_oid_set_member(&min_status,
|
|
&supported_mechs[i]->oid, supported, &present)))
|
|
present = 0;
|
|
if (present)
|
|
gss_add_oid_set_member(&min_status,
|
|
&supported_mechs[i]->oid, oidset);
|
|
i++;
|
|
}
|
|
|
|
gss_release_oid_set(&min_status, &supported);
|
|
}
|
|
|
|
|
|
/* Wrapper around accept_sec_context
|
|
* Requires that the context contains:
|
|
* oid
|
|
* credentials (from ssh_gssapi_acquire_cred)
|
|
*/
|
|
/* Privileged */
|
|
OM_uint32
|
|
ssh_gssapi_accept_ctx(Gssctxt *ctx, gss_buffer_desc *recv_tok,
|
|
gss_buffer_desc *send_tok, OM_uint32 *flags)
|
|
{
|
|
OM_uint32 status;
|
|
gss_OID mech;
|
|
|
|
ctx->major = gss_accept_sec_context(&ctx->minor,
|
|
&ctx->context, ctx->creds, recv_tok,
|
|
GSS_C_NO_CHANNEL_BINDINGS, &ctx->client, &mech,
|
|
send_tok, flags, NULL, &ctx->client_creds);
|
|
|
|
if (GSS_ERROR(ctx->major))
|
|
ssh_gssapi_error(ctx);
|
|
|
|
if (ctx->client_creds)
|
|
debug("Received some client credentials");
|
|
else
|
|
debug("Got no client credentials");
|
|
|
|
status = ctx->major;
|
|
|
|
/* Now, if we're complete and we have the right flags, then
|
|
* we flag the user as also having been authenticated
|
|
*/
|
|
|
|
if (((flags == NULL) || ((*flags & GSS_C_MUTUAL_FLAG) &&
|
|
(*flags & GSS_C_INTEG_FLAG))) && (ctx->major == GSS_S_COMPLETE)) {
|
|
if (ssh_gssapi_getclient(ctx, &gssapi_client))
|
|
fatal("Couldn't convert client name");
|
|
}
|
|
|
|
return (status);
|
|
}
|
|
|
|
/*
|
|
* This parses an exported name, extracting the mechanism specific portion
|
|
* to use for ACL checking. It verifies that the name belongs the mechanism
|
|
* originally selected.
|
|
*/
|
|
static OM_uint32
|
|
ssh_gssapi_parse_ename(Gssctxt *ctx, gss_buffer_t ename, gss_buffer_t name)
|
|
{
|
|
u_char *tok;
|
|
OM_uint32 offset;
|
|
OM_uint32 oidl;
|
|
|
|
tok = ename->value;
|
|
|
|
/*
|
|
* Check that ename is long enough for all of the fixed length
|
|
* header, and that the initial ID bytes are correct
|
|
*/
|
|
|
|
if (ename->length < 6 || memcmp(tok, "\x04\x01", 2) != 0)
|
|
return GSS_S_FAILURE;
|
|
|
|
/*
|
|
* Extract the OID, and check it. Here GSSAPI breaks with tradition
|
|
* and does use the OID type and length bytes. To confuse things
|
|
* there are two lengths - the first including these, and the
|
|
* second without.
|
|
*/
|
|
|
|
oidl = get_u16(tok+2); /* length including next two bytes */
|
|
oidl = oidl-2; /* turn it into the _real_ length of the variable OID */
|
|
|
|
/*
|
|
* Check the BER encoding for correct type and length, that the
|
|
* string is long enough and that the OID matches that in our context
|
|
*/
|
|
if (tok[4] != 0x06 || tok[5] != oidl ||
|
|
ename->length < oidl+6 ||
|
|
!ssh_gssapi_check_oid(ctx, tok+6, oidl))
|
|
return GSS_S_FAILURE;
|
|
|
|
offset = oidl+6;
|
|
|
|
if (ename->length < offset+4)
|
|
return GSS_S_FAILURE;
|
|
|
|
name->length = get_u32(tok+offset);
|
|
offset += 4;
|
|
|
|
if (UINT_MAX - offset < name->length)
|
|
return GSS_S_FAILURE;
|
|
if (ename->length < offset+name->length)
|
|
return GSS_S_FAILURE;
|
|
|
|
name->value = xmalloc(name->length+1);
|
|
memcpy(name->value, tok+offset, name->length);
|
|
((char *)name->value)[name->length] = 0;
|
|
|
|
return GSS_S_COMPLETE;
|
|
}
|
|
|
|
/* Extract the client details from a given context. This can only reliably
|
|
* be called once for a context */
|
|
|
|
/* Privileged (called from accept_secure_ctx) */
|
|
OM_uint32
|
|
ssh_gssapi_getclient(Gssctxt *ctx, ssh_gssapi_client *client)
|
|
{
|
|
int i = 0;
|
|
|
|
gss_buffer_desc ename;
|
|
|
|
client->mech = NULL;
|
|
|
|
while (supported_mechs[i]->name != NULL) {
|
|
if (supported_mechs[i]->oid.length == ctx->oid->length &&
|
|
(memcmp(supported_mechs[i]->oid.elements,
|
|
ctx->oid->elements, ctx->oid->length) == 0))
|
|
client->mech = supported_mechs[i];
|
|
i++;
|
|
}
|
|
|
|
if (client->mech == NULL)
|
|
return GSS_S_FAILURE;
|
|
|
|
if ((ctx->major = gss_display_name(&ctx->minor, ctx->client,
|
|
&client->displayname, NULL))) {
|
|
ssh_gssapi_error(ctx);
|
|
return (ctx->major);
|
|
}
|
|
|
|
if ((ctx->major = gss_export_name(&ctx->minor, ctx->client,
|
|
&ename))) {
|
|
ssh_gssapi_error(ctx);
|
|
return (ctx->major);
|
|
}
|
|
|
|
if ((ctx->major = ssh_gssapi_parse_ename(ctx,&ename,
|
|
&client->exportedname))) {
|
|
return (ctx->major);
|
|
}
|
|
|
|
/* We can't copy this structure, so we just move the pointer to it */
|
|
client->creds = ctx->client_creds;
|
|
ctx->client_creds = GSS_C_NO_CREDENTIAL;
|
|
return (ctx->major);
|
|
}
|
|
|
|
/* As user - called on fatal/exit */
|
|
void
|
|
ssh_gssapi_cleanup_creds(void)
|
|
{
|
|
if (gssapi_client.store.filename != NULL) {
|
|
/* Unlink probably isn't sufficient */
|
|
debug("removing gssapi cred file\"%s\"",
|
|
gssapi_client.store.filename);
|
|
unlink(gssapi_client.store.filename);
|
|
}
|
|
}
|
|
|
|
/* As user */
|
|
void
|
|
ssh_gssapi_storecreds(void)
|
|
{
|
|
if (options.gss_deleg_creds == 0) {
|
|
debug_f("delegate credential is disabled, doing nothing");
|
|
return;
|
|
}
|
|
|
|
if (gssapi_client.mech && gssapi_client.mech->storecreds) {
|
|
(*gssapi_client.mech->storecreds)(&gssapi_client);
|
|
} else
|
|
debug("ssh_gssapi_storecreds: Not a GSSAPI mechanism");
|
|
}
|
|
|
|
/* This allows GSSAPI methods to do things to the child's environment based
|
|
* on the passed authentication process and credentials.
|
|
*/
|
|
/* As user */
|
|
void
|
|
ssh_gssapi_do_child(char ***envp, u_int *envsizep)
|
|
{
|
|
|
|
if (gssapi_client.store.envvar != NULL &&
|
|
gssapi_client.store.envval != NULL) {
|
|
debug("Setting %s to %s", gssapi_client.store.envvar,
|
|
gssapi_client.store.envval);
|
|
child_set_env(envp, envsizep, gssapi_client.store.envvar,
|
|
gssapi_client.store.envval);
|
|
}
|
|
}
|
|
|
|
/* Privileged */
|
|
int
|
|
ssh_gssapi_userok(char *user)
|
|
{
|
|
OM_uint32 lmin;
|
|
|
|
if (gssapi_client.exportedname.length == 0 ||
|
|
gssapi_client.exportedname.value == NULL) {
|
|
debug("No suitable client data");
|
|
return 0;
|
|
}
|
|
if (gssapi_client.mech && gssapi_client.mech->userok)
|
|
if ((*gssapi_client.mech->userok)(&gssapi_client, user))
|
|
return 1;
|
|
else {
|
|
/* Destroy delegated credentials if userok fails */
|
|
gss_release_buffer(&lmin, &gssapi_client.displayname);
|
|
gss_release_buffer(&lmin, &gssapi_client.exportedname);
|
|
gss_release_cred(&lmin, &gssapi_client.creds);
|
|
explicit_bzero(&gssapi_client,
|
|
sizeof(ssh_gssapi_client));
|
|
return 0;
|
|
}
|
|
else
|
|
debug("ssh_gssapi_userok: Unknown GSSAPI mechanism");
|
|
return (0);
|
|
}
|
|
|
|
/* Privileged */
|
|
OM_uint32
|
|
ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
|
|
{
|
|
ctx->major = gss_verify_mic(&ctx->minor, ctx->context,
|
|
gssbuf, gssmic, NULL);
|
|
|
|
return (ctx->major);
|
|
}
|
|
|
|
/* Privileged */
|
|
const char *ssh_gssapi_displayname(void)
|
|
{
|
|
if (gssapi_client.displayname.length == 0 ||
|
|
gssapi_client.displayname.value == NULL)
|
|
return NULL;
|
|
return (char *)gssapi_client.displayname.value;
|
|
}
|
|
|
|
#endif
|