diff options
| -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) | 
