cram.c revision 251886
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