|
12 | 12 | // this is a PAM module implementing authentication (kinda) and session setup
|
13 | 13 | #define PAM_SM_AUTH
|
14 | 14 | #define PAM_SM_SESSION
|
| 15 | +#define PAM_SM_PASSWORD |
15 | 16 |
|
16 | 17 | // std and system includes
|
17 | 18 | #include <fcntl.h>
|
18 | 19 | #include <sys/fsuid.h>
|
19 | 20 | #include <sys/ioctl.h>
|
| 21 | +#include <sys/stat.h> |
| 22 | +#include <dirent.h> |
20 | 23 | #include <unistd.h>
|
21 | 24 | #include <syslog.h>
|
22 | 25 | #include <limits.h>
|
|
41 | 44 | #define EXT2FS_KEY_DESC_PREFIX_SIZE 5
|
42 | 45 | #define SHA512_LENGTH 64
|
43 | 46 | #define PAM_E4CRYPT_KEY_DATA "pam_e4crypt_key_data"
|
| 47 | +#define EXT4_IOC_SET_ENCRYPTION_POLICY _IOR('f', 19, struct ext4_encryption_policy) |
44 | 48 |
|
45 | 49 |
|
46 | 50 | /**
|
@@ -342,6 +346,77 @@ key_get_key_ref(
|
342 | 346 | memset(key_ref2, 0, sizeof(key_ref2));
|
343 | 347 | }
|
344 | 348 |
|
| 349 | +static |
| 350 | +long |
| 351 | +set_policy( |
| 352 | + int flags, |
| 353 | + char* path, |
| 354 | + struct ext4_encryption_key* key |
| 355 | +) { |
| 356 | + struct ext4_encryption_policy policy; |
| 357 | + |
| 358 | + policy.version = 0; |
| 359 | + policy.flags = 0; // TODO: Configure this? (What does it even do?) |
| 360 | + policy.contents_encryption_mode = |
| 361 | + EXT4_ENCRYPTION_MODE_AES_256_XTS; |
| 362 | + policy.filenames_encryption_mode = |
| 363 | + EXT4_ENCRYPTION_MODE_AES_256_CTS; |
| 364 | + |
| 365 | + key_get_key_ref(key, &policy.master_key_descriptor[0]); |
| 366 | + |
| 367 | + char key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE] = {0}; |
| 368 | + for (int i = 0; i < EXT4_KEY_DESCRIPTOR_SIZE; ++i) { |
| 369 | + sprintf(&key_ref_str[i * 2], "%02x", (unsigned char) policy.master_key_descriptor[i]); |
| 370 | + } |
| 371 | + key_ref_str[EXT4_KEY_REF_STR_BUF_SIZE - 1] = 0; |
| 372 | + |
| 373 | + pam_log(LOG_NOTICE, "Setting policy for '%s' to '%s'", path, key_ref_str); |
| 374 | + |
| 375 | + int fd = open(path, O_DIRECTORY); |
| 376 | + if (fd == -1) { |
| 377 | + pam_log(LOG_WARNING, "Couldn't open '%s'", path); |
| 378 | + return -1; |
| 379 | + } |
| 380 | + |
| 381 | + long ret = ioctl(fd, EXT4_IOC_SET_ENCRYPTION_POLICY, &policy); |
| 382 | + close(fd); |
| 383 | + return ret; |
| 384 | +} |
| 385 | + |
| 386 | +static |
| 387 | +int |
| 388 | +rmrf( |
| 389 | + int flags, |
| 390 | + char* path |
| 391 | +) { |
| 392 | + int ret = unlink(path); |
| 393 | + |
| 394 | + if (ret == -1 && errno == EISDIR) { |
| 395 | + struct dirent **namelist; |
| 396 | + int n = scandir(path, &namelist, NULL, alphasort); |
| 397 | + if (n < 0) { |
| 398 | + pam_log(LOG_ERR, "Couldn't scan directory '%s' for removal", path); |
| 399 | + } else { |
| 400 | + char subpath[PATH_MAX] = {0}; |
| 401 | + while (n--) { |
| 402 | + if (strcmp(namelist[n]->d_name, "..") == 0 || strcmp(namelist[n]->d_name, ".") == 0) { |
| 403 | + free(namelist[n]); |
| 404 | + continue; |
| 405 | + } |
| 406 | + |
| 407 | + snprintf(subpath, PATH_MAX, "%s/%s", path, namelist[n]->d_name); |
| 408 | + free(namelist[n]); |
| 409 | + subpath[PATH_MAX - 1] = 0; |
| 410 | + rmrf(flags, subpath); |
| 411 | + } |
| 412 | + free(namelist); |
| 413 | + } |
| 414 | + |
| 415 | + ret = rmdir(path); |
| 416 | + } |
| 417 | + |
| 418 | + return ret; |
| 419 | +} |
345 | 420 |
|
346 | 421 | /**
|
347 | 422 | * Add a key to keyring
|
@@ -706,3 +781,190 @@ pam_sm_close_session(
|
706 | 781 | }
|
707 | 782 |
|
708 | 783 |
|
| 784 | +/** |
| 785 | + * Update the wrapped salt/passphrase for this |
| 786 | + */ |
| 787 | +PAM_EXTERN |
| 788 | +int |
| 789 | +pam_sm_chauthtok( |
| 790 | + pam_handle_t * pamh, |
| 791 | + int flags, |
| 792 | + int argc, |
| 793 | + const char **argv |
| 794 | +) { |
| 795 | + char* auth_token = NULL; |
| 796 | + char* old_auth_token = NULL; |
| 797 | + |
| 798 | + int retval = pam_get_item(pamh, PAM_AUTHTOK, (const void**) &auth_token); |
| 799 | + if ((retval != PAM_SUCCESS) || !auth_token) { |
| 800 | + pam_log(LOG_ERR, "Failed to get auth token!"); |
| 801 | + return PAM_AUTHTOK_ERR; |
| 802 | + } |
| 803 | + |
| 804 | + retval = pam_get_item(pamh, PAM_OLDAUTHTOK, (const void**) &old_auth_token); |
| 805 | + if ((retval != PAM_SUCCESS) || !auth_token) { |
| 806 | + pam_log(LOG_ERR, "Failed to get old auth token!"); |
| 807 | + return PAM_AUTHTOK_RECOVERY_ERR; |
| 808 | + } |
| 809 | + |
| 810 | + const char *username; |
| 811 | + retval = pam_get_item(pamh, PAM_USER, (void*) &username); |
| 812 | + if (retval != PAM_SUCCESS) |
| 813 | + return retval; |
| 814 | + struct passwd const* pw = pam_modutil_getpwnam(pamh, username); |
| 815 | + if (!pw) { |
| 816 | + pam_log(LOG_ERR, "error looking up user"); |
| 817 | + return PAM_USER_UNKNOWN; |
| 818 | + } |
| 819 | + char saltpath[PATH_MAX]; |
| 820 | + char wrappath[PATH_MAX] = {0}; |
| 821 | + snprintf(saltpath, PATH_MAX, "%s/%s", pw->pw_dir, ".ext4_encryption_salt"); |
| 822 | + |
| 823 | + for (int i = 0; i < argc; ++i) { |
| 824 | + char const* option; |
| 825 | + |
| 826 | + if (option = get_modarg_value("saltpath", argv[i])) { |
| 827 | + // If a custom saltpath has been passed, use it instead |
| 828 | + snprintf(saltpath, PATH_MAX, "%s/%s", option, pw->pw_name); |
| 829 | + continue; |
| 830 | + } |
| 831 | + |
| 832 | + if (option = get_modarg_value("wrappath", argv[i])) { |
| 833 | + // If a custom wrappath has been passed, we'll use that |
| 834 | + snprintf(wrappath, PATH_MAX, "%s/%s", option, pw->pw_name); |
| 835 | + continue; |
| 836 | + } |
| 837 | + |
| 838 | + pam_log(LOG_WARNING, "Unknown option for authenticate: %s", argv[i]); |
| 839 | + } |
| 840 | + |
| 841 | + if (wrappath[0] == 0) { |
| 842 | + pam_log(LOG_WARNING, "No wrappath supplied!"); |
| 843 | + return PAM_SUCCESS; |
| 844 | + } |
| 845 | + |
| 846 | + struct ext4_encryption_key oldkey = generate_key(flags, saltpath, old_auth_token); |
| 847 | + add_key_to_keyring(flags, &oldkey, KEY_SPEC_SESSION_KEYRING, pw); |
| 848 | + |
| 849 | + int ret = PAM_SUCCESS; |
| 850 | + |
| 851 | + // Backup the old creds |
| 852 | + char wrapbakpath[PATH_MAX]; |
| 853 | + snprintf(wrapbakpath, PATH_MAX, "%s.bak", wrappath); |
| 854 | + rmrf(flags, wrapbakpath); |
| 855 | + rename(wrappath, wrapbakpath); |
| 856 | + |
| 857 | + char saltbakpath[PATH_MAX]; |
| 858 | + snprintf(saltbakpath, PATH_MAX, "%s.bak", saltpath); |
| 859 | + rmrf(flags, saltbakpath); |
| 860 | + rename(saltpath, saltbakpath); |
| 861 | + |
| 862 | + // Generate file paths |
| 863 | + char oldwrapauthpath[PATH_MAX]; |
| 864 | + char newwrapauthpath[PATH_MAX]; |
| 865 | + char oldwrapsaltpath[PATH_MAX]; |
| 866 | + char newwrapsaltpath[PATH_MAX]; |
| 867 | + |
| 868 | + snprintf(oldwrapauthpath, PATH_MAX, "%s/auth", wrapbakpath); |
| 869 | + snprintf(newwrapauthpath, PATH_MAX, "%s/auth", wrappath); |
| 870 | + snprintf(oldwrapsaltpath, PATH_MAX, "%s/salt", wrapbakpath); |
| 871 | + snprintf(newwrapsaltpath, PATH_MAX, "%s/salt", wrappath); |
| 872 | + |
| 873 | + // Some buffers |
| 874 | + char saltbuf[EXT4_MAX_SALT_SIZE] = {0}; |
| 875 | + char passbuf[EXT4_MAX_PASSPHRASE_SIZE] = {0}; |
| 876 | + |
| 877 | + // Get file descriptors |
| 878 | + int randfd = open("/dev/urandom", O_RDONLY); |
| 879 | + int saltfd = creat(saltpath, 0740); |
| 880 | + |
| 881 | + if (randfd == -1) { |
| 882 | + pam_log(LOG_ERR, "Couldn't open /dev/urandom. You're screwed kid!"); |
| 883 | + goto error; |
| 884 | + } |
| 885 | + |
| 886 | + if (saltfd == -1) { |
| 887 | + pam_log(LOG_ERR, "Couldn't open '%s'", saltpath); |
| 888 | + goto error; |
| 889 | + } |
| 890 | + |
| 891 | + // Generate new salt |
| 892 | + read(randfd, saltbuf, EXT4_MAX_SALT_SIZE); |
| 893 | + write(saltfd, saltbuf, EXT4_MAX_SALT_SIZE); |
| 894 | + |
| 895 | + // Set the new encrypted folder's policy |
| 896 | + mkdir(wrappath, 0740); |
| 897 | + struct ext4_encryption_key newkey = generate_key(flags, saltpath, auth_token); |
| 898 | + add_key_to_keyring(flags, &newkey, KEY_SPEC_SESSION_KEYRING, pw); |
| 899 | + |
| 900 | + retval = set_policy(flags, wrappath, &newkey); |
| 901 | + |
| 902 | + if (retval == -1) { |
| 903 | + pam_log(LOG_ERR, "Couldn't set policy for '%s', %d", wrappath, errno); |
| 904 | + goto error; |
| 905 | + } |
| 906 | + |
| 907 | + int oldwrapauthfd = open(oldwrapauthpath, O_RDONLY); |
| 908 | + int newwrapauthfd = creat(newwrapauthpath, 0740); |
| 909 | + int oldwrapsaltfd = open(oldwrapsaltpath, O_RDONLY); |
| 910 | + int newwrapsaltfd = creat(newwrapsaltpath, 0740); |
| 911 | + |
| 912 | + if (oldwrapauthfd == -1) { |
| 913 | + pam_log(LOG_ERR, "Couldn't open '%s'", oldwrapauthpath); |
| 914 | + goto error; |
| 915 | + } |
| 916 | + |
| 917 | + if (newwrapauthfd == -1) { |
| 918 | + pam_log(LOG_ERR, "Couldn't open '%s'", newwrapauthpath); |
| 919 | + goto error; |
| 920 | + } |
| 921 | + |
| 922 | + if (oldwrapsaltfd == -1) { |
| 923 | + pam_log(LOG_ERR, "Couldn't open '%s'", oldwrapsaltpath); |
| 924 | + goto error; |
| 925 | + } |
| 926 | + |
| 927 | + if (newwrapsaltfd == -1) { |
| 928 | + pam_log(LOG_ERR, "Couldn't open '%s'", newwrapsaltpath); |
| 929 | + goto error; |
| 930 | + } |
| 931 | + |
| 932 | + // Copy wrapped auth/salt |
| 933 | + retval = read(oldwrapauthfd, passbuf, EXT4_MAX_PASSPHRASE_SIZE); |
| 934 | + write(newwrapauthfd, passbuf, retval); |
| 935 | + retval = read(oldwrapsaltfd, saltbuf, EXT4_MAX_SALT_SIZE); |
| 936 | + write(newwrapsaltfd, saltbuf, retval); |
| 937 | + |
| 938 | + goto cleanup; |
| 939 | + |
| 940 | +error: |
| 941 | + ret = PAM_AUTHTOK_ERR; |
| 942 | +cleanup: |
| 943 | + close(randfd); |
| 944 | + close(saltfd); |
| 945 | + close(oldwrapauthfd); |
| 946 | + close(newwrapauthfd); |
| 947 | + close(oldwrapsaltfd); |
| 948 | + close(newwrapsaltfd); |
| 949 | + |
| 950 | + memset(saltbuf, 0, EXT4_MAX_SALT_SIZE); |
| 951 | + memset(passbuf, 0, EXT4_MAX_PASSPHRASE_SIZE); |
| 952 | + memset(&oldkey, 0, sizeof(oldkey)); |
| 953 | + memset(&newkey, 0, sizeof(newkey)); |
| 954 | + |
| 955 | + if (ret == PAM_SUCCESS) { |
| 956 | + pam_log(LOG_NOTICE, "Removing backups '%s', '%s'", saltbakpath, wrapbakpath); |
| 957 | + rmrf(flags, wrapbakpath); |
| 958 | + rmrf(flags, saltbakpath); |
| 959 | + } else { |
| 960 | + pam_log(LOG_WARNING, "Restoring backups"); |
| 961 | + rmrf(flags, wrappath); |
| 962 | + rmrf(flags, saltpath); |
| 963 | + rename(wrapbakpath, wrappath); |
| 964 | + rename(saltbakpath, saltpath); |
| 965 | + } |
| 966 | + |
| 967 | + // TODO: Remove keys from keyring afterwards |
| 968 | + |
| 969 | + return ret; |
| 970 | +} |
0 commit comments