1251881Speter/*
2251881Speter * cram.c :  Minimal standalone CRAM-MD5 implementation
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#define APR_WANT_STRFUNC
27251881Speter#define APR_WANT_STDIO
28251881Speter#include <apr_want.h>
29251881Speter#include <apr_general.h>
30251881Speter#include <apr_strings.h>
31251881Speter#include <apr_network_io.h>
32251881Speter#include <apr_time.h>
33251881Speter#include <apr_md5.h>
34251881Speter
35251881Speter#include "svn_types.h"
36251881Speter#include "svn_string.h"
37251881Speter#include "svn_error.h"
38251881Speter#include "svn_ra_svn.h"
39251881Speter#include "svn_config.h"
40251881Speter#include "svn_private_config.h"
41251881Speter
42251881Speter#include "ra_svn.h"
43251881Speter
44251881Speterstatic int hex_to_int(char c)
45251881Speter{
46251881Speter  return (c >= '0' && c <= '9') ? c - '0'
47251881Speter    : (c >= 'a' && c <= 'f') ? c - 'a' + 10
48251881Speter    : -1;
49251881Speter}
50251881Speter
51251881Speterstatic char int_to_hex(int v)
52251881Speter{
53251881Speter  return (char)((v < 10) ? '0' + v : 'a' + (v - 10));
54251881Speter}
55251881Speter
56251881Speterstatic svn_boolean_t hex_decode(unsigned char *hashval, const char *hexval)
57251881Speter{
58251881Speter  int i, h1, h2;
59251881Speter
60251881Speter  for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
61251881Speter    {
62251881Speter      h1 = hex_to_int(hexval[2 * i]);
63251881Speter      h2 = hex_to_int(hexval[2 * i + 1]);
64251881Speter      if (h1 == -1 || h2 == -1)
65251881Speter        return FALSE;
66251881Speter      hashval[i] = (unsigned char)((h1 << 4) | h2);
67251881Speter    }
68251881Speter  return TRUE;
69251881Speter}
70251881Speter
71251881Speterstatic void hex_encode(char *hexval, const unsigned char *hashval)
72251881Speter{
73251881Speter  int i;
74251881Speter
75251881Speter  for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
76251881Speter    {
77251881Speter      hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf);
78251881Speter      hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf);
79251881Speter    }
80251881Speter}
81251881Speter
82251881Speterstatic void compute_digest(unsigned char *digest, const char *challenge,
83251881Speter                           const char *password)
84251881Speter{
85251881Speter  unsigned char secret[64];
86251881Speter  apr_size_t len = strlen(password), i;
87251881Speter  apr_md5_ctx_t ctx;
88251881Speter
89251881Speter  /* Munge the password into a 64-byte secret. */
90251881Speter  memset(secret, 0, sizeof(secret));
91251881Speter  if (len <= sizeof(secret))
92251881Speter    memcpy(secret, password, len);
93251881Speter  else
94251881Speter    apr_md5(secret, password, len);
95251881Speter
96251881Speter  /* Compute MD5(secret XOR opad, MD5(secret XOR ipad, challenge)),
97251881Speter   * where ipad is a string of 0x36 and opad is a string of 0x5c. */
98251881Speter  for (i = 0; i < sizeof(secret); i++)
99251881Speter    secret[i] ^= 0x36;
100251881Speter  apr_md5_init(&ctx);
101251881Speter  apr_md5_update(&ctx, secret, sizeof(secret));
102251881Speter  apr_md5_update(&ctx, challenge, strlen(challenge));
103251881Speter  apr_md5_final(digest, &ctx);
104251881Speter  for (i = 0; i < sizeof(secret); i++)
105251881Speter    secret[i] ^= (0x36 ^ 0x5c);
106251881Speter  apr_md5_init(&ctx);
107251881Speter  apr_md5_update(&ctx, secret, sizeof(secret));
108251881Speter  apr_md5_update(&ctx, digest, APR_MD5_DIGESTSIZE);
109251881Speter  apr_md5_final(digest, &ctx);
110251881Speter}
111251881Speter
112251881Speter/* Fail the authentication, from the server's perspective. */
113251881Speterstatic svn_error_t *fail(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
114251881Speter                         const char *msg)
115251881Speter{
116251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg));
117289180Speter  return svn_error_trace(svn_ra_svn__flush(conn, pool));
118251881Speter}
119251881Speter
120251881Speter/* If we can, make the nonce with random bytes.  If we can't... well,
121251881Speter * it just has to be different each time.  The current time isn't
122251881Speter * absolutely guaranteed to be different for each connection, but it
123251881Speter * should prevent replay attacks in practice. */
124251881Speterstatic apr_status_t make_nonce(apr_uint64_t *nonce)
125251881Speter{
126251881Speter#if APR_HAS_RANDOM
127251881Speter  return apr_generate_random_bytes((unsigned char *) nonce, sizeof(*nonce));
128251881Speter#else
129251881Speter  *nonce = apr_time_now();
130251881Speter  return APR_SUCCESS;
131251881Speter#endif
132251881Speter}
133251881Speter
134251881Spetersvn_error_t *svn_ra_svn_cram_server(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
135251881Speter                                    svn_config_t *pwdb, const char **user,
136251881Speter                                    svn_boolean_t *success)
137251881Speter{
138251881Speter  apr_status_t status;
139251881Speter  apr_uint64_t nonce;
140251881Speter  char hostbuf[APRMAXHOSTLEN + 1];
141251881Speter  unsigned char cdigest[APR_MD5_DIGESTSIZE], sdigest[APR_MD5_DIGESTSIZE];
142251881Speter  const char *challenge, *sep, *password;
143362181Sdim  svn_ra_svn__item_t *item;
144251881Speter  svn_string_t *resp;
145251881Speter
146251881Speter  *success = FALSE;
147251881Speter
148251881Speter  /* Send a challenge. */
149251881Speter  status = make_nonce(&nonce);
150251881Speter  if (!status)
151251881Speter    status = apr_gethostname(hostbuf, sizeof(hostbuf), pool);
152251881Speter  if (status)
153251881Speter    return fail(conn, pool, "Internal server error in authentication");
154251881Speter  challenge = apr_psprintf(pool,
155251881Speter                           "<%" APR_UINT64_T_FMT ".%" APR_TIME_T_FMT "@%s>",
156251881Speter                           nonce, apr_time_now(), hostbuf);
157251881Speter  SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "step", challenge));
158251881Speter
159251881Speter  /* Read the client's response and decode it into *user and cdigest. */
160251881Speter  SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
161251881Speter  if (item->kind != SVN_RA_SVN_STRING)  /* Very wrong; don't report failure */
162251881Speter    return SVN_NO_ERROR;
163362181Sdim  resp = &item->u.string;
164251881Speter  sep = strrchr(resp->data, ' ');
165251881Speter  if (!sep || resp->len - (sep + 1 - resp->data) != APR_MD5_DIGESTSIZE * 2
166251881Speter      || !hex_decode(cdigest, sep + 1))
167251881Speter    return fail(conn, pool, "Malformed client response in authentication");
168251881Speter  *user = apr_pstrmemdup(pool, resp->data, sep - resp->data);
169251881Speter
170251881Speter  /* Verify the digest against the password in pwfile. */
171251881Speter  svn_config_get(pwdb, &password, SVN_CONFIG_SECTION_USERS, *user, NULL);
172251881Speter  if (!password)
173251881Speter    return fail(conn, pool, "Username not found");
174251881Speter  compute_digest(sdigest, challenge, password);
175251881Speter  if (memcmp(cdigest, sdigest, sizeof(sdigest)) != 0)
176251881Speter    return fail(conn, pool, "Password incorrect");
177251881Speter
178251881Speter  *success = TRUE;
179251881Speter  return svn_ra_svn__write_tuple(conn, pool, "w()", "success");
180251881Speter}
181251881Speter
182251881Spetersvn_error_t *svn_ra_svn__cram_client(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
183251881Speter                                     const char *user, const char *password,
184251881Speter                                     const char **message)
185251881Speter{
186251881Speter  const char *status, *str, *reply;
187251881Speter  unsigned char digest[APR_MD5_DIGESTSIZE];
188251881Speter  char hex[2 * APR_MD5_DIGESTSIZE + 1];
189251881Speter
190251881Speter  /* Read the server challenge. */
191251881Speter  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &str));
192251881Speter  if (strcmp(status, "failure") == 0 && str)
193251881Speter    {
194251881Speter      *message = str;
195251881Speter      return SVN_NO_ERROR;
196251881Speter    }
197251881Speter  else if (strcmp(status, "step") != 0 || !str)
198251881Speter    return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
199251881Speter                            _("Unexpected server response to authentication"));
200251881Speter
201251881Speter  /* Write our response. */
202251881Speter  compute_digest(digest, str, password);
203251881Speter  hex_encode(hex, digest);
204251881Speter  hex[sizeof(hex) - 1] = '\0';
205251881Speter  reply = apr_psprintf(pool, "%s %s", user, hex);
206251881Speter  SVN_ERR(svn_ra_svn__write_cstring(conn, pool, reply));
207251881Speter
208251881Speter  /* Read the success or failure response from the server. */
209251881Speter  SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &str));
210251881Speter  if (strcmp(status, "failure") == 0 && str)
211251881Speter    {
212251881Speter      *message = str;
213251881Speter      return SVN_NO_ERROR;
214251881Speter    }
215251881Speter  else if (strcmp(status, "success") != 0 || str)
216251881Speter    return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
217251881Speter                            _("Unexpected server response to authentication"));
218251881Speter
219251881Speter  *message = NULL;
220251881Speter  return SVN_NO_ERROR;
221251881Speter}
222