Webserver+3d print
Embed:
(wiki syntax)
Show/hide line numbers
dsa.c
Go to the documentation of this file.
00001 /** 00002 * @file dsa.c 00003 * @brief DSA (Digital Signature Algorithm) 00004 * 00005 * @section License 00006 * 00007 * Copyright (C) 2010-2017 Oryx Embedded SARL. All rights reserved. 00008 * 00009 * This file is part of CycloneCrypto Open. 00010 * 00011 * This program is free software; you can redistribute it and/or 00012 * modify it under the terms of the GNU General Public License 00013 * as published by the Free Software Foundation; either version 2 00014 * of the License, or (at your option) any later version. 00015 * 00016 * This program is distributed in the hope that it will be useful, 00017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00019 * GNU General Public License for more details. 00020 * 00021 * You should have received a copy of the GNU General Public License 00022 * along with this program; if not, write to the Free Software Foundation, 00023 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00024 * 00025 * @section Description 00026 * 00027 * The Digital Signature Algorithm (DSA) is a an algorithm developed by the 00028 * NSA to generate a digital signature for the authentication of electronic 00029 * documents. Refer to FIPS 186-3 for more details 00030 * 00031 * @author Oryx Embedded SARL (www.oryx-embedded.com) 00032 * @version 1.7.6 00033 **/ 00034 00035 //Switch to the appropriate trace level 00036 #define TRACE_LEVEL CRYPTO_TRACE_LEVEL 00037 00038 //Dependencies 00039 #include <stdlib.h> 00040 #include "crypto.h" 00041 #include "dsa.h" 00042 #include "mpi.h" 00043 #include "asn1.h" 00044 #include "debug.h" 00045 00046 //Check crypto library configuration 00047 #if (DSA_SUPPORT == ENABLED) 00048 00049 //DSA OID (1.2.840.10040.4.1) 00050 const uint8_t DSA_OID[7] = {0x2A, 0x86, 0x48, 0xCE, 0x38, 0x04, 0x01}; 00051 //DSA with SHA-1 OID (1.2.840.10040.4.3) 00052 const uint8_t DSA_WITH_SHA1_OID[7] = {0x2A, 0x86, 0x48, 0xCE, 0x38, 0x04, 0x03}; 00053 //DSA with SHA-224 OID (2.16.840.1.101.3.4.3.1) 00054 const uint8_t DSA_WITH_SHA224_OID[9] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x01}; 00055 //DSA with SHA-256 OID (2.16.840.1.101.3.4.3.2) 00056 const uint8_t DSA_WITH_SHA256_OID[9] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x02}; 00057 //DSA with SHA-384 OID (2.16.840.1.101.3.4.3.3) 00058 const uint8_t DSA_WITH_SHA384_OID[9] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x03}; 00059 //DSA with SHA-512 OID (2.16.840.1.101.3.4.3.4) 00060 const uint8_t DSA_WITH_SHA512_OID[9] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x04}; 00061 //DSA with SHA-3-224 OID (2.16.840.1.101.3.4.3.5) 00062 const uint8_t DSA_WITH_SHA3_224_OID[9] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x05}; 00063 //DSA with SHA-3-256 OID (2.16.840.1.101.3.4.3.6) 00064 const uint8_t DSA_WITH_SHA3_256_OID[9] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x06}; 00065 //DSA with SHA-3-384 OID (2.16.840.1.101.3.4.3.7) 00066 const uint8_t DSA_WITH_SHA3_384_OID[9] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x07}; 00067 //DSA with SHA-3-512 OID (2.16.840.1.101.3.4.3.8) 00068 const uint8_t DSA_WITH_SHA3_512_OID[9] = {0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x03, 0x08}; 00069 00070 00071 /** 00072 * @brief Initialize a DSA public key 00073 * @param[in] key Pointer to the DSA public key to initialize 00074 **/ 00075 00076 void dsaInitPublicKey(DsaPublicKey *key) 00077 { 00078 //Initialize multiple precision integers 00079 mpiInit(&key->p); 00080 mpiInit(&key->q); 00081 mpiInit(&key->g); 00082 mpiInit(&key->y); 00083 } 00084 00085 00086 /** 00087 * @brief Release a DSA public key 00088 * @param[in] key Pointer to the DSA public key to free 00089 **/ 00090 00091 void dsaFreePublicKey(DsaPublicKey *key) 00092 { 00093 //Free multiple precision integers 00094 mpiFree(&key->p); 00095 mpiFree(&key->q); 00096 mpiFree(&key->g); 00097 mpiFree(&key->y); 00098 } 00099 00100 00101 /** 00102 * @brief Initialize a DSA private key 00103 * @param[in] key Pointer to the DSA private key to initialize 00104 **/ 00105 00106 void dsaInitPrivateKey(DsaPrivateKey *key) 00107 { 00108 //Initialize multiple precision integers 00109 mpiInit(&key->p); 00110 mpiInit(&key->q); 00111 mpiInit(&key->g); 00112 mpiInit(&key->x); 00113 } 00114 00115 00116 /** 00117 * @brief Release a DSA private key 00118 * @param[in] key Pointer to the DSA public key to free 00119 **/ 00120 00121 void dsaFreePrivateKey(DsaPrivateKey *key) 00122 { 00123 //Free multiple precision integers 00124 mpiFree(&key->p); 00125 mpiFree(&key->q); 00126 mpiFree(&key->g); 00127 mpiFree(&key->x); 00128 } 00129 00130 00131 /** 00132 * @brief Initialize a DSA signature 00133 * @param[in] signature Pointer to the DSA signature to initialize 00134 **/ 00135 00136 void dsaInitSignature(DsaSignature *signature) 00137 { 00138 //Initialize multiple precision integers 00139 mpiInit(&signature->r); 00140 mpiInit(&signature->s); 00141 } 00142 00143 00144 /** 00145 * @brief Release a DSA signature 00146 * @param[in] signature Pointer to the DSA signature to free 00147 **/ 00148 00149 void dsaFreeSignature(DsaSignature *signature) 00150 { 00151 //Release multiple precision integers 00152 mpiFree(&signature->r); 00153 mpiFree(&signature->s); 00154 } 00155 00156 00157 /** 00158 * @brief Encode DSA signature using ASN.1 00159 * @param[in] signature (R, S) integer pair 00160 * @param[out] data Pointer to the buffer where to store the resulting ASN.1 structure 00161 * @param[out] length Length of the ASN.1 structure 00162 * @return Error code 00163 **/ 00164 00165 error_t dsaWriteSignature(const DsaSignature *signature, uint8_t *data, size_t *length) 00166 { 00167 error_t error; 00168 size_t k; 00169 size_t n; 00170 size_t rLen; 00171 size_t sLen; 00172 Asn1Tag tag; 00173 00174 //Debug message 00175 TRACE_DEBUG("Writing DSA signature...\r\n"); 00176 00177 //Dump (R, S) integer pair 00178 TRACE_DEBUG(" r:\r\n"); 00179 TRACE_DEBUG_MPI(" ", &signature->r); 00180 TRACE_DEBUG(" s:\r\n"); 00181 TRACE_DEBUG_MPI(" ", &signature->s); 00182 00183 //Calculate the length of R 00184 rLen = mpiGetByteLength(&signature->r); 00185 //Calculate the length of S 00186 sLen = mpiGetByteLength(&signature->s); 00187 00188 //Make sure the (R, S) integer pair is valid 00189 if(rLen == 0 || sLen == 0) 00190 return ERROR_INVALID_LENGTH; 00191 00192 //R and S are always encoded in the smallest possible number of octets 00193 if(mpiGetBitValue(&signature->r, (rLen * 8) - 1)) 00194 rLen++; 00195 if(mpiGetBitValue(&signature->s, (sLen * 8) - 1)) 00196 sLen++; 00197 00198 //The first pass computes the length of the ASN.1 sequence 00199 n = 0; 00200 00201 //The parameter R is encapsulated within an ASN.1 structure 00202 tag.constructed = FALSE; 00203 tag.objClass = ASN1_CLASS_UNIVERSAL; 00204 tag.objType = ASN1_TYPE_INTEGER; 00205 tag.length = rLen; 00206 tag.value = NULL; 00207 00208 //Compute the length of the corresponding ASN.1 structure 00209 error = asn1WriteTag(&tag, FALSE, NULL, NULL); 00210 //Any error to report? 00211 if(error) 00212 return error; 00213 00214 //Update the length of the ASN.1 sequence 00215 n += tag.totalLength; 00216 00217 //The parameter S is encapsulated within an ASN.1 structure 00218 tag.constructed = FALSE; 00219 tag.objClass = ASN1_CLASS_UNIVERSAL; 00220 tag.objType = ASN1_TYPE_INTEGER; 00221 tag.length = sLen; 00222 tag.value = NULL; 00223 00224 //Compute the length of the corresponding ASN.1 structure 00225 error = asn1WriteTag(&tag, FALSE, NULL, NULL); 00226 //Any error to report? 00227 if(error) 00228 return error; 00229 00230 //Update the length of the ASN.1 sequence 00231 n += tag.totalLength; 00232 00233 //The second pass encodes the ASN.1 structure 00234 k = 0; 00235 00236 //The (R, S) integer pair is encapsulated within a sequence 00237 tag.constructed = TRUE; 00238 tag.objClass = ASN1_CLASS_UNIVERSAL; 00239 tag.objType = ASN1_TYPE_SEQUENCE; 00240 tag.length = n; 00241 tag.value = NULL; 00242 00243 //Write the corresponding ASN.1 tag 00244 error = asn1WriteTag(&tag, FALSE, data + k, &n); 00245 //Any error to report? 00246 if(error) 00247 return error; 00248 00249 //Advance write pointer 00250 k += n; 00251 00252 //Encode the parameter R using ASN.1 00253 tag.constructed = FALSE; 00254 tag.objClass = ASN1_CLASS_UNIVERSAL; 00255 tag.objType = ASN1_TYPE_INTEGER; 00256 tag.length = rLen; 00257 tag.value = NULL; 00258 00259 //Write the corresponding ASN.1 tag 00260 error = asn1WriteTag(&tag, FALSE, data + k, &n); 00261 //Any error to report? 00262 if(error) 00263 return error; 00264 00265 //Advance write pointer 00266 k += n; 00267 00268 //Convert R to an octet string 00269 error = mpiWriteRaw(&signature->r, data + k, rLen); 00270 //Any error to report? 00271 if(error) 00272 return error; 00273 00274 //Advance write pointer 00275 k += rLen; 00276 00277 //Encode the parameter S using ASN.1 00278 tag.constructed = FALSE; 00279 tag.objClass = ASN1_CLASS_UNIVERSAL; 00280 tag.objType = ASN1_TYPE_INTEGER; 00281 tag.length = sLen; 00282 tag.value = NULL; 00283 00284 //Write the corresponding ASN.1 tag 00285 error = asn1WriteTag(&tag, FALSE, data + k, &n); 00286 //Any error to report? 00287 if(error) 00288 return error; 00289 00290 //Advance write pointer 00291 k += n; 00292 00293 //Convert S to an octet string 00294 error = mpiWriteRaw(&signature->s, data + k, sLen); 00295 //Any error to report? 00296 if(error) 00297 return error; 00298 00299 //Advance write pointer 00300 k += sLen; 00301 00302 //Dump DSA signature 00303 TRACE_DEBUG(" signature:\r\n"); 00304 TRACE_DEBUG_ARRAY(" ", data, k); 00305 00306 //Total length of the ASN.1 structure 00307 *length = k; 00308 //Successful processing 00309 return NO_ERROR; 00310 } 00311 00312 00313 /** 00314 * @brief Read an ASN.1 encoded DSA signature 00315 * @param[in] data Pointer to the ASN.1 structure to decode 00316 * @param[in] length Length of the ASN.1 structure 00317 * @param[out] signature (R, S) integer pair 00318 * @return Error code 00319 **/ 00320 00321 error_t dsaReadSignature(const uint8_t *data, size_t length, DsaSignature *signature) 00322 { 00323 error_t error; 00324 Asn1Tag tag; 00325 00326 //Debug message 00327 TRACE_DEBUG("Reading DSA signature...\r\n"); 00328 00329 //Dump DSA signature 00330 TRACE_DEBUG(" signature:\r\n"); 00331 TRACE_DEBUG_ARRAY(" ", data, length); 00332 00333 //Start of exception handling block 00334 do 00335 { 00336 //Display ASN.1 structure 00337 error = asn1DumpObject(data, length, 0); 00338 //Any error to report? 00339 if(error) 00340 break; 00341 00342 //Read the contents of the ASN.1 structure 00343 error = asn1ReadTag(data, length, &tag); 00344 //Failed to decode ASN.1 tag? 00345 if(error) 00346 break; 00347 00348 //Enforce encoding, class and type 00349 error = asn1CheckTag(&tag, TRUE, ASN1_CLASS_UNIVERSAL, ASN1_TYPE_SEQUENCE); 00350 //The tag does not match the criteria? 00351 if(error) 00352 break; 00353 00354 //Point to the first field 00355 data = tag.value; 00356 length = tag.length; 00357 00358 //Read the parameter R 00359 error = asn1ReadTag(data, length, &tag); 00360 //Failed to decode ASN.1 tag? 00361 if(error) 00362 break; 00363 00364 //Enforce encoding, class and type 00365 error = asn1CheckTag(&tag, FALSE, ASN1_CLASS_UNIVERSAL, ASN1_TYPE_INTEGER); 00366 //The tag does not match the criteria? 00367 if(error) 00368 break; 00369 00370 //Convert the octet string to a multiple precision integer 00371 error = mpiReadRaw(&signature->r, tag.value, tag.length); 00372 //Any error to report? 00373 if(error) 00374 break; 00375 00376 //Point to the next field 00377 data += tag.totalLength; 00378 length -= tag.totalLength; 00379 00380 //Read the parameter S 00381 error = asn1ReadTag(data, length, &tag); 00382 //Failed to decode ASN.1 tag? 00383 if(error) 00384 break; 00385 00386 //Enforce encoding, class and type 00387 error = asn1CheckTag(&tag, FALSE, ASN1_CLASS_UNIVERSAL, ASN1_TYPE_INTEGER); 00388 //The tag does not match the criteria? 00389 if(error) 00390 break; 00391 00392 //Convert the octet string to a multiple precision integer 00393 error = mpiReadRaw(&signature->s, tag.value, tag.length); 00394 //Any error to report? 00395 if(error) 00396 break; 00397 00398 //Dump (R, S) integer pair 00399 TRACE_DEBUG(" r:\r\n"); 00400 TRACE_DEBUG_MPI(" ", &signature->r); 00401 TRACE_DEBUG(" s:\r\n"); 00402 TRACE_DEBUG_MPI(" ", &signature->s); 00403 00404 //End of exception handling block 00405 } while(0); 00406 00407 //Clean up side effects if necessary 00408 if(error) 00409 dsaFreeSignature(signature); 00410 00411 //Return status code 00412 return error; 00413 } 00414 00415 00416 /** 00417 * @brief DSA signature generation 00418 * @param[in] prngAlgo PRNG algorithm 00419 * @param[in] prngContext Pointer to the PRNG context 00420 * @param[in] key Signer's DSA private key 00421 * @param[in] digest Digest of the message to be signed 00422 * @param[in] digestLength Length in octets of the digest 00423 * @param[out] signature (R, S) integer pair 00424 * @return Error code 00425 **/ 00426 00427 error_t dsaGenerateSignature(const PrngAlgo *prngAlgo, void *prngContext, 00428 const DsaPrivateKey *key, const uint8_t *digest, size_t digestLength, 00429 DsaSignature *signature) 00430 { 00431 error_t error; 00432 uint_t n; 00433 Mpi k; 00434 Mpi z; 00435 00436 //Check parameters 00437 if(key == NULL || digest == NULL || signature == NULL) 00438 return ERROR_INVALID_PARAMETER; 00439 00440 //Debug message 00441 TRACE_DEBUG("DSA signature generation...\r\n"); 00442 TRACE_DEBUG(" p:\r\n"); 00443 TRACE_DEBUG_MPI(" ", &key->p); 00444 TRACE_DEBUG(" q:\r\n"); 00445 TRACE_DEBUG_MPI(" ", &key->q); 00446 TRACE_DEBUG(" g:\r\n"); 00447 TRACE_DEBUG_MPI(" ", &key->g); 00448 TRACE_DEBUG(" x:\r\n"); 00449 TRACE_DEBUG_MPI(" ", &key->x); 00450 TRACE_DEBUG(" digest:\r\n"); 00451 TRACE_DEBUG_ARRAY(" ", digest, digestLength); 00452 00453 //Initialize multiple precision integers 00454 mpiInit(&k); 00455 mpiInit(&z); 00456 00457 //Let N be the bit length of q 00458 n = mpiGetBitLength(&key->q); 00459 00460 //Generated a pseudorandom number 00461 MPI_CHECK(mpiRand(&k, n, prngAlgo, prngContext)); 00462 00463 //Make sure that 0 < k < q 00464 if(mpiComp(&k, &key->q) >= 0) 00465 mpiShiftRight(&k, 1); 00466 00467 //Debug message 00468 TRACE_DEBUG(" k:\r\n"); 00469 TRACE_DEBUG_MPI(" ", &k); 00470 00471 //Compute N = MIN(N, outlen) 00472 n = MIN(n, digestLength * 8); 00473 00474 //Convert the digest to a multiple precision integer 00475 MPI_CHECK(mpiReadRaw(&z, digest, (n + 7) / 8)); 00476 00477 //Keep the leftmost N bits of the hash value 00478 if(n % 8) 00479 { 00480 MPI_CHECK(mpiShiftRight(&z, 8 - (n % 8))); 00481 } 00482 00483 //Debug message 00484 TRACE_DEBUG(" z:\r\n"); 00485 TRACE_DEBUG_MPI(" ", &z); 00486 00487 //Compute r = (g ^ k mod p) mod q 00488 MPI_CHECK(mpiExpMod(&signature->r, &key->g, &k, &key->p)); 00489 MPI_CHECK(mpiMod(&signature->r, &signature->r, &key->q)); 00490 00491 //Compute k ^ -1 mod q 00492 MPI_CHECK(mpiInvMod(&k, &k, &key->q)); 00493 00494 //Compute s = k ^ -1 * (z + x * r) mod q 00495 MPI_CHECK(mpiMul(&signature->s, &key->x, &signature->r)); 00496 MPI_CHECK(mpiAdd(&signature->s, &signature->s, &z)); 00497 MPI_CHECK(mpiMod(&signature->s, &signature->s, &key->q)); 00498 MPI_CHECK(mpiMulMod(&signature->s, &signature->s, &k, &key->q)); 00499 00500 //Dump DSA signature 00501 TRACE_DEBUG(" r:\r\n"); 00502 TRACE_DEBUG_MPI(" ", &signature->r); 00503 TRACE_DEBUG(" s:\r\n"); 00504 TRACE_DEBUG_MPI(" ", &signature->s); 00505 00506 end: 00507 //Release multiple precision integers 00508 mpiFree(&k); 00509 mpiFree(&z); 00510 00511 //Clean up side effects if necessary 00512 if(error) 00513 { 00514 //Release (R, S) integer pair 00515 mpiFree(&signature->r); 00516 mpiFree(&signature->r); 00517 } 00518 00519 //Return status code 00520 return error; 00521 } 00522 00523 00524 /** 00525 * @brief DSA signature verification 00526 * @param[in] key Signer's DSA public key 00527 * @param[in] digest Digest of the message whose signature is to be verified 00528 * @param[in] digestLength Length in octets of the digest 00529 * @param[in] signature (R, S) integer pair 00530 * @return Error code 00531 **/ 00532 00533 error_t dsaVerifySignature(const DsaPublicKey *key, 00534 const uint8_t *digest, size_t digestLength, const DsaSignature *signature) 00535 { 00536 error_t error; 00537 uint_t n; 00538 Mpi w; 00539 Mpi z; 00540 Mpi u1; 00541 Mpi u2; 00542 Mpi v; 00543 00544 //Check parameters 00545 if(key == NULL || digest == NULL || signature == NULL) 00546 return ERROR_INVALID_PARAMETER; 00547 00548 //Debug message 00549 TRACE_DEBUG("DSA signature verification...\r\n"); 00550 TRACE_DEBUG(" p:\r\n"); 00551 TRACE_DEBUG_MPI(" ", &key->p); 00552 TRACE_DEBUG(" q:\r\n"); 00553 TRACE_DEBUG_MPI(" ", &key->q); 00554 TRACE_DEBUG(" g:\r\n"); 00555 TRACE_DEBUG_MPI(" ", &key->g); 00556 TRACE_DEBUG(" y:\r\n"); 00557 TRACE_DEBUG_MPI(" ", &key->y); 00558 TRACE_DEBUG(" digest:\r\n"); 00559 TRACE_DEBUG_ARRAY(" ", digest, digestLength); 00560 TRACE_DEBUG(" r:\r\n"); 00561 TRACE_DEBUG_MPI(" ", &signature->r); 00562 TRACE_DEBUG(" s:\r\n"); 00563 TRACE_DEBUG_MPI(" ", &signature->s); 00564 00565 //The verifier shall check that 0 < r < q and 0 < s < q. If either 00566 //condition is violated, the signature shall be rejected as invalid 00567 if(mpiCompInt(&signature->r, 0) <= 0 || mpiComp(&signature->r, &key->q) >= 0) 00568 return ERROR_INVALID_SIGNATURE; 00569 if(mpiCompInt(&signature->s, 0) <= 0 || mpiComp(&signature->s, &key->q) >= 0) 00570 return ERROR_INVALID_SIGNATURE; 00571 00572 //Initialize multiple precision integers 00573 mpiInit(&w); 00574 mpiInit(&z); 00575 mpiInit(&u1); 00576 mpiInit(&u2); 00577 mpiInit(&v); 00578 00579 //Let N be the bit length of q 00580 n = mpiGetBitLength(&key->q); 00581 //Compute N = MIN(N, outlen) 00582 n = MIN(n, digestLength * 8); 00583 00584 //Convert the digest to a multiple precision integer 00585 MPI_CHECK(mpiReadRaw(&z, digest, (n + 7) / 8)); 00586 00587 //Keep the leftmost N bits of the hash value 00588 if(n % 8) 00589 { 00590 MPI_CHECK(mpiShiftRight(&z, 8 - (n % 8))); 00591 } 00592 00593 //Compute w = s ^ -1 mod q 00594 MPI_CHECK(mpiInvMod(&w, &signature->s, &key->q)); 00595 //Compute u1 = z * w mod q 00596 MPI_CHECK(mpiMulMod(&u1, &z, &w, &key->q)); 00597 //Compute u2 = r * w mod q 00598 MPI_CHECK(mpiMulMod(&u2, &signature->r, &w, &key->q)); 00599 00600 //Compute v = ((g ^ u1) * (y ^ u2) mod p) mod q 00601 MPI_CHECK(mpiExpMod(&v, &key->g, &u1, &key->p)); 00602 MPI_CHECK(mpiExpMod(&w, &key->y, &u2, &key->p)); 00603 MPI_CHECK(mpiMulMod(&v, &v, &w, &key->p)); 00604 MPI_CHECK(mpiMod(&v, &v, &key->q)); 00605 00606 //Debug message 00607 TRACE_DEBUG(" v:\r\n"); 00608 TRACE_DEBUG_MPI(" ", &v); 00609 00610 //If v = r, then the signature is verified. If v does not equal r, 00611 //then the message or the signature may have been modified 00612 if(!mpiComp(&v, &signature->r)) 00613 error = NO_ERROR; 00614 else 00615 error = ERROR_INVALID_SIGNATURE; 00616 00617 end: 00618 //Release multiple precision integers 00619 mpiFree(&w); 00620 mpiFree(&z); 00621 mpiFree(&u1); 00622 mpiFree(&u2); 00623 mpiFree(&v); 00624 00625 //Return status code 00626 return error; 00627 } 00628 00629 #endif 00630
Generated on Tue Jul 12 2022 17:10:13 by
