iPXE
httpdigest.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 /**
00027  * @file
00028  *
00029  * Hyper Text Transfer Protocol (HTTP) Digest authentication
00030  *
00031  */
00032 
00033 #include <stdio.h>
00034 #include <errno.h>
00035 #include <strings.h>
00036 #include <ipxe/uri.h>
00037 #include <ipxe/md5.h>
00038 #include <ipxe/base16.h>
00039 #include <ipxe/vsprintf.h>
00040 #include <ipxe/http.h>
00041 
00042 /* Disambiguate the various error causes */
00043 #define EACCES_USERNAME __einfo_error ( EINFO_EACCES_USERNAME )
00044 #define EINFO_EACCES_USERNAME                                           \
00045         __einfo_uniqify ( EINFO_EACCES, 0x01,                           \
00046                           "No username available for Digest authentication" )
00047 
00048 /** An HTTP Digest "WWW-Authenticate" response field */
00049 struct http_digest_field {
00050         /** Name */
00051         const char *name;
00052         /** Offset */
00053         size_t offset;
00054 };
00055 
00056 /** Define an HTTP Digest "WWW-Authenticate" response field */
00057 #define HTTP_DIGEST_FIELD( _name ) {                                    \
00058                 .name = #_name,                                         \
00059                 .offset = offsetof ( struct http_transaction,           \
00060                                      response.auth.digest._name ),      \
00061         }
00062 
00063 /**
00064  * Set HTTP Digest "WWW-Authenticate" response field value
00065  *
00066  * @v http              HTTP transaction
00067  * @v field             Response field
00068  * @v value             Field value
00069  */
00070 static inline void
00071 http_digest_field ( struct http_transaction *http,
00072                     struct http_digest_field *field, char *value ) {
00073         char **ptr;
00074 
00075         ptr = ( ( ( void * ) http ) + field->offset );
00076         *ptr = value;
00077 }
00078 
00079 /** HTTP Digest "WWW-Authenticate" fields */
00080 static struct http_digest_field http_digest_fields[] = {
00081         HTTP_DIGEST_FIELD ( realm ),
00082         HTTP_DIGEST_FIELD ( qop ),
00083         HTTP_DIGEST_FIELD ( algorithm ),
00084         HTTP_DIGEST_FIELD ( nonce ),
00085         HTTP_DIGEST_FIELD ( opaque ),
00086 };
00087 
00088 /**
00089  * Parse HTTP "WWW-Authenticate" header for Digest authentication
00090  *
00091  * @v http              HTTP transaction
00092  * @v line              Remaining header line
00093  * @ret rc              Return status code
00094  */
00095 static int http_parse_digest_auth ( struct http_transaction *http,
00096                                     char *line ) {
00097         struct http_digest_field *field;
00098         char *key;
00099         char *value;
00100         unsigned int i;
00101 
00102         /* Process fields */
00103         while ( ( key = http_token ( &line, &value ) ) ) {
00104                 for ( i = 0 ; i < ( sizeof ( http_digest_fields ) /
00105                                     sizeof ( http_digest_fields[0] ) ) ; i++){
00106                         field = &http_digest_fields[i];
00107                         if ( strcasecmp ( key, field->name ) == 0 )
00108                                 http_digest_field ( http, field, value );
00109                 }
00110         }
00111 
00112         /* Allow HTTP request to be retried if the request had not
00113          * already tried authentication.
00114          */
00115         if ( ! http->request.auth.auth )
00116                 http->response.flags |= HTTP_RESPONSE_RETRY;
00117 
00118         return 0;
00119 }
00120 
00121 /**
00122  * Initialise HTTP Digest
00123  *
00124  * @v ctx               Digest context
00125  * @v string            Initial string
00126  */
00127 static void http_digest_init ( struct md5_context *ctx ) {
00128 
00129         /* Initialise MD5 digest */
00130         digest_init ( &md5_algorithm, ctx );
00131 }
00132 
00133 /**
00134  * Update HTTP Digest with new data
00135  *
00136  * @v ctx               Digest context
00137  * @v string            String to append
00138  */
00139 static void http_digest_update ( struct md5_context *ctx, const char *string ) {
00140         static const char colon = ':';
00141 
00142         /* Add (possibly colon-separated) field to MD5 digest */
00143         if ( ctx->len )
00144                 digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) );
00145         digest_update ( &md5_algorithm, ctx, string, strlen ( string ) );
00146 }
00147 
00148 /**
00149  * Finalise HTTP Digest
00150  *
00151  * @v ctx               Digest context
00152  * @v out               Buffer for digest output
00153  * @v len               Buffer length
00154  */
00155 static void http_digest_final ( struct md5_context *ctx, char *out,
00156                                 size_t len ) {
00157         uint8_t digest[MD5_DIGEST_SIZE];
00158 
00159         /* Finalise and base16-encode MD5 digest */
00160         digest_final ( &md5_algorithm, ctx, digest );
00161         base16_encode ( digest, sizeof ( digest ), out, len );
00162 }
00163 
00164 /**
00165  * Perform HTTP Digest authentication
00166  *
00167  * @v http              HTTP transaction
00168  * @ret rc              Return status code
00169  */
00170 static int http_digest_authenticate ( struct http_transaction *http ) {
00171         struct http_request_auth_digest *req = &http->request.auth.digest;
00172         struct http_response_auth_digest *rsp = &http->response.auth.digest;
00173         char ha1[ base16_encoded_len ( MD5_DIGEST_SIZE ) + 1 /* NUL */ ];
00174         char ha2[ base16_encoded_len ( MD5_DIGEST_SIZE ) + 1 /* NUL */ ];
00175         static const char md5sess[] = "MD5-sess";
00176         static const char md5[] = "MD5";
00177         struct md5_context ctx;
00178         const char *password;
00179 
00180         /* Check for required response parameters */
00181         if ( ! rsp->realm ) {
00182                 DBGC ( http, "HTTP %p has no realm for Digest authentication\n",
00183                        http );
00184                 return -EINVAL;
00185         }
00186         if ( ! rsp->nonce ) {
00187                 DBGC ( http, "HTTP %p has no nonce for Digest authentication\n",
00188                        http );
00189                 return -EINVAL;
00190         }
00191 
00192         /* Record username and password */
00193         if ( ! http->uri->user ) {
00194                 DBGC ( http, "HTTP %p has no username for Digest "
00195                        "authentication\n", http );
00196                 return -EACCES_USERNAME;
00197         }
00198         req->username = http->uri->user;
00199         password = ( http->uri->password ? http->uri->password : "" );
00200 
00201         /* Handle quality of protection */
00202         if ( rsp->qop ) {
00203 
00204                 /* Use "auth" in subsequent request */
00205                 req->qop = "auth";
00206 
00207                 /* Generate a client nonce */
00208                 snprintf ( req->cnonce, sizeof ( req->cnonce ),
00209                            "%08lx", random() );
00210 
00211                 /* Determine algorithm */
00212                 req->algorithm = md5;
00213                 if ( rsp->algorithm &&
00214                      ( strcasecmp ( rsp->algorithm, md5sess ) == 0 ) ) {
00215                         req->algorithm = md5sess;
00216                 }
00217         }
00218 
00219         /* Generate HA1 */
00220         http_digest_init ( &ctx );
00221         http_digest_update ( &ctx, req->username );
00222         http_digest_update ( &ctx, rsp->realm );
00223         http_digest_update ( &ctx, password );
00224         http_digest_final ( &ctx, ha1, sizeof ( ha1 ) );
00225         if ( req->algorithm == md5sess ) {
00226                 http_digest_init ( &ctx );
00227                 http_digest_update ( &ctx, ha1 );
00228                 http_digest_update ( &ctx, rsp->nonce );
00229                 http_digest_update ( &ctx, req->cnonce );
00230                 http_digest_final ( &ctx, ha1, sizeof ( ha1 ) );
00231         }
00232 
00233         /* Generate HA2 */
00234         http_digest_init ( &ctx );
00235         http_digest_update ( &ctx, http->request.method->name );
00236         http_digest_update ( &ctx, http->request.uri );
00237         http_digest_final ( &ctx, ha2, sizeof ( ha2 ) );
00238 
00239         /* Generate response */
00240         http_digest_init ( &ctx );
00241         http_digest_update ( &ctx, ha1 );
00242         http_digest_update ( &ctx, rsp->nonce );
00243         if ( req->qop ) {
00244                 http_digest_update ( &ctx, HTTP_DIGEST_NC );
00245                 http_digest_update ( &ctx, req->cnonce );
00246                 http_digest_update ( &ctx, req->qop );
00247         }
00248         http_digest_update ( &ctx, ha2 );
00249         http_digest_final ( &ctx, req->response, sizeof ( req->response ) );
00250 
00251         return 0;
00252 }
00253 
00254 /**
00255  * Construct HTTP "Authorization" header for Digest authentication
00256  *
00257  * @v http              HTTP transaction
00258  * @v buf               Buffer
00259  * @v len               Length of buffer
00260  * @ret len             Length of header value, or negative error
00261  */
00262 static int http_format_digest_auth ( struct http_transaction *http,
00263                                      char *buf, size_t len ) {
00264         struct http_request_auth_digest *req = &http->request.auth.digest;
00265         struct http_response_auth_digest *rsp = &http->response.auth.digest;
00266         size_t used = 0;
00267 
00268         /* Sanity checks */
00269         assert ( rsp->realm != NULL );
00270         assert ( rsp->nonce != NULL );
00271         assert ( req->username != NULL );
00272         if ( req->qop ) {
00273                 assert ( req->algorithm != NULL );
00274                 assert ( req->cnonce[0] != '\0' );
00275         }
00276         assert ( req->response[0] != '\0' );
00277 
00278         /* Construct response */
00279         used += ssnprintf ( ( buf + used ), ( len - used ),
00280                             "realm=\"%s\", nonce=\"%s\", uri=\"%s\", "
00281                             "username=\"%s\"", rsp->realm, rsp->nonce,
00282                             http->request.uri, req->username );
00283         if ( rsp->opaque ) {
00284                 used += ssnprintf ( ( buf + used ), ( len - used ),
00285                                     ", opaque=\"%s\"", rsp->opaque );
00286         }
00287         if ( req->qop ) {
00288                 used += ssnprintf ( ( buf + used ), ( len - used ),
00289                                     ", qop=%s, algorithm=%s, cnonce=\"%s\", "
00290                                     "nc=" HTTP_DIGEST_NC, req->qop,
00291                                     req->algorithm, req->cnonce );
00292         }
00293         used += ssnprintf ( ( buf + used ), ( len - used ),
00294                             ", response=\"%s\"", req->response );
00295 
00296         return used;
00297 }
00298 
00299 /** HTTP Digest authentication scheme */
00300 struct http_authentication http_digest_auth __http_authentication = {
00301         .name = "Digest",
00302         .parse = http_parse_digest_auth,
00303         .authenticate = http_digest_authenticate,
00304         .format = http_format_digest_auth,
00305 };
00306 
00307 /* Drag in HTTP authentication support */
00308 REQUIRING_SYMBOL ( http_digest_auth );
00309 REQUIRE_OBJECT ( httpauth );