2 * cram.c : Minimal standalone CRAM-MD5 implementation
4 * ====================================================================
5 * Copyright (c) 2000-2004 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #define APR_WANT_STDIO
24 #include <apr_general.h>
25 #include <apr_strings.h>
26 #include <apr_network_io.h>
30 #include "svn_types.h"
31 #include "svn_string.h"
32 #include "svn_error.h"
33 #include "svn_ra_svn.h"
34 #include "svn_config.h"
35 #include "svn_private_config.h"
39 static int hex_to_int(char c
)
41 return (c
>= '0' && c
<= '9') ? c
- '0'
42 : (c
>= 'a' && c
<= 'f') ? c
- 'a' + 10
46 static char int_to_hex(int v
)
48 return (v
< 10) ? '0' + v
: 'a' + (v
- 10);
51 static svn_boolean_t
hex_decode(unsigned char *hashval
, const char *hexval
)
55 for (i
= 0; i
< APR_MD5_DIGESTSIZE
; i
++)
57 h1
= hex_to_int(hexval
[2 * i
]);
58 h2
= hex_to_int(hexval
[2 * i
+ 1]);
59 if (h1
== -1 || h2
== -1)
61 hashval
[i
] = (h1
<< 4) | h2
;
66 static void hex_encode(char *hexval
, const unsigned char *hashval
)
70 for (i
= 0; i
< APR_MD5_DIGESTSIZE
; i
++)
72 hexval
[2 * i
] = int_to_hex((hashval
[i
] >> 4) & 0xf);
73 hexval
[2 * i
+ 1] = int_to_hex(hashval
[i
] & 0xf);
77 static void compute_digest(unsigned char *digest
, const char *challenge
,
80 unsigned char secret
[64];
81 apr_size_t len
= strlen(password
), i
;
84 /* Munge the password into a 64-byte secret. */
85 memset(secret
, 0, sizeof(secret
));
86 if (len
<= sizeof(secret
))
87 memcpy(secret
, password
, len
);
89 apr_md5(secret
, password
, len
);
91 /* Compute MD5(secret XOR opad, MD5(secret XOR ipad, challenge)),
92 * where ipad is a string of 0x36 and opad is a string of 0x5c. */
93 for (i
= 0; i
< sizeof(secret
); i
++)
96 apr_md5_update(&ctx
, secret
, sizeof(secret
));
97 apr_md5_update(&ctx
, challenge
, strlen(challenge
));
98 apr_md5_final(digest
, &ctx
);
99 for (i
= 0; i
< sizeof(secret
); i
++)
100 secret
[i
] ^= (0x36 ^ 0x5c);
102 apr_md5_update(&ctx
, secret
, sizeof(secret
));
103 apr_md5_update(&ctx
, digest
, APR_MD5_DIGESTSIZE
);
104 apr_md5_final(digest
, &ctx
);
107 /* Fail the authentication, from the server's perspective. */
108 static svn_error_t
*fail(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
111 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(c)", "failure", msg
));
112 return svn_ra_svn_flush(conn
, pool
);
115 /* If we can, make the nonce with random bytes. If we can't... well,
116 * it just has to be different each time. The current time isn't
117 * absolutely guaranteed to be different for each connection, but it
118 * should prevent replay attacks in practice. */
119 static apr_status_t
make_nonce(apr_uint64_t
*nonce
)
122 return apr_generate_random_bytes((unsigned char *) nonce
, sizeof(*nonce
));
124 *nonce
= apr_time_now();
129 svn_error_t
*svn_ra_svn_cram_server(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
130 svn_config_t
*pwdb
, const char **user
,
131 svn_boolean_t
*success
)
135 char hostbuf
[APRMAXHOSTLEN
+ 1];
136 unsigned char cdigest
[APR_MD5_DIGESTSIZE
], sdigest
[APR_MD5_DIGESTSIZE
];
137 const char *challenge
, *sep
, *password
;
138 svn_ra_svn_item_t
*item
;
143 /* Send a challenge. */
144 status
= make_nonce(&nonce
);
146 status
= apr_gethostname(hostbuf
, sizeof(hostbuf
), pool
);
148 return fail(conn
, pool
, "Internal server error in authentication");
149 challenge
= apr_psprintf(pool
,
150 "<%" APR_UINT64_T_FMT
".%" APR_TIME_T_FMT
"@%s>",
151 nonce
, apr_time_now(), hostbuf
);
152 SVN_ERR(svn_ra_svn_write_tuple(conn
, pool
, "w(c)", "step", challenge
));
154 /* Read the client's response and decode it into *user and cdigest. */
155 SVN_ERR(svn_ra_svn_read_item(conn
, pool
, &item
));
156 if (item
->kind
!= SVN_RA_SVN_STRING
) /* Very wrong; don't report failure */
158 resp
= item
->u
.string
;
159 sep
= strrchr(resp
->data
, ' ');
160 if (!sep
|| resp
->len
- (sep
+ 1 - resp
->data
) != APR_MD5_DIGESTSIZE
* 2
161 || !hex_decode(cdigest
, sep
+ 1))
162 return fail(conn
, pool
, "Malformed client response in authentication");
163 *user
= apr_pstrmemdup(pool
, resp
->data
, sep
- resp
->data
);
165 /* Verify the digest against the password in pwfile. */
166 svn_config_get(pwdb
, &password
, SVN_CONFIG_SECTION_USERS
, *user
, NULL
);
168 return fail(conn
, pool
, "Username not found");
169 compute_digest(sdigest
, challenge
, password
);
170 if (memcmp(cdigest
, sdigest
, sizeof(sdigest
)) != 0)
171 return fail(conn
, pool
, "Password incorrect");
174 return svn_ra_svn_write_tuple(conn
, pool
, "w()", "success");
177 svn_error_t
*svn_ra_svn__cram_client(svn_ra_svn_conn_t
*conn
, apr_pool_t
*pool
,
178 const char *user
, const char *password
,
179 const char **message
)
181 const char *status
, *str
, *reply
;
182 unsigned char digest
[APR_MD5_DIGESTSIZE
];
183 char hex
[2 * APR_MD5_DIGESTSIZE
+ 1];
185 /* Read the server challenge. */
186 SVN_ERR(svn_ra_svn_read_tuple(conn
, pool
, "w(?c)", &status
, &str
));
187 if (strcmp(status
, "failure") == 0 && str
)
192 else if (strcmp(status
, "step") != 0 || !str
)
193 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
, NULL
,
194 _("Unexpected server response to authentication"));
196 /* Write our response. */
197 compute_digest(digest
, str
, password
);
198 hex_encode(hex
, digest
);
199 hex
[sizeof(hex
) - 1] = '\0';
200 reply
= apr_psprintf(pool
, "%s %s", user
, hex
);
201 SVN_ERR(svn_ra_svn_write_cstring(conn
, pool
, reply
));
203 /* Read the success or failure response from the server. */
204 SVN_ERR(svn_ra_svn_read_tuple(conn
, pool
, "w(?c)", &status
, &str
));
205 if (strcmp(status
, "failure") == 0 && str
)
210 else if (strcmp(status
, "success") != 0 || str
)
211 return svn_error_create(SVN_ERR_RA_NOT_AUTHORIZED
, NULL
,
212 _("Unexpected server response to authentication"));