1 /*
2 * (C) 2015,2017,2018 Jack Lloyd
3 *
4 * Botan is released under the Simplified BSD License (see license.txt)
5 */
6 
7 #include <botan/ffi.h>
8 #include <botan/internal/ffi_util.h>
9 #include <botan/internal/ffi_pkey.h>
10 
11 #if defined(BOTAN_HAS_X509_CERTIFICATES)
12    #include <botan/x509cert.h>
13    #include <botan/x509path.h>
14    #include <botan/x509_crl.h>
15    #include <botan/data_src.h>
16 #endif
17 
18 extern "C" {
19 
20 using namespace Botan_FFI;
21 
22 #if defined(BOTAN_HAS_X509_CERTIFICATES)
23 
24 BOTAN_FFI_DECLARE_STRUCT(botan_x509_cert_struct, Botan::X509_Certificate, 0x8F628937);
25 
26 #endif
27 
botan_x509_cert_load_file(botan_x509_cert_t * cert_obj,const char * cert_path)28 int botan_x509_cert_load_file(botan_x509_cert_t* cert_obj, const char* cert_path)
29    {
30    if(!cert_obj || !cert_path)
31       return BOTAN_FFI_ERROR_NULL_POINTER;
32 
33 #if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
34 
35    return ffi_guard_thunk(__func__, [=]() -> int {
36       std::unique_ptr<Botan::X509_Certificate> c(new Botan::X509_Certificate(cert_path));
37       *cert_obj = new botan_x509_cert_struct(c.release());
38       return BOTAN_FFI_SUCCESS;
39       });
40 
41 #else
42    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
43 #endif
44    }
45 
botan_x509_cert_dup(botan_x509_cert_t * cert_obj,botan_x509_cert_t cert)46 int botan_x509_cert_dup(botan_x509_cert_t* cert_obj, botan_x509_cert_t cert)
47    {
48    if(!cert_obj)
49       return BOTAN_FFI_ERROR_NULL_POINTER;
50 
51 #if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
52 
53    return ffi_guard_thunk(__func__, [=]() -> int {
54       std::unique_ptr<Botan::X509_Certificate> c(new Botan::X509_Certificate(safe_get(cert)));
55       *cert_obj = new botan_x509_cert_struct(c.release());
56       return BOTAN_FFI_SUCCESS;
57       });
58 
59 #else
60    BOTAN_UNUSED(cert);
61    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
62 #endif
63    }
64 
botan_x509_cert_load(botan_x509_cert_t * cert_obj,const uint8_t cert_bits[],size_t cert_bits_len)65 int botan_x509_cert_load(botan_x509_cert_t* cert_obj, const uint8_t cert_bits[], size_t cert_bits_len)
66    {
67    if(!cert_obj || !cert_bits)
68       return BOTAN_FFI_ERROR_NULL_POINTER;
69 
70 #if defined(BOTAN_HAS_X509_CERTIFICATES)
71    return ffi_guard_thunk(__func__, [=]() -> int {
72       Botan::DataSource_Memory bits(cert_bits, cert_bits_len);
73       std::unique_ptr<Botan::X509_Certificate> c(new Botan::X509_Certificate(bits));
74       *cert_obj = new botan_x509_cert_struct(c.release());
75       return BOTAN_FFI_SUCCESS;
76       });
77 #else
78    BOTAN_UNUSED(cert_bits_len);
79    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
80 #endif
81    }
82 
botan_x509_cert_get_public_key(botan_x509_cert_t cert,botan_pubkey_t * key)83 int botan_x509_cert_get_public_key(botan_x509_cert_t cert, botan_pubkey_t* key)
84    {
85    if(key == nullptr)
86       return BOTAN_FFI_ERROR_NULL_POINTER;
87 
88    *key = nullptr;
89 
90 #if defined(BOTAN_HAS_X509_CERTIFICATES)
91    return ffi_guard_thunk(__func__, [=]() -> int {
92       std::unique_ptr<Botan::Public_Key> publicKey = safe_get(cert).load_subject_public_key();
93       *key = new botan_pubkey_struct(publicKey.release());
94       return BOTAN_FFI_SUCCESS;
95       });
96 #else
97    BOTAN_UNUSED(cert);
98    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
99 #endif
100    }
101 
botan_x509_cert_get_issuer_dn(botan_x509_cert_t cert,const char * key,size_t index,uint8_t out[],size_t * out_len)102 int botan_x509_cert_get_issuer_dn(botan_x509_cert_t cert,
103                                   const char* key, size_t index,
104                                   uint8_t out[], size_t* out_len)
105    {
106 #if defined(BOTAN_HAS_X509_CERTIFICATES)
107    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.issuer_info(key).at(index)); });
108 #else
109    BOTAN_UNUSED(cert, key, index, out, out_len);
110    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
111 #endif
112    }
113 
botan_x509_cert_get_subject_dn(botan_x509_cert_t cert,const char * key,size_t index,uint8_t out[],size_t * out_len)114 int botan_x509_cert_get_subject_dn(botan_x509_cert_t cert,
115                                    const char* key, size_t index,
116                                    uint8_t out[], size_t* out_len)
117    {
118 #if defined(BOTAN_HAS_X509_CERTIFICATES)
119    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.subject_info(key).at(index)); });
120 #else
121    BOTAN_UNUSED(cert, key, index, out, out_len);
122    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
123 #endif
124    }
125 
botan_x509_cert_to_string(botan_x509_cert_t cert,char out[],size_t * out_len)126 int botan_x509_cert_to_string(botan_x509_cert_t cert, char out[], size_t* out_len)
127    {
128 #if defined(BOTAN_HAS_X509_CERTIFICATES)
129    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.to_string()); });
130 #else
131    BOTAN_UNUSED(cert, out, out_len);
132    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
133 #endif
134    }
135 
botan_x509_cert_allowed_usage(botan_x509_cert_t cert,unsigned int key_usage)136 int botan_x509_cert_allowed_usage(botan_x509_cert_t cert, unsigned int key_usage)
137    {
138 #if defined(BOTAN_HAS_X509_CERTIFICATES)
139    return BOTAN_FFI_RETURNING(Botan::X509_Certificate, cert, c, {
140       const Botan::Key_Constraints k = static_cast<Botan::Key_Constraints>(key_usage);
141       if(c.allowed_usage(k))
142          return BOTAN_FFI_SUCCESS;
143       return 1;
144       });
145 #else
146    BOTAN_UNUSED(cert, key_usage);
147    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
148 #endif
149    }
150 
botan_x509_cert_destroy(botan_x509_cert_t cert)151 int botan_x509_cert_destroy(botan_x509_cert_t cert)
152    {
153 #if defined(BOTAN_HAS_X509_CERTIFICATES)
154    return BOTAN_FFI_CHECKED_DELETE(cert);
155 #else
156    BOTAN_UNUSED(cert);
157    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
158 #endif
159    }
160 
botan_x509_cert_get_time_starts(botan_x509_cert_t cert,char out[],size_t * out_len)161 int botan_x509_cert_get_time_starts(botan_x509_cert_t cert, char out[], size_t* out_len)
162    {
163 #if defined(BOTAN_HAS_X509_CERTIFICATES)
164    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.not_before().to_string()); });
165 #else
166    BOTAN_UNUSED(cert, out, out_len);
167    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
168 #endif
169    }
170 
botan_x509_cert_get_time_expires(botan_x509_cert_t cert,char out[],size_t * out_len)171 int botan_x509_cert_get_time_expires(botan_x509_cert_t cert, char out[], size_t* out_len)
172    {
173 #if defined(BOTAN_HAS_X509_CERTIFICATES)
174    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.not_after().to_string()); });
175 #else
176    BOTAN_UNUSED(cert, out, out_len);
177    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
178 #endif
179    }
180 
botan_x509_cert_not_before(botan_x509_cert_t cert,uint64_t * time_since_epoch)181 int botan_x509_cert_not_before(botan_x509_cert_t cert, uint64_t* time_since_epoch)
182    {
183 #if defined(BOTAN_HAS_X509_CERTIFICATES)
184    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, {
185       *time_since_epoch = c.not_before().time_since_epoch();
186       });
187 #else
188    BOTAN_UNUSED(cert, time_since_epoch);
189    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
190 #endif
191    }
192 
botan_x509_cert_not_after(botan_x509_cert_t cert,uint64_t * time_since_epoch)193 int botan_x509_cert_not_after(botan_x509_cert_t cert, uint64_t* time_since_epoch)
194    {
195 #if defined(BOTAN_HAS_X509_CERTIFICATES)
196    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, {
197       *time_since_epoch = c.not_after().time_since_epoch();
198       });
199 #else
200    BOTAN_UNUSED(cert, time_since_epoch);
201    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
202 #endif
203    }
204 
botan_x509_cert_get_serial_number(botan_x509_cert_t cert,uint8_t out[],size_t * out_len)205 int botan_x509_cert_get_serial_number(botan_x509_cert_t cert, uint8_t out[], size_t* out_len)
206    {
207 #if defined(BOTAN_HAS_X509_CERTIFICATES)
208    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_vec_output(out, out_len, c.serial_number()); });
209 #else
210    BOTAN_UNUSED(cert, out, out_len);
211    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
212 #endif
213    }
214 
botan_x509_cert_get_fingerprint(botan_x509_cert_t cert,const char * hash,uint8_t out[],size_t * out_len)215 int botan_x509_cert_get_fingerprint(botan_x509_cert_t cert, const char* hash, uint8_t out[], size_t* out_len)
216    {
217 #if defined(BOTAN_HAS_X509_CERTIFICATES)
218    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_str_output(out, out_len, c.fingerprint(hash)); });
219 #else
220    BOTAN_UNUSED(cert, hash, out, out_len);
221    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
222 #endif
223    }
224 
botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert,uint8_t out[],size_t * out_len)225 int botan_x509_cert_get_authority_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len)
226    {
227 #if defined(BOTAN_HAS_X509_CERTIFICATES)
228    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_vec_output(out, out_len, c.authority_key_id()); });
229 #else
230    BOTAN_UNUSED(cert, out, out_len);
231    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
232 #endif
233    }
234 
botan_x509_cert_get_subject_key_id(botan_x509_cert_t cert,uint8_t out[],size_t * out_len)235 int botan_x509_cert_get_subject_key_id(botan_x509_cert_t cert, uint8_t out[], size_t* out_len)
236    {
237 #if defined(BOTAN_HAS_X509_CERTIFICATES)
238    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_vec_output(out, out_len, c.subject_key_id()); });
239 #else
240    BOTAN_UNUSED(cert, out, out_len);
241    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
242 #endif
243    }
244 
botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert,uint8_t out[],size_t * out_len)245 int botan_x509_cert_get_public_key_bits(botan_x509_cert_t cert, uint8_t out[], size_t* out_len)
246    {
247 #if defined(BOTAN_HAS_X509_CERTIFICATES)
248    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c, { return write_vec_output(out, out_len, c.subject_public_key_bits()); });
249 #else
250    BOTAN_UNUSED(cert, out, out_len);
251    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
252 #endif
253    }
254 
botan_x509_cert_hostname_match(botan_x509_cert_t cert,const char * hostname)255 int botan_x509_cert_hostname_match(botan_x509_cert_t cert, const char* hostname)
256    {
257    if(hostname == nullptr)
258       return BOTAN_FFI_ERROR_NULL_POINTER;
259 
260 #if defined(BOTAN_HAS_X509_CERTIFICATES)
261    return BOTAN_FFI_DO(Botan::X509_Certificate, cert, c,
262                        { return c.matches_dns_name(hostname) ? 0 : -1; });
263 #else
264    BOTAN_UNUSED(cert);
265    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
266 #endif
267    }
268 
botan_x509_cert_verify(int * result_code,botan_x509_cert_t cert,const botan_x509_cert_t * intermediates,size_t intermediates_len,const botan_x509_cert_t * trusted,size_t trusted_len,const char * trusted_path,size_t required_strength,const char * hostname_cstr,uint64_t reference_time)269 int botan_x509_cert_verify(int* result_code,
270                            botan_x509_cert_t cert,
271                            const botan_x509_cert_t* intermediates,
272                            size_t intermediates_len,
273                            const botan_x509_cert_t* trusted,
274                            size_t trusted_len,
275                            const char* trusted_path,
276                            size_t required_strength,
277                            const char* hostname_cstr,
278                            uint64_t reference_time)
279    {
280    if(required_strength == 0)
281       required_strength = 110;
282 
283 #if defined(BOTAN_HAS_X509_CERTIFICATES)
284    return ffi_guard_thunk(__func__, [=]() -> int {
285       const std::string hostname((hostname_cstr == nullptr) ? "" : hostname_cstr);
286       const Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED;
287       const auto validation_time = reference_time == 0 ?
288          std::chrono::system_clock::now() :
289          std::chrono::system_clock::from_time_t(static_cast<time_t>(reference_time));
290 
291       std::vector<Botan::X509_Certificate> end_certs;
292       end_certs.push_back(safe_get(cert));
293       for(size_t i = 0; i != intermediates_len; ++i)
294          end_certs.push_back(safe_get(intermediates[i]));
295 
296       std::unique_ptr<Botan::Certificate_Store> trusted_from_path;
297       std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_extra;
298       std::vector<Botan::Certificate_Store*> trusted_roots;
299 
300       if(trusted_path && *trusted_path)
301          {
302          trusted_from_path.reset(new Botan::Certificate_Store_In_Memory(trusted_path));
303          trusted_roots.push_back(trusted_from_path.get());
304          }
305 
306       if(trusted_len > 0)
307          {
308          trusted_extra.reset(new Botan::Certificate_Store_In_Memory);
309          for(size_t i = 0; i != trusted_len; ++i)
310             {
311             trusted_extra->add_certificate(safe_get(trusted[i]));
312             }
313          trusted_roots.push_back(trusted_extra.get());
314          }
315 
316       Botan::Path_Validation_Restrictions restrictions(false, required_strength);
317 
318       auto validation_result = Botan::x509_path_validate(end_certs,
319                                                          restrictions,
320                                                          trusted_roots,
321                                                          hostname,
322                                                          usage,
323                                                          validation_time);
324 
325       if(result_code)
326          *result_code = static_cast<int>(validation_result.result());
327 
328       if(validation_result.successful_validation())
329          return 0;
330       else
331          return 1;
332       });
333 #else
334    BOTAN_UNUSED(result_code, cert, intermediates, intermediates_len, trusted);
335    BOTAN_UNUSED(trusted_len, trusted_path, hostname_cstr, reference_time);
336    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
337 #endif
338    }
339 
botan_x509_cert_validation_status(int code)340 const char* botan_x509_cert_validation_status(int code)
341    {
342    if(code < 0)
343       return nullptr;
344 
345 #if defined(BOTAN_HAS_X509_CERTIFICATES)
346    Botan::Certificate_Status_Code sc = static_cast<Botan::Certificate_Status_Code>(code);
347    return Botan::to_string(sc);
348 #else
349    return nullptr;
350 #endif
351    }
352 
353 #if defined(BOTAN_HAS_X509_CERTIFICATES)
354 
355 BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910);
356 
357 #endif
358 
botan_x509_crl_load_file(botan_x509_crl_t * crl_obj,const char * crl_path)359 int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path)
360    {
361    if(!crl_obj || !crl_path)
362       return BOTAN_FFI_ERROR_NULL_POINTER;
363 
364 #if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
365 
366    return ffi_guard_thunk(__func__, [=]() -> int {
367       std::unique_ptr<Botan::X509_CRL> c(new Botan::X509_CRL(crl_path));
368       *crl_obj = new botan_x509_crl_struct(c.release());
369       return BOTAN_FFI_SUCCESS;
370       });
371 
372 #else
373    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
374 #endif
375    }
376 
botan_x509_crl_load(botan_x509_crl_t * crl_obj,const uint8_t crl_bits[],size_t crl_bits_len)377 int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len)
378    {
379    if(!crl_obj || !crl_bits)
380       return BOTAN_FFI_ERROR_NULL_POINTER;
381 
382 #if defined(BOTAN_HAS_X509_CERTIFICATES)
383    return ffi_guard_thunk(__func__, [=]() -> int {
384       Botan::DataSource_Memory bits(crl_bits, crl_bits_len);
385       std::unique_ptr<Botan::X509_CRL> c(new Botan::X509_CRL(bits));
386       *crl_obj = new botan_x509_crl_struct(c.release());
387       return BOTAN_FFI_SUCCESS;
388       });
389 #else
390    BOTAN_UNUSED(crl_bits_len);
391    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
392 #endif
393    }
394 
botan_x509_crl_destroy(botan_x509_crl_t crl)395 int botan_x509_crl_destroy(botan_x509_crl_t crl)
396    {
397 #if defined(BOTAN_HAS_X509_CERTIFICATES)
398    return BOTAN_FFI_CHECKED_DELETE(crl);
399 #else
400    BOTAN_UNUSED(crl);
401    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
402 #endif
403    }
404 
botan_x509_is_revoked(botan_x509_crl_t crl,botan_x509_cert_t cert)405 int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert)
406    {
407 #if defined(BOTAN_HAS_X509_CERTIFICATES)
408    return BOTAN_FFI_RETURNING(Botan::X509_CRL, crl, c, {
409       return c.is_revoked(safe_get(cert)) ? 0 : -1;
410       });
411 #else
412    BOTAN_UNUSED(cert);
413    BOTAN_UNUSED(crl);
414    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
415 #endif
416    }
417 
botan_x509_cert_verify_with_crl(int * result_code,botan_x509_cert_t cert,const botan_x509_cert_t * intermediates,size_t intermediates_len,const botan_x509_cert_t * trusted,size_t trusted_len,const botan_x509_crl_t * crls,size_t crls_len,const char * trusted_path,size_t required_strength,const char * hostname_cstr,uint64_t reference_time)418 int botan_x509_cert_verify_with_crl(
419    int* result_code,
420    botan_x509_cert_t cert,
421    const botan_x509_cert_t* intermediates,
422    size_t intermediates_len,
423    const botan_x509_cert_t* trusted,
424    size_t trusted_len,
425    const botan_x509_crl_t* crls,
426    size_t crls_len,
427    const char* trusted_path,
428    size_t required_strength,
429    const char* hostname_cstr,
430    uint64_t reference_time)
431    {
432    if(required_strength == 0)
433       required_strength = 110;
434 
435 #if defined(BOTAN_HAS_X509_CERTIFICATES)
436    return ffi_guard_thunk(__func__, [=]() -> int {
437       const std::string hostname((hostname_cstr == nullptr) ? "" : hostname_cstr);
438       const Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED;
439       const auto validation_time = reference_time == 0 ?
440          std::chrono::system_clock::now() :
441          std::chrono::system_clock::from_time_t(static_cast<time_t>(reference_time));
442 
443       std::vector<Botan::X509_Certificate> end_certs;
444       end_certs.push_back(safe_get(cert));
445       for(size_t i = 0; i != intermediates_len; ++i)
446          end_certs.push_back(safe_get(intermediates[i]));
447 
448       std::unique_ptr<Botan::Certificate_Store> trusted_from_path;
449       std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_extra;
450       std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_crls;
451       std::vector<Botan::Certificate_Store*> trusted_roots;
452 
453       if(trusted_path && *trusted_path)
454          {
455          trusted_from_path.reset(new Botan::Certificate_Store_In_Memory(trusted_path));
456          trusted_roots.push_back(trusted_from_path.get());
457          }
458 
459       if(trusted_len > 0)
460          {
461          trusted_extra.reset(new Botan::Certificate_Store_In_Memory);
462          for(size_t i = 0; i != trusted_len; ++i)
463             {
464             trusted_extra->add_certificate(safe_get(trusted[i]));
465             }
466          trusted_roots.push_back(trusted_extra.get());
467          }
468 
469       if(crls_len > 0)
470          {
471          trusted_crls.reset(new Botan::Certificate_Store_In_Memory);
472          for(size_t i = 0; i != crls_len; ++i)
473             {
474             trusted_crls->add_crl(safe_get(crls[i]));
475             }
476          trusted_roots.push_back(trusted_crls.get());
477          }
478 
479       Botan::Path_Validation_Restrictions restrictions(false, required_strength);
480 
481       auto validation_result = Botan::x509_path_validate(end_certs,
482                                                          restrictions,
483                                                          trusted_roots,
484                                                          hostname,
485                                                          usage,
486                                                          validation_time);
487 
488       if(result_code)
489          *result_code = static_cast<int>(validation_result.result());
490 
491       if(validation_result.successful_validation())
492          return 0;
493       else
494          return 1;
495       });
496 #else
497    BOTAN_UNUSED(result_code, cert, intermediates, intermediates_len, trusted);
498    BOTAN_UNUSED(trusted_len, trusted_path, hostname_cstr, reference_time, crls, crls_len);
499    return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
500 #endif
501    }
502 
503 }
504