2 * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
5 #pragma ident "%Z%%M% %I% %E% SMI"
7 /* CRAM-MD5 SASL plugin
10 * $Id: cram.c,v 1.79 2003/02/18 18:27:37 rjs3 Exp $
13 * Copyright (c) 1998-2003 Carnegie Mellon University. All rights reserved.
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
19 * 1. Redistributions of source code must retain the above copyright
20 * notice, this list of conditions and the following disclaimer.
22 * 2. Redistributions in binary form must reproduce the above copyright
23 * notice, this list of conditions and the following disclaimer in
24 * the documentation and/or other materials provided with the
27 * 3. The name "Carnegie Mellon University" must not be used to
28 * endorse or promote products derived from this software without
29 * prior written permission. For permission or any other legal
30 * details, please contact
31 * Office of Technology Transfer
32 * Carnegie Mellon University
34 * Pittsburgh, PA 15213-3890
35 * (412) 268-4387, fax: (412) 268-7395
36 * tech-transfer@andrew.cmu.edu
38 * 4. Redistributions of any form whatsoever must retain the following
40 * "This product includes software developed by Computing Services
41 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
43 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
44 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
45 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
46 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
47 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
48 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
49 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
68 #endif /* _SUN_SDK_ */
70 #include "plugin_common.h"
73 #include <sasl_cram_plugin_decl.h>
76 /***************************** Common Section *****************************/
79 static const char plugin_id
[] = "$Id: cram.c,v 1.79 2003/02/18 18:27:37 rjs3 Exp $";
80 #endif /* !_SUN_SDK_ */
82 /* convert a string of 8bit chars to it's representation in hex
83 * using lowercase letters
85 static char *convert16(unsigned char *in
, int inlen
, const sasl_utils_t
*utils
)
87 static char hex
[]="0123456789abcdef";
91 out
= utils
->malloc(inlen
*2+1);
92 if (out
== NULL
) return NULL
;
94 for (lup
=0; lup
< inlen
; lup
++) {
95 out
[lup
*2] = hex
[in
[lup
] >> 4];
96 out
[lup
*2+1] = hex
[in
[lup
] & 15];
104 /***************************** Server Section *****************************/
106 typedef struct server_context
{
113 crammd5_server_mech_new(void *glob_context
__attribute__((unused
)),
114 sasl_server_params_t
*sparams
,
115 const char *challenge
__attribute__((unused
)),
116 unsigned challen
__attribute__((unused
)),
119 server_context_t
*text
;
121 /* holds state are in */
122 text
= sparams
->utils
->malloc(sizeof(server_context_t
));
124 MEMERROR( sparams
->utils
);
128 memset(text
, 0, sizeof(server_context_t
));
132 *conn_context
= text
;
138 * Returns the current time (or part of it) in string form
141 static char *gettime(sasl_server_params_t
*sparams
)
147 ret
= sparams
->utils
->malloc(15);
148 if (ret
==NULL
) return NULL
;
150 /* the bottom bits are really the only random ones so if
151 we overflow we don't want to loose them */
152 snprintf(ret
,15,"%lu",t
%(0xFFFFFF));
157 static char *randomdigits(sasl_server_params_t
*sparams
)
161 unsigned char temp
[5]; /* random 32-bit number */
163 #if defined _DEV_URANDOM && defined _SUN_SDK_
165 int fd
= open(_DEV_URANDOM
, O_RDONLY
);
169 nread
= read(fd
, temp
, 4);
173 sparams
->utils
->rand(sparams
->utils
->rpool
,
177 sparams
->utils
->rand(sparams
->utils
->rpool
,(char *) temp
,4);
178 #endif /* _DEV_URANDOM && _SUN_SDK_ */
179 num
=(temp
[0] * 256 * 256 * 256) +
180 (temp
[1] * 256 * 256) +
184 ret
= sparams
->utils
->malloc(15); /* there's no way an unsigned can be longer than this right? */
185 if (ret
== NULL
) return NULL
;
186 sprintf(ret
, "%u", num
);
192 crammd5_server_mech_step1(server_context_t
*text
,
193 sasl_server_params_t
*sparams
,
194 const char *clientin
__attribute__((unused
)),
195 unsigned clientinlen
,
196 const char **serverout
,
197 unsigned *serveroutlen
,
198 sasl_out_params_t
*oparams
__attribute__((unused
)))
200 char *time
, *randdigits
;
202 /* we shouldn't have received anything */
203 if (clientinlen
!= 0) {
205 sparams
->utils
->log(sparams
->utils
->conn
, SASL_LOG_ERR
,
206 "CRAM-MD5 does not accept inital data");
208 SETERROR(sparams
->utils
, "CRAM-MD5 does not accpet inital data");
209 #endif /* _SUN_SDK_ */
213 /* get time and a random number for the nonce */
214 time
= gettime(sparams
);
215 randdigits
= randomdigits(sparams
);
216 if ((time
== NULL
) || (randdigits
== NULL
)) {
217 MEMERROR( sparams
->utils
);
221 /* allocate some space for the challenge */
222 text
->challenge
= sparams
->utils
->malloc(200 + 1);
223 if (text
->challenge
== NULL
) {
224 MEMERROR(sparams
->utils
);
228 /* create the challenge */
229 snprintf(text
->challenge
, 200, "<%s.%s@%s>", randdigits
, time
,
230 sparams
->serverFQDN
);
232 *serverout
= text
->challenge
;
233 *serveroutlen
= strlen(text
->challenge
);
236 sparams
->utils
->free(time
);
237 sparams
->utils
->free(randdigits
);
241 return SASL_CONTINUE
;
245 crammd5_server_mech_step2(server_context_t
*text
,
246 sasl_server_params_t
*sparams
,
247 const char *clientin
,
248 unsigned clientinlen
,
249 const char **serverout
__attribute__((unused
)),
250 unsigned *serveroutlen
__attribute__((unused
)),
251 sasl_out_params_t
*oparams
)
254 sasl_secret_t
*sec
= NULL
;
256 int result
= SASL_FAIL
;
257 const char *password_request
[] = { SASL_AUX_PASSWORD
,
258 "*cmusaslsecretCRAM-MD5",
260 struct propval auxprop_values
[3];
261 HMAC_MD5_CTX tmphmac
;
262 HMAC_MD5_STATE md5state
;
263 int clear_md5state
= 0;
264 char *digest_str
= NULL
;
267 /* extract userid; everything before last space */
269 while ((pos
> 0) && (clientin
[pos
] != ' ')) pos
--;
273 sparams
->utils
->log(sparams
->utils
->conn
, SASL_LOG_ERR
,
274 "need authentication name");
276 SETERROR( sparams
->utils
,"need authentication name");
277 #endif /* _SUN_SDK_ */
281 userid
= (char *) sparams
->utils
->malloc(pos
+1);
282 if (userid
== NULL
) {
283 MEMERROR( sparams
->utils
);
287 /* copy authstr out */
288 memcpy(userid
, clientin
, pos
);
291 result
= sparams
->utils
->prop_request(sparams
->propctx
, password_request
);
292 if (result
!= SASL_OK
) goto done
;
294 /* this will trigger the getting of the aux properties */
295 result
= sparams
->canon_user(sparams
->utils
->conn
,
296 userid
, 0, SASL_CU_AUTHID
| SASL_CU_AUTHZID
,
298 if (result
!= SASL_OK
) goto done
;
300 result
= sparams
->utils
->prop_getnames(sparams
->propctx
,
304 ((!auxprop_values
[0].name
|| !auxprop_values
[0].values
) &&
305 (!auxprop_values
[1].name
|| !auxprop_values
[1].values
))) {
306 /* We didn't find this username */
307 #ifdef _INTEGRATED_SOLARIS_
308 sparams
->utils
->seterror(sparams
->utils
->conn
,0,
309 gettext("no secret in database"));
311 sparams
->utils
->seterror(sparams
->utils
->conn
,0,
312 "no secret in database");
313 #endif /* _INTEGRATED_SOLARIS_ */
314 result
= SASL_NOUSER
;
318 if (auxprop_values
[0].name
&& auxprop_values
[0].values
) {
319 len
= strlen(auxprop_values
[0].values
[0]);
321 #ifdef _INTEGRATED_SOLARIS_
322 sparams
->utils
->seterror(sparams
->utils
->conn
,0,
323 gettext("empty secret"));
325 sparams
->utils
->seterror(sparams
->utils
->conn
,0,
327 #endif /* _INTEGRATED_SOLARIS_ */
332 sec
= sparams
->utils
->malloc(sizeof(sasl_secret_t
) + len
);
337 strncpy((char *)sec
->data
, auxprop_values
[0].values
[0], len
+ 1);
339 strncpy(sec
->data
, auxprop_values
[0].values
[0], len
+ 1);
340 #endif /* _SUN_SDK_ */
343 /* Do precalculation on plaintext secret */
344 sparams
->utils
->hmac_md5_precalc(&md5state
, /* OUT */
347 } else if (auxprop_values
[1].name
&& auxprop_values
[1].values
) {
348 /* We have a precomputed secret */
349 memcpy(&md5state
, auxprop_values
[1].values
[0],
350 sizeof(HMAC_MD5_STATE
));
353 sparams
->utils
->log(sparams
->utils
->conn
, SASL_LOG_ERR
,
354 "Have neither type of secret");
356 sparams
->utils
->seterror(sparams
->utils
->conn
, 0,
357 "Have neither type of secret");
358 #endif /* _SUN_SDK_ */
362 /* ok this is annoying:
363 so we have this half-way hmac transform instead of the plaintext
364 that means we half to:
365 -import it back into a md5 context
366 -do an md5update with the nonce
369 sparams
->utils
->hmac_md5_import(&tmphmac
, (HMAC_MD5_STATE
*) &md5state
);
370 sparams
->utils
->MD5Update(&(tmphmac
.ictx
),
371 (const unsigned char *) text
->challenge
,
372 strlen(text
->challenge
));
373 sparams
->utils
->hmac_md5_final((unsigned char *) &digest
, &tmphmac
);
375 /* convert to base 16 with lower case letters */
376 digest_str
= convert16((unsigned char *) digest
, 16, sparams
->utils
);
378 /* if same then verified
379 * - we know digest_str is null terminated but clientin might not be
381 if (strncmp(digest_str
, clientin
+pos
+1, strlen(digest_str
)) != 0) {
382 #ifdef _INTEGRATED_SOLARIS_
383 sparams
->utils
->seterror(sparams
->utils
->conn
, 0,
384 gettext("incorrect digest response"));
386 sparams
->utils
->seterror(sparams
->utils
->conn
, 0,
387 "incorrect digest response");
388 #endif /* _INTEGRATED_SOLARIS_ */
389 result
= SASL_BADAUTH
;
394 oparams
->doneflag
= 1;
395 oparams
->mech_ssf
= 0;
396 oparams
->maxoutbuf
= 0;
397 oparams
->encode_context
= NULL
;
398 oparams
->encode
= NULL
;
399 oparams
->decode_context
= NULL
;
400 oparams
->decode
= NULL
;
401 oparams
->param_version
= 0;
406 if (userid
) sparams
->utils
->free(userid
);
407 if (sec
) _plug_free_secret(sparams
->utils
, &sec
);
409 if (digest_str
) sparams
->utils
->free(digest_str
);
410 if (clear_md5state
) memset(&md5state
, 0, sizeof(md5state
));
415 static int crammd5_server_mech_step(void *conn_context
,
416 sasl_server_params_t
*sparams
,
417 const char *clientin
,
418 unsigned clientinlen
,
419 const char **serverout
,
420 unsigned *serveroutlen
,
421 sasl_out_params_t
*oparams
)
423 server_context_t
*text
= (server_context_t
*) conn_context
;
428 /* this should be well more than is ever needed */
429 if (clientinlen
> 1024) {
431 sparams
->utils
->log(sparams
->utils
->conn
, SASL_LOG_ERR
,
432 "CRAM-MD5 input longer than 1024 bytes");
434 SETERROR(sparams
->utils
, "CRAM-MD5 input longer than 1024 bytes");
435 #endif /* _SUN_SDK_ */
439 switch (text
->state
) {
442 return crammd5_server_mech_step1(text
, sparams
,
443 clientin
, clientinlen
,
444 serverout
, serveroutlen
,
448 return crammd5_server_mech_step2(text
, sparams
,
449 clientin
, clientinlen
,
450 serverout
, serveroutlen
,
453 default: /* should never get here */
455 sparams
->utils
->log(sparams
->utils
->conn
, SASL_LOG_ERR
,
456 "Invalid CRAM-MD5 server step %d", text
->state
);
458 sparams
->utils
->log(NULL
, SASL_LOG_ERR
,
459 "Invalid CRAM-MD5 server step %d\n", text
->state
);
460 #endif /* _SUN_SDK_ */
465 return SASL_FAIL
; /* should never get here */
466 #endif /* !_SUN_SDK_ */
469 static void crammd5_server_mech_dispose(void *conn_context
,
470 const sasl_utils_t
*utils
)
472 server_context_t
*text
= (server_context_t
*) conn_context
;
476 if (text
->challenge
) _plug_free_string(utils
,&(text
->challenge
));
481 static sasl_server_plug_t crammd5_server_plugins
[] =
484 "CRAM-MD5", /* mech_name */
487 | SASL_SEC_NOANONYMOUS
, /* security_flags */
488 SASL_FEAT_SERVER_FIRST
, /* features */
489 NULL
, /* glob_context */
490 &crammd5_server_mech_new
, /* mech_new */
491 &crammd5_server_mech_step
, /* mech_step */
492 &crammd5_server_mech_dispose
, /* mech_dispose */
493 NULL
, /* mech_free */
495 NULL
, /* user_query */
497 NULL
, /* mech avail */
502 int crammd5_server_plug_init(const sasl_utils_t
*utils
,
505 sasl_server_plug_t
**pluglist
,
508 if (maxversion
< SASL_SERVER_PLUG_VERSION
) {
510 utils
->log(NULL
, SASL_LOG_ERR
, "CRAM version mismatch");
512 SETERROR( utils
, "CRAM version mismatch");
513 #endif /* _SUN_SDK_ */
517 *out_version
= SASL_SERVER_PLUG_VERSION
;
518 *pluglist
= crammd5_server_plugins
;
524 /***************************** Client Section *****************************/
526 typedef struct client_context
{
528 unsigned out_buf_len
;
529 #ifdef _INTEGRATED_SOLARIS_
531 #endif /* _INTEGRATED_SOLARIS_ */
534 static int crammd5_client_mech_new(void *glob_context
__attribute__((unused
)),
535 sasl_client_params_t
*params
,
538 client_context_t
*text
;
540 /* holds state are in */
541 text
= params
->utils
->malloc(sizeof(client_context_t
));
543 MEMERROR(params
->utils
);
547 memset(text
, 0, sizeof(client_context_t
));
549 *conn_context
= text
;
554 static char *make_hashed(sasl_secret_t
*sec
, char *nonce
, int noncelen
,
555 const sasl_utils_t
*utils
)
558 unsigned char digest
[24];
562 if (sec
== NULL
) return NULL
;
565 memcpy(secret
, sec
->data
, sec
->len
);
567 /* fill in rest with 0's */
568 for (lup
= sec
->len
; lup
< 64; lup
++)
572 memcpy(secret
, sec
->data
, 64);
575 /* do the hmac md5 hash output 128 bits */
576 utils
->hmac_md5((unsigned char *) nonce
, noncelen
,
577 (unsigned char *) secret
, 64, digest
);
579 /* convert that to hex form */
580 in16
= convert16(digest
, 16, utils
);
581 if (in16
== NULL
) return NULL
;
586 static int crammd5_client_mech_step(void *conn_context
,
587 sasl_client_params_t
*params
,
588 const char *serverin
,
589 unsigned serverinlen
,
590 sasl_interact_t
**prompt_need
,
591 const char **clientout
,
592 unsigned *clientoutlen
,
593 sasl_out_params_t
*oparams
)
595 client_context_t
*text
= (client_context_t
*) conn_context
;
597 sasl_secret_t
*password
= NULL
;
598 unsigned int free_password
= 0; /* set if we need to free password */
599 int auth_result
= SASL_OK
;
600 int pass_result
= SASL_OK
;
608 /* First check for absurd lengths */
609 if (serverinlen
> 1024) {
611 params
->utils
->log(params
->utils
->conn
, SASL_LOG_ERR
,
612 "CRAM-MD5 input longer than 1024 bytes");
614 params
->utils
->seterror(params
->utils
->conn
, 0,
615 "CRAM-MD5 input longer than 1024 bytes");
616 #endif /* _SUN_SDK_ */
620 /* check if sec layer strong enough */
621 if (params
->props
.min_ssf
> params
->external_ssf
) {
623 params
->utils
->log(params
->utils
->conn
, SASL_LOG_ERR
,
624 "SSF requested of CRAM-MD5 plugin");
626 SETERROR( params
->utils
, "SSF requested of CRAM-MD5 plugin");
627 #endif /* _SUN_SDK_ */
631 /* try to get the userid */
632 if (oparams
->authid
== NULL
) {
633 auth_result
=_plug_get_authid(params
->utils
, &authid
, prompt_need
);
635 if ((auth_result
!= SASL_OK
) && (auth_result
!= SASL_INTERACT
))
639 /* try to get the password */
640 if (password
== NULL
) {
641 pass_result
=_plug_get_password(params
->utils
, &password
,
642 &free_password
, prompt_need
);
644 if ((pass_result
!= SASL_OK
) && (pass_result
!= SASL_INTERACT
))
648 /* free prompts we got */
649 if (prompt_need
&& *prompt_need
) {
650 params
->utils
->free(*prompt_need
);
654 /* if there are prompts not filled in */
655 if ((auth_result
== SASL_INTERACT
) || (pass_result
== SASL_INTERACT
)) {
656 /* make the prompt list */
658 #ifdef _INTEGRATED_SOLARIS_
659 _plug_make_prompts(params
->utils
, &text
->h
, prompt_need
,
661 auth_result
== SASL_INTERACT
?
662 convert_prompt(params
->utils
, &text
->h
,
663 gettext("Please enter your authentication name"))
665 pass_result
== SASL_INTERACT
?
666 convert_prompt(params
->utils
, &text
->h
,
667 gettext("Please enter your password"))
672 _plug_make_prompts(params
->utils
, prompt_need
,
674 auth_result
== SASL_INTERACT
?
675 "Please enter your authentication name" : NULL
,
677 pass_result
== SASL_INTERACT
?
678 "Please enter your password" : NULL
, NULL
,
681 #endif /* _INTEGRATED_SOLARIS_ */
682 if (result
!= SASL_OK
) goto cleanup
;
684 return SASL_INTERACT
;
688 PARAMERROR(params
->utils
);
689 return SASL_BADPARAM
;
692 result
= params
->canon_user(params
->utils
->conn
, authid
, 0,
693 SASL_CU_AUTHID
| SASL_CU_AUTHZID
, oparams
);
694 if (result
!= SASL_OK
) goto cleanup
;
697 * username SP digest (keyed md5 where key is passwd)
700 in16
= make_hashed(password
, (char *) serverin
, serverinlen
,
705 params
->utils
->log(params
->utils
->conn
, SASL_LOG_ERR
,
706 "make_hashed failed");
708 SETERROR(params
->utils
, "whoops, make_hashed failed us this time");
709 #endif /* _SUN_SDK_ */
714 maxsize
= 32+1+strlen(oparams
->authid
)+30;
715 result
= _plug_buf_alloc(params
->utils
, &(text
->out_buf
),
716 &(text
->out_buf_len
), maxsize
);
717 if (result
!= SASL_OK
) goto cleanup
;
719 snprintf(text
->out_buf
, maxsize
, "%s %s", oparams
->authid
, in16
);
721 *clientout
= text
->out_buf
;
722 *clientoutlen
= strlen(*clientout
);
725 oparams
->doneflag
= 1;
726 oparams
->mech_ssf
= 0;
727 oparams
->maxoutbuf
= 0;
728 oparams
->encode_context
= NULL
;
729 oparams
->encode
= NULL
;
730 oparams
->decode_context
= NULL
;
731 oparams
->decode
= NULL
;
732 oparams
->param_version
= 0;
737 /* get rid of private information */
738 if (in16
) _plug_free_string(params
->utils
, &in16
);
740 /* get rid of all sensitive info */
741 if (free_password
) _plug_free_secret(params
-> utils
, &password
);
746 static void crammd5_client_mech_dispose(void *conn_context
,
747 const sasl_utils_t
*utils
)
749 client_context_t
*text
= (client_context_t
*) conn_context
;
753 #ifdef _INTEGRATED_SOLARIS_
754 convert_prompt(utils
, &text
->h
, NULL
);
755 #endif /* _INTEGRATED_SOLARIS_ */
756 if (text
->out_buf
) utils
->free(text
->out_buf
);
761 static sasl_client_plug_t crammd5_client_plugins
[] =
764 "CRAM-MD5", /* mech_name */
767 | SASL_SEC_NOANONYMOUS
, /* security_flags */
768 SASL_FEAT_SERVER_FIRST
, /* features */
769 NULL
, /* required_prompts */
770 NULL
, /* glob_context */
771 &crammd5_client_mech_new
, /* mech_new */
772 &crammd5_client_mech_step
, /* mech_step */
773 &crammd5_client_mech_dispose
, /* mech_dispose */
774 NULL
, /* mech_free */
781 int crammd5_client_plug_init(const sasl_utils_t
*utils
,
784 sasl_client_plug_t
**pluglist
,
787 if (maxversion
< SASL_CLIENT_PLUG_VERSION
) {
789 utils
->log(NULL
, SASL_LOG_ERR
, "CRAM version mismatch");
791 SETERROR( utils
, "CRAM version mismatch");
792 #endif /* _SUN_SDK_ */
796 *out_version
= SASL_CLIENT_PLUG_VERSION
;
797 *pluglist
= crammd5_client_plugins
;