logo

checkpassword-ng

Uniform password checking interface for applications
commit: 6af392c004c287007566dc8a2b5f538d11844bd2
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Wed, 28 Apr 2021 01:32:40 +0200

Initial Commit

Diffstat:

A.clang-format21+++++++++++++++++++++
A.gitignore4++++
ADCO-1.1.txt37+++++++++++++++++++++++++++++++++++++
AMakefile57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME.md15+++++++++++++++
Acheckpassword.874++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acheckpassword.c82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Achkpw.336++++++++++++++++++++++++++++++++++++
Achkpw.c92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Achkpw.h6++++++
10 files changed, 424 insertions(+), 0 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,21 @@ +AlignAfterOpenBracket: true +AlignConsecutiveAssignments: true +AlignOperands: true +AlignTrailingComments: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AlwaysBreakAfterReturnType: AllDefinitions +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Allman +SpaceBeforeParens: Never +IncludeBlocks: Regroup +ReflowComments: false +SortIncludes: true +UseTab: ForIndentation +IndentWidth: 2 +TabWidth: 2 +ColumnLimit: 100 + +NamespaceIndentation: All diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +/checkpassword +*.o +*.so +*.a diff --git a/DCO-1.1.txt b/DCO-1.1.txt @@ -0,0 +1,37 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/Makefile b/Makefile @@ -0,0 +1,57 @@ +# POSIX-ish Makefile with extensions common to *BSD and GNU such as: +# - Usage of backticks for shell evaluation +# - Usage of ?= for defining variables when not already defined +# - Usage of += for appending to a variable + +VERSION = 0.1.0 +VERSION_FULL = $(VERSION)`./version.sh` + +PREFIX = /usr/local +BINDIR = $(PREFIX)/bin +LIBDIR = $(PREFIX)/lib +MANDIR = $(PREFIX)/share/man + +CC ?= cc +AR ?= ar +EXE_CFLAGS = -pie -fPIE +LIB_CFLAGS = -fpic -fPIC +CFLAGS ?= -g -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2 -Wall -Wextra -Wconversion -Wsign-conversion -O2 -Werror=implicit-function-declaration -Werror=implicit-int -Werror=vla + +LIBS = -lcrypt + +EXE = checkpassword +LIBSO = libchkpw.so +LIBA = libchkpw.a +SRC = checkpassword.c chkpw.c + +all: $(EXE) $(LIBSO) $(LIBA) + +checkpassword: checkpassword.c chkpw.o + $(CC) -std=c99 $(EXE_CFLAGS) $(CFLAGS) -o $@ $< chkpw.o $(LIBS) $(LDFLAGS) + +libchkpw.so: chkpw.c + $(CC) -std=c99 -c -shared $(LIB_CFLAGS) $(CFLAGS) -o $@ $< + +chkpw.o: chkpw.c + $(CC) -std=c99 -c $(LIB_CFLAGS) $(CFLAGS) -o $@ $< + +libchkpw.a: chkpw.o + $(AR) -rv $@ $< + +.PHONY: install +install: all + mkdir -p $(DESTDIR)$(BINDIR) + cp -p $(EXE) $(DESTDIR)$(BINDIR)/$(EXE) + mkdir -p $(DESTDIR)$(LIBDIR) + cp -p $(LIBSO) $(LIBA) $(DESTDIR)$(LIBDIR)/ + mkdir -p $(DESTDIR)$(MANDIR)/man3 + cp -p chkpwd.8 $(DESTDIR)$(MANDIR)/man3 + mkdir -p $(DESTDIR)$(MANDIR)/man8 + cp -p $(EXE).8 $(DESTDIR)$(MANDIR)/man8 + +.PHONY: clean +clean: + rm -fr $(EXE) chkpw.o $(LIBSO) $(LIBA) + +format: *.c + clang-format -style=file -assume-filename=.clang-format -i *.c diff --git a/README.md b/README.md @@ -0,0 +1,15 @@ +# checkpassword-ng - Uniform password checking interface for applications +``` +Copyright © 2021 checkpassword-ng Authors <https://hacktivis.me/git/checkpassword-ng> +SPDX-License-Identifier: GPL-3.0-only +``` + +This is a clean-room reimplementation of D. J. Bernstein [checkpassword](https://cr.yp.to/checkpwd.html) using <https://cr.yp.to/checkpwd/interface.html> as documentation. +- The licensing status of the original is unclear but is considered to be all-rights-reserved +- The original last version dates back to 2000 and distributions have been adding patches on top of it + +## Sign your work - the Developer's Certificate of Origin + +To make sure that everyone has proper rights on the code they are submitting, every contributor will sign-off their commits under the ` Developer's Certificate of Origin 1.1` (copy included in `DCO-1.1.txt`). + +Pseudonyms are tolerated but it is highly recommended to use accountable names. diff --git a/checkpassword.8 b/checkpassword.8 @@ -0,0 +1,74 @@ +.\" checkpassword-ng: Uniform password checking interface for applications +.\" Copyright © 2021 checkpassword-ng Authors <https://hacktivis.me/git/checkpassword-ng> +.\" SPDX-License-Identifier: GPL-3.0-only +.Dd 2021-04-27 +.Dt CHECKPASSWORD 8 +.Sh NAME +.Nm checkpassword +.Nd Uniform password checking interface for applications +.Sh SYNOPSIS +.Nm +.Ar prog +.Sh DESCRIPTION +.Nm +reads at most 512-octets from file descriptor 3 and then closes it. +zero-byte-separated data is given on file descriptor 3: +.Bl -bullet -compact +.It +login name +.It +password +.It +timestamp (ignored in this implementation) +.It +possibly more data (also ignored) +.El +.Pp +.Nm +is for applications where separating authentication to a small optionally setuid-root process makes sense. +Typically this is: +.Bl -bullet -compact +.It +screen lockers +.It +display managers (where root/UID-0 is excluded) +.It +critical user-consent dialogs +.It +daemons wanting to use regular logins (consider ssh-keys though) +.El +.Pp +Out-of-scope applications are: +.Bl -tag -width Ds -compact +.It sudo, doas, … +These applications typically allow to login as root and would only widen their attack surface by using a separated process. +A library would be a better fit for them. +.El +.Pp +Client applications are highly recommended to use a restricted +.Ev PATH +or a direct path to +.Nm . +.Sh ENVIRONMENT VARIABLES +Ignored in this implementation, used in others to pass options. +.Sh EXIT STATUS +If the password is invalid, +.Nm +returns 1. +If +.Nm +is misused, it may return 2. +If there is a temporary problem, +.Nm +returns 111. +If the password is correct, +.Nm +runs +.Ar prog . +.Sh SEE ALSO +.Lk https://cr.yp.to/checkpwd/interface.html The checkpassword interface +.Sh Author +This implementation of +.Nm +was written from scratch by +.An Haelwenn (lanodan) Monnier Aq Mt contact+chkpw@hacktivis.me diff --git a/checkpassword.c b/checkpassword.c @@ -0,0 +1,82 @@ +// checkpassword-ng: Uniform password checking interface for applications +// Copyright © 2021 checkpassword-ng Authors <https://hacktivis.me/git/checkpassword-ng> +// SPDX-License-Identifier: GPL-3.0-only + +#define _POSIX_C_SOURCE 200809L +// explicit_bzero +#define _DEFAULT_SOURCE +#include "chkpw.h" + +#include <assert.h> /* assert() */ +#include <stdio.h> /* fprintf, perror */ +#include <string.h> /* explicit_bzero, strlen, memcpy */ +#include <unistd.h> /* read */ + +// At most 512 bytes of data before EOF +#define ERR_MAX_LEN 512 +// invalid password +#define ERR_INVALID 1 +// misused +#define ERR_MISUSED 2 +// temporary problem +#define ERR_ETMP 111 + +int +main(int argc, char *argv[]) +{ + char input[ERR_MAX_LEN], username[ERR_MAX_LEN] = "", password[ERR_MAX_LEN] = ""; + ssize_t bytes_read = -1; + + // Note: getopt isn't used + if((argc < 2) && argv[1]) + { + fprintf(stderr, "prog argument missing, exiting...\n"); + return ERR_MISUSED; + } + + argc--; + argv++; + + bytes_read = read(3, input, ERR_MAX_LEN); + // At least 3 \0 plus some data + if(bytes_read < 3) + { + perror("read(3, _, _)"); + return ERR_MISUSED; + } + + char *buf = input; + + memcpy(username, buf, strlen(buf)); + printf("username = %s\n", username); + if(username == NULL) + { + fprintf(stderr, "couldn't extract username, exiting...\n"); + return ERR_MISUSED; + } + + buf += strlen(buf) + 1; + memcpy(password, buf, strlen(buf)); + printf("password = %s\n", password); + if(password == NULL) + { + fprintf(stderr, "couldn't extract password, exiting...\n"); + return ERR_MISUSED; + } + + char *res = chkpw(username, password, NULL); + explicit_bzero(password, sizeof(password)); + + if(res == CHKPW_VALID) + { + assert(argv[0]); + execvp(argv[0], argv); + } + else + { + fprintf(stderr, "chkpw: %s\n", res); + return ERR_INVALID; + } + + assert(1); +} diff --git a/chkpw.3 b/chkpw.3 @@ -0,0 +1,36 @@ +.\" checkpassword-ng: Uniform password checking interface for applications +.\" Copyright © 2021 checkpassword-ng Authors <https://hacktivis.me/git/checkpassword-ng> +.\" SPDX-License-Identifier: GPL-3.0-only +.Dd 2021-04-27 +.Dt CHKPW 3 +.Sh NAME +.Lb chkpw +.Nd Uniform password checking interface for applications +.Sh SYNOPSIS +.In chkpw.h +.Vt extern struct chkpw_extra; +.Ft char * +.Fn chkpw "const char *username, const char *password, struct chkpw_extra" +.Sh DESCRIPTION +.Fn chkpw +will verify +.Aq password +against +.Aq username . +Additionnal options might be passed in the future via +.Aq chkpw_extra , +for now it is safe to give it +.Aq NULL . +.Pp +This interface is for applications which already have access to files like +.Pa /etc/shadow , +for others they should consider using +.Xr checkpassword 8 +which doesn't requires your application to be running with special privileges. +.Sh RETURN VALUES +.Fn chkpw +returns +.Aq NULL +on success, on failure it returns an error message to be passed to the user. +.Sh Author +.An Haelwenn (lanodan) Monnier Aq Mt contact+chkpw@hacktivis.me diff --git a/chkpw.c b/chkpw.c @@ -0,0 +1,92 @@ +// checkpassword-ng: Uniform password checking interface for applications +// Copyright © 2021 checkpassword-ng Authors <https://hacktivis.me/git/checkpassword-ng> +// SPDX-License-Identifier: GPL-3.0-only + +#define _POSIX_C_SOURCE 200809L + +#include "chkpw.h" + +#include <assert.h> /* assert */ +#include <errno.h> /* errno */ +#include <pwd.h> /* getpwnam */ +#include <stdio.h> /* sprintf() */ +#include <string.h> /* strerror, strcmp */ +#include <unistd.h> /* crypt */ +#ifdef __GLIBC__ +// GNU's Not POSIX +#include <crypt.h> +#endif +#ifdef __linux__ +// I love linux extensions (no) +#include <shadow.h> /* getspnam */ +#endif + +char strerror_buf[1024]; + +struct chkpw_extra +{ + // intentionally left blank +} chkpw_extra; + +static void +safe_strerror(char *desc, int errnum) +{ + char *err = strerror(errnum); + + snprintf(strerror_buf, 1024, "%s: %s", desc, err); +} + +char * +chkpw(const char *username, const char *password, struct chkpw_extra *chkpw_extra) +{ + (void)chkpw_extra; + char *pw_hash = ""; + + struct passwd *pwent = getpwnam(username); + if(!pwent) + { + safe_strerror("getpwnam", errno); + return strerror_buf; + } + + assert(pwent->pw_passwd); + pw_hash = pwent->pw_passwd; + + if(strcmp(pw_hash, "") == 0) + { + return CHKPW_EMPTY; + } + + if(strcmp(pw_hash, "x") == 0) + { +#ifdef __linux__ + struct spwd *swent = getspnam(username); + if(!swent) + { + safe_strerror("getspnam", errno); + return strerror_buf; + } + pw_hash = swent->sp_pwdp; +#else /* __linux__ */ + return "Invalid password entry"; +#endif + } + + char *chk_hash = crypt(password, pw_hash); + if(chk_hash == NULL) + { + safe_strerror("crypt", errno); + return strerror_buf; + } + + if(strcmp(chk_hash, pw_hash) == 0) + { + return CHKPW_VALID; + } + else + { + return CHKPW_INVALID; + } + + assert(1); +} diff --git a/chkpw.h b/chkpw.h @@ -0,0 +1,6 @@ +#define CHKPW_VALID NULL +#define CHKPW_INVALID "Invalid password" +#define CHKPW_EMPTY "Empty password entry" + +extern struct chkpw_extra chkpw_extra; +char *chkpw(const char *username, const char *password, struct chkpw_extra *chkpw_extra);