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 #include "blapi.h"
6 #include "ec.h"
7 #include "ecl-curve.h"
8 #include "prprf.h"
9 #include "basicutil.h"
10 #include "pkcs11.h"
11 #include "nspr.h"
12 #include <stdio.h>
13 
14 #define __PASTE(x, y) x##y
15 
16 /*
17  * Get the NSS specific PKCS #11 function names.
18  */
19 #undef CK_PKCS11_FUNCTION_INFO
20 #undef CK_NEED_ARG_LIST
21 
22 #define CK_EXTERN extern
23 #define CK_PKCS11_FUNCTION_INFO(func) \
24     CK_RV __PASTE(NS, func)
25 #define CK_NEED_ARG_LIST 1
26 
27 #include "pkcs11f.h"
28 
29 typedef SECStatus (*op_func)(void *, void *, void *);
30 typedef SECStatus (*pk11_op_func)(CK_SESSION_HANDLE, void *, void *, void *);
31 
32 typedef struct ThreadDataStr {
33     op_func op;
34     void *p1;
35     void *p2;
36     void *p3;
37     int iters;
38     PRLock *lock;
39     int count;
40     SECStatus status;
41     int isSign;
42 } ThreadData;
43 
44 typedef SECItem SECKEYECParams;
45 
46 void
PKCS11Thread(void * data)47 PKCS11Thread(void *data)
48 {
49     ThreadData *threadData = (ThreadData *)data;
50     pk11_op_func op = (pk11_op_func)threadData->op;
51     int iters = threadData->iters;
52     unsigned char sigData[256];
53     SECItem sig;
54     CK_SESSION_HANDLE session;
55     CK_RV crv;
56 
57     threadData->status = SECSuccess;
58     threadData->count = 0;
59 
60     /* get our thread's session */
61     PR_Lock(threadData->lock);
62     crv = NSC_OpenSession(1, CKF_SERIAL_SESSION, NULL, 0, &session);
63     PR_Unlock(threadData->lock);
64     if (crv != CKR_OK) {
65         return;
66     }
67 
68     if (threadData->isSign) {
69         sig.data = sigData;
70         sig.len = sizeof(sigData);
71         threadData->p2 = (void *)&sig;
72     }
73 
74     while (iters--) {
75         threadData->status = (*op)(session, threadData->p1,
76                                    threadData->p2, threadData->p3);
77         if (threadData->status != SECSuccess) {
78             break;
79         }
80         threadData->count++;
81     }
82     return;
83 }
84 
85 void
genericThread(void * data)86 genericThread(void *data)
87 {
88     ThreadData *threadData = (ThreadData *)data;
89     int iters = threadData->iters;
90     unsigned char sigData[256];
91     SECItem sig;
92 
93     threadData->status = SECSuccess;
94     threadData->count = 0;
95 
96     if (threadData->isSign) {
97         sig.data = sigData;
98         sig.len = sizeof(sigData);
99         threadData->p2 = (void *)&sig;
100     }
101 
102     while (iters--) {
103         threadData->status = (*threadData->op)(threadData->p1,
104                                                threadData->p2, threadData->p3);
105         if (threadData->status != SECSuccess) {
106             break;
107         }
108         threadData->count++;
109     }
110     return;
111 }
112 
113 /* Time iter repetitions of operation op. */
114 SECStatus
M_TimeOperation(void (* threadFunc)(void *),op_func opfunc,char * op,void * param1,void * param2,void * param3,int iters,int numThreads,PRLock * lock,CK_SESSION_HANDLE session,int isSign,double * rate)115 M_TimeOperation(void (*threadFunc)(void *),
116                 op_func opfunc, char *op, void *param1, void *param2,
117                 void *param3, int iters, int numThreads, PRLock *lock,
118                 CK_SESSION_HANDLE session, int isSign, double *rate)
119 {
120     double dUserTime;
121     int i, total;
122     PRIntervalTime startTime, totalTime;
123     PRThread **threadIDs;
124     ThreadData *threadData;
125     pk11_op_func pk11_op = (pk11_op_func)opfunc;
126     SECStatus rv;
127 
128     /* verify operation works before testing performance */
129     if (session) {
130         rv = (*pk11_op)(session, param1, param2, param3);
131     } else {
132         rv = (*opfunc)(param1, param2, param3);
133     }
134     if (rv != SECSuccess) {
135         SECU_PrintError("Error:", op);
136         return rv;
137     }
138 
139     /* get Data structures */
140     threadIDs = (PRThread **)PORT_Alloc(numThreads * sizeof(PRThread *));
141     threadData = (ThreadData *)PORT_Alloc(numThreads * sizeof(ThreadData));
142 
143     startTime = PR_Now();
144     if (numThreads == 1) {
145         for (i = 0; i < iters; i++) {
146             if (session) {
147                 rv = (*pk11_op)(session, param1, param2, param3);
148             } else {
149                 rv = (*opfunc)(param1, param2, param3);
150             }
151             if (rv != SECSuccess) {
152                 PORT_Free(threadIDs);
153                 PORT_Free(threadData);
154                 SECU_PrintError("Error:", op);
155                 return rv;
156             }
157         }
158         total = iters;
159     } else {
160         for (i = 0; i < numThreads; i++) {
161             threadData[i].op = opfunc;
162             threadData[i].p1 = (void *)param1;
163             threadData[i].p2 = (void *)param2;
164             threadData[i].p3 = (void *)param3;
165             threadData[i].iters = iters;
166             threadData[i].lock = lock;
167             threadData[i].isSign = isSign;
168             threadIDs[i] = PR_CreateThread(PR_USER_THREAD, threadFunc,
169                                            (void *)&threadData[i], PR_PRIORITY_NORMAL,
170                                            PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
171         }
172 
173         total = 0;
174         for (i = 0; i < numThreads; i++) {
175             PR_JoinThread(threadIDs[i]);
176             /* check the status */
177             total += threadData[i].count;
178         }
179     }
180 
181     totalTime = PR_Now() - startTime;
182     /* SecondsToInterval seems to be broken here ... */
183     dUserTime = (double)totalTime / (double)1000000;
184     if (dUserTime) {
185         printf("    %-15s count:%4d sec: %3.2f op/sec: %6.2f\n",
186                op, total, dUserTime, (double)total / dUserTime);
187         if (rate) {
188             *rate = ((double)total) / dUserTime;
189         }
190     }
191     PORT_Free(threadIDs);
192     PORT_Free(threadData);
193 
194     return SECSuccess;
195 }
196 
197 /* Test curve using specific field arithmetic. */
198 #define ECTEST_NAMED_GFP(name_c, name_v)                                        \
199     if (usefreebl) {                                                            \
200         printf("Testing %s using freebl implementation...\n", name_c);          \
201         rv = ectest_curve_freebl(name_v, iterations, numThreads, ec_field_GFp); \
202         if (rv != SECSuccess)                                                   \
203             goto cleanup;                                                       \
204         printf("... okay.\n");                                                  \
205     }                                                                           \
206     if (usepkcs11) {                                                            \
207         printf("Testing %s using pkcs11 implementation...\n", name_c);          \
208         rv = ectest_curve_pkcs11(name_v, iterations, numThreads);               \
209         if (rv != SECSuccess)                                                   \
210             goto cleanup;                                                       \
211         printf("... okay.\n");                                                  \
212     }
213 
214 /* Test curve using specific field arithmetic. */
215 #define ECTEST_NAMED_CUSTOM(name_c, name_v)                                       \
216     if (usefreebl) {                                                              \
217         printf("Testing %s using freebl implementation...\n", name_c);            \
218         rv = ectest_curve_freebl(name_v, iterations, numThreads, ec_field_plain); \
219         if (rv != SECSuccess)                                                     \
220             goto cleanup;                                                         \
221         printf("... okay.\n");                                                    \
222     }                                                                             \
223     if (usepkcs11) {                                                              \
224         printf("Testing %s using pkcs11 implementation...\n", name_c);            \
225         rv = ectest_curve_pkcs11(name_v, iterations, numThreads);                 \
226         if (rv != SECSuccess)                                                     \
227             goto cleanup;                                                         \
228         printf("... okay.\n");                                                    \
229     }
230 
231 #define PK11_SETATTRS(x, id, v, l) \
232     (x)->type = (id);              \
233     (x)->pValue = (v);             \
234     (x)->ulValueLen = (l);
235 
236 SECStatus
PKCS11_Derive(CK_SESSION_HANDLE session,CK_OBJECT_HANDLE * hKey,CK_MECHANISM * pMech,int * dummy)237 PKCS11_Derive(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE *hKey,
238               CK_MECHANISM *pMech, int *dummy)
239 {
240     CK_RV crv;
241     CK_OBJECT_HANDLE newKey;
242     CK_BBOOL cktrue = CK_TRUE;
243     CK_OBJECT_CLASS keyClass = CKO_SECRET_KEY;
244     CK_KEY_TYPE keyType = CKK_GENERIC_SECRET;
245     CK_ATTRIBUTE keyTemplate[3];
246     CK_ATTRIBUTE *attrs = keyTemplate;
247 
248     PK11_SETATTRS(attrs, CKA_CLASS, &keyClass, sizeof(keyClass));
249     attrs++;
250     PK11_SETATTRS(attrs, CKA_KEY_TYPE, &keyType, sizeof(keyType));
251     attrs++;
252     PK11_SETATTRS(attrs, CKA_DERIVE, &cktrue, 1);
253     attrs++;
254 
255     crv = NSC_DeriveKey(session, pMech, *hKey, keyTemplate, 3, &newKey);
256     if (crv != CKR_OK) {
257         printf("Derive Failed CK_RV=0x%x\n", (int)crv);
258         return SECFailure;
259     }
260     return SECSuccess;
261 }
262 
263 SECStatus
PKCS11_Sign(CK_SESSION_HANDLE session,CK_OBJECT_HANDLE * hKey,SECItem * sig,SECItem * digest)264 PKCS11_Sign(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE *hKey,
265             SECItem *sig, SECItem *digest)
266 {
267     CK_RV crv;
268     CK_MECHANISM mech;
269     CK_ULONG sigLen = sig->len;
270 
271     mech.mechanism = CKM_ECDSA;
272     mech.pParameter = NULL;
273     mech.ulParameterLen = 0;
274 
275     crv = NSC_SignInit(session, &mech, *hKey);
276     if (crv != CKR_OK) {
277         printf("Sign Failed CK_RV=0x%x\n", (int)crv);
278         return SECFailure;
279     }
280     crv = NSC_Sign(session, digest->data, digest->len, sig->data, &sigLen);
281     if (crv != CKR_OK) {
282         printf("Sign Failed CK_RV=0x%x\n", (int)crv);
283         return SECFailure;
284     }
285     sig->len = (unsigned int)sigLen;
286     return SECSuccess;
287 }
288 
289 SECStatus
PKCS11_Verify(CK_SESSION_HANDLE session,CK_OBJECT_HANDLE * hKey,SECItem * sig,SECItem * digest)290 PKCS11_Verify(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE *hKey,
291               SECItem *sig, SECItem *digest)
292 {
293     CK_RV crv;
294     CK_MECHANISM mech;
295 
296     mech.mechanism = CKM_ECDSA;
297     mech.pParameter = NULL;
298     mech.ulParameterLen = 0;
299 
300     crv = NSC_VerifyInit(session, &mech, *hKey);
301     if (crv != CKR_OK) {
302         printf("Verify Failed CK_RV=0x%x\n", (int)crv);
303         return SECFailure;
304     }
305     crv = NSC_Verify(session, digest->data, digest->len, sig->data, sig->len);
306     if (crv != CKR_OK) {
307         printf("Verify Failed CK_RV=0x%x\n", (int)crv);
308         return SECFailure;
309     }
310     return SECSuccess;
311 }
312 
313 /* Performs basic tests of elliptic curve cryptography over prime fields.
314  * If tests fail, then it prints an error message, aborts, and returns an
315  * error code. Otherwise, returns 0. */
316 SECStatus
ectest_curve_pkcs11(ECCurveName curve,int iterations,int numThreads)317 ectest_curve_pkcs11(ECCurveName curve, int iterations, int numThreads)
318 {
319     CK_OBJECT_HANDLE ecPriv;
320     CK_OBJECT_HANDLE ecPub;
321     CK_SESSION_HANDLE session;
322     SECItem sig;
323     SECItem digest;
324     SECKEYECParams ecParams;
325     CK_MECHANISM mech;
326     CK_ECDH1_DERIVE_PARAMS ecdh_params;
327     unsigned char sigData[256];
328     unsigned char digestData[20];
329     unsigned char pubKeyData[256];
330     PRLock *lock = NULL;
331     double signRate, deriveRate = 0;
332     CK_ATTRIBUTE template;
333     SECStatus rv;
334     CK_RV crv;
335 
336     ecParams.data = NULL;
337     ecParams.len = 0;
338     rv = SECU_ecName2params(curve, &ecParams);
339     if (rv != SECSuccess) {
340         goto cleanup;
341     }
342 
343     crv = NSC_OpenSession(1, CKF_SERIAL_SESSION, NULL, 0, &session);
344     if (crv != CKR_OK) {
345         printf("OpenSession Failed CK_RV=0x%x\n", (int)crv);
346         return SECFailure;
347     }
348 
349     PORT_Memset(digestData, 0xa5, sizeof(digestData));
350     digest.data = digestData;
351     digest.len = sizeof(digestData);
352     sig.data = sigData;
353     sig.len = sizeof(sigData);
354 
355     template.type = CKA_EC_PARAMS;
356     template.pValue = ecParams.data;
357     template.ulValueLen = ecParams.len;
358     mech.mechanism = CKM_EC_KEY_PAIR_GEN;
359     mech.pParameter = NULL;
360     mech.ulParameterLen = 0;
361     crv = NSC_GenerateKeyPair(session, &mech,
362                               &template, 1, NULL, 0, &ecPub, &ecPriv);
363     if (crv != CKR_OK) {
364         printf("GenerateKeyPair Failed CK_RV=0x%x\n", (int)crv);
365         return SECFailure;
366     }
367 
368     template.type = CKA_EC_POINT;
369     template.pValue = pubKeyData;
370     template.ulValueLen = sizeof(pubKeyData);
371     crv = NSC_GetAttributeValue(session, ecPub, &template, 1);
372     if (crv != CKR_OK) {
373         printf("GenerateKeyPair Failed CK_RV=0x%x\n", (int)crv);
374         return SECFailure;
375     }
376 
377     ecdh_params.kdf = CKD_NULL;
378     ecdh_params.ulSharedDataLen = 0;
379     ecdh_params.pSharedData = NULL;
380     ecdh_params.ulPublicDataLen = template.ulValueLen;
381     ecdh_params.pPublicData = template.pValue;
382 
383     mech.mechanism = CKM_ECDH1_DERIVE;
384     mech.pParameter = (void *)&ecdh_params;
385     mech.ulParameterLen = sizeof(ecdh_params);
386 
387     lock = PR_NewLock();
388 
389     if (ecCurve_map[curve]->usage & KU_KEY_AGREEMENT) {
390         rv = M_TimeOperation(PKCS11Thread, (op_func)PKCS11_Derive, "ECDH_Derive",
391                              &ecPriv, &mech, NULL, iterations, numThreads,
392                              lock, session, 0, &deriveRate);
393         if (rv != SECSuccess) {
394             goto cleanup;
395         }
396     }
397 
398     if (ecCurve_map[curve]->usage & KU_DIGITAL_SIGNATURE) {
399         rv = M_TimeOperation(PKCS11Thread, (op_func)PKCS11_Sign, "ECDSA_Sign",
400                              (void *)&ecPriv, &sig, &digest, iterations, numThreads,
401                              lock, session, 1, &signRate);
402         if (rv != SECSuccess) {
403             goto cleanup;
404         }
405         printf("        ECDHE max rate = %.2f\n", (deriveRate + signRate) / 4.0);
406         /* get a signature */
407         rv = PKCS11_Sign(session, &ecPriv, &sig, &digest);
408         if (rv != SECSuccess) {
409             goto cleanup;
410         }
411         rv = M_TimeOperation(PKCS11Thread, (op_func)PKCS11_Verify, "ECDSA_Verify",
412                              (void *)&ecPub, &sig, &digest, iterations, numThreads,
413                              lock, session, 0, NULL);
414         if (rv != SECSuccess) {
415             goto cleanup;
416         }
417     }
418 
419 cleanup:
420     if (lock) {
421         PR_DestroyLock(lock);
422     }
423     return rv;
424 }
425 
426 SECStatus
ECDH_DeriveWrap(ECPrivateKey * priv,ECPublicKey * pub,int * dummy)427 ECDH_DeriveWrap(ECPrivateKey *priv, ECPublicKey *pub, int *dummy)
428 {
429     SECItem secret;
430     unsigned char secretData[256];
431     SECStatus rv;
432 
433     secret.data = secretData;
434     secret.len = sizeof(secretData);
435 
436     rv = ECDH_Derive(&pub->publicValue, &pub->ecParams,
437                      &priv->privateValue, 0, &secret);
438     SECITEM_FreeItem(&secret, PR_FALSE);
439     return rv;
440 }
441 
442 /* Performs basic tests of elliptic curve cryptography over prime fields.
443  * If tests fail, then it prints an error message, aborts, and returns an
444  * error code. Otherwise, returns 0. */
445 SECStatus
ectest_curve_freebl(ECCurveName curve,int iterations,int numThreads,ECFieldType fieldType)446 ectest_curve_freebl(ECCurveName curve, int iterations, int numThreads,
447                     ECFieldType fieldType)
448 {
449     ECParams ecParams = { 0 };
450     ECPrivateKey *ecPriv = NULL;
451     ECPublicKey ecPub;
452     SECItem sig;
453     SECItem digest;
454     unsigned char sigData[256];
455     unsigned char digestData[20];
456     double signRate, deriveRate = 0;
457     SECStatus rv = SECFailure;
458     PLArenaPool *arena;
459     SECItem ecEncodedParams = { siBuffer, NULL, 0 };
460 
461     arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
462     if (!arena) {
463         return SECFailure;
464     }
465 
466     if ((curve < ECCurve_noName) || (curve > ECCurve_pastLastCurve)) {
467         PORT_FreeArena(arena, PR_FALSE);
468         return SECFailure;
469     }
470 
471     rv = SECU_ecName2params(curve, &ecEncodedParams);
472     if (rv != SECSuccess) {
473         goto cleanup;
474     }
475     EC_FillParams(arena, &ecEncodedParams, &ecParams);
476 
477     PORT_Memset(digestData, 0xa5, sizeof(digestData));
478     digest.data = digestData;
479     digest.len = sizeof(digestData);
480     sig.data = sigData;
481     sig.len = sizeof(sigData);
482 
483     rv = EC_NewKey(&ecParams, &ecPriv);
484     if (rv != SECSuccess) {
485         goto cleanup;
486     }
487     ecPub.ecParams = ecParams;
488     ecPub.publicValue = ecPriv->publicValue;
489 
490     if (ecCurve_map[curve]->usage & KU_KEY_AGREEMENT) {
491         rv = M_TimeOperation(genericThread, (op_func)ECDH_DeriveWrap, "ECDH_Derive",
492                              ecPriv, &ecPub, NULL, iterations, numThreads, 0, 0, 0, &deriveRate);
493         if (rv != SECSuccess) {
494             goto cleanup;
495         }
496     }
497 
498     if (ecCurve_map[curve]->usage & KU_DIGITAL_SIGNATURE) {
499         rv = M_TimeOperation(genericThread, (op_func)ECDSA_SignDigest, "ECDSA_Sign",
500                              ecPriv, &sig, &digest, iterations, numThreads, 0, 0, 1, &signRate);
501         if (rv != SECSuccess)
502             goto cleanup;
503         printf("        ECDHE max rate = %.2f\n", (deriveRate + signRate) / 4.0);
504         rv = ECDSA_SignDigest(ecPriv, &sig, &digest);
505         if (rv != SECSuccess) {
506             goto cleanup;
507         }
508         rv = M_TimeOperation(genericThread, (op_func)ECDSA_VerifyDigest, "ECDSA_Verify",
509                              &ecPub, &sig, &digest, iterations, numThreads, 0, 0, 0, NULL);
510         if (rv != SECSuccess) {
511             goto cleanup;
512         }
513     }
514 
515 cleanup:
516     SECITEM_FreeItem(&ecEncodedParams, PR_FALSE);
517     PORT_FreeArena(arena, PR_FALSE);
518     if (ecPriv) {
519         PORT_FreeArena(ecPriv->ecParams.arena, PR_FALSE);
520     }
521     return rv;
522 }
523 
524 /* Prints help information. */
525 void
printUsage(char * prog)526 printUsage(char *prog)
527 {
528     printf("Usage: %s [-i iterations] [-t threads ] [-ans] [-fp] [-Al]\n"
529            "-a: ansi\n-n: nist\n-s: secp\n-f: usefreebl\n-p: usepkcs11\n-A: all\n",
530            prog);
531 }
532 
533 /* Performs tests of elliptic curve cryptography over prime fields If
534  * tests fail, then it prints an error message, aborts, and returns an
535  * error code. Otherwise, returns 0. */
536 int
main(int argv,char ** argc)537 main(int argv, char **argc)
538 {
539     int ansi = 0;
540     int nist = 0;
541     int secp = 0;
542     int usefreebl = 0;
543     int usepkcs11 = 0;
544     int i;
545     SECStatus rv = SECSuccess;
546     int iterations = 100;
547     int numThreads = 1;
548 
549     const CK_C_INITIALIZE_ARGS pk11args = {
550         NULL, NULL, NULL, NULL, CKF_LIBRARY_CANT_CREATE_OS_THREADS,
551         (void *)"flags=readOnly,noCertDB,noModDB", NULL
552     };
553 
554     /* read command-line arguments */
555     for (i = 1; i < argv; i++) {
556         if (PL_strcasecmp(argc[i], "-i") == 0) {
557             i++;
558             iterations = atoi(argc[i]);
559         } else if (PL_strcasecmp(argc[i], "-t") == 0) {
560             i++;
561             numThreads = atoi(argc[i]);
562         } else if (PL_strcasecmp(argc[i], "-A") == 0) {
563             ansi = nist = secp = 1;
564             usepkcs11 = usefreebl = 1;
565         } else if (PL_strcasecmp(argc[i], "-a") == 0) {
566             ansi = 1;
567         } else if (PL_strcasecmp(argc[i], "-n") == 0) {
568             nist = 1;
569         } else if (PL_strcasecmp(argc[i], "-s") == 0) {
570             secp = 1;
571         } else if (PL_strcasecmp(argc[i], "-p") == 0) {
572             usepkcs11 = 1;
573         } else if (PL_strcasecmp(argc[i], "-f") == 0) {
574             usefreebl = 1;
575         } else {
576             printUsage(argc[0]);
577             return 0;
578         }
579     }
580 
581     if ((ansi | nist | secp) == 0) {
582         nist = 1;
583     }
584     if ((usepkcs11 | usefreebl) == 0) {
585         usefreebl = 1;
586     }
587 
588     rv = RNG_RNGInit();
589     if (rv != SECSuccess) {
590         SECU_PrintError("Error:", "RNG_RNGInit");
591         return -1;
592     }
593     RNG_SystemInfoForRNG();
594 
595     rv = SECOID_Init();
596     if (rv != SECSuccess) {
597         SECU_PrintError("Error:", "SECOID_Init");
598         goto cleanup;
599     }
600 
601     if (usepkcs11) {
602         CK_RV crv = NSC_Initialize((CK_VOID_PTR)&pk11args);
603         if (crv != CKR_OK) {
604             fprintf(stderr, "NSC_Initialize failed crv=0x%x\n", (unsigned int)crv);
605             return SECFailure;
606         }
607     }
608 
609     /* specific arithmetic tests */
610     if (nist) {
611         ECTEST_NAMED_GFP("NIST-P256", ECCurve_NIST_P256);
612         ECTEST_NAMED_GFP("NIST-P384", ECCurve_NIST_P384);
613         ECTEST_NAMED_GFP("NIST-P521", ECCurve_NIST_P521);
614         ECTEST_NAMED_CUSTOM("Curve25519", ECCurve25519);
615     }
616 
617 cleanup:
618     rv |= SECOID_Shutdown();
619     RNG_RNGShutdown();
620 
621     if (rv != SECSuccess) {
622         printf("Error: exiting with error value\n");
623     }
624     return rv;
625 }
626