/* // Copyright (c) 2018 Intel Corporation // // Licensed 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * This module is intended to save password of special group user * */ #define MAX_SPEC_GRP_PASS_LENGTH 20 #define MAX_SPEC_GRP_USER_LENGTH 16 #define MAX_KEY_SIZE 8 #define DEFAULT_SPEC_PASS_FILE "/etc/ipmi_pass" #define META_PASSWD_SIG "=OPENBMC=" /* * Meta data struct for storing the encrypted password file * Note: Followed by this structure, the real data of hash, iv, encrypted data * with pad and mac are stored. * Decrypted data will hold user name & password for every new line with format * like :\n */ typedef struct metapassstruct { char signature[10]; unsigned char reserved[2]; size_t hashsize; size_t ivsize; size_t datasize; size_t padsize; size_t macsize; } metapassstruct; /** * @brief to acquire lock for atomic operation * Internally uses lckpwdf to acquire the lock. Tries to acquire the lock * using lckpwdf() in interval of 1ms, with maximum of 100 attempts. * * @return PAM_SUCCESS for success / PAM_AUTHTOK_LOCK_BUSY for failure */ int lock_pwdf(void) { int i; int retval; i = 0; while ((retval = lckpwdf()) != 0 && i < 100) { usleep(1000); i++; } if (retval != 0) { return PAM_AUTHTOK_LOCK_BUSY; } return PAM_SUCCESS; } /** * @brief unlock the acquired lock * Internally uses ulckpwdf to release the lock */ void unlock_pwdf(void) { ulckpwdf(); } /** * @brief to get argument value of option * Function to get the value of argument options. * * @param[in] pamh - pam handle * @param[in] option - argument option to which value has to returned * @param[in] argc - argument count * @param[in] argv - array of arguments */ static const char *get_option(const pam_handle_t *pamh, const char *option, int argc, const char **argv) { if (!pamh) { return NULL; } int i; size_t len; len = strlen(option); for (i = 0; i < argc; ++i) { if (strncmp(option, argv[i], len) == 0) { if (argv[i][len] == '=') { return &argv[i][len + 1]; } } } return NULL; } /** * @brief encrypt or decrypt function * Function which will do the encryption or decryption of the data. * * @param[in] pamh - pam handle. * @param[in] isencrypt - encrypt or decrypt option. * @param[in] cipher - EVP_CIPHER to be used * @param[in] key - key which has to be used in EVP_CIPHER api's. * @param[in] keylen - Length of the key. * @param[in] iv - Initialization vector data, used along with key * @param[in] ivlen - Length of IV. * @param[in] inbytes - buffer which has to be encrypted or decrypted. * @param[in] inbyteslen - length of input buffer. * @param[in] outbytes - buffer to store decrypted or encrypted data. * @param[in] outbyteslen - length of output buffer * @param[in/out] mac - checksum to cross verify. Will be verified for decrypt * and returns for encrypt. * @param[in/out] maclen - length of checksum * @return - 0 for success -1 for failures. */ int encrypt_decrypt_data(const pam_handle_t *pamh, int isencrypt, const EVP_CIPHER *cipher, const unsigned char *key, int keylen, const unsigned char *iv, int ivlen, const unsigned char *inbytes, size_t inbyteslen, unsigned char *outbytes, size_t *outbyteslen, unsigned char *mac, unsigned int *maclen) { EVP_CIPHER_CTX *ctx; const EVP_MD *digest; int outEVPlen = 0; int retval = 0; size_t outlen = 0; if (cipher == NULL || key == NULL || iv == NULL || inbytes == NULL || outbytes == NULL || mac == NULL || inbyteslen == 0 || EVP_CIPHER_key_length(cipher) > keylen || EVP_CIPHER_iv_length(cipher) > ivlen) { pam_syslog(pamh, LOG_DEBUG, "Invalid inputs"); return -1; } digest = EVP_sha256(); if (!isencrypt) { unsigned char calmac[EVP_MAX_MD_SIZE]; unsigned int calmaclen = 0; // calculate MAC for the encrypted message. if (NULL == HMAC(digest, key, keylen, inbytes, inbyteslen, calmac, &calmaclen)) { pam_syslog(pamh, LOG_DEBUG, "Failed to verify authentication %d", retval); return -1; } if (!((calmaclen == *maclen) && (memcmp(calmac, mac, calmaclen) == 0))) { pam_syslog(pamh, LOG_DEBUG, "Authenticated message doesn't match %d, %d", calmaclen, *maclen); return -1; } } ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_set_padding(ctx, 1); // Set key & IV retval = EVP_CipherInit_ex(ctx, cipher, NULL, key, iv, isencrypt); if (!retval) { pam_syslog(pamh, LOG_DEBUG, "EVP_CipherInit_ex failed with %d", retval); EVP_CIPHER_CTX_free(ctx); return -1; } if ((retval = EVP_CipherUpdate(ctx, outbytes + outlen, &outEVPlen, inbytes, inbyteslen))) { outlen += outEVPlen; if ((retval = EVP_CipherFinal(ctx, outbytes + outlen, &outEVPlen))) { outlen += outEVPlen; *outbyteslen = outlen; } else { pam_syslog(pamh, LOG_DEBUG, "EVP_CipherFinal returns with %d", retval); EVP_CIPHER_CTX_free(ctx); return -1; } } else { pam_syslog(pamh, LOG_DEBUG, "EVP_CipherUpdate returns with %d", retval); EVP_CIPHER_CTX_free(ctx); return -1; } EVP_CIPHER_CTX_free(ctx); if (isencrypt) { // Create MAC for the encrypted message. if (NULL == HMAC(digest, key, keylen, outbytes, *outbyteslen, mac, maclen)) { pam_syslog(pamh, LOG_DEBUG, "Failed to create authentication %d", retval); return -1; } } return 0; } /** * @brief get temporary file handle * Function to get the temporary file handle, created using mkstemp * * @param[in] pamh - pam handle. * @param[in/out] tempfilename - tempfilename, which will be used in mkstemp. * @return - FILE handle for success. NULL for failure */ FILE *get_temp_file_handle(const pam_handle_t *pamh, char *const tempfilename) { FILE *tempfile = NULL; int fd; int oldmask = umask(077); fd = mkstemp(tempfilename); if (fd == -1) { pam_syslog(pamh, LOG_DEBUG, "Error in creating temp file"); umask(oldmask); return NULL; } pam_syslog(pamh, LOG_DEBUG, "Temporary file name is %s", tempfilename); tempfile = fdopen(fd, "w"); umask(oldmask); return tempfile; } /** * @brief updates special password file * Function to update the special password file. Stores the password against * username in encrypted form along with meta data * * @param[in] pamh - pam handle. * @param[in] keyfilename - file name where key seed is stored. * @param[in] filename - special password file name * @param[in] forwho - name of the user * @param[in] towhat - password that has to stored in encrypted form * @return - PAM_SUCCESS for success or PAM_AUTHTOK_ERR for failure */ int update_pass_special_file(const pam_handle_t *pamh, const char *keyfilename, const char *filename, const char *forwho, const char *towhat) { struct stat st; FILE *pwfile = NULL, *opwfile = NULL, *keyfile = NULL; int err = 0, wroteentry = 0; char tempfilename[1024]; size_t forwholen = strlen(forwho); size_t towhatlen = strlen(towhat); char keybuff[MAX_KEY_SIZE] = { 0 }; size_t keybuffsize = sizeof(keybuff); const EVP_CIPHER *cipher = EVP_aes_128_cbc(); const EVP_MD *digest = EVP_sha256(); char *linebuff = NULL; unsigned char *opwfilebuff = NULL; unsigned char *opwptext = NULL; size_t opwptextlen = 0, opwfilesize = 0; metapassstruct *opwmp = NULL; unsigned char *pwptext = NULL; unsigned char *pwctext = NULL; size_t pwctextlen = 0, pwptextlen = 0; unsigned int maclen = 0; size_t writtensize = 0; unsigned int keylen = 0; metapassstruct pwmp = { META_PASSWD_SIG, { 0, 0 }, .0, 0, 0, 0, 0 }; unsigned char mac[EVP_MAX_MD_SIZE] = { 0 }; unsigned char key[EVP_MAX_KEY_LENGTH]; unsigned char iv[EVP_CIPHER_iv_length(cipher)]; unsigned char hash[EVP_MD_block_size(digest)]; // Following steps are performed in this function. // Step 1: Create a temporary file - always update temporary file, and // then swap it with original one, only if everything succeeded at the // end. Step 2: If file already exists, read the old file and decrypt it // in buffer Step 3: Copy user/password pair from old buffer to new // buffer, and update, if the user already exists with the new password // Step 4: Encrypt the new buffer and write it to the temp file created // at Step 1. // Step 5. rename the temporary file name as special password file. // verify the tempfilename buffer is enough to hold // filename_XXXXXX (+1 for null). if (strlen(filename) > (sizeof(tempfilename) - strlen("__XXXXXX") - 1)) { pam_syslog(pamh, LOG_DEBUG, "Not enough buffer, bailing out"); return PAM_AUTHTOK_ERR; } // Fetch the key from key file name. keyfile = fopen(keyfilename, "r"); if (keyfile == NULL) { pam_syslog(pamh, LOG_DEBUG, "Unable to open key file %s", keyfilename); return PAM_AUTHTOK_ERR; } if (fread(keybuff, 1, keybuffsize, keyfile) != keybuffsize) { pam_syslog(pamh, LOG_DEBUG, "Key file read failed"); fclose(keyfile); return PAM_AUTHTOK_ERR; } fclose(keyfile); // Step 1: Try to create a temporary file, in which all the update will // happen then it will be renamed to the original file. This is done to // have atomic operation. snprintf(tempfilename, sizeof(tempfilename), "%s__XXXXXX", filename); pwfile = get_temp_file_handle(pamh, tempfilename); if (pwfile == NULL) { err = 1; goto done; } // Update temporary file stat by reading the special password file opwfile = fopen(filename, "r"); if (opwfile != NULL) { if (fstat(fileno(opwfile), &st) == -1) { fclose(opwfile); fclose(pwfile); err = 1; goto done; } } else { // Create with this settings if file is not present. memset(&st, 0, sizeof(st)); } // Override the file permission with S_IWUSR | S_IRUSR st.st_mode = S_IWUSR | S_IRUSR; if ((fchown(fileno(pwfile), st.st_uid, st.st_gid) == -1) || (fchmod(fileno(pwfile), st.st_mode) == -1)) { if (opwfile != NULL) { fclose(opwfile); } fclose(pwfile); err = 1; goto done; } opwfilesize = st.st_size; // Step 2: Read existing special password file and decrypt the data. if (opwfilesize) { opwfilebuff = malloc(opwfilesize); if (opwfilebuff == NULL) { fclose(opwfile); fclose(pwfile); err = 1; goto done; } if (fread(opwfilebuff, 1, opwfilesize, opwfile)) { opwmp = (metapassstruct *)opwfilebuff; opwptext = malloc(opwmp->datasize + opwmp->padsize); if (opwptext == NULL) { free(opwfilebuff); fclose(opwfile); fclose(pwfile); err = 1; goto done; } // User & password pairs are mapped as :\n. Add +3 for special chars ':', // '\n' and '\0'. pwptextlen = opwmp->datasize + forwholen + towhatlen + 3 + EVP_CIPHER_block_size(cipher); pwptext = malloc(pwptextlen); if (pwptext == NULL) { free(opwptext); free(opwfilebuff); fclose(opwfile); fclose(pwfile); err = 1; goto done; } // First get the hashed key to decrypt HMAC(digest, keybuff, keybuffsize, opwfilebuff + sizeof(*opwmp), opwmp->hashsize, key, &keylen); unsigned int tmpmacsize = opwmp->macsize; // Skip decryption if there is no data if (opwmp->datasize != 0) { // Do the decryption if (encrypt_decrypt_data( pamh, 0, cipher, key, keylen, opwfilebuff + sizeof(*opwmp) + opwmp->hashsize, opwmp->ivsize, opwfilebuff + sizeof(*opwmp) + opwmp->hashsize + opwmp->ivsize, opwmp->datasize + opwmp->padsize, opwptext, &opwptextlen, opwfilebuff + sizeof(*opwmp) + opwmp->hashsize + opwmp->ivsize + opwmp->datasize + opwmp->padsize, &tmpmacsize) != 0) { pam_syslog(pamh, LOG_DEBUG, "Decryption failed"); free(pwptext); free(opwptext); free(opwfilebuff); fclose(opwfile); fclose(pwfile); err = 1; goto done; } opwmp->macsize = tmpmacsize; } // NULL terminate it, before using it in strtok(). opwptext[opwmp->datasize] = '\0'; linebuff = strtok((char *)opwptext, "\n"); // Step 3: Copy the existing user/password pair // to the new buffer, and update the password if user // already exists. while (linebuff != NULL) { if ((!strncmp(linebuff, forwho, forwholen)) && (linebuff[forwholen] == ':')) { writtensize += snprintf( (char *)pwptext + writtensize, pwptextlen - writtensize, "%s:%s\n", forwho, towhat); wroteentry = 1; } else { writtensize += snprintf( (char *)pwptext + writtensize, pwptextlen - writtensize, "%s\n", linebuff); } linebuff = strtok(NULL, "\n"); } } // Clear the old password related buffers here, as we are done // with it. free(opwfilebuff); free(opwptext); } else { pwptextlen = forwholen + towhatlen + 3 + EVP_CIPHER_block_size(cipher); pwptext = malloc(pwptextlen); if (pwptext == NULL) { if (opwfile != NULL) { fclose(opwfile); } fclose(pwfile); err = 1; goto done; } } if (opwfile != NULL) { fclose(opwfile); } if (!wroteentry) { // Write the new user:password pair at the end. writtensize += snprintf((char *)pwptext + writtensize, pwptextlen - writtensize, "%s:%s\n", forwho, towhat); } pwptextlen = writtensize; // Step 4: Encrypt the data and write to the temporary file if (RAND_bytes(hash, EVP_MD_block_size(digest)) != 1) { pam_syslog(pamh, LOG_DEBUG, "Hash generation failed, bailing out"); free(pwptext); fclose(pwfile); err = 1; goto done; } // Generate hash key, which will be used for encryption. HMAC(digest, keybuff, keybuffsize, hash, EVP_MD_block_size(digest), key, &keylen); // Generate IV values if (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) != 1) { pam_syslog(pamh, LOG_DEBUG, "IV generation failed, bailing out"); free(pwptext); fclose(pwfile); err = 1; goto done; } // Buffer to store encrypted message. pwctext = malloc(pwptextlen + EVP_CIPHER_block_size(cipher)); if (pwctext == NULL) { pam_syslog(pamh, LOG_DEBUG, "Ctext buffer failed, bailing out"); free(pwptext); fclose(pwfile); err = 1; goto done; } // Do the encryption if (encrypt_decrypt_data(pamh, 1, cipher, key, keylen, iv, EVP_CIPHER_iv_length(cipher), pwptext, pwptextlen, pwctext, &pwctextlen, mac, &maclen) != 0) { pam_syslog(pamh, LOG_DEBUG, "Encryption failed"); free(pwctext); free(pwptext); fclose(pwfile); err = 1; goto done; } // Update the meta password structure. pwmp.hashsize = EVP_MD_block_size(digest); pwmp.ivsize = EVP_CIPHER_iv_length(cipher); pwmp.datasize = writtensize; pwmp.padsize = pwctextlen - writtensize; pwmp.macsize = maclen; // Write the meta password structure, followed by hash, iv, encrypted // data & mac. if (fwrite(&pwmp, 1, sizeof(pwmp), pwfile) != sizeof(pwmp)) { pam_syslog(pamh, LOG_DEBUG, "Error in writing meta data"); err = 1; } if (fwrite(hash, 1, pwmp.hashsize, pwfile) != pwmp.hashsize) { pam_syslog(pamh, LOG_DEBUG, "Error in writing hash data"); err = 1; } if (fwrite(iv, 1, pwmp.ivsize, pwfile) != pwmp.ivsize) { pam_syslog(pamh, LOG_DEBUG, "Error in writing IV data"); err = 1; } if (fwrite(pwctext, 1, pwctextlen, pwfile) != pwctextlen) { pam_syslog(pamh, LOG_DEBUG, "Error in encrypted data"); err = 1; } if (fwrite(mac, 1, maclen, pwfile) != maclen) { pam_syslog(pamh, LOG_DEBUG, "Error in writing MAC"); err = 1; } free(pwctext); free(pwptext); if (fflush(pwfile) || fsync(fileno(pwfile))) { pam_syslog( pamh, LOG_DEBUG, "fflush or fsync error writing entries to special file: %s", tempfilename); err = 1; } if (fclose(pwfile)) { pam_syslog(pamh, LOG_DEBUG, "fclose error writing entries to special file: %s", tempfilename); err = 1; } done: if (!err) { // Step 5: Rename the temporary file as special password file. if (!rename(tempfilename, filename)) { pam_syslog(pamh, LOG_DEBUG, "password changed for %s in special file", forwho); } else { err = 1; } } // Clear out the key buff. memset(keybuff, 0, keybuffsize); if (!err) { return PAM_SUCCESS; } else { unlink(tempfilename); return PAM_AUTHTOK_ERR; } } /* Password Management API's */ /** * @brief pam_sm_chauthtok API * Function which will be called for pam_chauthtok() calls. * * @param[in] pamh - pam handle * @param[in] flags - pam calls related flags * @param[in] argc - argument counts / options * @param[in] argv - array of arguments / options * @return - PAM_SUCCESS for success, others for failure */ int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) { int retval = -1; const void *item = NULL; const char *user = NULL; const char *pass_new = NULL, *pass_old = NULL; const char *spec_grp_name = get_option(pamh, "spec_grp_name", argc, argv); const char *spec_pass_file = get_option(pamh, "spec_pass_file", argc, argv); const char *key_file = get_option(pamh, "key_file", argc, argv); if (spec_grp_name == NULL || key_file == NULL) { return PAM_IGNORE; } if (flags & PAM_PRELIM_CHECK) { // send success to verify other stacked modules prelim check. return PAM_SUCCESS; } retval = pam_get_user(pamh, &user, NULL); if (retval != PAM_SUCCESS) { return retval; } // get already read password by the stacked pam module // Note: If there are no previous stacked pam module which read // the new password, then return with AUTHTOK_ERR retval = pam_get_item(pamh, PAM_AUTHTOK, &item); if (retval != PAM_SUCCESS || item == NULL) { return PAM_AUTHTOK_ERR; } pass_new = item; struct group *grp; int spec_grp_usr = 0; // Verify whether the user belongs to special group. grp = pam_modutil_getgrnam(pamh, spec_grp_name); if (grp != NULL) { while (*(grp->gr_mem) != NULL) { if (strcmp(user, *grp->gr_mem) == 0) { spec_grp_usr = 1; break; } (grp->gr_mem)++; } } pam_syslog(pamh, LOG_DEBUG, "User belongs to special grp: %x", spec_grp_usr); if (spec_grp_usr) { // verify the new password is acceptable. size_t pass_len = strlen(pass_new); size_t user_len = strlen(user); if (pass_len > MAX_SPEC_GRP_PASS_LENGTH || user_len > MAX_SPEC_GRP_USER_LENGTH) { pam_syslog(pamh, LOG_ERR, "Password length (%zu) / User name length " "(%zu) is not acceptable for IPMI", pass_len, user_len); pass_new = pass_old = NULL; return PAM_AUTHTOK_ERR; } if (spec_pass_file == NULL) { spec_pass_file = DEFAULT_SPEC_PASS_FILE; pam_syslog( pamh, LOG_ERR, "Using default special password file name :%s", spec_pass_file); } if ((retval = lock_pwdf())) { pam_syslog(pamh, LOG_ERR, "Failed to lock the passwd file"); return retval; } retval = update_pass_special_file( pamh, key_file, spec_pass_file, user, pass_new); unlock_pwdf(); return retval; } return PAM_SUCCESS; } /* end of module definition */