You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@subversion.apache.org by Ben Collins-Sussman <su...@collab.net> on 2001/07/26 17:48:13 UTC

authentication architecture.

I'm filling in the list on an authentication architecture we'll be
using for upcoming M3.  It's about as generalized as it can be.

Here are the parameters of the problem:

   * The callers of any RA (Repository Access) library need to provide
     a structure that represents who's doing the access.  It's up to
     the RA layer to actually *do* the authentication (and
     authorization).  It's up to the caller to provide the necessary
     authentication info.

   * If new RA schemas are written, or new RA layers are written, it's
     inevitable that every top-level client in the world will need to
     be tweaked and rebuilt to support them -- to *some* degree.

     The issue here is that an RA library cannot directly prompt the
     user for information -- like a password.  So the top-level client
     will need to "learn" about each RA library's authentication
     needs.  However, we'd like as little code churn as possible.  For
     example: if ra_foo needs information X to authenticate, and
     somebody writes a new ra_bar module that needs the same
     information, ra_bar should be able to make use of the fact that
     the client-side libraries *already* know how to get it.


Here is what we're going to do:


* create a transparent svn_ra_user_t structure.

  This structure contains the *union* of all authentication data
  needed by all RA implementations.  (There's no way around this.)

* all svn_client_*() routines now take a new argument:  a 'hook'
  routine from the command-line (or GUI) client.

   * when svn_client_*() fetches an RA library appropriate to the URL
     schema, it will get back a flag state.  This flag-state indicates
     which fields are needed within the user structure by the
     particular RA library.

   * libsvn_client now attempts to fill in every user_t field it can.
     (for example, it can easily fill in a Unix UID field.)

   * if libsvn_client can't fill in all the fields, it "kicks" the
     user_t structure up to its client caller (using the hook routine)
     and then the client finishes the job.  (for example, prompting
     the user for a password, reading a config file, etc.)

   * the user_t object is now passed (along with the URL) to ra->open().

         * the RA library does whatever authentication/authorization 
           it wants under the hood.

         * the RA library (presumably) stashes the user_t object in
           the "session baton" returned to libsvn_client.

   * libsvn_client now goes on and uses the RA session baton as usual.

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: authentication architecture.

Posted by Ben Collins-Sussman <su...@collab.net>.
Wow, this is really a great architeture, and it's only a tiny bit
different than what we were thinking.  I especially like the way
multiphase challenges can be covered.  I'll definitely implement a
clone of this system.  Thanks, Mark!


Branko =?ISO-8859-2?Q?=C8ibej?= <br...@xbc.nu> writes:

> ++1
> 
> This design is similar to how PAM -- and other flexible authentication 
> systems -- work. A lot of thought has gone into the design of such 
> systems. Let's not reinvent the wheel here.
> 
> Mark C. Chu-Carroll wrote:
> 
> >I'm trying not to be too annoying with contributions about how our
> >project does things, but I think this might be useful to you. 
> >We've just gone through a similar discussion in Synedra, with similar
> >constraints. In particular, we share the layering constraint where we
> >want no backward calls from the server to the client. At the same time,
> >we (like subversion) want full access through the network, and we don't
> >want to be transmitting passwords around *at all*.
> >
> >Our solution is object based, but I think there's a decent
> >pointer/structure based equivalent. And I think it meets the
> >constraints that Ben was talking about. And it's got one other big
> >advantage: you can add or alter authentication mechanisms without
> >requiring corresponding changes to client code.
> >
> >The idea is that we  use "authentication components". An authentication
> >component is an object with a set of methods that can be used by the
> >client to prove that it has the appropriate credentials. In Java,
> >a trivial example is an authenticator for password authentication looks
> >like:
> >
> >	class TrivialPasswordAuthenticator {
> >            public void setUser(String user);
> >	    public void setPassword(String pass);
> >            public RepositoryHandle authenticate();
> >    	}
> >
> >When a client connects, it can request from the server a list of
> >available authentication methods. If the client can handle any of the
> >methods provided by the server, then it requests an authenticator of
> >the correct type, and uses that to authenticate itself.
> >
> >This gets pretty nice when you consider challenge methods; an
> >HTTP digest style challenge can be done using a class with
> >a signature like the following:
> >
> >class HTTP_Authenticator {
> >   HTTP_Authenticator(java.sql.Connection dbconn, String reposname);
> >   Repository authenticate() throws PriviledgeException;
> >   void setUserName(String name);
> >   void setClientNonce(String cn);
> >   void setURI(String uri);
> >   String getURI(String uri);
> >   void setOperation(String op);
> >   String getServerNonce();
> >   String getRealm();
> >   void setChallengeCode(byte hashcode[]);
> >}
> >
> >
> >The client is expected to know the protocol for the authenticator. In
> >this
> >case, it can retrieve the information it needs to compute the challenge
> >result; and then it can provide the client nonce and the challenge
> >result
> >to the server before finally authenticating.
> >
> >Multiphase challenge can be done using an authenticator that returns a
> >second-stage authenticator from the "authenticate" call.
> >
> >So... A typical client interacting with this authentication method
> >would do:
> >	Set<String> auths = getAuthenticators();
> >	if (auths.contains("password")) {
> >	    PasswordAuthenticator pa = getAuthenticator("password");
> >            pa.setUser(username);
> >            .. prompt user for password ..
> >            pa.setPassword(password);
> >            RepositoryHandle handle = pa.authenticate();
> >	    ...
> >         }
> >
> >Again, this method manages to provide solid authentication, with the
> >ability
> >to extend the set of recognized authenticators, but without requiring
> >changes
> >to old client. It should be fairly easy to provide this kind of
> >functionality
> >using closure style structs.
> >
> >	-Mark
> >
> >
> >--
> >
> >Mark Craig Chu-Carroll,  IBM T.J. Watson Research Center 
> ><mc...@watson.ibm.com>
> >*** The Synedra project:
> >http://domino.research.ibm.com/synedra/synedra.nsf
> >
> >
> >---------------------------------------------------------------------
> >To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
> >For additional commands, e-mail: dev-help@subversion.tigris.org
> >

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: authentication architecture.

Posted by Branko Čibej <br...@xbc.nu>.
++1

This design is similar to how PAM -- and other flexible authentication 
systems -- work. A lot of thought has gone into the design of such 
systems. Let's not reinvent the wheel here.

Mark C. Chu-Carroll wrote:

>I'm trying not to be too annoying with contributions about how our
>project does things, but I think this might be useful to you. 
>We've just gone through a similar discussion in Synedra, with similar
>constraints. In particular, we share the layering constraint where we
>want no backward calls from the server to the client. At the same time,
>we (like subversion) want full access through the network, and we don't
>want to be transmitting passwords around *at all*.
>
>Our solution is object based, but I think there's a decent
>pointer/structure based equivalent. And I think it meets the
>constraints that Ben was talking about. And it's got one other big
>advantage: you can add or alter authentication mechanisms without
>requiring corresponding changes to client code.
>
>The idea is that we  use "authentication components". An authentication
>component is an object with a set of methods that can be used by the
>client to prove that it has the appropriate credentials. In Java,
>a trivial example is an authenticator for password authentication looks
>like:
>
>	class TrivialPasswordAuthenticator {
>            public void setUser(String user);
>	    public void setPassword(String pass);
>            public RepositoryHandle authenticate();
>    	}
>
>When a client connects, it can request from the server a list of
>available authentication methods. If the client can handle any of the
>methods provided by the server, then it requests an authenticator of
>the correct type, and uses that to authenticate itself.
>
>This gets pretty nice when you consider challenge methods; an
>HTTP digest style challenge can be done using a class with
>a signature like the following:
>
>class HTTP_Authenticator {
>   HTTP_Authenticator(java.sql.Connection dbconn, String reposname);
>   Repository authenticate() throws PriviledgeException;
>   void setUserName(String name);
>   void setClientNonce(String cn);
>   void setURI(String uri);
>   String getURI(String uri);
>   void setOperation(String op);
>   String getServerNonce();
>   String getRealm();
>   void setChallengeCode(byte hashcode[]);
>}
>
>
>The client is expected to know the protocol for the authenticator. In
>this
>case, it can retrieve the information it needs to compute the challenge
>result; and then it can provide the client nonce and the challenge
>result
>to the server before finally authenticating.
>
>Multiphase challenge can be done using an authenticator that returns a
>second-stage authenticator from the "authenticate" call.
>
>So... A typical client interacting with this authentication method
>would do:
>	Set<String> auths = getAuthenticators();
>	if (auths.contains("password")) {
>	    PasswordAuthenticator pa = getAuthenticator("password");
>            pa.setUser(username);
>            .. prompt user for password ..
>            pa.setPassword(password);
>            RepositoryHandle handle = pa.authenticate();
>	    ...
>         }
>
>Again, this method manages to provide solid authentication, with the
>ability
>to extend the set of recognized authenticators, but without requiring
>changes
>to old client. It should be fairly easy to provide this kind of
>functionality
>using closure style structs.
>
>	-Mark
>
>
>--
>
>Mark Craig Chu-Carroll,  IBM T.J. Watson Research Center 
><mc...@watson.ibm.com>
>*** The Synedra project:
>http://domino.research.ibm.com/synedra/synedra.nsf
>
>
>---------------------------------------------------------------------
>To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
>For additional commands, e-mail: dev-help@subversion.tigris.org
>




---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: authentication architecture.

Posted by "Mark C. Chu-Carroll" <mc...@watson.ibm.com>.
I'm trying not to be too annoying with contributions about how our
project does things, but I think this might be useful to you. 
We've just gone through a similar discussion in Synedra, with similar
constraints. In particular, we share the layering constraint where we
want no backward calls from the server to the client. At the same time,
we (like subversion) want full access through the network, and we don't
want to be transmitting passwords around *at all*.

Our solution is object based, but I think there's a decent
pointer/structure based equivalent. And I think it meets the
constraints that Ben was talking about. And it's got one other big
advantage: you can add or alter authentication mechanisms without
requiring corresponding changes to client code.

The idea is that we  use "authentication components". An authentication
component is an object with a set of methods that can be used by the
client to prove that it has the appropriate credentials. In Java,
a trivial example is an authenticator for password authentication looks
like:

	class TrivialPasswordAuthenticator {
            public void setUser(String user);
	    public void setPassword(String pass);
            public RepositoryHandle authenticate();
    	}

When a client connects, it can request from the server a list of
available authentication methods. If the client can handle any of the
methods provided by the server, then it requests an authenticator of
the correct type, and uses that to authenticate itself.

This gets pretty nice when you consider challenge methods; an
HTTP digest style challenge can be done using a class with
a signature like the following:

class HTTP_Authenticator {
   HTTP_Authenticator(java.sql.Connection dbconn, String reposname);
   Repository authenticate() throws PriviledgeException;
   void setUserName(String name);
   void setClientNonce(String cn);
   void setURI(String uri);
   String getURI(String uri);
   void setOperation(String op);
   String getServerNonce();
   String getRealm();
   void setChallengeCode(byte hashcode[]);
}


The client is expected to know the protocol for the authenticator. In
this
case, it can retrieve the information it needs to compute the challenge
result; and then it can provide the client nonce and the challenge
result
to the server before finally authenticating.

Multiphase challenge can be done using an authenticator that returns a
second-stage authenticator from the "authenticate" call.

So... A typical client interacting with this authentication method
would do:
	Set<String> auths = getAuthenticators();
	if (auths.contains("password")) {
	    PasswordAuthenticator pa = getAuthenticator("password");
            pa.setUser(username);
            .. prompt user for password ..
            pa.setPassword(password);
            RepositoryHandle handle = pa.authenticate();
	    ...
         }

Again, this method manages to provide solid authentication, with the
ability
to extend the set of recognized authenticators, but without requiring
changes
to old client. It should be fairly easy to provide this kind of
functionality
using closure style structs.

	-Mark


--

Mark Craig Chu-Carroll,  IBM T.J. Watson Research Center 
<mc...@watson.ibm.com>
*** The Synedra project:
http://domino.research.ibm.com/synedra/synedra.nsf


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: authentication architecture.

Posted by Ben Collins-Sussman <su...@collab.net>.
"Justus Pendleton" <su...@ryoohki.net> writes:

> Ben Collins-Sussman writes: 
> 
> > We have an architecture that looks like 
> > 
> >    client app -->  libsvn_client -->  libsvn_ra 
> > 
> > The arrows indicate the direction of function calls.  As a matter of
> > principle, we don't want an RA library to directly call some
> > libsvn_client routine (or client app routine) like
> > prompt_for_passphase().  It breaks the layered design if we start
> > allowing circular dependencies.
> 
> I guess I'm not clear on why this is such an iron principle.  Even if it 
> adds a circular dependency -- I'm not really sure what you mean by that -- 
> that's orthogonal to the layered design concern, isn't it?  Why can't 
> something with circular dependencies have a layered design? 

By "circular dependency", I mean "a pain to link."  In fact, I'm not
even sure that the linker *can* deal with the situation.

Our layered design is client-centric in the sense our code-flow always
tries to keep the client in control.  That's why we want function
calls to flow one direction.

Here are some examples:

  * commits:

      Client calls svn_client_commit(), which calls ra_commit(), which
      returns an editor object.  The client then describes the change
      it's making by making calls into the editor;  these calls move
      all the way to the repository.

  * updates:

      Client calls svn_client_update(), which calls ra_update(), which
      returns a reporter object.  The client uses this object to tell
      the repository what the working copy looks like, and then the
      repository edits the working copy.


> For instance, I'm not sure how an authentication method that relied
> on a series of challenges and responses would be implemented with
> the method you described.  But that might just be lack of
> imagination on my part :-)

