Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
8.7.1 Problem
You need to prompt an interactive user for a password. 8.7.2 Solution
On Unix systems, you can use the standard C runtime function getpass( ) if you can accept limiting passwords to _PASSWORD_LEN, which is typically defined to be 128 characters. If you want to read longer passwords, you can use the function described in the following Section 8.7.3. On Windows, you can use the standard EDIT control with ES_PASSWORD specified as a style flag to mask the characters typed by a user. 8.7.3 Discussion
In the following subsections we'll look at several different approaches to prompting for passwords. 8.7.3.1 Prompting for a password on Unix using getpass( ) or readpassphrase( )
The standard C runtime function getpass( ) is the most portable way to obtain a password from a user interactively. Unfortunately, it does have several limitations that you may find unacceptable. The first is that only up to _PASSWORD_LEN (typically 128) characters may be entered; any characters after that are simply discarded. The second is that the password is stored in a statically defined buffer, so it is not thread-safe, but ordinarily this is not much of a problem because there is fundamentally no way to read from the terminal in a thread-safe manner anyway. The getpass( ) function has the following signature: #include <sys/types.h> #include <unistd.h> char *getpass(const char *prompt); The text passed as the function's only argument is displayed on the terminal, terminal echo is disabled, and input is gathered in a buffer internal to the function until the user presses Enter. The return value from the function is a pointer to the internal buffer, which will be at most _PASSWORD_LEN + 1 bytes in size, with the additional byte left to hold the NULL terminator. FreeBSD and OpenBSD both support an alternative function, readpassphrase( ), that provides the underlying implementation for getpass( ). It is more flexible than getpass( ), allowing the caller to preallocate a buffer to hold a password or passphrase of any size. In addition, it also supports a variety of control flags that control its behavior. The readpassphrase( ) function has the following signature: #include <sys/types.h> #include <readpassphrase.h> char *readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags); This function has the following arguments:
A number of flags are defined as macros in the readpassphrase.h header file. While some of the flags are mutually exclusive, some of them may be logically combined together:
For both getpass( ) and readpassphrase( ), a pointer to the input buffer will be returned if the function completes successfully; otherwise, a NULL pointer will be returned, and the error that occurred will be stored in the global errno variable.
Once getpass( ) or readpassphrase( ) return successfully, you should perform as quickly as possible whatever operation you need to perform with the password that was obtained. Then clear the contents of the returned buffer so that the cleartext password or passphrase will not be left visible in memory to a potential attacker. 8.7.3.2 Prompting for a password on Unix without getpass( ) or readpassphrase( )
The function presented in this subsection, spc_read_password( ), requires two arguments. The first is a prompt to be displayed to the user, and the second is the FILE object that points to the input source. If the input source is specified as NULL, spc_read_password( ) will use _PATH_TTY, which is usually defined to be /dev/tty. The function reads as much data from the input source as memory is available to hold. It allocates an internal buffer, which grows incrementally as it is filled. If the function is successful, the return value will be a pointer to this buffer; otherwise, it will be a NULL pointer. Note that we use the unbuffered I/O API for reading data from the input source. The unbuffered read is necessary to avoid potential odd side effects in the I/O. We cannot use the stream API because there is no way to save and restore the size of the stream buffer. That is, we cannot know whether the stream was previously buffered. #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <termios.h> #include <signal.h> #include <paths.h> #define BUF_STEP 1024 /* Allocate this much space for the password, and if it gets * this long, reallocate twice the space. * Rinse, lather, repeat. */ static unsigned char *read_password(int termfd) { unsigned char ch, *ret, *tmp; unsigned long ctr = 0; if (!(ret = (unsigned char *)malloc(BUF_STEP + 1))) return 0; for (;;) { switch (read(termfd, &ch, 1)) { case 1: if (ch != '\n') break; /* FALL THROUGH */ case 0: ret[ctr] = 0; return ret; default: free(ret); return 0; } ret[ctr] = ch; if (ctr && !(ctr & BUF_STEP)) { if (!(tmp = (unsigned char *)realloc(ret, ctr + BUF_STEP + 1))) { free(ret); return 0; } ret = tmp; } ctr++; } } unsigned char *spc_read_password(unsigned char *prompt, FILE *term) { int close = 0, termfd; sigset_t saved_signals, set_signals; unsigned char *retval; struct termios saved_term, set_term; if (!term) { if (!(term = fopen(_PATH_TTY, "r+"))) return 0; close = 1; } termfd = fileno(term); fprintf(term, "%s", prompt); fflush(term); /* Defer interruption when echo is turned off */ sigemptyset(&set_signals); sigaddset(&set_signals, SIGINT); sigaddset(&set_signals, SIGTSTP); sigprocmask(SIG_BLOCK, &set_signals, &saved_signals); /*Save the current state and set the terminal to not echo */ tcgetattr(termfd, &saved_term); set_term = saved_term; set_term.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL); tcsetattr(termfd, TCSAFLUSH, &set_term); retval = read_password(termfd); fprintf(term, "\n"); tcsetattr(termfd, TCSAFLUSH, &saved_term); sigprocmask(SIG_SETMASK, &saved_signals, 0); if (close) fclose(term); return retval; } 8.7.3.3 Prompting for a password on Windows
On Windows, prompting for a password is as simple as setting the ES_PASSWORD style flag for an EDIT control. When this flag is set, Windows will not display the characters typed by the user. Instead, the password character will be displayed for each character that is typed. By default, the password character is an asterisk (*), but you can change it by sending the control an EM_SETPASSWORDCHAR message with wParam set to the character to display. Unfortunately, there is no way to prevent Windows from displaying something as the user types. The closest that can be achieved is to set the password character to a space, which will make it difficult for an onlooker to determine how many characters have been typed. To safely retrieve the password stored in the EDIT control's internal buffer, the control should first be queried to determine how many characters it holds. Allocate a buffer to hold the data and query the data from the control. The control will make a copy of the data but leave the original internal buffer unchanged. To be safe, it's a good idea to set the contents of the buffer to clear the password from internal memory used by the EDIT control. Simply setting the control's internal buffer to an empty string is not sufficient. Instead, set a string that is the length of the string retrieved, then set an empty string if you wish. For example: #include <windows.h> BOOL IsPasswordValid(HWND hwndPassword) { BOOL bValid = FALSE; DWORD dwTextLength; LPTSTR lpText; if (!(dwTextLength = (DWORD)SendMessage(hwndPassword, WM_GETTEXTLENGTH, 0, 0))) return FALSE; lpText = (LPTSTR)LocalAlloc(LMEM_FIXED, (dwTextLength + 1) * sizeof(TCHAR)); if (!lpText) return FALSE; SendMessage(hwndPassword, WM_GETTEXT, dwTextLength + 1, (LPARAM)lpText); /* Do something to validate the password */ while (dwTextLength--) *(lpText + dwTextLength) = ' '; SendMessage(hwndPassword, WM_SETTEXT, 0, (LPARAM)lpText); LocalFree(lpText); return bValid; }
|