summaryrefslogtreecommitdiffstats
path: root/content/posts/WIP-how-bsd-authentication-works
diff options
context:
space:
mode:
authorDante Catalfamo2020-07-07 22:28:28 -0400
committerDante Catalfamo2020-07-07 22:28:28 -0400
commit118b0019658890837ab8e19608ce0779cb6f4d9a (patch)
treef1146c980182a3fa19dcc37553a2932583e193f3 /content/posts/WIP-how-bsd-authentication-works
parenta3bf350943b99c475a281b00a8354654fe4b26f9 (diff)
downloadblog-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')
-rw-r--r--content/posts/WIP-how-bsd-authentication-works/graph.dot67
-rw-r--r--content/posts/WIP-how-bsd-authentication-works/index.org473
2 files changed, 540 insertions, 0 deletions
diff --git a/content/posts/WIP-how-bsd-authentication-works/graph.dot b/content/posts/WIP-how-bsd-authentication-works/graph.dot
new file mode 100644
index 0000000..6414b11
--- /dev/null
+++ b/content/posts/WIP-how-bsd-authentication-works/graph.dot
@@ -0,0 +1,67 @@
+digraph G {
+ subgraph cluster_authenticate {
+ label = "authenticate.c"
+ auth_userokay;
+ auth_usercheck;
+ auth_verify;
+ }
+
+ subgraph cluster_auth_subr {
+ label = "auth_subr.c"
+ auth_open;
+ auth_call;
+ auth_close;
+ // auth_setitem;
+ // auth_setdata;
+ // auth_setopts;
+ auth_set[label="auth_set*"];
+ auth_setstate;
+ // _auth_spool;
+ }
+
+ subgraph cluster_login_cap {
+ label = "libc/login_cap.c"
+ login_getclass
+ login_getstyle
+ }
+
+ subgraph cluster_getpwent {
+ label = "libc/getpwent.c"
+ getpwnam_r;
+ }
+
+ subgraph cluster_exec {
+ login[label="login_*"];
+ execve;
+ }
+
+
+ start -> auth_userokay;
+ auth_userokay -> auth_usercheck;
+ auth_usercheck -> getpwnam_r;
+ auth_usercheck -> login_getclass;
+ auth_usercheck -> login_getstyle;
+ // if password given
+ auth_usercheck -> auth_open;
+ // auth_usercheck -> auth_setitem;
+ // auth_usercheck -> auth_setdata;
+ auth_usercheck -> auth_set;
+ // fi
+ auth_usercheck -> auth_verify;
+
+ auth_verify -> auth_setstate;
+ auth_verify -> auth_call;
+
+ auth_call -> execve[label="fork()"];
+ // auth_call -> _auth_spool;
+
+ execve -> login;
+ login -> auth_call[label="back channel"];
+ // login -> _auth_spool[label="back channel"];
+
+
+ // auth_usercheck -> { auth_setitem auth_setdata auth_setopts }
+
+ // auth_call -> auth_userokay;
+ auth_userokay -> auth_close;
+}
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.