No, this would be very tricky.  I'm not sure how we'd do this either.
Hmm.

> 
> > But a single "opaque shared secret" -- is that enough?
> 
> I don't see why not.  It's really just an index into a matrix of actions and 
> whether they are allowed or not.  The matrix would be constructed at 
> authentication time by the RA.  After authentication it doesn't matter how 
> many pieces of information were required to generate the matrix.  But I 
> think maybe I'm misunderstanding something and the client session handle is 
> all you really need.  I guess it just seems like you only need the user 
> authentication information to set up the permission matrix. 

True -- but the authorization matrix exists not in the RA library, but
*beyond* it, in the actual repository/filesystem libraries.  It might
be created by successful authentication, or it may have existed
already.

Either way, the critical idea here is that once libsvn_client has
opened a "session" with an RA library, it can repeatedly use the
session_baton to make different kinds of repository requests.


> After that's done the info is not only superfluous but potentially
> damaging, given that for at least some authentication schemes it
> will contain a cleartext password, isn't it?

Sure... but why are you assuming that the sensitive information
continues to live on, either in libsvn_client *or* in the RA layer?

For example -- perhaps the RA layer opened up a continuous, encrypted
network channel to a repository.  Like ssh, the sensitive info was
needed only once to create the secure channel;  but after its
creation, only a temporary throw-away keypair is used for encryption.
The original sensitive data can be freed.


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: authentication architecture.

