commit 2103dfdac660f499b0c6688d17e2e98b08f38848 Author: Albert S Date: Mon Nov 7 10:06:13 2022 +0100 Initial commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8949bf5 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +asteriskify: asteriskify.c + $(CC) -Wall -Wextra -static -o asteriskify asteriskify.c + +clean: asteriskify + rm -f asteriskify diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1993f5 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# asteriskify - Linux console password prompt with asterisks + +## Usage +``` +asteriskify | cryptsetup luksOpen ... +``` + +Pipe it to anything that receives credentials over stdin, such as cryptsetup. + +To switch between clear and asterisk mode, press TAB. To reveal only the last character, use CTRL + R. + +Note: Expects ASCII or UTF-8 encoding for asterisk mode + +## Alternatives +Others may exist, but my quick research only found systemd-ask-password which is unsuitable for my use case (inside a my own minimal initramfs boot image). + diff --git a/asteriskify.c b/asteriskify.c new file mode 100644 index 0000000..7ea11f4 --- /dev/null +++ b/asteriskify.c @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2022 Albert Schwarzkopf + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PWBUF_SIZE 256 + +#define MODE_ECHO 0 +#define MODE_STARS 1 + +struct termios saved_termios; +uint8_t *pwbuf = NULL; +size_t pwbufsize = 0; +size_t pwindex = 0; +int current_mode = MODE_ECHO; + +void enter_raw_mode() { + struct termios raw = saved_termios; + raw.c_lflag &= ~(ECHO | ICANON); + if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != 0) + { + fprintf(stderr, "Failed tcsetattr\n"); + exit(EXIT_FAILURE); + } +} + +void setup_console() +{ + if(tcgetattr(STDIN_FILENO, &saved_termios) != 0) + { + fprintf(stderr, "Failed tcgetattr\n"); + exit(EXIT_FAILURE); + } + enter_raw_mode(); +} + +void restore_console() +{ + if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_termios) != 0) + { + fprintf(stderr, "Failed tcsetattr\n"); + exit(EXIT_FAILURE); + } +} + + +void allocate_pw_buf() +{ + pwbuf = calloc(PWBUF_SIZE, sizeof(uint8_t)); + if(pwbuf == NULL) + { + fprintf(stderr, "Failed to allocate memory\n"); + exit(EXIT_FAILURE); + } + int lock = mlock(pwbuf, PWBUF_SIZE); + if(lock != 0) + { + fprintf(stderr, "Failed to mlock buffer\n"); + exit(EXIT_FAILURE); + } + pwbufsize = PWBUF_SIZE; +} + +void grow_pw_buf() +{ + size_t newsize = pwbufsize + PWBUF_SIZE; + uint8_t *newbuf = calloc(newsize, sizeof(uint8_t)); + if(newbuf == NULL) + { + fprintf(stderr, "Failed to allocate memory\n"); + exit(EXIT_FAILURE); + } + int lock = mlock(newbuf, newsize); + if(lock != 0) + { + fprintf(stderr, "Failed to mlock buffer\n"); + exit(EXIT_FAILURE); + } + memcpy(newbuf, pwbuf, pwbufsize); + explicit_bzero(pwbuf, pwbufsize); + pwbuf = newbuf; + pwbufsize = newsize; +} + +void exit_handler() +{ + explicit_bzero(pwbuf, pwbufsize); + explicit_bzero(&pwbufsize, sizeof(pwbufsize)); + explicit_bzero(&pwindex, sizeof(pwbufsize)); + + restore_console(); +} + +void clear_term_line() +{ + const char *clearcmd = "\33[2K\r"; + write(STDERR_FILENO, clearcmd, strlen(clearcmd)); +} + +void print_password() +{ + clear_term_line(); + char *prompt = "Password: "; + write(STDERR_FILENO, prompt, strlen(prompt)); + if(current_mode == MODE_ECHO) + { + write(STDERR_FILENO, pwbuf, pwindex); + } + if(current_mode == MODE_STARS) + { + for(size_t i = 0; i < pwindex; i++) + { + uint8_t n = pwbuf[i]; + /* Skip utf-8 byte 2-4, we won't print an asterisk for those*/ + if((n >>6) == 0b10) + { + continue; + } + char mask = '*'; + write(STDERR_FILENO, &mask, 1); + } + } + fsync(STDERR_FILENO); +} + +void switch_mode() +{ + current_mode = ( current_mode + 1 ) % 2; +} + +int main() +{ + if(atexit(exit_handler) != 0) + { + fprintf(stderr, "Failed to register exit handler\n"); + exit(EXIT_FAILURE); + } + + setup_console(); + + allocate_pw_buf(); + + print_password(); + + uint8_t c; + while (read(STDIN_FILENO, &c, 1) == 1) + { + if(iscntrl(c)) + { + if(c == '\t') + { + switch_mode(); + print_password(); + } + if(c == 27) /* Escape sequence */ + { + int remaining = 0; + int ret = ioctl (STDIN_FILENO,FIONREAD,&remaining); + if(ret != 0) + { + fprintf(stderr, "ioctl() failed\n"); + exit(EXIT_FAILURE); + } + char tmp; + /* Just consume them, we don't care */ + while(remaining--) + { + read(STDIN_FILENO, &tmp, 1); + } + } + if(c == 18) /* Control + R, device control 2. or "reveal" for us :-) */ + { + if(pwindex > 0) + { + char c = '\b'; + write(STDERR_FILENO, &c, 1); + write(STDERR_FILENO, &pwbuf[pwindex-1], 1); + fsync(STDERR_FILENO); + } + } + if(c == 127) /* Backspace */ + { + if(pwindex > 0) + { + --pwindex; + } + print_password(); + continue; + } + if(c == '\n') + { + break; + } + continue; + } + if(pwindex == pwbufsize) + { + grow_pw_buf(); + } + + pwbuf[pwindex] = c; + ++pwindex; + + print_password(); + } + clear_term_line(); + + fsync(STDERR_FILENO); + write(STDOUT_FILENO, pwbuf, pwindex); + fsync(STDOUT_FILENO); + + return 0; +}