diff options
Diffstat (limited to 'content')
-rw-r--r-- | content/posts/how-bsd-authentication-works/index.org | 545 |
1 files changed, 275 insertions, 270 deletions
diff --git a/content/posts/how-bsd-authentication-works/index.org b/content/posts/how-bsd-authentication-works/index.org index 6eae6a5..76fe4a4 100644 --- a/content/posts/how-bsd-authentication-works/index.org +++ b/content/posts/how-bsd-authentication-works/index.org @@ -10,303 +10,308 @@ [[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]]) ([[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 =pledge(3)= or -=unveil(3)=. + + 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]]) ([[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 =pledge(3)= or + =unveil(3)=. * 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. + 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 =login.conf(5)= -- =service= is the service type. Typically authentication methods will - accept three values here, =login=, =challenge=, or =response=. Some - styles take different service arguments, read the style's man page - for details. - - =login= is typically the default method -- =-v key=value= is an optional argument. 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. + 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 =login.conf(5)= under the =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. 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. * Documentation -All of the high level authentication functions are described in -=authenticate(3)=, with the lower level functions being described in -=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. + All of the high level authentication functions are described in + =authenticate(3)=, with the lower level functions being described in + =auth_subr(3)=. -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 +* auth_userokay -- =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. + 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 + #+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_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 calls =auth_open=, which -allocates and returns the pointer to an =auth_session_t=, and sets its -default =service= to =login=, and it's =fd= to =-1=. After that's -returned, =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. + #+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 calls =auth_open=, which + allocates and returns the pointer to an =auth_session_t=, and sets its + default =service= to =login=, and it's =fd= to =-1=. After that's + returned, =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 + #+BEGIN_SRC c + auth_session_t *auth_verify(auth_session_t *as, char *style, char *name, ...) + #+END_SRC -=auth_verify= creates an auth session 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=. + =auth_verify= creates an auth session 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=. + 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. + 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 + #+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 - -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 - - -This is the integer returned by -=auth_userokay=. + #+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 + + 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 + + + This is the integer returned by + =auth_userokay=. * grapgh? # Setting env on auth_close(as) |