Posted by Justus Pendleton <su...@ryoohki.net>.
Ben Collins-Sussman writes: 

> We have an architecture that looks like 
> 
>    client app -->  libsvn_client -->  libsvn_ra 
> 
> The arrows indicate the direction of function calls.  As a matter of
> principle, we don't want an RA library to directly call some
> libsvn_client routine (or client app routine) like
> prompt_for_passphase().  It breaks the layered design if we start
> allowing circular dependencies.

I guess I'm not clear on why this is such an iron principle.  Even if it 
adds a circular dependency -- I'm not really sure what you mean by that -- 
that's orthogonal to the layered design concern, isn't it?  Why can't 
something with circular dependencies have a layered design? 

> Instead, the RA library returns a set of flags to libsvn_client which
> indicates what kind of authentication info it needs.  Then it's up to
> libsvn_client, and perhaps it's caller, to get the info.

I think perhaps what I was thinking isn't very different from what you 
propose, only I think of the server being in control -- after all it knows 
what it needs and when so why shouldn't it drive the transaction?  I also 
see it as possibly being a series of exchanges rather than a simply "here 
are the fields that need to be filled in" single exchange.  For instance, 
I'm not sure how an authentication method that relied on a series of 
challenges and responses would be implemented with the method you described. 
But that might just be lack of imagination on my part :-) 

