1/*
2 * cram.c :  Minimal standalone CRAM-MD5 implementation
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#define APR_WANT_STRFUNC
27#define APR_WANT_STDIO
28#include <apr_want.h>
29#include <apr_general.h>
30#include <apr_strings.h>
31#include <apr_network_io.h>
32#include <apr_time.h>
33#include <apr_md5.h>
34
35#include "svn_types.h"
36#include "svn_string.h"
37#include "svn_error.h"
38#include "svn_ra_svn.h"
39#include "svn_config.h"
40#include "svn_private_config.h"
41
42#include "ra_svn.h"
43
44static int hex_to_int(char c)
45{
46  return (c >= '0' && c <= '9') ? c - '0'
47    : (c >= 'a' && c <= 'f') ? c - 'a' + 10
48    : -1;
49}
50
51static char int_to_hex(int v)
52{
53  return (char)((v < 10) ? '0' + v : 'a' + (v - 10));
54}
55
56static svn_boolean_t hex_decode(unsigned char *hashval, const char *hexval)
57{
58  int i, h1, h2;
59
60  for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
61    {
62      h1 = hex_to_int(hexval[2 * i]);
63      h2 = hex_to_int(hexval[2 * i + 1]);
64      if (h1 == -1 || h2 == -1)
65        return FALSE;
66      hashval[i] = (unsigned char)((h1 << 4) | h2);
67    }
68  return TRUE;
69}
70
71static void hex_encode(char *hexval, const unsigned char *hashval)
72{
73  int i;
74
75  for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
76    {
77      hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf);
78      hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf);
79    }
80}
81
82static void compute_digest(unsigned char *digest, const char *challenge,
83                           const char *password)
84{
85  unsigned char secret[64];
86  apr_size_t len = strlen(password), i;
87  apr_md5_ctx_t ctx;
88
89  /* Munge the password into a 64-byte secret. */
90  memset(secret, 0, sizeof(secret));
91  if (len <= sizeof(secret))
92    memcpy(secret, password, len);
93  else
94    apr_md5(secret, password, len);
95
96  /* Compute MD5(secret XOR opad, MD5(secret XOR ipad, challenge)),
97   * where ipad is a string of 0x36 and opad is a string of 0x5c. */
98  for (i = 0; i < sizeof(secret); i++)
99    secret[i] ^= 0x36;
100  apr_md5_init(&ctx);
101  apr_md5_update(&ctx, secret, sizeof(secret));
102  apr_md5_update(&ctx, challenge, strlen(challenge));
103  apr_md5_final(digest, &ctx);
104  for (i = 0; i < sizeof(secret); i++)
105    secret[i] ^= (0x36 ^ 0x5c);
106  apr_md5_init(&ctx);
107  apr_md5_update(&ctx, secret, sizeof(secret));
108  apr_md5_update(&ctx, digest, APR_MD5_DIGESTSIZE);
109  apr_md5_final(digest, &ctx);
110}
111
112/* Fail the authentication, from the server's perspective. */
113static svn_error_t *fail(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
114                         const char *msg)
115{
116  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg));
117  return svn_ra_svn__flush(conn, pool);
118}
119
120/* If we can, make the nonce with random bytes.  If we can't... well,
121 * it just has to be different each time.  The current time isn't
122 * absolutely guaranteed to be different for each connection, but it
123 * should prevent replay attacks in practice. */
124static apr_status_t make_nonce(apr_uint64_t *nonce)
125{
126#if APR_HAS_RANDOM
127  return apr_generate_random_bytes((unsigned char *) nonce, sizeof(*nonce));
128#else
129  *nonce = apr_time_now();
130  return APR_SUCCESS;
131#endif
132}
133
134svn_error_t *svn_ra_svn_cram_server(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
135                                    svn_config_t *pwdb, const char **user,
136                                    svn_boolean_t *success)
137{
138  apr_status_t status;
139  apr_uint64_t nonce;
140  char hostbuf[APRMAXHOSTLEN + 1];
141  unsigned char cdigest[APR_MD5_DIGESTSIZE], sdigest[APR_MD5_DIGESTSIZE];
142  const char *challenge, *sep, *password;
143  svn_ra_svn_item_t *item;
144  svn_string_t *resp;
145
146  *success = FALSE;
147
148  /* Send a challenge. */
149  status = make_nonce(&nonce);
150  if (!status)
151    status = apr_gethostname(hostbuf, sizeof(hostbuf), pool);
152  if (status)
153    return fail(conn, pool, "Internal server error in authentication");
154  challenge = apr_psprintf(pool,
155                           "<%" APR_UINT64_T_FMT ".%" APR_TIME_T_FMT "@%s>",
156                           nonce, apr_time_now(), hostbuf);
157  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "step", challenge));
158
159  /* Read the client's response and decode it into *user and cdigest. */
160  SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
161  if (item->kind != SVN_RA_SVN_STRING)  /* Very wrong; don't report failure */
162    return SVN_NO_ERROR;
163  resp = item->u.string;
164  sep = strrchr(resp->data, ' ');
165  if (!sep || resp->len - (sep + 1 - resp->data) != APR_MD5_DIGESTSIZE * 2
166      || !hex_decode(cdigest, sep + 1))
167    return fail(conn, pool, "Malformed client response in authentication");
168  *user = apr_pstrmemdup(pool, resp->data, sep - resp->data);
169
170  /* Verify the digest against the password in pwfile. */
171  svn_config_get(pwdb, &password, SVN_CONFIG_SECTION_USERS, *user, NULL);
172  if (!password)
173    return fail(conn, pool, "Username not found");
174  compute_digest(sdigest, challenge, password);
175  if (memcmp(cdigest, sdigest, sizeof(sdigest)) != 0)
176    return fail(conn, pool, "Password incorrect");
177
178  *success = TRUE;
179  return svn_ra_svn__write_tuple(conn, pool, "w()", "success");
180}
181
182svn_error_t *svn_ra_svn__cram_client(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
183                                     const char *user, const char *password,
184                                     const char **message)
185{
186  const char *status, *str, *reply;
187  unsigned char digest[APR_MD5_DIGESTSIZE];
188  char hex[2 * APR_MD5_DIGESTSIZE + 1];
189
190  /* Read the server challenge. */
191  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &str));
192  if (strcmp(status, "failure") == 0 && str)
193    {
194      *message = str;
195      return SVN_NO_ERROR;
196    }
197  else if (strcmp(status, "step") != 0 || !str)
198    return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
199                            _("Unexpected server response to authentication"));
200
201  /* Write our response. */
202  compute_digest(digest, str, password);
203  hex_encode(hex, digest);
204  hex[sizeof(hex) - 1] = '\0';
205  reply = apr_psprintf(pool, "%s %s", user, hex);
206  SVN_ERR(svn_ra_svn__write_cstring(conn, pool, reply));
207
208  /* Read the success or failure response from the server. */
209  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &str));
210  if (strcmp(status, "failure") == 0 && str)
211    {
212      *message = str;
213      return SVN_NO_ERROR;
214    }
215  else if (strcmp(status, "success") != 0 || str)
216    return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
217                            _("Unexpected server response to authentication"));
218
219  *message = NULL;
220  return SVN_NO_ERROR;
221}
222