netatalk  4.6.0
Free and Open Source Apple Filing Protocol (AFP) Server
Loading...
Searching...
No Matches
uams_srp.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2026 Daniel Markstedt <daniel@mindani.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
20
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#endif
24
25#include <arpa/inet.h>
26#include <ctype.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <gcrypt.h>
30#include <inttypes.h>
31#include <pwd.h>
32#include <stdarg.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <sys/param.h>
36#include <string.h>
37#include <sys/stat.h>
38#include <sys/types.h>
39#include <unistd.h>
40
41#include <atalk/afp.h>
42#include <atalk/compat.h>
43#include <atalk/constant_time.h>
44#include <atalk/logger.h>
45#include <atalk/srp.h>
46#include <atalk/uam.h>
47
48/* -------------------- Constants -------------------- */
49
50#define SRP_INIT_MARKER 0x0001
51#define SRP_CLIENT_PROOF 0x0003
52#define SRP_SERVER_PROOF 0x0004
53
54#define SRP_SESSION_KEY_LEN 40
55
56/* Error code for authentication failure (as observed on Apple servers) */
57#define SRP_AUTH_FAILURE (-6754)
58
59/* -------------------- Per-session state -------------------- */
60
61static gcry_mpi_t session_N;
62static gcry_mpi_t session_g;
63static gcry_mpi_t session_v; /* verifier */
64static gcry_mpi_t session_b; /* server secret ephemeral */
65static gcry_mpi_t session_B; /* server public ephemeral */
66static unsigned char session_salt[SRP_SALT_LEN];
67static unsigned char session_B_buf[SRP_NBYTES]; /* B in wire format */
69static struct passwd *srppwd;
70
71/* -------------------- Crypto helpers -------------------- */
72
77static const unsigned char *strip_leading_zeros(const unsigned char *buf,
78 size_t len, size_t *out_len)
79{
80 while (len > 1 && *buf == 0) {
81 buf++;
82 len--;
83 }
84
85 *out_len = len;
86 return buf;
87}
88
97static int mgf1_sha1(const unsigned char *seed, size_t seed_len,
98 unsigned char *out, size_t out_len)
99{
100 unsigned char counter_be[4];
101 unsigned char hash[SRP_SHA1_LEN];
102 size_t pos = 0;
103 uint32_t counter = 0;
104
105 while (pos < out_len) {
106 counter_be[0] = (counter >> 24) & 0xFF;
107 counter_be[1] = (counter >> 16) & 0xFF;
108 counter_be[2] = (counter >> 8) & 0xFF;
109 counter_be[3] = counter & 0xFF;
110 gcry_md_hd_t hd;
111
112 if (gcry_md_open(&hd, GCRY_MD_SHA1, 0) != 0) {
113 return -1;
114 }
115
116 gcry_md_write(hd, seed, seed_len);
117 gcry_md_write(hd, counter_be, 4);
118 memcpy(hash, gcry_md_read(hd, GCRY_MD_SHA1), SRP_SHA1_LEN);
119 gcry_md_close(hd);
120 size_t to_copy = out_len - pos;
121
122 if (to_copy > SRP_SHA1_LEN) {
123 to_copy = SRP_SHA1_LEN;
124 }
125
126 memcpy(out + pos, hash, to_copy);
127 pos += to_copy;
128 counter++;
129 }
130
131 return 0;
132}
133
144static int sha1_multi(unsigned char *out, ...)
145{
146 gcry_md_hd_t hd;
147 va_list ap;
148 gcry_error_t err = gcry_md_open(&hd, GCRY_MD_SHA1, 0);
149
150 if (err != 0) {
151 return -1;
152 }
153
154 va_start(ap, out);
155
156 for (;;) {
157 const unsigned char *data = va_arg(ap, const unsigned char *);
158
159 if (data == NULL) {
160 break;
161 }
162
163 size_t len = va_arg(ap, size_t);
164 gcry_md_write(hd, data, len);
165 }
166
167 va_end(ap);
168 memcpy(out, gcry_md_read(hd, GCRY_MD_SHA1), SRP_SHA1_LEN);
169 gcry_md_close(hd);
170 return 0;
171}
172
180static void mpi_to_padded_buf(unsigned char *buf, size_t nbytes, gcry_mpi_t m)
181{
182 size_t nwritten;
183 memset(buf, 0, nbytes);
184 gcry_mpi_print(GCRYMPI_FMT_USG, buf, nbytes, &nwritten, m);
185
186 if (nwritten < nbytes) {
187 memmove(buf + nbytes - nwritten, buf, nwritten);
188 memset(buf, 0, nbytes - nwritten);
189 }
190}
191
198static void write_uint16_be(unsigned char **p, uint16_t val)
199{
200 (*p)[0] = (val >> 8) & 0xFF;
201 (*p)[1] = val & 0xFF;
202 *p += 2;
203}
204
211static uint16_t read_uint16_be(unsigned char **p)
212{
213 uint16_t val = (uint16_t)((*p)[0] << 8 | (*p)[1]);
214 *p += 2;
215 return val;
216}
217
218/* -------------------- Verifier directory I/O -------------------- */
219
220#define unhex(x) (isdigit(x) ? (x) - '0' : toupper(x) + 10 - 'A')
221
230
249 const char *username,
250 uid_t uid,
251 unsigned char *salt_out,
252 gcry_mpi_t *v_out)
253{
254 FILE *fp = NULL;
255 struct stat st;
256 char uid_name[3 * sizeof(uid_t) + 1];
257 char line[SRP_USERNAME_MAX_LEN + SRP_FORMAT_LEN + 1];
258 const char *p;
259 size_t ulen = strnlen(username, SRP_USERNAME_MAX_LEN + 1);
260 int dirfd = -1, fd = -1;
262
263 if (!srp_valid_username(username)) {
265 }
266
267 int uid_len = snprintf(uid_name, sizeof(uid_name), "%ju", (uintmax_t)uid);
268
269 if (uid_len < 0 || uid_len >= (int)sizeof(uid_name)) {
271 }
272
273 dirfd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
274
275 if (dirfd < 0 || fstat(dirfd, &st) < 0 || !S_ISDIR(st.st_mode) ||
276 st.st_uid != 0 || (st.st_mode & (S_IWGRP | S_IWOTH))) {
277 if (dirfd < 0 && lstat(path, &st) == 0 && S_ISREG(st.st_mode)) {
279 "srp_lookup_verifier: %s is a legacy flat SRP verifier file; stop afpd and run 'afppasswd -m'",
280 path);
281 } else {
283 "srp_lookup_verifier: unsafe or unavailable verifier directory %s",
284 path);
285 }
286
288 goto done;
289 }
290
291 fd = openat(dirfd, uid_name, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
292
293 if (fd < 0) {
294 if (errno != ENOENT) {
296 "srp_lookup_verifier: can't open verifier %s/%s: %s", path,
297 uid_name, strerror(errno));
299 }
300
301 goto done;
302 }
303
304 if (fstat(fd, &st) < 0) {
306 "srp_lookup_verifier: can't stat verifier %s/%s: %s", path,
307 uid_name, strerror(errno));
309 goto done;
310 }
311
312 if (!S_ISREG(st.st_mode)) {
314 "srp_lookup_verifier: verifier %s/%s for user %s is not a regular file",
315 path, uid_name, username);
317 goto done;
318 }
319
320 if ((st.st_uid != uid && st.st_uid != 0) ||
321 !srp_verifier_mode_is_safe(st.st_mode) || st.st_nlink != 1) {
323 "srp_lookup_verifier: unsafe verifier %s/%s for user %s: "
324 "owner uid %ju (expected %ju), mode %04o (must not grant group "
325 "or other access), link count %ju (expected 1)",
326 path, uid_name, username, (uintmax_t)st.st_uid, (uintmax_t)uid,
327 (unsigned int)(st.st_mode & 07777), (uintmax_t)st.st_nlink);
329 goto done;
330 }
331
332 fp = fdopen(fd, "r");
333
334 if (fp == NULL) {
336 "srp_lookup_verifier: can't open verifier stream %s/%s: %s",
337 path, uid_name, strerror(errno));
339 goto done;
340 }
341
342 fd = -1;
343
344 if (fgets(line, sizeof(line), fp) == NULL || fgetc(fp) != EOF) {
346 "srp_lookup_verifier: invalid verifier %s/%s for user %s: must contain exactly one record",
347 path, uid_name, username);
349 goto done;
350 }
351
352 p = strchr(line, ':');
353
354 if (p == NULL || (size_t)(p - line) != ulen ||
355 strncmp(line, username, ulen) != 0) {
357 "srp_lookup_verifier: invalid verifier %s/%s for user %s: username does not match",
358 path, uid_name, username);
360 goto done;
361 }
362
363 p++;
364
365 if (!srp_valid_fields(p)) {
367 "srp_lookup_verifier: invalid verifier %s/%s for user %s: malformed fields",
368 path, uid_name, username);
370 goto done;
371 }
372
373 if (*p == SRP_DISABLED_CHAR) {
374 ret = st.st_uid == 0 && uid != 0 ? SRP_VERIFIER_NOT_ENROLLED :
376 goto done;
377 }
378
379 if (st.st_uid != uid) {
381 "srp_lookup_verifier: unsafe active verifier %s/%s for user %s: "
382 "owner uid %ju (expected %ju)", path, uid_name, username,
383 (uintmax_t)st.st_uid, (uintmax_t)uid);
385 goto done;
386 }
387
388 for (int i = 0; i < SRP_SALT_LEN; i++) {
389 salt_out[i] = (unsigned char)((unhex(p[0]) << 4) | unhex(p[1]));
390 p += 2;
391 }
392
393 p++; /* Skip the validated separator. */
394 unsigned char v_bytes[SRP_NBYTES];
395
396 for (int i = 0; i < SRP_NBYTES; i++) {
397 v_bytes[i] = (unsigned char)((unhex(p[0]) << 4) | unhex(p[1]));
398 p += 2;
399 }
400
401 if (gcry_mpi_scan(v_out, GCRYMPI_FMT_USG, v_bytes, SRP_NBYTES,
402 NULL) == 0) {
403 ret = SRP_VERIFIER_OK;
404 } else {
406 "srp_lookup_verifier: invalid verifier %s/%s for user %s",
407 path, uid_name, username);
409 }
410
411 explicit_bzero(v_bytes, sizeof(v_bytes));
412done:
413
414 if (fp != NULL) {
415 fclose(fp);
416 }
417
418 if (fd >= 0) {
419 close(fd);
420 }
421
422 if (dirfd >= 0) {
423 close(dirfd);
424 }
425
426 if (ret != 0) {
427 explicit_bzero(salt_out, SRP_SALT_LEN);
428 }
429
430 return ret;
431}
432
433/* -------------------- SRP protocol handlers -------------------- */
434
436static void srp_session_free(void)
437{
438 gcry_mpi_release(session_N);
439 gcry_mpi_release(session_g);
440 gcry_mpi_release(session_v);
441 gcry_mpi_release(session_b);
442 gcry_mpi_release(session_B);
443 session_N = NULL;
444 session_g = NULL;
445 session_v = NULL;
446 session_b = NULL;
447 session_B = NULL;
448 srppwd = NULL;
452}
453
463static int srp_setup(void *obj, char *ibuf _U_, size_t ibuflen _U_,
464 char *rbuf, size_t *rbuflen)
465{
466 char *verifier_path = NULL;
467 size_t len;
468 unsigned char *b_binary = NULL;
469 gcry_mpi_t k = NULL, kv = NULL;
470 enum srp_verifier_status verifier_status;
471 int ret;
472 *rbuflen = 0;
473 /* Get the SRP verifier directory path */
475
477 (void *)&verifier_path, &len) < 0) {
479 "srp_setup: can't get SRP verifier path option");
480 return AFPERR_MISC;
481 }
482
483 if (!verifier_path || len == 0) {
485 "srp_setup: SRP verifier path not configured");
486 return AFPERR_MISC;
487 }
488
489 /* Initialize group parameters */
490 gcry_mpi_scan(&session_N, GCRYMPI_FMT_USG, srp_N_bytes, SRP_NBYTES, NULL);
491 gcry_mpi_scan(&session_g, GCRYMPI_FMT_USG, &srp_g_byte, 1, NULL);
492 /* Look up verifier */
493 session_v = NULL;
494 verifier_status = srp_lookup_verifier(verifier_path, session_username,
495 srppwd->pw_uid, session_salt,
496 &session_v);
497
498 if (verifier_status != SRP_VERIFIER_OK) {
499 switch (verifier_status) {
501 LOG(log_info, logtype_uams, "srp_setup: no verifier for user %s",
503 break;
504
507 "srp_setup: user %s is not enrolled (root-owned disabled verifier)",
509 break;
510
513 "srp_setup: SRP verifier is disabled for user %s",
515 break;
516
519 /* srp_lookup_verifier() emitted the specific error. */
520 break;
521
522 default:
524 "srp_setup: unknown verifier state for user %s",
526 break;
527 }
528
529 /*
530 * Unknown user: return AFPERR_NOTAUTH with 2-byte init marker payload.
531 * This matches the observed Apple server behavior.
532 */
533 unsigned char *rbufp = (unsigned char *)rbuf;
535 *rbuflen = 2;
536 ret = AFPERR_NOTAUTH;
537 goto error;
538 }
539
540 /* Generate server ephemeral: b random, B = k*v + g^b mod N */
541 b_binary = calloc(1, SRP_NBYTES);
542
543 if (b_binary == NULL) {
544 ret = AFPERR_MISC;
545 goto error;
546 }
547
548 session_b = gcry_mpi_new(0);
549 session_B = gcry_mpi_new(0);
550 kv = gcry_mpi_new(0);
551 /* k = SHA1(N | PAD(g)) */
552 unsigned char g_padded[SRP_NBYTES];
553 memset(g_padded, 0, SRP_NBYTES);
554 g_padded[SRP_NBYTES - 1] = srp_g_byte;
555 unsigned char k_hash[SRP_SHA1_LEN];
556
557 if (sha1_multi(k_hash,
558 srp_N_bytes, (size_t)SRP_NBYTES,
559 g_padded, (size_t)SRP_NBYTES,
560 NULL) != 0) {
561 ret = AFPERR_MISC;
562 goto error;
563 }
564
565 gcry_mpi_scan(&k, GCRYMPI_FMT_USG, k_hash, SRP_SHA1_LEN, NULL);
566 /* Generate b, compute B = (k*v + g^b) mod N */
567 gcry_mpi_t gb = gcry_mpi_new(0);
568
569 do {
570 gcry_randomize(b_binary, SRP_NBYTES, GCRY_STRONG_RANDOM);
571 gcry_mpi_release(session_b);
572 session_b = NULL;
573 gcry_mpi_scan(&session_b, GCRYMPI_FMT_USG, b_binary, SRP_NBYTES, NULL);
574 gcry_mpi_mod(session_b, session_b, session_N);
575 gcry_mpi_powm(gb, session_g, session_b, session_N);
576 gcry_mpi_mulm(kv, k, session_v, session_N);
577 gcry_mpi_addm(session_B, kv, gb, session_N);
578 } while (gcry_mpi_cmp_ui(session_B, 0) == 0);
579
580 gcry_mpi_release(gb);
581 /* Store B in wire format for later use in u computation */
583 /* Build response:
584 * context(2) | group_index(2) | N_len(2) | N(192) |
585 * g_len(2) | g(1) | salt_len(2) | salt(16) | B_len(2) | B(192)
586 * Total: 413 bytes
587 */
588 unsigned char *p = (unsigned char *)rbuf;
589 /* Transaction context (echoed from client, or server-assigned) */
590 write_uint16_be(&p, 0x0000);
591 /* Group index */
593 /* N */
595 memcpy(p, srp_N_bytes, SRP_NBYTES);
596 p += SRP_NBYTES;
597 /* g */
598 write_uint16_be(&p, 1);
599 *p++ = srp_g_byte;
600 /* salt */
602 memcpy(p, session_salt, SRP_SALT_LEN);
603 p += SRP_SALT_LEN;
604 /* B */
606 memcpy(p, session_B_buf, SRP_NBYTES);
607 p += SRP_NBYTES;
608 *rbuflen = p - (unsigned char *)rbuf;
609 ret = AFPERR_AUTHCONT;
610 free(b_binary);
611 gcry_mpi_release(k);
612 gcry_mpi_release(kv);
613 return ret;
614error:
615 free(b_binary);
616 gcry_mpi_release(k);
617 gcry_mpi_release(kv);
619 return ret;
620}
621
628static int srp_login(void *obj, struct passwd **uam_pwd _U_,
629 char *ibuf, size_t ibuflen,
630 char *rbuf, size_t *rbuflen)
631{
632 char *username;
633 size_t len, ulen;
635 *rbuflen = 0;
636
637 if (ibuflen < 1) {
638 return AFPERR_PARAM;
639 }
640
642 (void *)&username, &ulen) < 0) {
643 return AFPERR_PARAM;
644 }
645
646 len = (unsigned char) * ibuf++;
647 ibuflen--;
648
649 if (len > ulen || len > ibuflen) {
650 return AFPERR_PARAM;
651 }
652
653 memcpy(username, ibuf, len);
654 ibuf += len;
655 username[len] = '\0';
656
657 if ((unsigned long)ibuf & 1) {
658 ++ibuf;
659 }
660
661 if ((srppwd = uam_getname(obj, username, (int)ulen)) == NULL) {
662 LOG(log_info, logtype_uams, "srp_login: unknown username");
663 return AFPERR_NOTAUTH;
664 }
665
666 if (uam_checkuser(obj, srppwd) < 0) {
667 LOG(log_info, logtype_uams, "srp_login: user not allowed: %s", username);
668 return AFPERR_NOTAUTH;
669 }
670
671 LOG(log_info, logtype_uams, "srp_login: login: %s", username);
672 strlcpy(session_username, username, sizeof(session_username));
673 return srp_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
674}
675
682static int srp_login_ext(void *obj, char *uname, struct passwd **uam_pwd _U_,
683 char *ibuf, size_t ibuflen,
684 char *rbuf, size_t *rbuflen)
685{
686 char *username;
687 size_t len, ulen;
688 uint16_t temp16;
690 *rbuflen = 0;
691
693 (void *)&username, &ulen) < 0) {
694 return AFPERR_PARAM;
695 }
696
697 if (*uname != 3) {
698 return AFPERR_PARAM;
699 }
700
701 uname++;
702 memcpy(&temp16, uname, sizeof(temp16));
703 len = ntohs(temp16);
704
705 if (!len || len > ulen) {
706 return AFPERR_PARAM;
707 }
708
709 memcpy(username, uname + 2, len);
710 username[len] = '\0';
711
712 if ((srppwd = uam_getname(obj, username, (int)ulen)) == NULL) {
713 LOG(log_info, logtype_uams, "srp_login_ext: unknown username");
714 return AFPERR_NOTAUTH;
715 }
716
717 if (uam_checkuser(obj, srppwd) < 0) {
718 LOG(log_info, logtype_uams, "srp_login_ext: user not allowed: %s", username);
719 return AFPERR_NOTAUTH;
720 }
721
722 LOG(log_info, logtype_uams, "srp_login_ext: login: %s", username);
723 strlcpy(session_username, username, sizeof(session_username));
724 return srp_setup(obj, ibuf, ibuflen, rbuf, rbuflen);
725}
726
736static int srp_logincont(void *obj _U_, struct passwd **uam_pwd,
737 char *ibuf, size_t ibuflen,
738 char *rbuf, size_t *rbuflen)
739{
740 int ret = AFPERR_NOTAUTH;
741 gcry_mpi_t A = NULL, u = NULL, S = NULL;
742 gcry_mpi_t vu = NULL, Avu = NULL, tmp = NULL;
743 unsigned char *S_binary = NULL;
744 unsigned char A_buf[SRP_NBYTES];
745 unsigned char K[SRP_SESSION_KEY_LEN];
746 *rbuflen = 0;
747
748 /* Make sure srp_setup actually ran and established session state */
749 if (session_v == NULL) {
750 LOG(log_error, logtype_uams, "srp_logincont: called without completing setup");
751 ret = AFPERR_PARAM;
752 goto fail;
753 }
754
755 unsigned char *d = (unsigned char *)ibuf;
756 const unsigned char *end = d + ibuflen;
757
758 /* Skip 2-byte AFP ID prefix. The framework's afp_logincont() only strips
759 * the command + pad bytes; the AFP ID is still at the start of ibuf. */
760 if (d + 2 > end) {
761 goto fail;
762 }
763
764 d += 2;
765
766 /* Step marker */
767 if (d + 2 > end) {
768 goto fail;
769 }
770
771 uint16_t step = read_uint16_be(&d);
772
773 if (step != SRP_CLIENT_PROOF) {
774 LOG(log_error, logtype_uams, "srp_logincont: unexpected step marker 0x%04X",
775 step);
776 goto fail;
777 }
778
779 /* A_len + A */
780 if (d + 2 > end) {
781 goto fail;
782 }
783
784 uint16_t A_len = read_uint16_be(&d);
785
786 if (A_len > SRP_NBYTES || d + A_len > end) {
787 LOG(log_error, logtype_uams, "srp_logincont: invalid A length %u", A_len);
788 goto fail;
789 }
790
791 const unsigned char *A_raw = d;
792 gcry_mpi_scan(&A, GCRYMPI_FMT_USG, d, A_len, NULL);
793 d += A_len;
794 /* Validate A % N != 0 */
795 tmp = gcry_mpi_new(0);
796 gcry_mpi_mod(tmp, A, session_N);
797
798 if (gcry_mpi_cmp_ui(tmp, 0) == 0) {
799 LOG(log_error, logtype_uams, "srp_logincont: A mod N == 0");
800 goto fail;
801 }
802
803 /* M1_len + M1 */
804 if (d + 2 > end) {
805 goto fail;
806 }
807
808 uint16_t M1_len = read_uint16_be(&d);
809
810 if (M1_len != SRP_SHA1_LEN || d + M1_len > end) {
811 LOG(log_error, logtype_uams, "srp_logincont: invalid M1 length %u", M1_len);
812 goto fail;
813 }
814
815 const unsigned char *M1_received = d;
816 /* Compute A padded to N length for u computation */
817 memset(A_buf, 0, SRP_NBYTES);
818
819 if (A_len <= SRP_NBYTES) {
820 memcpy(A_buf + SRP_NBYTES - A_len, A_raw, A_len);
821 } else {
822 memcpy(A_buf, A_raw + A_len - SRP_NBYTES, SRP_NBYTES);
823 }
824
825 /* u = SHA1(PAD(A) | PAD(B)) */
826 unsigned char u_hash[SRP_SHA1_LEN];
827
828 if (sha1_multi(u_hash,
829 A_buf, (size_t)SRP_NBYTES,
830 session_B_buf, (size_t)SRP_NBYTES,
831 NULL) != 0) {
832 ret = AFPERR_MISC;
833 goto fail;
834 }
835
836 gcry_mpi_scan(&u, GCRYMPI_FMT_USG, u_hash, SRP_SHA1_LEN, NULL);
837
838 if (gcry_mpi_cmp_ui(u, 0) == 0) {
839 LOG(log_error, logtype_uams, "srp_logincont: u == 0, aborting");
840 goto fail;
841 }
842
843 /* Server-side shared secret: S = (A * v^u)^b mod N */
844 vu = gcry_mpi_new(0);
845 Avu = gcry_mpi_new(0);
846 S = gcry_mpi_new(0);
847 gcry_mpi_powm(vu, session_v, u, session_N); /* v^u mod N */
848 gcry_mpi_mulm(Avu, A, vu, session_N); /* A * v^u mod N */
849 gcry_mpi_powm(S, Avu, session_b, session_N); /* (A * v^u)^b mod N */
850 /* K = MGF1-SHA1(strip(S), 40) */
851 S_binary = calloc(1, SRP_NBYTES);
852
853 if (S_binary == NULL) {
854 ret = AFPERR_MISC;
855 goto fail;
856 }
857
858 mpi_to_padded_buf(S_binary, SRP_NBYTES, S);
859 size_t S_stripped_len;
860 const unsigned char *S_stripped = strip_leading_zeros(S_binary, SRP_NBYTES,
861 &S_stripped_len);
862
863 if (mgf1_sha1(S_stripped, S_stripped_len, K, sizeof(K)) != 0) {
864 LOG(log_error, logtype_uams, "srp_logincont: MGF1 failed");
865 ret = AFPERR_MISC;
866 goto fail;
867 }
868
869 /*
870 * Verify M1 = SHA1(H(N)^H(g) | H(username) | salt | strip(A) | strip(B) | K)
871 */
872 /* H(N) — hash of N with leading zeros stripped */
873 size_t N_stripped_len;
874 const unsigned char *N_stripped = strip_leading_zeros(srp_N_bytes, SRP_NBYTES,
875 &N_stripped_len);
876 unsigned char H_N[SRP_SHA1_LEN];
877
878 if (sha1_multi(H_N, N_stripped, N_stripped_len, NULL) != 0) {
879 ret = AFPERR_MISC;
880 goto fail;
881 }
882
883 /* H(g) — hash of g as minimal bytes */
884 unsigned char H_g[SRP_SHA1_LEN];
885
886 if (sha1_multi(H_g, &srp_g_byte, (size_t)1, NULL) != 0) {
887 ret = AFPERR_MISC;
888 goto fail;
889 }
890
891 /* H(N) XOR H(g) */
892 unsigned char xor_ng[SRP_SHA1_LEN];
893
894 for (int i = 0; i < SRP_SHA1_LEN; i++) {
895 xor_ng[i] = H_N[i] ^ H_g[i];
896 }
897
898 /* H(username) */
899 unsigned char H_user[SRP_SHA1_LEN];
900 size_t uname_len = strnlen(session_username, UAM_USERNAMELEN + 1);
901
902 if (uname_len > UAM_USERNAMELEN) {
903 ret = AFPERR_MISC;
904 goto fail;
905 }
906
907 if (sha1_multi(H_user, (const unsigned char *)session_username, uname_len,
908 NULL) != 0) {
909 ret = AFPERR_MISC;
910 goto fail;
911 }
912
913 /*
914 * strip(A) and strip(B) for M1.
915 *
916 * Latent ambiguity: it is not fully verified whether real Apple peers
917 * (Time Capsule, macOS Tahoe client) feed strip(A)/strip(B) or
918 * PAD(A)/PAD(B) into M1. The two are byte-for-byte identical whenever
919 * the high byte of A and B is non-zero, which is ~99.6% of random
920 * values, so every interop test we have run so far cannot distinguish
921 * the two conventions. We chose strip() because it matches Tom Wu's
922 * reference SRP-6a derivation, but if intermittent M1 mismatches are ever
923 * observed at roughly a 1-in-256 rate, A or B with a leading zero byte
924 * is the prime suspect and the fix is likely to switch this to PAD()
925 * (or to regenerate b until B has no leading zero,
926 * sidestepping the question entirely).
927 */
928 size_t A_stripped_len, B_stripped_len;
929 const unsigned char *A_stripped = strip_leading_zeros(A_buf, SRP_NBYTES,
930 &A_stripped_len);
931 const unsigned char *B_stripped = strip_leading_zeros(session_B_buf, SRP_NBYTES,
932 &B_stripped_len);
933 unsigned char M1_expected[SRP_SHA1_LEN];
934
935 if (sha1_multi(M1_expected,
936 xor_ng, (size_t)SRP_SHA1_LEN,
937 H_user, (size_t)SRP_SHA1_LEN,
938 session_salt, (size_t)SRP_SALT_LEN,
939 A_stripped, A_stripped_len,
940 B_stripped, B_stripped_len,
941 K, (size_t)SRP_SESSION_KEY_LEN,
942 NULL) != 0) {
943 ret = AFPERR_MISC;
944 goto fail;
945 }
946
947 if (atalk_ct_memcmp(M1_received, M1_expected, SRP_SHA1_LEN)) {
948 LOG(log_info, logtype_uams, "srp_logincont: M1 verification failed for %s",
950 ret = SRP_AUTH_FAILURE;
951 goto fail;
952 }
953
954 /* Compute M2 = SHA1(strip(A) | M1 | K) */
955 unsigned char M2[SRP_SHA1_LEN];
956
957 if (sha1_multi(M2,
958 A_stripped, A_stripped_len,
959 M1_received, (size_t)SRP_SHA1_LEN,
960 K, (size_t)SRP_SESSION_KEY_LEN,
961 NULL) != 0) {
962 ret = AFPERR_MISC;
963 goto fail;
964 }
965
966 /* Build response: step_marker(2) | M2_len(2) | M2(20) = 24 bytes */
967 unsigned char *p = (unsigned char *)rbuf;
970 memcpy(p, M2, SRP_SHA1_LEN);
971 p += SRP_SHA1_LEN;
972 *rbuflen = p - (unsigned char *)rbuf;
973 /* Authentication successful */
974 *uam_pwd = srppwd;
975 ret = AFP_OK;
976fail:
977 explicit_bzero(K, sizeof(K));
978 free(S_binary);
979 gcry_mpi_release(A);
980 gcry_mpi_release(u);
981 gcry_mpi_release(S);
982 gcry_mpi_release(vu);
983 gcry_mpi_release(Avu);
984 gcry_mpi_release(tmp);
986 return ret;
987}
988
989/* -------------------- UAM module interface -------------------- */
990
991static int uam_setup(void *obj _U_, const char *path)
992{
993 /* Libgcrypt must be initialized before the SRP login handlers use it. */
994 if (!gcry_check_version(UAM_NEED_LIBGCRYPT_VERSION)) {
996 "SRP: unable to initialize libgcrypt");
997 return -1;
998 }
999
1002 return -1;
1003 }
1004
1005 return 0;
1006}
1007
1008static void uam_cleanup(void)
1009{
1011}
1012
#define AFPERR_PARAM
Definition afp.h:73
#define AFPERR_NOTAUTH
Definition afp.h:77
#define AFP_OK
Definition afp.h:51
#define AFPERR_AUTHCONT
Definition afp.h:55
#define AFPERR_MISC
Definition afp.h:68
int uam_checkuser(void *private, const struct passwd *pwd)
Definition afpd/uam.c:316
int uam_afpserver_option(void *private, const int what, void *option, size_t *len)
Definition afpd/uam.c:402
int uam_register(const int type, const char *path, const char *name,...)
Definition afpd/uam.c:114
struct passwd * uam_getname(void *private, char *name, const int len)
helper functions for plugin uams
Definition afpd/uam.c:219
void uam_unregister(const int type, const char *name)
Definition afpd/uam.c:194
static char buf[MAXPATHLEN+1]
Definition afppasswd.c:85
void explicit_bzero(void *s, size_t n)
Definition explicit_bzero.c:32
size_t strlcpy(char *, const char *, size_t)
Definition strlcpy.c:36
size_t strnlen(const char *s, size_t n)
Definition misc.c:19
int seed(struct interface *iface, char **av)
Definition config.c:522
int atalk_ct_memcmp(const void *, const void *, size_t)
Constant-time memory equality check.
Definition constant_time.c:27
#define data
Definition hash.c:52
#define LOG(log_level, type,...)
Definition logger.h:148
#define UAM_MODULE_EXPORT
Definition logger.h:14
@ logtype_uams
Definition logger.h:41
@ log_error
Definition logger.h:20
@ log_info
Definition logger.h:23
#define S_ISDIR(s)
Definition megatron.h:96
#define SRP_USERNAME_MAX_LEN
Definition srp.h:14
#define SRP_NBYTES
Definition srp.h:11
#define SRP_GROUP_INDEX
Definition srp.h:13
#define SRP_SALT_LEN
Definition srp.h:10
#define SRP_SHA1_LEN
Definition srp.h:12
#define SRP_DISABLED_CHAR
Definition srp.h:15
#define SRP_FORMAT_LEN
Definition srp.h:23
Definition include/atalk/directory.h:140
Definition uam.h:61
#define fstat
Definition test_afppasswd.c:44
#define UAM_SERVER_LOGIN_EXT
Definition uam.h:30
#define UAM_USERNAMELEN
Definition uam.h:57
#define UAM_NEED_LIBGCRYPT_VERSION
Definition uam.h:24
#define UAM_OPTION_PASSWDOPT
Definition uam.h:35
#define UAM_OPTION_USERNAME
Definition uam.h:33
#define UAM_MODULE_VERSION
Definition uam.h:21
#define UAM_SERVER_LOGIN
Definition uam.h:27
#define UAM_MODULE_SERVER
Definition uam.h:17
#define UAM_PASSWD_SRP_VERIFIER_PATH
Definition uam.h:53
gcry_mpi_t K
Definition uams_dhx_pam.c:46
static gcry_mpi_t session_g
Definition uams_srp.c:62
static struct passwd * srppwd
Definition uams_srp.c:69
static uint16_t read_uint16_be(unsigned char **p)
Read a 2-byte big-endian unsigned integer and advance the pointer.
Definition uams_srp.c:211
static void write_uint16_be(unsigned char **p, uint16_t val)
Write a 2-byte big-endian unsigned integer and advance the pointer.
Definition uams_srp.c:198
#define unhex(x)
Definition uams_srp.c:220
srp_verifier_status
Definition uams_srp.c:222
@ SRP_VERIFIER_NOT_ENROLLED
Definition uams_srp.c:225
@ SRP_VERIFIER_INVALID
Definition uams_srp.c:228
@ SRP_VERIFIER_DISABLED
Definition uams_srp.c:226
@ SRP_VERIFIER_OK
Definition uams_srp.c:223
@ SRP_VERIFIER_MISSING
Definition uams_srp.c:224
@ SRP_VERIFIER_UNSAFE
Definition uams_srp.c:227
static int mgf1_sha1(const unsigned char *seed, size_t seed_len, unsigned char *out, size_t out_len)
MGF1 mask generation function (PKCS#1 v2.1) with SHA-1.
Definition uams_srp.c:97
static unsigned char session_B_buf[SRP_NBYTES]
Definition uams_srp.c:67
#define SRP_SERVER_PROOF
Definition uams_srp.c:52
static enum srp_verifier_status srp_lookup_verifier(const char *path, const char *username, uid_t uid, unsigned char *salt_out, gcry_mpi_t *v_out)
Look up a user's salt and verifier from their SRP verifier file.
Definition uams_srp.c:248
static unsigned char session_salt[SRP_SALT_LEN]
Definition uams_srp.c:66
#define SRP_INIT_MARKER
Definition uams_srp.c:50
#define SRP_CLIENT_PROOF
Definition uams_srp.c:51
static gcry_mpi_t session_N
Definition uams_srp.c:61
#define SRP_SESSION_KEY_LEN
Definition uams_srp.c:54
UAM_MODULE_EXPORT struct uam_export uams_srp
Definition uams_srp.c:1013
static gcry_mpi_t session_v
Definition uams_srp.c:63
static void mpi_to_padded_buf(unsigned char *buf, size_t nbytes, gcry_mpi_t m)
Write an MPI to a buffer as a big-endian integer, zero-padded.
Definition uams_srp.c:180
static int sha1_multi(unsigned char *out,...)
Compute SHA-1 incrementally from multiple buffers.
Definition uams_srp.c:144
static void srp_session_free(void)
Release all per-session SRP state and zero sensitive buffers.
Definition uams_srp.c:436
#define SRP_AUTH_FAILURE
Definition uams_srp.c:57
static gcry_mpi_t session_b
Definition uams_srp.c:64
static int srp_login(void *obj, struct passwd **uam_pwd, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
FPLogin handler for SRP UAM.
Definition uams_srp.c:628
static int uam_setup(void *obj, const char *path)
Definition uams_srp.c:991
static const unsigned char * strip_leading_zeros(const unsigned char *buf, size_t len, size_t *out_len)
Strip leading zero bytes from a big-endian integer buffer.
Definition uams_srp.c:77
static char session_username[UAM_USERNAMELEN+1]
Definition uams_srp.c:68
static int srp_login_ext(void *obj, char *uname, struct passwd **uam_pwd, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
FPLoginExt handler for SRP UAM.
Definition uams_srp.c:682
static void uam_cleanup(void)
Definition uams_srp.c:1008
static int srp_logincont(void *obj, struct passwd **uam_pwd, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
SRP Round 2: FPLoginCont handler.
Definition uams_srp.c:736
static gcry_mpi_t session_B
Definition uams_srp.c:65
static int srp_setup(void *obj, char *ibuf, size_t ibuflen, char *rbuf, size_t *rbuflen)
SRP Round 1 setup.
Definition uams_srp.c:463
#define NULL
Definition utf8util.c:47