> But a single "opaque shared secret" -- is that enough?

I don't see why not.  It's really just an index into a matrix of actions and 
whether they are allowed or not.  The matrix would be constructed at 
authentication time by the RA.  After authentication it doesn't matter how 
many pieces of information were required to generate the matrix.  But I 
think maybe I'm misunderstanding something and the client session handle is 
all you really need.  I guess it just seems like you only need the user 
authentication information to set up the permission matrix.  After that's 
done the info is not only superfluous but potentially damaging, given that 
for at least some authentication schemes it will contain a cleartext 
password, isn't it? 

Justus 

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: authentication architecture.

Posted by Ben Collins-Sussman <su...@collab.net>.
"Justus Pendleton" <su...@ryoohki.net> writes:

> >      The issue here is that an RA library cannot directly prompt the
> >      user for information -- like a password.
> 
> But the client only needs to support four or five methods and the RA library 
> can indirectly prompt for whatever information it desires.  I'm not sure I 
> see a huge difference between directly prompting and indirectly promting 
> only one step removed. 

I'm not sure what you're proposing...?

We have an architecture that looks like

   client app -->  libsvn_client -->  libsvn_ra

The arrows indicate the direction of function calls.  As a matter of
principle, we don't want an RA library to directly call some
libsvn_client routine (or client app routine) like
prompt_for_passphase().  It breaks the layered design if we start
allowing circular dependencies.

