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