res_pjsip_outbound_authenticator_digest: Be tolerant of RFC8760 UASs

RFC7616 and RFC8760 allow more than one WWW-Authenticate or
Proxy-Authenticate header per realm, each with different digest
algorithms (including new ones like SHA-256 and SHA-512-256).
Thankfully however a UAS can NOT send back multiple Authenticate
headers for the same realm with the same digest algorithm.  The
UAS is also supposed to send the headers in order of preference
with the first one being the most preferred.  We're supposed to
send an Authorization header for the first one we encounter for a
realm that we can support.

The UAS can also send multiple realms, especially when it's a
proxy that has forked the request in which case the proxy will
aggregate all of the Authenticate headers and then send them all
back to the UAC.

It doesn't stop there though... Each realm can require a
different username from the others.  There's also nothing
preventing each digest algorithm from having a unique password
although I'm not sure if that adds any benefit.

So now... For each Authenticate header we encounter, we have to
determine if we support the digest algorithm and, if not, just
skip the header.  We then have to find an auth object that
matches the realm AND the digest algorithm or find a wildcard
object that matches the digest algorithm. If we find one, we add
it to the results vector and read the next Authenticate header.
If the next header is for the same realm AND we already added an
auth object for that realm, we skip the header. Otherwise we
repeat the process for the next header.

In the end, we'll have accumulated a list of credentials we can
pass to pjproject that it can use to add Authentication headers
to a request.

NOTE: Neither we nor pjproject can currently handle digest
algorithms other than MD5.  We don't even have a place for it in
the ast_sip_auth object. For this reason, we just skip processing
any Authenticate header that's not MD5.  When we support the
others, we'll move the check into the loop that searches the
objects.

Changes:

 * Added a new API ast_sip_retrieve_auths_vector() that takes in
   a vector of auth ids (usually supplied on a call to
   ast_sip_create_request_with_auth()) and populates another
   vector with the actual objects.

 * Refactored res_pjsip_outbound_authenticator_digest to handle
   multiple Authenticate headers and set the stage for handling
   additional digest algorithms.

 * Added a pjproject patch that allows them to ignore digest
   algorithms they don't support.  This patch has already been
   merged upstream.

 * Updated documentation for auth objects in the XML and
   in pjsip.conf.sample.

 * Although res_pjsip_authenticator_digest isn't affected
   by this change, some debugging and a testsuite AMI event
   was added to facilitate testing.

Discovered during OpenSIPit 2021.

ASTERISK-29397

Change-Id: I3aef5ce4fe1d27e48d61268520f284d15d650281
This commit is contained in:
George Joseph
2021-04-15 09:43:48 -06:00
parent 3cccdf6d98
commit 9cc1d6fc22
7 changed files with 784 additions and 97 deletions

View File

@@ -1504,13 +1504,24 @@
This option specifies which of the password style config options should be read
when trying to authenticate an endpoint inbound request. If set to <literal>userpass</literal>
then we'll read from the 'password' option. For <literal>md5</literal> we'll read
from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the refresh_token/oauth_clientid/oauth_secret fields.
from 'md5_cred'. If set to <literal>google_oauth</literal> then we'll read from the
refresh_token/oauth_clientid/oauth_secret fields. The following values are valid:
</para>
<enumlist>
<enum name="md5"/>
<enum name="userpass"/>
<enum name="google_oauth"/>
</enumlist>
<para>
</para>
<note>
<para>
This setting only describes whether the password is in
plain text or has been pre-hashed with MD5. It doesn't describe
the acceptable digest algorithms we'll accept in a received
challenge.
</para>
</note>
</description>
</configOption>
<configOption name="nonce_lifetime" default="32">
@@ -1542,11 +1553,12 @@
<para>
</para>
<para>
For outgoing authentication (asterisk is the client),
For outgoing authentication (asterisk is the UAC),
the realm must match what the server will be sending
in their WWW-Authenticate header. It can't be blank
unless you expect the server to be sending a blank
realm in the header.
realm in the header. You can't use pre-hashed
paswords with a wildcard auth object.
You can generate the hash with the following shell
command:
</para>
@@ -1578,7 +1590,7 @@
<configOption name="realm" default="">
<synopsis>SIP realm for endpoint</synopsis>
<description><para>
For incoming authentication (asterisk is the server),
For incoming authentication (asterisk is the UAS),
this is the realm to be sent on WWW-Authenticate
headers. If not specified, the <replaceable>global</replaceable>
object's <variable>default_realm</variable> will be used.
@@ -1586,12 +1598,12 @@
<para>
</para>
<para>
For outgoing authentication (asterisk is the client), this
For outgoing authentication (asterisk is the UAS), this
must either be the realm the server is expected to send,
or blank to automatically use the realm sent by the server.
If you have multiple auth object for an endpoint, the realm
is also used to match the auth object to the realm the
server sent.
or left blank or contain a single '*' to automatically
use the realm sent by the server. If you have multiple
auth object for an endpoint, the realm is also used to
match the auth object to the realm the server sent.
</para>
<para>
</para>
@@ -1600,7 +1612,19 @@
Using the same auth section for inbound and outbound
authentication is not recommended. There is a difference in
meaning for an empty realm setting between inbound and outbound
authentication uses.</para></note>
authentication uses.
</para>
</note>
<para>
</para>
<note>
<para>
If more than one auth object with the same realm or
more than one wildcard auth object associated to
an endpoint, we can only use the first one of
each defined on the endpoint.
</para>
</note>
</description>
</configOption>
<configOption name="type">