/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "pkcs12.h" #include "plarena.h" #include "secpkcs7.h" #include "p12local.h" #include "secoid.h" #include "secitem.h" #include "secport.h" #include "secasn1.h" #include "secder.h" #include "secerr.h" #include "cert.h" #include "certdb.h" #include "p12plcy.h" #include "p12.h" #include "secpkcs5.h" /* PFX extraction and validation routines */ /* decode the DER encoded PFX item. if unable to decode, check to see if it * is an older PFX item. If that fails, assume the file was not a valid * pfx file. * the returned pfx structure should be destroyed using SEC_PKCS12DestroyPFX */ static SEC_PKCS12PFXItem * sec_pkcs12_decode_pfx(SECItem *der_pfx) { SEC_PKCS12PFXItem *pfx; SECStatus rv; if (der_pfx == NULL) { return NULL; } /* allocate the space for a new PFX item */ pfx = sec_pkcs12_new_pfx(); if (pfx == NULL) { return NULL; } rv = SEC_ASN1DecodeItem(pfx->poolp, pfx, SEC_PKCS12PFXItemTemplate, der_pfx); /* if a failure occurred, check for older version... * we also get rid of the old pfx structure, because we don't * know where it failed and what data in may contain */ if (rv != SECSuccess) { SEC_PKCS12DestroyPFX(pfx); pfx = sec_pkcs12_new_pfx(); if (pfx == NULL) { return NULL; } rv = SEC_ASN1DecodeItem(pfx->poolp, pfx, SEC_PKCS12PFXItemTemplate_OLD, der_pfx); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_PKCS12_DECODING_PFX); PORT_FreeArena(pfx->poolp, PR_TRUE); return NULL; } pfx->old = PR_TRUE; rv = SGN_CopyDigestInfo(pfx->poolp, &pfx->macData.safeMac, &pfx->old_safeMac); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_NO_MEMORY); PORT_FreeArena(pfx->poolp, PR_TRUE); return NULL; } rv = SECITEM_CopyItem(pfx->poolp, &pfx->macData.macSalt, &pfx->old_macSalt); if (rv != SECSuccess) { PORT_SetError(SEC_ERROR_NO_MEMORY); PORT_FreeArena(pfx->poolp, PR_TRUE); return NULL; } } else { pfx->old = PR_FALSE; } /* convert bit string from bits to bytes */ pfx->macData.macSalt.len /= 8; return pfx; } /* validate the integrity MAC used in the PFX. The MAC is generated * per the PKCS 12 document. If the MAC is incorrect, it is most likely * due to an invalid password. * pwitem is the integrity password * pfx is the decoded pfx item */ static PRBool sec_pkcs12_check_pfx_mac(SEC_PKCS12PFXItem *pfx, SECItem *pwitem) { SECItem *key = NULL, *mac = NULL, *data = NULL; SECItem *vpwd = NULL; SECOidTag algorithm; PRBool ret = PR_FALSE; if (pfx == NULL) { return PR_FALSE; } algorithm = SECOID_GetAlgorithmTag(&pfx->macData.safeMac.digestAlgorithm); switch (algorithm) { /* only SHA1 hashing supported as a MACing algorithm */ case SEC_OID_SHA1: if (pfx->old == PR_FALSE) { pfx->swapUnicode = PR_FALSE; } recheckUnicodePassword: vpwd = sec_pkcs12_create_virtual_password(pwitem, &pfx->macData.macSalt, pfx->swapUnicode); if (vpwd == NULL) { return PR_FALSE; } key = sec_pkcs12_generate_key_from_password(algorithm, &pfx->macData.macSalt, (pfx->old ? pwitem : vpwd)); /* free vpwd only for newer PFX */ if (vpwd) { SECITEM_ZfreeItem(vpwd, PR_TRUE); } if (key == NULL) { return PR_FALSE; } data = SEC_PKCS7GetContent(&pfx->authSafe); if (data == NULL) { break; } /* check MAC */ mac = sec_pkcs12_generate_mac(key, data, pfx->old); ret = PR_TRUE; if (mac) { SECItem *safeMac = &pfx->macData.safeMac.digest; if (SECITEM_CompareItem(mac, safeMac) != SECEqual) { /* if we encounter an invalid mac, lets invert the * password in case of unicode changes */ if (((!pfx->old) && pfx->swapUnicode) || (pfx->old)) { PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC); ret = PR_FALSE; } else { SECITEM_ZfreeItem(mac, PR_TRUE); pfx->swapUnicode = PR_TRUE; goto recheckUnicodePassword; } } SECITEM_ZfreeItem(mac, PR_TRUE); } else { ret = PR_FALSE; } break; default: PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_MAC_ALGORITHM); ret = PR_FALSE; break; } /* let success fall through */ if (key != NULL) SECITEM_ZfreeItem(key, PR_TRUE); return ret; } /* check the validity of the pfx structure. we currently only support * password integrity mode, so we check the MAC. */ static PRBool sec_pkcs12_validate_pfx(SEC_PKCS12PFXItem *pfx, SECItem *pwitem) { SECOidTag contentType; contentType = SEC_PKCS7ContentType(&pfx->authSafe); switch (contentType) { case SEC_OID_PKCS7_DATA: return sec_pkcs12_check_pfx_mac(pfx, pwitem); break; case SEC_OID_PKCS7_SIGNED_DATA: default: PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE); break; } return PR_FALSE; } /* decode and return the valid PFX. if the PFX item is not valid, * NULL is returned. */ static SEC_PKCS12PFXItem * sec_pkcs12_get_pfx(SECItem *pfx_data, SECItem *pwitem) { SEC_PKCS12PFXItem *pfx; PRBool valid_pfx; if ((pfx_data == NULL) || (pwitem == NULL)) { return NULL; } pfx = sec_pkcs12_decode_pfx(pfx_data); if (pfx == NULL) { return NULL; } valid_pfx = sec_pkcs12_validate_pfx(pfx, pwitem); if (valid_pfx != PR_TRUE) { SEC_PKCS12DestroyPFX(pfx); pfx = NULL; } return pfx; } /* authenticated safe decoding, validation, and access routines */ /* convert dogbert beta 3 authenticated safe structure to a post * beta three structure, so that we don't have to change more routines. */ static SECStatus sec_pkcs12_convert_old_auth_safe(SEC_PKCS12AuthenticatedSafe *asafe) { SEC_PKCS12Baggage *baggage; SEC_PKCS12BaggageItem *bag; SECStatus rv = SECSuccess; if (asafe->old_baggage.espvks == NULL) { /* XXX should the ASN1 engine produce a single NULL element list * rather than setting the pointer to NULL? * There is no need to return an error -- assume that the list * was empty. */ return SECSuccess; } baggage = sec_pkcs12_create_baggage(asafe->poolp); if (!baggage) { return SECFailure; } bag = sec_pkcs12_create_external_bag(baggage); if (!bag) { return SECFailure; } PORT_Memcpy(&asafe->baggage, baggage, sizeof(SEC_PKCS12Baggage)); /* if there are shrouded keys, append them to the bag */ rv = SECSuccess; if (asafe->old_baggage.espvks[0] != NULL) { int nEspvk = 0; rv = SECSuccess; while ((asafe->old_baggage.espvks[nEspvk] != NULL) && (rv == SECSuccess)) { rv = sec_pkcs12_append_shrouded_key(bag, asafe->old_baggage.espvks[nEspvk]); nEspvk++; } } return rv; } /* decodes the authenticated safe item. a return of NULL indicates * an error. however, the error will have occurred either in memory * allocation or in decoding the authenticated safe. * * if an old PFX item has been found, we want to convert the * old authenticated safe to the new one. */ static SEC_PKCS12AuthenticatedSafe * sec_pkcs12_decode_authenticated_safe(SEC_PKCS12PFXItem *pfx) { SECItem *der_asafe = NULL; SEC_PKCS12AuthenticatedSafe *asafe = NULL; SECStatus rv; if (pfx == NULL) { return NULL; } der_asafe = SEC_PKCS7GetContent(&pfx->authSafe); if (der_asafe == NULL) { /* XXX set error ? */ goto loser; } asafe = sec_pkcs12_new_asafe(pfx->poolp); if (asafe == NULL) { goto loser; } if (pfx->old == PR_FALSE) { rv = SEC_ASN1DecodeItem(pfx->poolp, asafe, SEC_PKCS12AuthenticatedSafeTemplate, der_asafe); asafe->old = PR_FALSE; asafe->swapUnicode = pfx->swapUnicode; } else { /* handle beta exported files */ rv = SEC_ASN1DecodeItem(pfx->poolp, asafe, SEC_PKCS12AuthenticatedSafeTemplate_OLD, der_asafe); asafe->safe = &(asafe->old_safe); rv = sec_pkcs12_convert_old_auth_safe(asafe); asafe->old = PR_TRUE; } if (rv != SECSuccess) { goto loser; } asafe->poolp = pfx->poolp; return asafe; loser: return NULL; } /* validates the safe within the authenticated safe item. * in order to be valid: * 1. the privacy salt must be present * 2. the encryption algorithm must be supported (including * export policy) * PR_FALSE indicates an error, PR_TRUE indicates a valid safe */ static PRBool sec_pkcs12_validate_encrypted_safe(SEC_PKCS12AuthenticatedSafe *asafe) { PRBool valid = PR_FALSE; SECAlgorithmID *algid; if (asafe == NULL) { return PR_FALSE; } /* if mode is password privacy, then privacySalt is assumed * to be non-zero. */ if (asafe->privacySalt.len != 0) { valid = PR_TRUE; asafe->privacySalt.len /= 8; } else { PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE); return PR_FALSE; } /* until spec changes, content will have between 2 and 8 bytes depending * upon the algorithm used if certs are unencrypted... * also want to support case where content is empty -- which we produce */ if (SEC_PKCS7IsContentEmpty(asafe->safe, 8) == PR_TRUE) { asafe->emptySafe = PR_TRUE; return PR_TRUE; } asafe->emptySafe = PR_FALSE; /* make sure that a pbe algorithm is being used */ algid = SEC_PKCS7GetEncryptionAlgorithm(asafe->safe); if (algid != NULL) { if (SEC_PKCS5IsAlgorithmPBEAlg(algid)) { valid = SEC_PKCS12DecryptionAllowed(algid); if (valid == PR_FALSE) { PORT_SetError(SEC_ERROR_BAD_EXPORT_ALGORITHM); } } else { PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM); valid = PR_FALSE; } } else { valid = PR_FALSE; PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM); } return valid; } /* validates authenticates safe: * 1. checks that the version is supported * 2. checks that only password privacy mode is used (currently) * 3. further, makes sure safe has appropriate policies per above function * PR_FALSE indicates failure. */ static PRBool sec_pkcs12_validate_auth_safe(SEC_PKCS12AuthenticatedSafe *asafe) { PRBool valid = PR_TRUE; SECOidTag safe_type; int version; if (asafe == NULL) { return PR_FALSE; } /* check version, since it is default it may not be present. * therefore, assume ok */ if ((asafe->version.len > 0) && (asafe->old == PR_FALSE)) { version = DER_GetInteger(&asafe->version); if (version > SEC_PKCS12_PFX_VERSION) { PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_VERSION); return PR_FALSE; } } /* validate password mode is being used */ safe_type = SEC_PKCS7ContentType(asafe->safe); switch (safe_type) { case SEC_OID_PKCS7_ENCRYPTED_DATA: valid = sec_pkcs12_validate_encrypted_safe(asafe); break; case SEC_OID_PKCS7_ENVELOPED_DATA: default: PORT_SetError(SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE); valid = PR_FALSE; break; } return valid; } /* retrieves the authenticated safe item from the PFX item * before returning the authenticated safe, the validity of the * authenticated safe is checked and if valid, returned. * a return of NULL indicates that an error occurred. */ static SEC_PKCS12AuthenticatedSafe * sec_pkcs12_get_auth_safe(SEC_PKCS12PFXItem *pfx) { SEC_PKCS12AuthenticatedSafe *asafe; PRBool valid_safe; if (pfx == NULL) { return NULL; } asafe = sec_pkcs12_decode_authenticated_safe(pfx); if (asafe == NULL) { return NULL; } valid_safe = sec_pkcs12_validate_auth_safe(asafe); if (valid_safe != PR_TRUE) { asafe = NULL; } else if (asafe) { asafe->baggage.poolp = asafe->poolp; } return asafe; } /* decrypts the authenticated safe. * a return of anything but SECSuccess indicates an error. the * password is not known to be valid until the call to the * function sec_pkcs12_get_safe_contents. If decoding the safe * fails, it is assumed the password was incorrect and the error * is set then. any failure here is assumed to be due to * internal problems in SEC_PKCS7DecryptContents or below. */ static SECStatus sec_pkcs12_decrypt_auth_safe(SEC_PKCS12AuthenticatedSafe *asafe, SECItem *pwitem, void *wincx) { SECStatus rv = SECFailure; SECItem *vpwd = NULL; if ((asafe == NULL) || (pwitem == NULL)) { return SECFailure; } if (asafe->old == PR_FALSE) { vpwd = sec_pkcs12_create_virtual_password(pwitem, &asafe->privacySalt, asafe->swapUnicode); if (vpwd == NULL) { return SECFailure; } } rv = SEC_PKCS7DecryptContents(asafe->poolp, asafe->safe, (asafe->old ? pwitem : vpwd), wincx); if (asafe->old == PR_FALSE) { SECITEM_ZfreeItem(vpwd, PR_TRUE); } return rv; } /* extract the safe from the authenticated safe. * if we are unable to decode the safe, then it is likely that the * safe has not been decrypted or the password used to decrypt * the safe was invalid. we assume that the password was invalid and * set an error accordingly. * a return of NULL indicates that an error occurred. */ static SEC_PKCS12SafeContents * sec_pkcs12_get_safe_contents(SEC_PKCS12AuthenticatedSafe *asafe) { SECItem *src = NULL; SEC_PKCS12SafeContents *safe = NULL; SECStatus rv = SECFailure; if (asafe == NULL) { return NULL; } safe = (SEC_PKCS12SafeContents *)PORT_ArenaZAlloc(asafe->poolp, sizeof(SEC_PKCS12SafeContents)); if (safe == NULL) { return NULL; } safe->poolp = asafe->poolp; safe->old = asafe->old; safe->swapUnicode = asafe->swapUnicode; src = SEC_PKCS7GetContent(asafe->safe); if (src != NULL) { const SEC_ASN1Template *theTemplate; if (asafe->old != PR_TRUE) { theTemplate = SEC_PKCS12SafeContentsTemplate; } else { theTemplate = SEC_PKCS12SafeContentsTemplate_OLD; } rv = SEC_ASN1DecodeItem(asafe->poolp, safe, theTemplate, src); /* if we could not decode the item, password was probably invalid */ if (rv != SECSuccess) { safe = NULL; PORT_SetError(SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT); } } else { PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE); rv = SECFailure; } return safe; } /* import PFX item * der_pfx is the der encoded pfx structure * pbef and pbearg are the integrity/encryption password call back * ncCall is the nickname collision calllback * slot is the destination token * wincx window handler * * on error, error code set and SECFailure returned */ SECStatus SEC_PKCS12PutPFX(SECItem *der_pfx, SECItem *pwitem, SEC_PKCS12NicknameCollisionCallback ncCall, PK11SlotInfo *slot, void *wincx) { SEC_PKCS12PFXItem *pfx; SEC_PKCS12AuthenticatedSafe *asafe; SEC_PKCS12SafeContents *safe_contents = NULL; SECStatus rv; if (!der_pfx || !pwitem || !slot) { return SECFailure; } /* decode and validate each section */ rv = SECFailure; pfx = sec_pkcs12_get_pfx(der_pfx, pwitem); if (pfx != NULL) { asafe = sec_pkcs12_get_auth_safe(pfx); if (asafe != NULL) { /* decrypt safe -- only if not empty */ if (asafe->emptySafe != PR_TRUE) { rv = sec_pkcs12_decrypt_auth_safe(asafe, pwitem, wincx); if (rv == SECSuccess) { safe_contents = sec_pkcs12_get_safe_contents(asafe); if (safe_contents == NULL) { rv = SECFailure; } } } else { safe_contents = sec_pkcs12_create_safe_contents(asafe->poolp); if (safe_contents == NULL) { rv = SECFailure; } else { safe_contents->swapUnicode = pfx->swapUnicode; rv = SECSuccess; } } /* get safe contents and begin import */ if (rv == SECSuccess) { SEC_PKCS12DecoderContext *p12dcx; p12dcx = sec_PKCS12ConvertOldSafeToNew(pfx->poolp, slot, pfx->swapUnicode, pwitem, wincx, safe_contents, &asafe->baggage); if (!p12dcx) { rv = SECFailure; goto loser; } if (SEC_PKCS12DecoderValidateBags(p12dcx, ncCall) != SECSuccess) { rv = SECFailure; goto loser; } rv = SEC_PKCS12DecoderImportBags(p12dcx); } } } loser: if (pfx) { SEC_PKCS12DestroyPFX(pfx); } return rv; } PRBool SEC_PKCS12ValidData(char *buf, int bufLen, long int totalLength) { int lengthLength; PRBool valid = PR_FALSE; if (buf == NULL) { return PR_FALSE; } /* check for constructed sequence identifier tag */ if (*buf == (SEC_ASN1_CONSTRUCTED | SEC_ASN1_SEQUENCE)) { totalLength--; /* header byte taken care of */ buf++; lengthLength = (long int)SEC_ASN1LengthLength(totalLength - 1); if (totalLength > 0x7f) { lengthLength--; *buf &= 0x7f; /* remove bit 8 indicator */ if ((*buf - (char)lengthLength) == 0) { valid = PR_TRUE; } } else { lengthLength--; if ((*buf - (char)lengthLength) == 0) { valid = PR_TRUE; } } } return valid; }