summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_ra_svn/cram.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_ra_svn/cram.c')
-rw-r--r--subversion/libsvn_ra_svn/cram.c221
1 files changed, 221 insertions, 0 deletions
diff --git a/subversion/libsvn_ra_svn/cram.c b/subversion/libsvn_ra_svn/cram.c
new file mode 100644
index 0000000..1e54ac8
--- /dev/null
+++ b/subversion/libsvn_ra_svn/cram.c
@@ -0,0 +1,221 @@
+/*
+ * cram.c : Minimal standalone CRAM-MD5 implementation
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+
+
+#define APR_WANT_STRFUNC
+#define APR_WANT_STDIO
+#include <apr_want.h>
+#include <apr_general.h>
+#include <apr_strings.h>
+#include <apr_network_io.h>
+#include <apr_time.h>
+#include <apr_md5.h>
+
+#include "svn_types.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_ra_svn.h"
+#include "svn_config.h"
+#include "svn_private_config.h"
+
+#include "ra_svn.h"
+
+static int hex_to_int(char c)
+{
+ return (c >= '0' && c <= '9') ? c - '0'
+ : (c >= 'a' && c <= 'f') ? c - 'a' + 10
+ : -1;
+}
+
+static char int_to_hex(int v)
+{
+ return (char)((v < 10) ? '0' + v : 'a' + (v - 10));
+}
+
+static svn_boolean_t hex_decode(unsigned char *hashval, const char *hexval)
+{
+ int i, h1, h2;
+
+ for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
+ {
+ h1 = hex_to_int(hexval[2 * i]);
+ h2 = hex_to_int(hexval[2 * i + 1]);
+ if (h1 == -1 || h2 == -1)
+ return FALSE;
+ hashval[i] = (unsigned char)((h1 << 4) | h2);
+ }
+ return TRUE;
+}
+
+static void hex_encode(char *hexval, const unsigned char *hashval)
+{
+ int i;
+
+ for (i = 0; i < APR_MD5_DIGESTSIZE; i++)
+ {
+ hexval[2 * i] = int_to_hex((hashval[i] >> 4) & 0xf);
+ hexval[2 * i + 1] = int_to_hex(hashval[i] & 0xf);
+ }
+}
+
+static void compute_digest(unsigned char *digest, const char *challenge,
+ const char *password)
+{
+ unsigned char secret[64];
+ apr_size_t len = strlen(password), i;
+ apr_md5_ctx_t ctx;
+
+ /* Munge the password into a 64-byte secret. */
+ memset(secret, 0, sizeof(secret));
+ if (len <= sizeof(secret))
+ memcpy(secret, password, len);
+ else
+ apr_md5(secret, password, len);
+
+ /* Compute MD5(secret XOR opad, MD5(secret XOR ipad, challenge)),
+ * where ipad is a string of 0x36 and opad is a string of 0x5c. */
+ for (i = 0; i < sizeof(secret); i++)
+ secret[i] ^= 0x36;
+ apr_md5_init(&ctx);
+ apr_md5_update(&ctx, secret, sizeof(secret));
+ apr_md5_update(&ctx, challenge, strlen(challenge));
+ apr_md5_final(digest, &ctx);
+ for (i = 0; i < sizeof(secret); i++)
+ secret[i] ^= (0x36 ^ 0x5c);
+ apr_md5_init(&ctx);
+ apr_md5_update(&ctx, secret, sizeof(secret));
+ apr_md5_update(&ctx, digest, APR_MD5_DIGESTSIZE);
+ apr_md5_final(digest, &ctx);
+}
+
+/* Fail the authentication, from the server's perspective. */
+static svn_error_t *fail(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *msg)
+{
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "failure", msg));
+ return svn_ra_svn__flush(conn, pool);
+}
+
+/* If we can, make the nonce with random bytes. If we can't... well,
+ * it just has to be different each time. The current time isn't
+ * absolutely guaranteed to be different for each connection, but it
+ * should prevent replay attacks in practice. */
+static apr_status_t make_nonce(apr_uint64_t *nonce)
+{
+#if APR_HAS_RANDOM
+ return apr_generate_random_bytes((unsigned char *) nonce, sizeof(*nonce));
+#else
+ *nonce = apr_time_now();
+ return APR_SUCCESS;
+#endif
+}
+
+svn_error_t *svn_ra_svn_cram_server(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ svn_config_t *pwdb, const char **user,
+ svn_boolean_t *success)
+{
+ apr_status_t status;
+ apr_uint64_t nonce;
+ char hostbuf[APRMAXHOSTLEN + 1];
+ unsigned char cdigest[APR_MD5_DIGESTSIZE], sdigest[APR_MD5_DIGESTSIZE];
+ const char *challenge, *sep, *password;
+ svn_ra_svn_item_t *item;
+ svn_string_t *resp;
+
+ *success = FALSE;
+
+ /* Send a challenge. */
+ status = make_nonce(&nonce);
+ if (!status)
+ status = apr_gethostname(hostbuf, sizeof(hostbuf), pool);
+ if (status)
+ return fail(conn, pool, "Internal server error in authentication");
+ challenge = apr_psprintf(pool,
+ "<%" APR_UINT64_T_FMT ".%" APR_TIME_T_FMT "@%s>",
+ nonce, apr_time_now(), hostbuf);
+ SVN_ERR(svn_ra_svn__write_tuple(conn, pool, "w(c)", "step", challenge));
+
+ /* Read the client's response and decode it into *user and cdigest. */
+ SVN_ERR(svn_ra_svn__read_item(conn, pool, &item));
+ if (item->kind != SVN_RA_SVN_STRING) /* Very wrong; don't report failure */
+ return SVN_NO_ERROR;
+ resp = item->u.string;
+ sep = strrchr(resp->data, ' ');
+ if (!sep || resp->len - (sep + 1 - resp->data) != APR_MD5_DIGESTSIZE * 2
+ || !hex_decode(cdigest, sep + 1))
+ return fail(conn, pool, "Malformed client response in authentication");
+ *user = apr_pstrmemdup(pool, resp->data, sep - resp->data);
+
+ /* Verify the digest against the password in pwfile. */
+ svn_config_get(pwdb, &password, SVN_CONFIG_SECTION_USERS, *user, NULL);
+ if (!password)
+ return fail(conn, pool, "Username not found");
+ compute_digest(sdigest, challenge, password);
+ if (memcmp(cdigest, sdigest, sizeof(sdigest)) != 0)
+ return fail(conn, pool, "Password incorrect");
+
+ *success = TRUE;
+ return svn_ra_svn__write_tuple(conn, pool, "w()", "success");
+}
+
+svn_error_t *svn_ra_svn__cram_client(svn_ra_svn_conn_t *conn, apr_pool_t *pool,
+ const char *user, const char *password,
+ const char **message)
+{
+ const char *status, *str, *reply;
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+ char hex[2 * APR_MD5_DIGESTSIZE + 1];
+
+ /* Read the server challenge. */
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &str));
+ if (strcmp(status, "failure") == 0 && str)
+ {
+ *message = str;
+ return SVN_NO_ERROR;
+ }
+ else if (strcmp(status, "step") != 0 || !str)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Unexpected server response to authentication"));
+
+ /* Write our response. */
+ compute_digest(digest, str, password);
+ hex_encode(hex, digest);
+ hex[sizeof(hex) - 1] = '\0';
+ reply = apr_psprintf(pool, "%s %s", user, hex);
+ SVN_ERR(svn_ra_svn__write_cstring(conn, pool, reply));
+
+ /* Read the success or failure response from the server. */
+ SVN_ERR(svn_ra_svn__read_tuple(conn, pool, "w(?c)", &status, &str));
+ if (strcmp(status, "failure") == 0 && str)
+ {
+ *message = str;
+ return SVN_NO_ERROR;
+ }
+ else if (strcmp(status, "success") != 0 || str)
+ return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
+ _("Unexpected server response to authentication"));
+
+ *message = NULL;
+ return SVN_NO_ERROR;
+}
OpenPOWER on IntegriCloud