/** BEGIN COPYRIGHT BLOCK * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. * Copyright (C) 2015 Red Hat, Inc. * All rights reserved. * * License: GPL (version 3 or any later version). * See LICENSE for details. * END COPYRIGHT BLOCK **/ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include /* #include #include */ #include #include #include #include #include #include #include #include #define NEED_TOK_PBE /* see slap.h - defines tokPBE and ptokPBE */ #include "rever.h" #include #include "slapi-plugin.h" #include #include struct pk11MechItem { CK_MECHANISM_TYPE type; const char *mechName; }; static const struct pk11MechItem DESmech = { CKM_DES_CBC, "DES CBC encryption" }; static const struct pk11MechItem AESmech = { CKM_AES_CBC, "AES CBC encryption" }; static Slapi_Mutex *pbe_lock = NULL; struct pk11ContextStore { PK11SlotInfo *slot; const struct pk11MechItem *mech; PK11SymKey *key; SECItem *params; int length; unsigned char *crypt; char *algid_base64; }; /* * der_algid converting functions: * * SECStatus ATOB_ConvertAsciiToItem(SECItem *binary_item, const char *ascii); * char * BTOA_ConvertItemToAscii(SECItem *binary_item); * */ static int encode_path(char *inPlain, char **outCipher, char *path, int mech); static int decode_path(char *inCipher, char **outPlain, char *path, int mech, char *algid); static SVRCOREError genKey(struct pk11ContextStore **out, const char *token, char *path, int mech, PRArenaPool *arena, char *algid); static SVRCOREError cryptPassword(struct pk11ContextStore *store, char * clear, unsigned char **out); static SVRCOREError decryptPassword(struct pk11ContextStore *store, unsigned char *cipher, char **out, int len); static void freePBE(struct pk11ContextStore *store); void init_pbe_plugin() { if(!pbe_lock){ pbe_lock = slapi_new_mutex(); } } int encode(char *inPlain, char **outCipher, int mech) { return encode_path(inPlain, outCipher, NULL, mech); } static int encode_path(char *inPlain, char **outCipher, char *path, int mech) { struct pk11ContextStore *context = NULL; PRArenaPool *arena = NULL; unsigned char *cipher = NULL; char *tmp = NULL; char *base = NULL; int len = 0; int err; *outCipher = NULL; err = 1; if ( genKey(&context, tokPBE, path, mech, arena, NULL) == SVRCORE_Success ){ /* Try an encryption */ if ( cryptPassword(context, inPlain, &cipher) == SVRCORE_Success ){ base = BTOA_DataToAscii(cipher, context->length); if ( base != NULL ){ const char *scheme; if (mech == AES_MECH){ scheme = AES_REVER_SCHEME_NAME; len = 3 + strlen(scheme)+ strlen(context->algid_base64) + strlen(base) + 1; if( (tmp = slapi_ch_malloc( len )) ){ /* * {AES-} */ sprintf( tmp, "%c%s-%s%c%s", PWD_HASH_PREFIX_START, scheme, context->algid_base64,PWD_HASH_PREFIX_END, base); } } else { /* Old school DES */ scheme = DES_REVER_SCHEME_NAME; if((tmp = slapi_ch_malloc( 3 + strlen(scheme) + strlen(base)))){ sprintf( tmp, "%c%s%c%s", PWD_HASH_PREFIX_START, scheme, PWD_HASH_PREFIX_END, base); } } if ( tmp != NULL ){ *outCipher = tmp; tmp = NULL; err = 0; } PORT_Free(base); } } } freePBE(context); return(err); } int decode(char *inCipher, char **outPlain, int mech, char *algid) { return decode_path(inCipher, outPlain, NULL, mech, algid); } static int decode_path(char *inCipher, char **outPlain, char *path, int mech, char *algid) { struct pk11ContextStore *context = NULL; PRArenaPool *arena = NULL; unsigned char *base = NULL; char *plain = NULL; int err; int len = 0; *outPlain = NULL; err = 1; if ( genKey(&context, tokPBE, path, mech, arena, algid) == SVRCORE_Success ){ /* it seems that there is memory leak in that function: bug 400170 */ base = ATOB_AsciiToData(inCipher, (unsigned int*)&len); if ( base != NULL ){ if ( decryptPassword(context, base, &plain, len) == SVRCORE_Success ){ *outPlain = plain; err = 0; } } } slapi_ch_free_string(&algid); PORT_Free(base); PORT_FreeArena(arena, PR_TRUE); freePBE(context); return(err); } static void freePBE(struct pk11ContextStore *store) { if (store){ if (store->slot) slapd_pk11_freeSlot(store->slot); if (store->key) slapd_pk11_freeSymKey(store->key); if (store->params) SECITEM_FreeItem(store->params, PR_TRUE); slapi_ch_free((void **)&store->crypt); slapi_ch_free_string(&store->algid_base64); slapi_ch_free((void **)&store); } } static SVRCOREError genKey(struct pk11ContextStore **out, const char *token, char *path, int mech, PRArenaPool *arena, char *alg) { SVRCOREError err = SVRCORE_Success; struct pk11ContextStore *store = NULL; SECItem *pwitem = NULL; SECItem *result = NULL; SECItem *salt = NULL; SECItem der_algid; SECAlgorithmID *algid = NULL; SECOidTag algoid; CK_MECHANISM pbeMech; CK_MECHANISM cryptoMech; SECAlgorithmID my_algid; char *configdir = NULL; char *der_ascii = NULL; char *iv = NULL; int free_it = 0; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); store = (struct pk11ContextStore*)slapi_ch_calloc(1, sizeof(*store)); if (store == NULL){ err = SVRCORE_NoMemory_Error; goto done; } *out = store; /* Use the tokenName to find a PKCS11 slot */ store->slot = slapd_pk11_findSlotByName((char *)token); if (store->slot == NULL){ err = SVRCORE_NoSuchToken_Error; goto done; } /* Generate a key and parameters to do the encryption */ if(mech == AES_MECH){ store->mech = &AESmech; algoid = SEC_OID_AES_256_CBC; } else { store->mech = &DESmech; algoid = SEC_OID_PKCS5_PBE_WITH_MD2_AND_DES_CBC; } /* Generate a unique id, used as salt for the key generation */ if ( path == NULL ){ configdir = config_get_configdir(); if ( configdir == NULL ){ err = SVRCORE_System_Error; goto done; } } else { configdir = slapi_ch_strdup(path); } if ( slapi_uniqueIDGenerateFromNameString (&iv, NULL, configdir, strlen(configdir)) != UID_SUCCESS ){ err = SVRCORE_System_Error; goto done; } pwitem = (SECItem *) PORT_Alloc(sizeof(SECItem)); if (pwitem == NULL){ err = SVRCORE_NoMemory_Error; goto done; } pwitem->type = siBuffer; pwitem->data = (unsigned char *)PORT_Alloc(strlen(iv)+1); if (pwitem->data == NULL){ err = SVRCORE_NoMemory_Error; goto done; } strcpy((char*)pwitem->data, iv); pwitem->len = strlen(iv) + 1; salt = (SECItem *) PORT_Alloc(sizeof(SECItem)); if (salt == NULL){ err = SVRCORE_NoMemory_Error; goto done; } salt->type = siBuffer; salt->data = (unsigned char *)PORT_Alloc(strlen(iv)+1); if ( salt->data == NULL ){ err = SVRCORE_NoMemory_Error; goto done; } strcpy((char*)salt->data, iv); salt->len = strlen(iv) + 1; PORT_Memset(&der_algid, 0, sizeof(der_algid)); if(!alg){ /* * This is DES, or we are encoding AES - the process is the same. */ algid = slapd_pk11_createPBEAlgorithmID(algoid, 2, salt); free_it = 1; /* we need to free this algid */ /* * The following is only need for AES - we need to store * algid for future decodings(unlike with DES). So convert * algid to its DER encoding. Then convert the DER to ascii, * and finally convert the DER ascii to base64 so we can store * it in the cipher prefix. */ SEC_ASN1EncodeItem(arena, &der_algid, algid, SEC_ASN1_GET(SECOID_AlgorithmIDTemplate)); der_ascii = BTOA_ConvertItemToAscii(&der_algid); store->algid_base64 = PL_Base64Encode(der_ascii,strlen(der_ascii), NULL); slapi_ch_free_string(&der_ascii); } else { /* * We are decoding AES - use the supplied algid */ PORT_Memset(&my_algid, 0, sizeof(my_algid)); /* Decode the base64 der encoding */ der_ascii = PL_Base64Decode(alg, strlen(alg), NULL); /* convert the der ascii to the SEC item */ ATOB_ConvertAsciiToItem(&der_algid, der_ascii); SEC_ASN1DecodeItem(arena, &my_algid, SEC_ASN1_GET(SECOID_AlgorithmIDTemplate), &der_algid); SECITEM_FreeItem(&der_algid, PR_FALSE); algid = &my_algid; slapi_ch_free_string(&der_ascii); } slapi_lock_mutex(pbe_lock); store->key = slapd_pk11_pbeKeyGen(store->slot, algid, pwitem, 0, 0); if (store->key == 0){ slapi_unlock_mutex(pbe_lock); err = SVRCORE_System_Error; goto done; } slapi_unlock_mutex(pbe_lock); if(mech == AES_MECH) { cryptoMech.mechanism = PK11_GetPBECryptoMechanism(algid, &store->params, pwitem); if (cryptoMech.mechanism == CKM_INVALID_MECHANISM) { err = SVRCORE_System_Error; goto done; } } else { /* DES */ pbeMech.mechanism = slapd_pk11_algtagToMechanism(algoid); result = slapd_pk11_paramFromAlgid(algid); if(result){ pbeMech.pParameter = result->data; pbeMech.ulParameterLen = result->len; } if(slapd_pk11_mapPBEMechanismToCryptoMechanism(&pbeMech, &cryptoMech, pwitem, PR_FALSE) != CKR_OK){ err = SVRCORE_System_Error; goto done; } store->params = (SECItem *) PORT_Alloc(sizeof(SECItem)); if (store->params == NULL){ err = SVRCORE_System_Error; goto done; } store->params->type = store->mech->type; store->params->data = (unsigned char *)PORT_Alloc(cryptoMech.ulParameterLen); if (store->params->data == NULL){ err = SVRCORE_System_Error; goto done; } memcpy(store->params->data, (unsigned char *)cryptoMech.pParameter, cryptoMech.ulParameterLen); store->params->len = cryptoMech.ulParameterLen; PORT_Free(cryptoMech.pParameter); } done: SECITEM_FreeItem(result, PR_TRUE); SECITEM_FreeItem(pwitem, PR_TRUE); SECITEM_FreeItem(salt, PR_TRUE); if(free_it){ secoid_destroyAlgorithmID(algid, PR_TRUE); } slapi_ch_free_string(&configdir); slapi_ch_free_string(&iv); if (arena) { PORT_FreeArena(arena, PR_TRUE); } return (err); } static SVRCOREError decryptPassword(struct pk11ContextStore *store, unsigned char *cipher, char **out, int len) { SVRCOREError err = SVRCORE_Success; unsigned char *plain = NULL; unsigned char *cipher_with_padding = NULL; SECStatus rv; PK11Context *ctx = NULL; int outLen = 0; int blocksize = 0; blocksize = slapd_pk11_getBlockSize(store->mech->type, 0); store->length = len; /* * store->length is the max. length of the returned clear text - * must be >= length of crypted bytes - also must be a multiple * of blocksize */ if (blocksize != 0){ store->length += blocksize - (store->length % blocksize); } /* plain will hold the returned clear text */ plain = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char), store->length+1); if (!plain){ err = SVRCORE_NoMemory_Error; goto done; } /* * create a buffer holding the original cipher bytes, padded with * zeros to a multiple of blocksize - do not need +1 since buffer is not * a string */ cipher_with_padding = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char), store->length); if (!cipher_with_padding){ err = SVRCORE_NoMemory_Error; goto done; } memcpy(cipher_with_padding, cipher, len); ctx = slapd_pk11_createContextBySymKey(store->mech->type, CKA_DECRYPT, store->key, store->params); if (!ctx) { err = SVRCORE_System_Error; goto done; } /* * Warning - there is a purify UMR in the NSS des code - you may see it when the * password is not a multiple of 8 bytes long */ rv = slapd_pk11_cipherOp(ctx, plain, &outLen, store->length, cipher_with_padding, store->length); if (rv){ err = SVRCORE_System_Error; } rv = slapd_pk11_finalize(ctx); /* * We must do the finalize, but we only want to set the err return * code if it is not already set */ if (rv && (SVRCORE_Success == err)) err = SVRCORE_System_Error; done: if (err == SVRCORE_Success){ *out = (char *)plain; } else { slapi_ch_free((void **)&plain); } slapi_ch_free((void **)&cipher_with_padding); /* We should free the PK11Context... Something like : */ if (ctx){ slapd_pk11_destroyContext(ctx, PR_TRUE); } return err; } static SVRCOREError cryptPassword(struct pk11ContextStore *store, char * clear, unsigned char **out) { SVRCOREError err = SVRCORE_Success; SECStatus rv; PK11Context *ctx = NULL; unsigned char *clear_with_padding = NULL; /* clear with padding up to blocksize */ int blocksize = 0; int outLen = 0; blocksize = slapd_pk11_getBlockSize(store->mech->type, 0); store->length = strlen(clear); /* * The size of the clear text buffer passed to the encryption functions * must be a multiple of blocksize (usually 8 bytes) - we allocate a buffer * of this size, copy the clear text password into it, and pad the rest with * zeros. */ if (blocksize != 0){ store->length += blocksize - (store->length % blocksize); } /* * store->crypt will hold the crypted password - it must be >= clear length * store->crypt is freed in NSS; let's not use slapi_ch_calloc */ store->crypt = (unsigned char *)calloc(sizeof(unsigned char), store->length+1); if (!store->crypt) { err = SVRCORE_NoMemory_Error; goto done; } /* Create a buffer big enough to hold the clear text password and padding */ clear_with_padding = (unsigned char *)slapi_ch_calloc(sizeof(unsigned char), store->length+1); if (!clear_with_padding){ err = SVRCORE_NoMemory_Error; goto done; } /* * Copy the clear text password into the buffer - the calloc insures the * remainder is zero padded . */ strcpy((char *)clear_with_padding, clear); ctx = slapd_pk11_createContextBySymKey(store->mech->type, CKA_ENCRYPT, store->key, store->params); if (!ctx) { err = SVRCORE_System_Error; goto done; } rv = slapd_pk11_cipherOp(ctx, store->crypt, &outLen, store->length, clear_with_padding, store->length); if (rv) { err = SVRCORE_System_Error; } rv = slapd_pk11_finalize(ctx); /* * We must do the finalize, but we only want to set the err return * code if it is not already set */ if (rv && (SVRCORE_Success == err)){ err = SVRCORE_System_Error; } done: if (err == SVRCORE_Success){ *out = store->crypt; } slapi_ch_free((void **)&clear_with_padding); /* We should free the PK11Context... Something like : */ if (ctx){ slapd_pk11_destroyContext(ctx, PR_TRUE); } return err; } /* * The UUID name based generator was broken on x86 platforms. We use * this to generate the password encryption key. During migration, * we have to fix this so we can use the fixed generator. The env. * var USE_BROKEN_UUID tells the uuid generator to use the old * broken method to create the UUID. That will allow us to decrypt * the password to the correct clear text, then we can turn off * the broken method and use the fixed method to encrypt the * password. */ char * migrateCredentials(char *oldpath, char *newpath, char *oldcred) { static char *useBrokenUUID = "USE_BROKEN_UUID=1"; static char *disableBrokenUUID = "USE_BROKEN_UUID=0"; char *plain = NULL; char *cipher = NULL; init_pbe_plugin(); slapd_pk11_configurePKCS11(NULL, NULL, tokPBE, ptokPBE, NULL, NULL, NULL, NULL, 0, 0 ); NSS_NoDB_Init(NULL); if (getenv("MIGRATE_BROKEN_PWD")) { putenv(useBrokenUUID); } if ( decode_path(oldcred, &plain, oldpath, DES_MECH, NULL) == 0 ){ if (getenv("MIGRATE_BROKEN_PWD")) { putenv(disableBrokenUUID); } if ( encode_path(plain, &cipher, newpath, AES_MECH) != 0 ){ return(NULL); } else { return(cipher); } } else { return(NULL); } }