tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

pkistore.c (18509B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #ifndef PKIM_H
      6 #include "pkim.h"
      7 #endif /* PKIM_H */
      8 
      9 #ifndef PKI_H
     10 #include "pki.h"
     11 #endif /* PKI_H */
     12 
     13 #ifndef NSSPKI_H
     14 #include "nsspki.h"
     15 #endif /* NSSPKI_H */
     16 
     17 #ifndef BASE_H
     18 #include "base.h"
     19 #endif /* BASE_H */
     20 
     21 #ifndef PKISTORE_H
     22 #include "pkistore.h"
     23 #endif /* PKISTORE_H */
     24 
     25 #include "cert.h"
     26 #include "pki3hack.h"
     27 
     28 #include "prbit.h"
     29 
     30 /*
     31 * Certificate Store
     32 *
     33 * This differs from the cache in that it is a true storage facility.  Items
     34 * stay in until they are explicitly removed.  It is only used by crypto
     35 * contexts at this time, but may be more generally useful...
     36 *
     37 */
     38 
     39 struct nssCertificateStoreStr {
     40    PRBool i_alloced_arena;
     41    NSSArena *arena;
     42    PZLock *lock;
     43    nssHash *subject;
     44    nssHash *issuer_and_serial;
     45 };
     46 
     47 typedef struct certificate_hash_entry_str certificate_hash_entry;
     48 
     49 struct certificate_hash_entry_str {
     50    NSSCertificate *cert;
     51    NSSTrust *trust;
     52    nssSMIMEProfile *profile;
     53 };
     54 
     55 /* forward static declarations */
     56 static NSSCertificate *
     57 nssCertStore_FindCertByIssuerAndSerialNumberLocked(
     58    nssCertificateStore *store,
     59    NSSDER *issuer,
     60    NSSDER *serial);
     61 
     62 NSS_IMPLEMENT nssCertificateStore *
     63 nssCertificateStore_Create(NSSArena *arenaOpt)
     64 {
     65    NSSArena *arena;
     66    nssCertificateStore *store;
     67    PRBool i_alloced_arena;
     68    if (arenaOpt) {
     69        arena = arenaOpt;
     70        i_alloced_arena = PR_FALSE;
     71    } else {
     72        arena = nssArena_Create();
     73        if (!arena) {
     74            return NULL;
     75        }
     76        i_alloced_arena = PR_TRUE;
     77    }
     78    store = nss_ZNEW(arena, nssCertificateStore);
     79    if (!store) {
     80        goto loser;
     81    }
     82    store->lock = PZ_NewLock(nssILockOther);
     83    if (!store->lock) {
     84        goto loser;
     85    }
     86    /* Create the issuer/serial --> {cert, trust, S/MIME profile } hash */
     87    store->issuer_and_serial = nssHash_CreateCertificate(arena, 0);
     88    if (!store->issuer_and_serial) {
     89        goto loser;
     90    }
     91    /* Create the subject DER --> subject list hash */
     92    store->subject = nssHash_CreateItem(arena, 0);
     93    if (!store->subject) {
     94        goto loser;
     95    }
     96    store->arena = arena;
     97    store->i_alloced_arena = i_alloced_arena;
     98    return store;
     99 loser:
    100    if (store) {
    101        if (store->lock) {
    102            PZ_DestroyLock(store->lock);
    103        }
    104        if (store->issuer_and_serial) {
    105            nssHash_Destroy(store->issuer_and_serial);
    106        }
    107        if (store->subject) {
    108            nssHash_Destroy(store->subject);
    109        }
    110    }
    111    if (i_alloced_arena) {
    112        nssArena_Destroy(arena);
    113    }
    114    return NULL;
    115 }
    116 
    117 extern const NSSError NSS_ERROR_BUSY;
    118 
    119 NSS_IMPLEMENT PRStatus
    120 nssCertificateStore_Destroy(nssCertificateStore *store)
    121 {
    122    if (nssHash_Count(store->issuer_and_serial) > 0) {
    123        nss_SetError(NSS_ERROR_BUSY);
    124        return PR_FAILURE;
    125    }
    126    PZ_DestroyLock(store->lock);
    127    nssHash_Destroy(store->issuer_and_serial);
    128    nssHash_Destroy(store->subject);
    129    if (store->i_alloced_arena) {
    130        nssArena_Destroy(store->arena);
    131    } else {
    132        nss_ZFreeIf(store);
    133    }
    134    return PR_SUCCESS;
    135 }
    136 
    137 static PRStatus
    138 add_certificate_entry(
    139    nssCertificateStore *store,
    140    NSSCertificate *cert)
    141 {
    142    PRStatus nssrv;
    143    certificate_hash_entry *entry;
    144    entry = nss_ZNEW(cert->object.arena, certificate_hash_entry);
    145    if (!entry) {
    146        return PR_FAILURE;
    147    }
    148    entry->cert = cert;
    149    nssrv = nssHash_Add(store->issuer_and_serial, cert, entry);
    150    if (nssrv != PR_SUCCESS) {
    151        nss_ZFreeIf(entry);
    152    }
    153    return nssrv;
    154 }
    155 
    156 static PRStatus
    157 add_subject_entry(
    158    nssCertificateStore *store,
    159    NSSCertificate *cert)
    160 {
    161    PRStatus nssrv;
    162    nssList *subjectList;
    163    subjectList = (nssList *)nssHash_Lookup(store->subject, &cert->subject);
    164    if (subjectList) {
    165        /* The subject is already in, add this cert to the list */
    166        nssrv = nssList_AddUnique(subjectList, cert);
    167    } else {
    168        /* Create a new subject list for the subject */
    169        subjectList = nssList_Create(NULL, PR_FALSE);
    170        if (!subjectList) {
    171            return PR_FAILURE;
    172        }
    173        nssList_SetSortFunction(subjectList, nssCertificate_SubjectListSort);
    174        /* Add the cert entry to this list of subjects */
    175        nssrv = nssList_Add(subjectList, cert);
    176        if (nssrv != PR_SUCCESS) {
    177            return nssrv;
    178        }
    179        /* Add the subject list to the cache */
    180        nssrv = nssHash_Add(store->subject, &cert->subject, subjectList);
    181    }
    182    return nssrv;
    183 }
    184 
    185 /* declared below */
    186 static void
    187 remove_certificate_entry(
    188    nssCertificateStore *store,
    189    NSSCertificate *cert);
    190 
    191 /* Caller must hold store->lock */
    192 static PRStatus
    193 nssCertificateStore_AddLocked(
    194    nssCertificateStore *store,
    195    NSSCertificate *cert)
    196 {
    197    PRStatus nssrv = add_certificate_entry(store, cert);
    198    if (nssrv == PR_SUCCESS) {
    199        nssrv = add_subject_entry(store, cert);
    200        if (nssrv == PR_FAILURE) {
    201            remove_certificate_entry(store, cert);
    202        }
    203    }
    204    return nssrv;
    205 }
    206 
    207 NSS_IMPLEMENT NSSCertificate *
    208 nssCertificateStore_FindOrAdd(
    209    nssCertificateStore *store,
    210    NSSCertificate *c)
    211 {
    212    PRStatus nssrv;
    213    NSSCertificate *rvCert = NULL;
    214 
    215    PZ_Lock(store->lock);
    216    rvCert = nssCertStore_FindCertByIssuerAndSerialNumberLocked(
    217        store, &c->issuer, &c->serial);
    218    if (!rvCert) {
    219        nssrv = nssCertificateStore_AddLocked(store, c);
    220        if (PR_SUCCESS == nssrv) {
    221            rvCert = nssCertificate_AddRef(c);
    222        }
    223    }
    224    PZ_Unlock(store->lock);
    225    return rvCert;
    226 }
    227 
    228 static void
    229 remove_certificate_entry(
    230    nssCertificateStore *store,
    231    NSSCertificate *cert)
    232 {
    233    certificate_hash_entry *entry;
    234    entry = (certificate_hash_entry *)
    235        nssHash_Lookup(store->issuer_and_serial, cert);
    236    if (entry) {
    237        nssHash_Remove(store->issuer_and_serial, cert);
    238        if (entry->trust) {
    239            nssTrust_Destroy(entry->trust);
    240        }
    241        if (entry->profile) {
    242            nssSMIMEProfile_Destroy(entry->profile);
    243        }
    244        nss_ZFreeIf(entry);
    245    }
    246 }
    247 
    248 static void
    249 remove_subject_entry(
    250    nssCertificateStore *store,
    251    NSSCertificate *cert)
    252 {
    253    nssList *subjectList;
    254    /* Get the subject list for the cert's subject */
    255    subjectList = (nssList *)nssHash_Lookup(store->subject, &cert->subject);
    256    if (subjectList) {
    257        /* Remove the cert from the subject hash */
    258        nssList_Remove(subjectList, cert);
    259        nssHash_Remove(store->subject, &cert->subject);
    260        if (nssList_Count(subjectList) == 0) {
    261            nssList_Destroy(subjectList);
    262        } else {
    263            /* The cert being released may have keyed the subject entry.
    264             * Since there are still subject certs around, get another and
    265             * rekey the entry just in case.
    266             */
    267            NSSCertificate *subjectCert;
    268            (void)nssList_GetArray(subjectList, (void **)&subjectCert, 1);
    269            nssHash_Add(store->subject, &subjectCert->subject, subjectList);
    270        }
    271    }
    272 }
    273 
    274 NSS_IMPLEMENT void
    275 nssCertificateStore_RemoveCertLOCKED(
    276    nssCertificateStore *store,
    277    NSSCertificate *cert)
    278 {
    279    certificate_hash_entry *entry;
    280    entry = (certificate_hash_entry *)
    281        nssHash_Lookup(store->issuer_and_serial, cert);
    282    if (entry && entry->cert == cert) {
    283        remove_certificate_entry(store, cert);
    284        remove_subject_entry(store, cert);
    285    }
    286 }
    287 
    288 NSS_IMPLEMENT void
    289 nssCertificateStore_Lock(nssCertificateStore *store, nssCertificateStoreTrace *out)
    290 {
    291 #ifdef DEBUG
    292    PORT_Assert(out);
    293    out->store = store;
    294    out->lock = store->lock;
    295    out->locked = PR_TRUE;
    296    PZ_Lock(out->lock);
    297 #else
    298    PZ_Lock(store->lock);
    299 #endif
    300 }
    301 
    302 NSS_IMPLEMENT void
    303 nssCertificateStore_Unlock(
    304    nssCertificateStore *store, const nssCertificateStoreTrace *in,
    305    nssCertificateStoreTrace *out)
    306 {
    307 #ifdef DEBUG
    308    PORT_Assert(in);
    309    PORT_Assert(out);
    310    out->store = store;
    311    out->lock = store->lock;
    312    PORT_Assert(!out->locked);
    313    out->unlocked = PR_TRUE;
    314 
    315    PORT_Assert(in->store == out->store);
    316    PORT_Assert(in->lock == out->lock);
    317    PORT_Assert(in->locked);
    318    PORT_Assert(!in->unlocked);
    319 
    320    PZ_Unlock(out->lock);
    321 #else
    322    PZ_Unlock(store->lock);
    323 #endif
    324 }
    325 
    326 static NSSCertificate **
    327 get_array_from_list(
    328    nssList *certList,
    329    NSSCertificate *rvOpt[],
    330    PRUint32 maximumOpt,
    331    NSSArena *arenaOpt)
    332 {
    333    PRUint32 count;
    334    NSSCertificate **rvArray = NULL;
    335    count = nssList_Count(certList);
    336    if (count == 0) {
    337        return NULL;
    338    }
    339    if (maximumOpt > 0) {
    340        count = PR_MIN(maximumOpt, count);
    341    }
    342    if (rvOpt) {
    343        nssList_GetArray(certList, (void **)rvOpt, count);
    344    } else {
    345        rvArray = nss_ZNEWARRAY(arenaOpt, NSSCertificate *, count + 1);
    346        if (rvArray) {
    347            nssList_GetArray(certList, (void **)rvArray, count);
    348        }
    349    }
    350    return rvArray;
    351 }
    352 
    353 NSS_IMPLEMENT NSSCertificate **
    354 nssCertificateStore_FindCertificatesBySubject(
    355    nssCertificateStore *store,
    356    NSSDER *subject,
    357    NSSCertificate *rvOpt[],
    358    PRUint32 maximumOpt,
    359    NSSArena *arenaOpt)
    360 {
    361    NSSCertificate **rvArray = NULL;
    362    nssList *subjectList;
    363    PZ_Lock(store->lock);
    364    subjectList = (nssList *)nssHash_Lookup(store->subject, subject);
    365    if (subjectList) {
    366        nssCertificateList_AddReferences(subjectList);
    367        rvArray = get_array_from_list(subjectList,
    368                                      rvOpt, maximumOpt, arenaOpt);
    369    }
    370    PZ_Unlock(store->lock);
    371    return rvArray;
    372 }
    373 
    374 /* Because only subject indexing is implemented, all other lookups require
    375 * full traversal (unfortunately, PLHashTable doesn't allow you to exit
    376 * early from the enumeration).  The assumptions are that 1) lookups by
    377 * fields other than subject will be rare, and 2) the hash will not have
    378 * a large number of entries.  These assumptions will be tested.
    379 *
    380 * XXX
    381 * For NSS 3.4, it is worth consideration to do all forms of indexing,
    382 * because the only crypto context is global and persistent.
    383 */
    384 
    385 struct nickname_template_str {
    386    NSSUTF8 *nickname;
    387    nssList *subjectList;
    388 };
    389 
    390 static void
    391 match_nickname(const void *k, void *v, void *a)
    392 {
    393    PRStatus nssrv;
    394    NSSCertificate *c;
    395    NSSUTF8 *nickname;
    396    nssList *subjectList = (nssList *)v;
    397    struct nickname_template_str *nt = (struct nickname_template_str *)a;
    398    nssrv = nssList_GetArray(subjectList, (void **)&c, 1);
    399    nickname = nssCertificate_GetNickname(c, NULL);
    400    if (nssrv == PR_SUCCESS && nickname &&
    401        nssUTF8_Equal(nickname, nt->nickname, &nssrv)) {
    402        nt->subjectList = subjectList;
    403    }
    404    nss_ZFreeIf(nickname);
    405 }
    406 
    407 /*
    408 * Find all cached certs with this label.
    409 */
    410 NSS_IMPLEMENT NSSCertificate **
    411 nssCertificateStore_FindCertificatesByNickname(
    412    nssCertificateStore *store,
    413    const NSSUTF8 *nickname,
    414    NSSCertificate *rvOpt[],
    415    PRUint32 maximumOpt,
    416    NSSArena *arenaOpt)
    417 {
    418    NSSCertificate **rvArray = NULL;
    419    struct nickname_template_str nt;
    420    nt.nickname = (char *)nickname;
    421    nt.subjectList = NULL;
    422    PZ_Lock(store->lock);
    423    nssHash_Iterate(store->subject, match_nickname, &nt);
    424    if (nt.subjectList) {
    425        nssCertificateList_AddReferences(nt.subjectList);
    426        rvArray = get_array_from_list(nt.subjectList,
    427                                      rvOpt, maximumOpt, arenaOpt);
    428    }
    429    PZ_Unlock(store->lock);
    430    return rvArray;
    431 }
    432 
    433 struct email_template_str {
    434    NSSASCII7 *email;
    435    nssList *emailList;
    436 };
    437 
    438 static void
    439 match_email(const void *k, void *v, void *a)
    440 {
    441    PRStatus nssrv;
    442    NSSCertificate *c;
    443    nssList *subjectList = (nssList *)v;
    444    struct email_template_str *et = (struct email_template_str *)a;
    445    nssrv = nssList_GetArray(subjectList, (void **)&c, 1);
    446    if (nssrv == PR_SUCCESS &&
    447        nssUTF8_Equal(c->email, et->email, &nssrv)) {
    448        nssListIterator *iter = nssList_CreateIterator(subjectList);
    449        if (iter) {
    450            for (c = (NSSCertificate *)nssListIterator_Start(iter);
    451                 c != (NSSCertificate *)NULL;
    452                 c = (NSSCertificate *)nssListIterator_Next(iter)) {
    453                nssList_Add(et->emailList, c);
    454            }
    455            nssListIterator_Finish(iter);
    456            nssListIterator_Destroy(iter);
    457        }
    458    }
    459 }
    460 
    461 /*
    462 * Find all cached certs with this email address.
    463 */
    464 NSS_IMPLEMENT NSSCertificate **
    465 nssCertificateStore_FindCertificatesByEmail(
    466    nssCertificateStore *store,
    467    NSSASCII7 *email,
    468    NSSCertificate *rvOpt[],
    469    PRUint32 maximumOpt,
    470    NSSArena *arenaOpt)
    471 {
    472    NSSCertificate **rvArray = NULL;
    473    struct email_template_str et;
    474    et.email = email;
    475    et.emailList = nssList_Create(NULL, PR_FALSE);
    476    if (!et.emailList) {
    477        return NULL;
    478    }
    479    PZ_Lock(store->lock);
    480    nssHash_Iterate(store->subject, match_email, &et);
    481    if (et.emailList) {
    482        /* get references before leaving the store's lock protection */
    483        nssCertificateList_AddReferences(et.emailList);
    484    }
    485    PZ_Unlock(store->lock);
    486    if (et.emailList) {
    487        rvArray = get_array_from_list(et.emailList,
    488                                      rvOpt, maximumOpt, arenaOpt);
    489        nssList_Destroy(et.emailList);
    490    }
    491    return rvArray;
    492 }
    493 
    494 /* Caller holds store->lock */
    495 static NSSCertificate *
    496 nssCertStore_FindCertByIssuerAndSerialNumberLocked(
    497    nssCertificateStore *store,
    498    NSSDER *issuer,
    499    NSSDER *serial)
    500 {
    501    certificate_hash_entry *entry;
    502    NSSCertificate *rvCert = NULL;
    503    NSSCertificate index;
    504 
    505    index.issuer = *issuer;
    506    index.serial = *serial;
    507    entry = (certificate_hash_entry *)
    508        nssHash_Lookup(store->issuer_and_serial, &index);
    509    if (entry) {
    510        rvCert = nssCertificate_AddRef(entry->cert);
    511    }
    512    return rvCert;
    513 }
    514 
    515 NSS_IMPLEMENT NSSCertificate *
    516 nssCertificateStore_FindCertificateByIssuerAndSerialNumber(
    517    nssCertificateStore *store,
    518    NSSDER *issuer,
    519    NSSDER *serial)
    520 {
    521    NSSCertificate *rvCert = NULL;
    522 
    523    PZ_Lock(store->lock);
    524    rvCert = nssCertStore_FindCertByIssuerAndSerialNumberLocked(
    525        store, issuer, serial);
    526    PZ_Unlock(store->lock);
    527    return rvCert;
    528 }
    529 
    530 NSS_IMPLEMENT NSSCertificate *
    531 nssCertificateStore_FindCertificateByEncodedCertificate(
    532    nssCertificateStore *store,
    533    NSSDER *encoding)
    534 {
    535    PRStatus nssrv = PR_FAILURE;
    536    NSSDER issuer, serial;
    537    NSSCertificate *rvCert = NULL;
    538    nssrv = nssPKIX509_GetIssuerAndSerialFromDER(encoding, &issuer, &serial);
    539    if (nssrv != PR_SUCCESS) {
    540        return NULL;
    541    }
    542    rvCert = nssCertificateStore_FindCertificateByIssuerAndSerialNumber(store,
    543                                                                        &issuer,
    544                                                                        &serial);
    545    PORT_Free(issuer.data);
    546    PORT_Free(serial.data);
    547    return rvCert;
    548 }
    549 
    550 NSS_EXTERN PRStatus
    551 nssCertificateStore_AddTrust(
    552    nssCertificateStore *store,
    553    NSSTrust *trust)
    554 {
    555    NSSCertificate *cert;
    556    certificate_hash_entry *entry;
    557    cert = trust->certificate;
    558    PZ_Lock(store->lock);
    559    entry = (certificate_hash_entry *)
    560        nssHash_Lookup(store->issuer_and_serial, cert);
    561    if (entry) {
    562        NSSTrust *newTrust = nssTrust_AddRef(trust);
    563        if (entry->trust) {
    564            nssTrust_Destroy(entry->trust);
    565        }
    566        entry->trust = newTrust;
    567    }
    568    PZ_Unlock(store->lock);
    569    return (entry) ? PR_SUCCESS : PR_FAILURE;
    570 }
    571 
    572 NSS_IMPLEMENT NSSTrust *
    573 nssCertificateStore_FindTrustForCertificate(
    574    nssCertificateStore *store,
    575    NSSCertificate *cert)
    576 {
    577    certificate_hash_entry *entry;
    578    NSSTrust *rvTrust = NULL;
    579    PZ_Lock(store->lock);
    580    entry = (certificate_hash_entry *)
    581        nssHash_Lookup(store->issuer_and_serial, cert);
    582    if (entry && entry->trust) {
    583        rvTrust = nssTrust_AddRef(entry->trust);
    584    }
    585    PZ_Unlock(store->lock);
    586    return rvTrust;
    587 }
    588 
    589 NSS_EXTERN PRStatus
    590 nssCertificateStore_AddSMIMEProfile(
    591    nssCertificateStore *store,
    592    nssSMIMEProfile *profile)
    593 {
    594    NSSCertificate *cert;
    595    certificate_hash_entry *entry;
    596    cert = profile->certificate;
    597    PZ_Lock(store->lock);
    598    entry = (certificate_hash_entry *)
    599        nssHash_Lookup(store->issuer_and_serial, cert);
    600    if (entry) {
    601        nssSMIMEProfile *newProfile = nssSMIMEProfile_AddRef(profile);
    602        if (entry->profile) {
    603            nssSMIMEProfile_Destroy(entry->profile);
    604        }
    605        entry->profile = newProfile;
    606    }
    607    PZ_Unlock(store->lock);
    608    return (entry) ? PR_SUCCESS : PR_FAILURE;
    609 }
    610 
    611 NSS_IMPLEMENT nssSMIMEProfile *
    612 nssCertificateStore_FindSMIMEProfileForCertificate(
    613    nssCertificateStore *store,
    614    NSSCertificate *cert)
    615 {
    616    certificate_hash_entry *entry;
    617    nssSMIMEProfile *rvProfile = NULL;
    618    PZ_Lock(store->lock);
    619    entry = (certificate_hash_entry *)
    620        nssHash_Lookup(store->issuer_and_serial, cert);
    621    if (entry && entry->profile) {
    622        rvProfile = nssSMIMEProfile_AddRef(entry->profile);
    623    }
    624    PZ_Unlock(store->lock);
    625    return rvProfile;
    626 }
    627 
    628 /* XXX this is also used by cache and should be somewhere else */
    629 
    630 static PLHashNumber
    631 nss_certificate_hash(const void *key)
    632 {
    633    unsigned int i;
    634    PLHashNumber h;
    635    NSSCertificate *c = (NSSCertificate *)key;
    636    h = 0;
    637    for (i = 0; i < c->issuer.size; i++)
    638        h = PR_ROTATE_LEFT32(h, 4) ^ ((unsigned char *)c->issuer.data)[i];
    639    for (i = 0; i < c->serial.size; i++)
    640        h = PR_ROTATE_LEFT32(h, 4) ^ ((unsigned char *)c->serial.data)[i];
    641    return h;
    642 }
    643 
    644 static int
    645 nss_compare_certs(const void *v1, const void *v2)
    646 {
    647    PRStatus ignore;
    648    NSSCertificate *c1 = (NSSCertificate *)v1;
    649    NSSCertificate *c2 = (NSSCertificate *)v2;
    650    return (int)(nssItem_Equal(&c1->issuer, &c2->issuer, &ignore) &&
    651                 nssItem_Equal(&c1->serial, &c2->serial, &ignore));
    652 }
    653 
    654 NSS_IMPLEMENT nssHash *
    655 nssHash_CreateCertificate(
    656    NSSArena *arenaOpt,
    657    PRUint32 numBuckets)
    658 {
    659    return nssHash_Create(arenaOpt,
    660                          numBuckets,
    661                          nss_certificate_hash,
    662                          nss_compare_certs,
    663                          PL_CompareValues);
    664 }
    665 
    666 NSS_IMPLEMENT void
    667 nssCertificateStore_DumpStoreInfo(
    668    nssCertificateStore *store,
    669    void (*cert_dump_iter)(const void *, void *, void *),
    670    void *arg)
    671 {
    672    PZ_Lock(store->lock);
    673    nssHash_Iterate(store->issuer_and_serial, cert_dump_iter, arg);
    674    PZ_Unlock(store->lock);
    675 }