diff options
author | Dante Catalfamo | 2020-07-07 22:28:28 -0400 |
---|---|---|
committer | Dante Catalfamo | 2020-07-07 22:28:28 -0400 |
commit | 118b0019658890837ab8e19608ce0779cb6f4d9a (patch) | |
tree | f1146c980182a3fa19dcc37553a2932583e193f3 /content/posts/WIP-how-bsd-authentication-works/index.org | |
parent | a3bf350943b99c475a281b00a8354654fe4b26f9 (diff) | |
download | blog-118b0019658890837ab8e19608ce0779cb6f4d9a.tar.gz blog-118b0019658890837ab8e19608ce0779cb6f4d9a.tar.bz2 blog-118b0019658890837ab8e19608ce0779cb6f4d9a.zip |
Move WIP posts to WIP folders
Diffstat (limited to 'content/posts/WIP-how-bsd-authentication-works/index.org')
-rw-r--r-- | content/posts/WIP-how-bsd-authentication-works/index.org | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/content/posts/WIP-how-bsd-authentication-works/index.org b/content/posts/WIP-how-bsd-authentication-works/index.org new file mode 100644 index 0000000..4126284 --- /dev/null +++ b/content/posts/WIP-how-bsd-authentication-works/index.org @@ -0,0 +1,473 @@ +#+TITLE: How BSD Authentication Works +#+DATE: 2020-06-26T18:31:36-04:00 +#+DRAFT: true +#+DESCRIPTION: +#+TAGS[]: openbsd +#+KEYWORDS[]: openbsd +#+SLUG: +#+SUMMARY: +#+SHOWTOC: true + +[[https://web.archive.org/web/20170327150148/http://www.penzin.net/bsdauth/]] +* History + + OpenBSD is quite different from many other Unix-like operating systems + in many ways, but one way which I find interesting is the + authentication system. Most systems from AIX, Solaris, and Linux to + most BSDs including MacOS use some form of a system called Pluggable + Authentication Module (PAM). The two main implementations of PAM are + [[http://www.linux-pam.org/][Linux PAM]] and [[https://www.openpam.org/][OpenPAM]]. PAM modules are created a dynamically loaded + shared objects, which communicate using a set of standard + interfaces ([[https://linux.die.net/man/3/pam][Linux-PAM]] and [[https://www.freebsd.org/cgi/man.cgi?query=pam&apropos=0&sektion=3&manpath=FreeBSD+12.1-RELEASE+and+Ports&arch=default&format=html][OpenPAM]]). PAM is configured using the [[https://linux.die.net/man/5/pam.d][pam.d]] + directory and [[https://www.freebsd.org/cgi/man.cgi?query=pam.conf&sektion=5&apropos=0&manpath=FreeBSD+12.1-RELEASE+and+Ports][pam.conf]]. + + OpenBSD on the other hand uses a mechanism called BSD + Authentication. It was originally developed for a proprietary + operating system called [[https://en.wikipedia.org/wiki/BSD/OS][BSD/OS]] by [[https://en.wikipedia.org/wiki/Berkeley_Software_Design][Berkeley Software Design Inc.]], who + later donated the system. It was adopted by OpenBSD in release + 2.9. BSD Auth is comparatively much simpler than PAM. Modules or, + authentication "styles", are instead stand alone applications or + scripts that communicate over IPC (=PF_LOCAL, SOCK_STREAM=, + specifically). The program or script has no ability to interfere + with the parent and can very easily revoke permissions using + [[https://man.openbsd.org/pledge][=pledge(2)=]] or [[https://man.openbsd.org/unveil][=unveil(2)=]]. The BSD Authentication system of + configured through [[https://man.openbsd.org/login.conf][=login.conf(5)=]]. + +* Why + + This one is pretty difficult, since there seems to be very little + information about how BSD Auth works apart from the source code + itself. This is my best attempt to understand the flow of BSD Auth + from what I've read. + +* BSD Auth Modules + + These programs or scripts are located in =/usr/libexec/auth/= with the + naming convention =login_<style>=. They take arguments in the form of + + #+BEGIN_SRC shell + login_<style> [-s service] [-v key=value] user [class] + #+END_SRC + + - =<style>= is the authentication method. This could be =passwd=, + =radius=, =skey=, =yubikey=, etc. There's more information about + available styles in [[https://man.openbsd.org/login.conf][=login.conf(5)=]] under the [[https://man.openbsd.org/login.conf#AUTHENTICATION][=AUTHENTICATION=]] + header. + - =service= is the service type. Typically authentication methods will + accept one of three values here, =login=, =challenge=, or + =response=. =login= is the default if it's not specified, and is + usually the right choice. Read the style's man page for details. + - =-v key=value= is an optional argument. There is no limit to the + number of =-v= arguments. This is used to pass extra data to the + program under certain circumstances. + - =user= is the name of the user to be authenticated. + - =class= is optional and specifies the class of the user to be + authenticated. + + =login= and =su= pass in extra data as =-v= flags. + + #+CAPTION: Taken from [[https://man.openbsd.org/login.conf][=login.conf(5)=]] + #+BEGIN_SRC + The login(1) program provides the following through the -v option: + + auth_type The type of authentication to use. + + fqdn The hostname provided to login by the -h option. + + hostname The name login(1) will place in the utmp file for the + remote hostname. + + local_addr The local IP address given to login(1) by the -L option. + + lastchance Set to "yes" when a user's password has expired but the + user is being given one last chance to login and update + the password. + + login This is a new login session (as opposed to a simple + identity check). + + remote_addr The remote IP address given to login(1) by the -R option. + + style The style of authentication used for this user (see + approval scripts below). + + The su(1) program provides the following through the -v option: + + wheel Set to either "yes" or "no" to indicate if the user is in + group wheel when they are trying to become root. Some + authentication types require the user to be in group + wheel when using the su(1) program to become super user. + #+END_SRC + + The auth module communicates with its caller through file descriptor 3. + +* Documentation + + All of the high level authentication functions are described in + [[https://man.openbsd.org/authenticate][=authenticate(3)=]], with the lower level functions being described in + [[https://man.openbsd.org/auth_subr][=auth_subr(3)=]]. + +* auth_userokay + + The highest level function, and easiest to use is =auth_userokay=. It + takes four character arrays as arguments, =name=, =style=, =type=, and + =password=. It returns either a =0= for failure, of a non-zero value + for success. + + This function lives inside =/lib/libc/gen/authenticate.c= + + #+BEGIN_SRC c + int auth_userokay(char *name, char *style, char *type, char *password); + #+END_SRC + + - =name= is the name of the user to be authenticated + - =style= is the login method to be used + - If =style= is =NULL=, the user's default login style will be + used. By default this is =passwd= on normal accounts. + - The style can be one of the installed authentication methods, like + =passwd=, =radius=, =skey=, =yubikey=, etc. + - There's more information about available styles in =login.conf(5)= + - Styles can also be installed through BSD Auth module packages + - =type= is the authentication type + - Types are defined in =login.conf= and define a group of allowed + auth styles + - If =type= is =NULL=, use the auth type for the user's login + class. The default type is =auth-default=, which allows + =psaswd= and =skey= auth methods. + - There's more information about how to add methods in =login.conf(5)= + - =password= is the password to test + - If =password= is =NULL=, then the user is interactively + prompted. This is required for auth styles using + challenge-response methods. + - If =password= is specified, then it's non-interactively tested + + =auth_userokay= is just a wrapper around =auth_usercheck=, which + returns a finished auth session of type =auth_session_t=. It closes + the auth session using =auth_close= and returns the value returned + from closing. + +* auth_session_t + + #+BEGIN_SRC c + struct auth_session_t { + char *name; /* name of use being authenticated */ + char *style; /* style of authentication used */ + char *class; /* class of user */ + char *service; /* type of service being performed */ + char *challenge; /* last challenge issued */ + int flags; /* see below */ + struct passwd *pwd; /* password entry for user */ + struct timeval now; /* time of authentication */ + + int state; /* authenticated state */ + + struct rmfiles *rmlist; /* list of files to remove on failure */ + struct authopts *optlist; /* list of options to scripts */ + struct authdata *data; /* additional data to send to scripts */ + + char spool[MAXSPOOLSIZE]; /* data returned from login script */ + int index; /* how much returned thus far */ + + int fd; /* connection to authenticator */ + + va_list ap0; /* argument list to auth_call */ + va_list ap; /* additional arguments to auth_call */ + }; + #+END_SRC + + Where =authdata=, =authopts=, and =rmfiles= are defined as + + #+BEGIN_SRC c + struct rmfiles { + struct rmfiles *next; + char *file; + }; + + struct authopts { + struct authopts *next; + char *opt; + }; + + struct authdata { + struct authdata *next; + void *ptr; + size_t len; + }; + #+END_SRC + +** auth_setdata + +** auth_setitem + +** auth_setoption + +** auth_setstate +* auth_open + + The =auth_open= function is used by several functions to create a + new auth session. It allocates an =auth_session_t= struct on the + heap, sets its default =service= to =login=, and it's =fd= to =-1=, + and returns the pointer. + +* auth_usercheck + + #+BEGIN_SRC c + auth_session_t *auth_usercheck(char *name, char *style, char *type, char *password) + #+END_SRC + + =auth_usercheck= checks the user name against the passwd db. It also + checks the login class against the =login.conf= db, along with + confirming the login styles available. + + If the password is non-=NULL=, then it creates a new session using + =auth_open=. With the new session, =auth_usercheck= calls (with =as= + as the session struct) + + #+BEGIN_SRC c + auth_setitem(as, AUTHV_SERVICE, "response"); + auth_setdata(as, "", 1); + auth_setdata(as, password, strlen(password) + 1); + #+END_SRC + + setting the service protocol to =response=, adding an empty line to + the session data, then adding the password as data. If the password is + =NULL=, it sets the =auth_session_t= pointer to =NULL=. It then passes + the user name, style, login class, and =NULL= char pointer to + =auth_verify=. The last two variables are received as variable + arguments. It then returns the auth session pointer the call + returns. + +* auth_verify + + #+BEGIN_SRC c + auth_session_t *auth_verify(auth_session_t *as, char *style, char *name, ...) + #+END_SRC + + =auth_verify= creates an auth session using =auth_open= if =as= is + =NULL=. It then sets the user name and style of the session, if the + respective arguments are non-=NULL=. It then copies its variable + arguments to the auth session's =va_list ap=, which is used inside + of =auth_call=. + + After that it constructs the path of the authentication module by + combining =_PATH_AUTHPROG=, which is defined in =login_cap.h= as + =/usr/libexec/auth/login_=, and the authentication style. For the + case of auth style =passwd=, it would result in the path + =/usr/libexec/auth/login_passwd=. + + Then =auth_call= is called with the struct, the path to the auth + module, the auth style, the "-s" flag followed by the service (login, + challenge, response), a double dash, the user name, and a =NULL= + character pointer. The return value of =auth_call= is ignored and a + pointer to the auth session is returned immediately afterwards. + + #+BEGIN_SRC c + auth_call(as, path, auth_getitem(as, AUTHV_STYLE), "-s", + auth_getitem(as, AUTHV_SERVICE), "--", name, (char *)NULL); + #+END_SRC + +* auth_call + + #+BEGIN_SRC c + int auth_call(auth_session_t *as, char *path, ...) + #+END_SRC + + <<here>> + + --- + note: In the man page auth_subr it says + #+begin_quote + path The full path name of the login script to run. The call will + fail if path does not pass the requirements of the secure_path(3) + function. + #+end_quote + However I don't see this enforced anywhere, I even wrote a small test + script to prove that's the case on =vfwall ~/authtest=. + + The manpage also says the path is limited to =/bin/= and =/usr/bin=, + which is also not the case. + + Ask jcs about the file descriptor situation, I don't understand it + after reading both the man page and source. + --- + + Inside of =auth_call=, a socket pair of type =PF_LOCAL, SOCK_STREAM= + is created. This is called the "back channel", and is used to + communicate with the authentication module. The process then forks, + calling ~execve(path, argv, auth_environ)~, where the =argv= is + everything after =path= in the =auth_call= arguments. Any =authopts= + set in the auth session are also passed as arguments in the format =-v + opt1 -v opt2 -v opt3=, etc. =auth_environ= is defined at the top of + the file as + + #+BEGIN_SRC c + static char *auth_environ[] = { + "PATH=" _PATH_DEFPATH, + "SHELL=" _PATH_BSHELL, + NULL, + }; + #+END_SRC + + Where both constants are defined in =paths.h= as + + #+BEGIN_SRC c + #define _PATH_DEFPATH "/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin" + #define _PATH_BSHELL "/bin/sh" + #+END_SRC + + + The =exec='d process then listens on FD 3, which is one half of the + =sockpair= that was created earlier. + + In the non-exec'd process, first the contents of the auth session's + =*data= are read in one at a time. + + The data received through the back channel is then put into the + =spool= of the auth session using =_auth_spool(as, pfd[0])=. After + that the spooled data is scanned for key words defined in + =login_cap.h=. + + #+BEGIN_SRC c + #define BI_AUTH "authorize" /* Accepted authentication */ + #define BI_REJECT "reject" /* Rejected authentication */ + #define BI_CHALLENGE "reject challenge" /* Reject with a challenge */ + #define BI_SILENT "reject silent" /* Reject silently */ + #define BI_REMOVE "remove" /* remove file on error */ + #define BI_ROOTOKAY "authorize root" /* root authenticated */ + #define BI_SECURE "authorize secure" /* okay on non-secure line */ + #define BI_SETENV "setenv" /* set environment variable */ + #define BI_UNSETENV "unsetenv" /* unset environment variable */ + #define BI_VALUE "value" /* set local variable */ + #define BI_EXPIRED "reject expired" /* account expired */ + #define BI_PWEXPIRED "reject pwexpired" /* password expired */ + #define BI_FDPASS "fd" /* child is passing an fd */ + #+END_SRC + + The [[https://man.openbsd.org/login.conf][=login.conf(5)=]] man page once again goes into greater detail on + these values. + + #+BEGIN_SRC + authorize The user has been authorized. + + authorize secure + The user has been authorized and root should be allowed to + login even if this is not a secure terminal. This should only + be sent by authentication styles that are secure over insecure + lines. + + reject Authorization is rejected. This overrides any indication that + the user was authorized (though one would question the wisdom + in sending both a reject and an authorize command). + + reject challenge + Authorization was rejected and a challenge has been made + available via the value challenge. + + reject silent + Authorization is rejected, but no error messages should be + generated. + + remove file + If the login session fails for any reason, remove file before + termination. + + setenv name value + If the login session succeeds, the environment variable name + should be set to the specified value. + + unsetenv name + If the login session succeeds, the environment variable name + should be removed. + + value name value + Set the internal variable name to the specified value. The + value should only contain printable characters. Several \ + sequences may be used to introduce non printing characters. + These are: + + \n A newline. + + \r A carriage return. + + \t A tab. + + \xxx The character represented by the octal value xxx. The + value may be one, two, or three octal digits. + + \c The string is replaced by the value of c. This allows + quoting an initial space or the \ character itself. + + + The following values are currently defined: + + challenge + See section on challenges below. + + errormsg + If set, the value is the reason authentication failed. + The calling program may choose to display this when + rejecting the user, but display is not required. + + #+END_SRC + + + It is looking for lines that start with either =BI_AUTH= + (=authorize=), or =BI_REJECT= (=reject=). If the line is still longer, + it continues to scan for any other qualifiers such as =pwexpired= or + =silent=. The struct's =state= is set to one using the =AUTH_= values + from =login_cap.h= accordingly. + + #+BEGIN_SRC c + /* + * bits which can be returned by authenticate()/auth_scan() + */ + #define AUTH_OKAY 0x01 /* user authenticated */ + #define AUTH_ROOTOKAY 0x02 /* authenticated as root */ + #define AUTH_SECURE 0x04 /* secure login */ + #define AUTH_SILENT 0x08 /* silent rejection */ + #define AUTH_CHALLENGE 0x10 /* a challenge was given */ + #define AUTH_EXPIRED 0x20 /* account expired */ + #define AUTH_PWEXPIRED 0x40 /* password expired */ + #+END_SRC + +** _auth_spool + +** _recv_fd + +* auth_close + =auth_close= is the function responsible for cleaning up the session + and taking care of the values returned though the back channel. + + It first sets the environment variables returned through the back + channel by passing the auth session to =auth_setenv=. It then goes + through the =rmlist= of the session, deleting the files if the + session reported a failure. It then zeroes out all sensitive + information, and frees the various structs associated with the current + =auth_session_t=, and then the session itself. Finally it returns + the session's state =&='ed with =AUTH_ALLOW=. +* grapgh? +# Setting env on auth_close(as) +# partual rewrite below + +The call graph for =auth_userokay= looks something like this: + +#+BEGIN_SRC c +int auth_userokay(char *name, char *style, char *type, char *password) +#+END_SRC + +calls ~auth_usercheck~ and then calls ~auth_close~ on the returned +~auth_session_t~. The value returned from ~auth_close~ is then +returned. + +#+BEGIN_SRC c +auth_session_t *auth_usercheck(char *name, char *style, char *type, char *password) +#+END_SRC + +Validates the checks that the user exists, gets the user's login +class, verifies the auth type, and that the auth style can be used. + +It creates an auth session struct. + +If the password is provided it sets the service type to =response=, +and adds the adds the password to the auth data. Otherwise it +leaves it empty. |