1.1.12
by Devid Antonio Filoni
Import upstream version 1.8.1.16+nobinonly |
1 |
/*
|
2 |
*
|
|
3 |
* ***** BEGIN LICENSE BLOCK *****
|
|
4 |
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
5 |
*
|
|
6 |
* The contents of this file are subject to the Mozilla Public License Version
|
|
7 |
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
8 |
* the License. You may obtain a copy of the License at
|
|
9 |
* http://www.mozilla.org/MPL/
|
|
10 |
*
|
|
11 |
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
12 |
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
13 |
* for the specific language governing rights and limitations under the
|
|
14 |
* License.
|
|
15 |
*
|
|
16 |
* The Original Code is the Netscape security libraries.
|
|
17 |
*
|
|
18 |
* The Initial Developer of the Original Code is
|
|
19 |
* Netscape Communications Corporation.
|
|
20 |
* Portions created by the Initial Developer are Copyright (C) 1994-2000
|
|
21 |
* the Initial Developer. All Rights Reserved.
|
|
22 |
*
|
|
23 |
* Contributor(s):
|
|
24 |
*
|
|
25 |
* Alternatively, the contents of this file may be used under the terms of
|
|
26 |
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
27 |
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
28 |
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
29 |
* of those above. If you wish to allow use of your version of this file only
|
|
30 |
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
31 |
* use your version of this file under the terms of the MPL, indicate your
|
|
32 |
* decision by deleting the provisions above and replace them with the notice
|
|
33 |
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
34 |
* the provisions above, a recipient may use your version of this file under
|
|
35 |
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
36 |
*
|
|
37 |
* ***** END LICENSE BLOCK ***** */
|
|
38 |
/* $Id: dsa.c,v 1.18 2005/10/12 00:48:25 wtchang%redhat.com Exp $ */
|
|
39 |
||
40 |
#include "secerr.h" |
|
41 |
||
42 |
#include "prtypes.h" |
|
43 |
#include "prinit.h" |
|
44 |
#include "blapi.h" |
|
45 |
#include "nssilock.h" |
|
46 |
#include "secitem.h" |
|
47 |
#include "blapi.h" |
|
48 |
#include "mpi.h" |
|
49 |
#include "secmpi.h" |
|
50 |
||
51 |
/* XXX to be replaced by define in blapit.h */
|
|
52 |
#define NSS_FREEBL_DSA_DEFAULT_CHUNKSIZE 2048
|
|
53 |
||
54 |
/* DSA-specific random number function defined in prng_fips1861.c. */
|
|
55 |
extern SECStatus |
|
56 |
DSA_GenerateGlobalRandomBytes(void *dest, size_t len, const unsigned char *q); |
|
57 |
||
58 |
static void translate_mpi_error(mp_err err) |
|
59 |
{
|
|
60 |
MP_TO_SEC_ERROR(err); |
|
61 |
}
|
|
62 |
||
63 |
SECStatus
|
|
64 |
dsa_NewKey(const PQGParams *params, DSAPrivateKey **privKey, |
|
65 |
const unsigned char *xb) |
|
66 |
{
|
|
67 |
mp_int p, g; |
|
68 |
mp_int x, y; |
|
69 |
mp_err err; |
|
70 |
PRArenaPool *arena; |
|
71 |
DSAPrivateKey *key; |
|
72 |
/* Check args. */
|
|
73 |
if (!params || !privKey) { |
|
74 |
PORT_SetError(SEC_ERROR_INVALID_ARGS); |
|
75 |
return SECFailure; |
|
76 |
}
|
|
77 |
/* Initialize an arena for the DSA key. */
|
|
78 |
arena = PORT_NewArena(NSS_FREEBL_DSA_DEFAULT_CHUNKSIZE); |
|
79 |
if (!arena) { |
|
80 |
PORT_SetError(SEC_ERROR_NO_MEMORY); |
|
81 |
return SECFailure; |
|
82 |
}
|
|
83 |
key = (DSAPrivateKey *)PORT_ArenaZAlloc(arena, sizeof(DSAPrivateKey)); |
|
84 |
if (!key) { |
|
85 |
PORT_SetError(SEC_ERROR_NO_MEMORY); |
|
86 |
PORT_FreeArena(arena, PR_TRUE); |
|
87 |
return SECFailure; |
|
88 |
}
|
|
89 |
key->params.arena = arena; |
|
90 |
/* Initialize MPI integers. */
|
|
91 |
MP_DIGITS(&p) = 0; |
|
92 |
MP_DIGITS(&g) = 0; |
|
93 |
MP_DIGITS(&x) = 0; |
|
94 |
MP_DIGITS(&y) = 0; |
|
95 |
CHECK_MPI_OK( mp_init(&p) ); |
|
96 |
CHECK_MPI_OK( mp_init(&g) ); |
|
97 |
CHECK_MPI_OK( mp_init(&x) ); |
|
98 |
CHECK_MPI_OK( mp_init(&y) ); |
|
99 |
/* Copy over the PQG params */
|
|
100 |
CHECK_MPI_OK( SECITEM_CopyItem(arena, &key->params.prime, |
|
101 |
¶ms->prime) ); |
|
102 |
CHECK_MPI_OK( SECITEM_CopyItem(arena, &key->params.subPrime, |
|
103 |
¶ms->subPrime) ); |
|
104 |
CHECK_MPI_OK( SECITEM_CopyItem(arena, &key->params.base, ¶ms->base) ); |
|
105 |
/* Convert stored p, g, and received x into MPI integers. */
|
|
106 |
SECITEM_TO_MPINT(params->prime, &p); |
|
107 |
SECITEM_TO_MPINT(params->base, &g); |
|
108 |
OCTETS_TO_MPINT(xb, &x, DSA_SUBPRIME_LEN); |
|
109 |
/* Store x in private key */
|
|
110 |
SECITEM_AllocItem(arena, &key->privateValue, DSA_SUBPRIME_LEN); |
|
111 |
memcpy(key->privateValue.data, xb, DSA_SUBPRIME_LEN); |
|
112 |
/* Compute public key y = g**x mod p */
|
|
113 |
CHECK_MPI_OK( mp_exptmod(&g, &x, &p, &y) ); |
|
114 |
/* Store y in public key */
|
|
115 |
MPINT_TO_SECITEM(&y, &key->publicValue, arena); |
|
116 |
*privKey = key; |
|
117 |
key = NULL; |
|
118 |
cleanup: |
|
119 |
mp_clear(&p); |
|
120 |
mp_clear(&g); |
|
121 |
mp_clear(&x); |
|
122 |
mp_clear(&y); |
|
123 |
if (key) |
|
124 |
PORT_FreeArena(key->params.arena, PR_TRUE); |
|
125 |
if (err) { |
|
126 |
translate_mpi_error(err); |
|
127 |
return SECFailure; |
|
128 |
}
|
|
129 |
return SECSuccess; |
|
130 |
}
|
|
131 |
||
132 |
/*
|
|
133 |
** Generate and return a new DSA public and private key pair,
|
|
134 |
** both of which are encoded into a single DSAPrivateKey struct.
|
|
135 |
** "params" is a pointer to the PQG parameters for the domain
|
|
136 |
** Uses a random seed.
|
|
137 |
*/
|
|
138 |
SECStatus
|
|
139 |
DSA_NewKey(const PQGParams *params, DSAPrivateKey **privKey) |
|
140 |
{
|
|
141 |
SECStatus rv; |
|
142 |
unsigned char seed[DSA_SUBPRIME_LEN]; |
|
143 |
int retries = 10; |
|
144 |
int i; |
|
145 |
PRBool good; |
|
146 |
||
147 |
do { |
|
148 |
/* Generate seed bytes for x according to FIPS 186-1 appendix 3 */
|
|
149 |
if (DSA_GenerateGlobalRandomBytes(seed, DSA_SUBPRIME_LEN, |
|
150 |
params->subPrime.data)) |
|
151 |
return SECFailure; |
|
152 |
/* Disallow values of 0 and 1 for x. */
|
|
153 |
good = PR_FALSE; |
|
154 |
for (i = 0; i < DSA_SUBPRIME_LEN-1; i++) { |
|
155 |
if (seed[i] != 0) { |
|
156 |
good = PR_TRUE; |
|
157 |
break; |
|
158 |
}
|
|
159 |
}
|
|
160 |
if (!good && seed[i] > 1) { |
|
161 |
good = PR_TRUE; |
|
162 |
}
|
|
163 |
} while (!good && --retries > 0); |
|
164 |
||
165 |
if (!good) { |
|
166 |
PORT_SetError(SEC_ERROR_NEED_RANDOM); |
|
167 |
return SECFailure; |
|
168 |
}
|
|
169 |
||
170 |
/* Generate a new DSA key using random seed. */
|
|
171 |
rv = dsa_NewKey(params, privKey, seed); |
|
172 |
return rv; |
|
173 |
}
|
|
174 |
||
175 |
/* For FIPS compliance testing. Seed must be exactly 20 bytes long */
|
|
176 |
SECStatus
|
|
177 |
DSA_NewKeyFromSeed(const PQGParams *params, |
|
178 |
const unsigned char *seed, |
|
179 |
DSAPrivateKey **privKey) |
|
180 |
{
|
|
181 |
SECStatus rv; |
|
182 |
rv = dsa_NewKey(params, privKey, seed); |
|
183 |
return rv; |
|
184 |
}
|
|
185 |
||
186 |
static SECStatus |
|
187 |
dsa_SignDigest(DSAPrivateKey *key, SECItem *signature, const SECItem *digest, |
|
188 |
const unsigned char *kb) |
|
189 |
{
|
|
190 |
mp_int p, q, g; /* PQG parameters */ |
|
191 |
mp_int x, k; /* private key & pseudo-random integer */ |
|
192 |
mp_int r, s; /* tuple (r, s) is signature) */ |
|
193 |
mp_err err = MP_OKAY; |
|
194 |
SECStatus rv = SECSuccess; |
|
195 |
||
196 |
/* FIPS-compliance dictates that digest is a SHA1 hash. */
|
|
197 |
/* Check args. */
|
|
198 |
if (!key || !signature || !digest || |
|
199 |
(signature->len < DSA_SIGNATURE_LEN) || |
|
200 |
(digest->len != SHA1_LENGTH)) { |
|
201 |
PORT_SetError(SEC_ERROR_INVALID_ARGS); |
|
202 |
return SECFailure; |
|
203 |
}
|
|
204 |
||
205 |
/* Initialize MPI integers. */
|
|
206 |
MP_DIGITS(&p) = 0; |
|
207 |
MP_DIGITS(&q) = 0; |
|
208 |
MP_DIGITS(&g) = 0; |
|
209 |
MP_DIGITS(&x) = 0; |
|
210 |
MP_DIGITS(&k) = 0; |
|
211 |
MP_DIGITS(&r) = 0; |
|
212 |
MP_DIGITS(&s) = 0; |
|
213 |
CHECK_MPI_OK( mp_init(&p) ); |
|
214 |
CHECK_MPI_OK( mp_init(&q) ); |
|
215 |
CHECK_MPI_OK( mp_init(&g) ); |
|
216 |
CHECK_MPI_OK( mp_init(&x) ); |
|
217 |
CHECK_MPI_OK( mp_init(&k) ); |
|
218 |
CHECK_MPI_OK( mp_init(&r) ); |
|
219 |
CHECK_MPI_OK( mp_init(&s) ); |
|
220 |
/*
|
|
221 |
** Convert stored PQG and private key into MPI integers.
|
|
222 |
*/
|
|
223 |
SECITEM_TO_MPINT(key->params.prime, &p); |
|
224 |
SECITEM_TO_MPINT(key->params.subPrime, &q); |
|
225 |
SECITEM_TO_MPINT(key->params.base, &g); |
|
226 |
SECITEM_TO_MPINT(key->privateValue, &x); |
|
227 |
OCTETS_TO_MPINT(kb, &k, DSA_SUBPRIME_LEN); |
|
228 |
/*
|
|
229 |
** FIPS 186-1, Section 5, Step 1
|
|
230 |
**
|
|
231 |
** r = (g**k mod p) mod q
|
|
232 |
*/
|
|
233 |
CHECK_MPI_OK( mp_exptmod(&g, &k, &p, &r) ); /* r = g**k mod p */ |
|
234 |
CHECK_MPI_OK( mp_mod(&r, &q, &r) ); /* r = r mod q */ |
|
235 |
/*
|
|
236 |
** FIPS 186-1, Section 5, Step 2
|
|
237 |
**
|
|
238 |
** s = (k**-1 * (SHA1(M) + x*r)) mod q
|
|
239 |
*/
|
|
240 |
SECITEM_TO_MPINT(*digest, &s); /* s = SHA1(M) */ |
|
241 |
CHECK_MPI_OK( mp_invmod(&k, &q, &k) ); /* k = k**-1 mod q */ |
|
242 |
CHECK_MPI_OK( mp_mulmod(&x, &r, &q, &x) ); /* x = x * r mod q */ |
|
243 |
CHECK_MPI_OK( mp_addmod(&s, &x, &q, &s) ); /* s = s + x mod q */ |
|
244 |
CHECK_MPI_OK( mp_mulmod(&s, &k, &q, &s) ); /* s = s * k mod q */ |
|
245 |
/*
|
|
246 |
** verify r != 0 and s != 0
|
|
247 |
** mentioned as optional in FIPS 186-1.
|
|
248 |
*/
|
|
249 |
if (mp_cmp_z(&r) == 0 || mp_cmp_z(&s) == 0) { |
|
250 |
PORT_SetError(SEC_ERROR_NEED_RANDOM); |
|
251 |
rv = SECFailure; |
|
252 |
goto cleanup; |
|
253 |
}
|
|
254 |
/*
|
|
255 |
** Step 4
|
|
256 |
**
|
|
257 |
** Signature is tuple (r, s)
|
|
258 |
*/
|
|
259 |
err = mp_to_fixlen_octets(&r, signature->data, DSA_SUBPRIME_LEN); |
|
260 |
if (err < 0) goto cleanup; |
|
261 |
err = mp_to_fixlen_octets(&s, signature->data + DSA_SUBPRIME_LEN, |
|
262 |
DSA_SUBPRIME_LEN); |
|
263 |
if (err < 0) goto cleanup; |
|
264 |
err = MP_OKAY; |
|
265 |
signature->len = DSA_SIGNATURE_LEN; |
|
266 |
cleanup: |
|
267 |
mp_clear(&p); |
|
268 |
mp_clear(&q); |
|
269 |
mp_clear(&g); |
|
270 |
mp_clear(&x); |
|
271 |
mp_clear(&k); |
|
272 |
mp_clear(&r); |
|
273 |
mp_clear(&s); |
|
274 |
if (err) { |
|
275 |
translate_mpi_error(err); |
|
276 |
rv = SECFailure; |
|
277 |
}
|
|
278 |
return rv; |
|
279 |
}
|
|
280 |
||
281 |
/* signature is caller-supplied buffer of at least 40 bytes.
|
|
282 |
** On input, signature->len == size of buffer to hold signature.
|
|
283 |
** digest->len == size of digest.
|
|
284 |
** On output, signature->len == size of signature in buffer.
|
|
285 |
** Uses a random seed.
|
|
286 |
*/
|
|
287 |
SECStatus
|
|
288 |
DSA_SignDigest(DSAPrivateKey *key, SECItem *signature, const SECItem *digest) |
|
289 |
{
|
|
290 |
SECStatus rv; |
|
291 |
int retries = 10; |
|
292 |
unsigned char kSeed[DSA_SUBPRIME_LEN]; |
|
293 |
int i; |
|
294 |
PRBool good; |
|
295 |
||
296 |
PORT_SetError(0); |
|
297 |
do { |
|
298 |
rv = DSA_GenerateGlobalRandomBytes(kSeed, DSA_SUBPRIME_LEN, |
|
299 |
key->params.subPrime.data); |
|
300 |
if (rv != SECSuccess) |
|
301 |
break; |
|
302 |
/* Disallow a value of 0 for k. */
|
|
303 |
good = PR_FALSE; |
|
304 |
for (i = 0; i < DSA_SUBPRIME_LEN; i++) { |
|
305 |
if (kSeed[i] != 0) { |
|
306 |
good = PR_TRUE; |
|
307 |
break; |
|
308 |
}
|
|
309 |
}
|
|
310 |
if (!good) { |
|
311 |
PORT_SetError(SEC_ERROR_NEED_RANDOM); |
|
312 |
rv = SECFailure; |
|
313 |
continue; |
|
314 |
}
|
|
315 |
rv = dsa_SignDigest(key, signature, digest, kSeed); |
|
316 |
} while (rv != SECSuccess && PORT_GetError() == SEC_ERROR_NEED_RANDOM && |
|
317 |
--retries > 0); |
|
318 |
return rv; |
|
319 |
}
|
|
320 |
||
321 |
/* For FIPS compliance testing. Seed must be exactly 20 bytes. */
|
|
322 |
SECStatus
|
|
323 |
DSA_SignDigestWithSeed(DSAPrivateKey * key, |
|
324 |
SECItem * signature, |
|
325 |
const SECItem * digest, |
|
326 |
const unsigned char * seed) |
|
327 |
{
|
|
328 |
SECStatus rv; |
|
329 |
rv = dsa_SignDigest(key, signature, digest, seed); |
|
330 |
return rv; |
|
331 |
}
|
|
332 |
||
333 |
/* signature is caller-supplied buffer of at least 20 bytes.
|
|
334 |
** On input, signature->len == size of buffer to hold signature.
|
|
335 |
** digest->len == size of digest.
|
|
336 |
*/
|
|
337 |
SECStatus
|
|
338 |
DSA_VerifyDigest(DSAPublicKey *key, const SECItem *signature, |
|
339 |
const SECItem *digest) |
|
340 |
{
|
|
341 |
/* FIPS-compliance dictates that digest is a SHA1 hash. */
|
|
342 |
mp_int p, q, g; /* PQG parameters */ |
|
343 |
mp_int r_, s_; /* tuple (r', s') is received signature) */ |
|
344 |
mp_int u1, u2, v, w; /* intermediate values used in verification */ |
|
345 |
mp_int y; /* public key */ |
|
346 |
mp_err err; |
|
347 |
SECStatus verified = SECFailure; |
|
348 |
||
349 |
/* Check args. */
|
|
350 |
if (!key || !signature || !digest || |
|
351 |
(signature->len != DSA_SIGNATURE_LEN) || |
|
352 |
(digest->len != SHA1_LENGTH)) { |
|
353 |
PORT_SetError(SEC_ERROR_INVALID_ARGS); |
|
354 |
return SECFailure; |
|
355 |
}
|
|
356 |
/* Initialize MPI integers. */
|
|
357 |
MP_DIGITS(&p) = 0; |
|
358 |
MP_DIGITS(&q) = 0; |
|
359 |
MP_DIGITS(&g) = 0; |
|
360 |
MP_DIGITS(&y) = 0; |
|
361 |
MP_DIGITS(&r_) = 0; |
|
362 |
MP_DIGITS(&s_) = 0; |
|
363 |
MP_DIGITS(&u1) = 0; |
|
364 |
MP_DIGITS(&u2) = 0; |
|
365 |
MP_DIGITS(&v) = 0; |
|
366 |
MP_DIGITS(&w) = 0; |
|
367 |
CHECK_MPI_OK( mp_init(&p) ); |
|
368 |
CHECK_MPI_OK( mp_init(&q) ); |
|
369 |
CHECK_MPI_OK( mp_init(&g) ); |
|
370 |
CHECK_MPI_OK( mp_init(&y) ); |
|
371 |
CHECK_MPI_OK( mp_init(&r_) ); |
|
372 |
CHECK_MPI_OK( mp_init(&s_) ); |
|
373 |
CHECK_MPI_OK( mp_init(&u1) ); |
|
374 |
CHECK_MPI_OK( mp_init(&u2) ); |
|
375 |
CHECK_MPI_OK( mp_init(&v) ); |
|
376 |
CHECK_MPI_OK( mp_init(&w) ); |
|
377 |
/*
|
|
378 |
** Convert stored PQG and public key into MPI integers.
|
|
379 |
*/
|
|
380 |
SECITEM_TO_MPINT(key->params.prime, &p); |
|
381 |
SECITEM_TO_MPINT(key->params.subPrime, &q); |
|
382 |
SECITEM_TO_MPINT(key->params.base, &g); |
|
383 |
SECITEM_TO_MPINT(key->publicValue, &y); |
|
384 |
/*
|
|
385 |
** Convert received signature (r', s') into MPI integers.
|
|
386 |
*/
|
|
387 |
OCTETS_TO_MPINT(signature->data, &r_, DSA_SUBPRIME_LEN); |
|
388 |
OCTETS_TO_MPINT(signature->data + DSA_SUBPRIME_LEN, &s_, DSA_SUBPRIME_LEN); |
|
389 |
/*
|
|
390 |
** Verify that 0 < r' < q and 0 < s' < q
|
|
391 |
*/
|
|
392 |
if (mp_cmp_z(&r_) <= 0 || mp_cmp_z(&s_) <= 0 || |
|
393 |
mp_cmp(&r_, &q) >= 0 || mp_cmp(&s_, &q) >= 0) { |
|
394 |
/* err is zero here. */
|
|
395 |
PORT_SetError(SEC_ERROR_BAD_SIGNATURE); |
|
396 |
goto cleanup; /* will return verified == SECFailure */ |
|
397 |
}
|
|
398 |
/*
|
|
399 |
** FIPS 186-1, Section 6, Step 1
|
|
400 |
**
|
|
401 |
** w = (s')**-1 mod q
|
|
402 |
*/
|
|
403 |
CHECK_MPI_OK( mp_invmod(&s_, &q, &w) ); /* w = (s')**-1 mod q */ |
|
404 |
/*
|
|
405 |
** FIPS 186-1, Section 6, Step 2
|
|
406 |
**
|
|
407 |
** u1 = ((SHA1(M')) * w) mod q
|
|
408 |
*/
|
|
409 |
SECITEM_TO_MPINT(*digest, &u1); /* u1 = SHA1(M') */ |
|
410 |
CHECK_MPI_OK( mp_mulmod(&u1, &w, &q, &u1) ); /* u1 = u1 * w mod q */ |
|
411 |
/*
|
|
412 |
** FIPS 186-1, Section 6, Step 3
|
|
413 |
**
|
|
414 |
** u2 = ((r') * w) mod q
|
|
415 |
*/
|
|
416 |
CHECK_MPI_OK( mp_mulmod(&r_, &w, &q, &u2) ); |
|
417 |
/*
|
|
418 |
** FIPS 186-1, Section 6, Step 4
|
|
419 |
**
|
|
420 |
** v = ((g**u1 * y**u2) mod p) mod q
|
|
421 |
*/
|
|
422 |
CHECK_MPI_OK( mp_exptmod(&g, &u1, &p, &g) ); /* g = g**u1 mod p */ |
|
423 |
CHECK_MPI_OK( mp_exptmod(&y, &u2, &p, &y) ); /* y = y**u2 mod p */ |
|
424 |
CHECK_MPI_OK( mp_mulmod(&g, &y, &p, &v) ); /* v = g * y mod p */ |
|
425 |
CHECK_MPI_OK( mp_mod(&v, &q, &v) ); /* v = v mod q */ |
|
426 |
/*
|
|
427 |
** Verification: v == r'
|
|
428 |
*/
|
|
429 |
if (mp_cmp(&v, &r_)) { |
|
430 |
PORT_SetError(SEC_ERROR_BAD_SIGNATURE); |
|
431 |
verified = SECFailure; /* Signature failed to verify. */ |
|
432 |
} else { |
|
433 |
verified = SECSuccess; /* Signature verified. */ |
|
434 |
}
|
|
435 |
cleanup: |
|
436 |
mp_clear(&p); |
|
437 |
mp_clear(&q); |
|
438 |
mp_clear(&g); |
|
439 |
mp_clear(&y); |
|
440 |
mp_clear(&r_); |
|
441 |
mp_clear(&s_); |
|
442 |
mp_clear(&u1); |
|
443 |
mp_clear(&u2); |
|
444 |
mp_clear(&v); |
|
445 |
mp_clear(&w); |
|
446 |
if (err) { |
|
447 |
translate_mpi_error(err); |
|
448 |
}
|
|
449 |
return verified; |
|
450 |
}
|