Instead, the RA library returns a set of flags to libsvn_client which
indicates what kind of authentication info it needs.  Then it's up to
libsvn_client, and perhaps it's caller, to get the info.


> > * create a transparent svn_ra_user_t structure. 
> > 
> >   This structure contains the *union* of all authentication data
> >   needed by all RA implementations.  (There's no way around this.)
> 
> I don't follow why there is no way around this, maybe you can explain?  
> Wouldn't some opaque shared secret (that doesn't contain possibly sensitive 
> authentication data) work just as well?  That way potentially sensitive data 
> isn't just sitting around in memory and isn't being sent across the wire 
> with every action requested?  And that way you don't need to stick a 
> potentially large svn_ra_user_t structure in the baton, only about 20 bytes 
> of fingerprint. 

We're certainly not *requiring* that the user_t structure be stored in
the RA session baton, nor do we know if the authentication is going to
happen on every request.  It's more probable that an RA layer (after
authenticating the first RA request) stores an expiration-timed cookie
or something in the session baton.  Whatever it wants to do -- it's
not the client's business.  The client merely has an opaque "session"
object with the RA library that it can reuse.

Certainly, after the client creates the user_t structure and exchanges
it for a session baton, it has no need to hang on to the user_t
struct.  It can free it, so the sensitive data is no longer just
hanging around in memory.

But a single "opaque shared secret" -- is that enough?  What if some
RA layer needs 4 pieces of information.  Should we create a new opaque
struct for that RA layer and then teach all client libraries how to
fill it in?  I suppose we could.  It just seems easier to add fields
to the user_t structure instead.  Is there a disadvantage to this?


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: authentication architecture.

Posted by Justus Pendleton <su...@ryoohki.net>.
Ben Collins-Sussman writes: 

> I'm filling in the list on an authentication architecture we'll be
> using for upcoming M3.  It's about as generalized as it can be.

I don't if you've already seen this or not but isn't this pretty much the 
problem that they try to solve with: 

http://www.kernel.org/pub/linux/libs/pam/pre/doc/current-draft.txt 

>      The issue here is that an RA library cannot directly prompt the
>      user for information -- like a password.

But the client only needs to support four or five methods and the RA library 
can indirectly prompt for whatever information it desires.  I'm not sure I 
see a huge difference between directly prompting and indirectly promting 
only one step removed. 

> * create a transparent svn_ra_user_t structure. 
> 
>   This structure contains the *union* of all authentication data
>   needed by all RA implementations.  (There's no way around this.)

I don't follow why there is no way around this, maybe you can explain?  
Wouldn't some opaque shared secret (that doesn't contain possibly sensitive 
authentication data) work just as well?  That way potentially sensitive data 
isn't just sitting around in memory and isn't being sent across the wire 
with every action requested?  And that way you don't need to stick a 
potentially large svn_ra_user_t structure in the baton, only about 20 bytes 
of fingerprint. 

Justus

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org

Re: authentication architecture.

Posted by Joe Orton <jo...@btconnect.com>.
Hi,

On Thu, Jul 26, 2001 at 12:48:13PM -0500, Ben Collins-Sussman wrote:
...
>    * if libsvn_client can't fill in all the fields, it "kicks" the
>      user_t structure up to its client caller (using the hook routine)
>      and then the client finishes the job.  (for example, prompting
>      the user for a password, reading a config file, etc.)
>
>   * the user_t object is now passed (along with the URL) to ra->open().
...

You can't really request usernames/passwords/... from the user before
opening the session (which is what you're suggesting if I understand
correctly?), since you don't know at that time whether the server will
require authentication or not.

I really like the idea of having an object which gets filled in - to get
a little bikesheddy I'd call this a "credentials" object.  ra_dav can
also require HTTP proxy auth and SSL client certs as "credentials" too.

I hacked up auth callbacks for ra_dav a while ago (wasn't ready for
prime time though), the way it worked was:

 - the ra_plugin structure grew an 'authenticate' function which is used
to register a callback which is called whenever the RA implementation
needs to get auth credentials.

 - svn_client_* take an extra callback, and passes it through to the RA
layer by calling ra->authenticate(session, callback, baton).

 - whenever the RA layer finds it requires authentication, it calls the
new callback, which goes back up into the client, which fills in the
credentials (e.g. by prompting for username/password)

Regards,

joe

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org