00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011 #include "ossl.h"
00012
00013 #define WrapCipher(obj, klass, ctx) \
00014 (obj) = Data_Wrap_Struct((klass), 0, ossl_cipher_free, (ctx))
00015 #define MakeCipher(obj, klass, ctx) \
00016 (obj) = Data_Make_Struct((klass), EVP_CIPHER_CTX, 0, ossl_cipher_free, (ctx))
00017 #define AllocCipher(obj, ctx) \
00018 memset(DATA_PTR(obj) = (ctx) = ALLOC(EVP_CIPHER_CTX), 0, sizeof(EVP_CIPHER_CTX))
00019 #define GetCipherInit(obj, ctx) do { \
00020 Data_Get_Struct((obj), EVP_CIPHER_CTX, (ctx)); \
00021 } while (0)
00022 #define GetCipher(obj, ctx) do { \
00023 GetCipherInit((obj), (ctx)); \
00024 if (!(ctx)) { \
00025 ossl_raise(rb_eRuntimeError, "Cipher not inititalized!"); \
00026 } \
00027 } while (0)
00028 #define SafeGetCipher(obj, ctx) do { \
00029 OSSL_Check_Kind((obj), cCipher); \
00030 GetCipher((obj), (ctx)); \
00031 } while (0)
00032
00033
00034
00035
00036 VALUE cCipher;
00037 VALUE eCipherError;
00038
00039 static VALUE ossl_cipher_alloc(VALUE klass);
00040
00041
00042
00043
00044 const EVP_CIPHER *
00045 GetCipherPtr(VALUE obj)
00046 {
00047 EVP_CIPHER_CTX *ctx;
00048
00049 SafeGetCipher(obj, ctx);
00050
00051 return EVP_CIPHER_CTX_cipher(ctx);
00052 }
00053
00054 VALUE
00055 ossl_cipher_new(const EVP_CIPHER *cipher)
00056 {
00057 VALUE ret;
00058 EVP_CIPHER_CTX *ctx;
00059
00060 ret = ossl_cipher_alloc(cCipher);
00061 AllocCipher(ret, ctx);
00062 EVP_CIPHER_CTX_init(ctx);
00063 if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, -1) != 1)
00064 ossl_raise(eCipherError, NULL);
00065
00066 return ret;
00067 }
00068
00069
00070
00071
00072 static void
00073 ossl_cipher_free(EVP_CIPHER_CTX *ctx)
00074 {
00075 if (ctx) {
00076 EVP_CIPHER_CTX_cleanup(ctx);
00077 ruby_xfree(ctx);
00078 }
00079 }
00080
00081 static VALUE
00082 ossl_cipher_alloc(VALUE klass)
00083 {
00084 VALUE obj;
00085
00086 WrapCipher(obj, klass, 0);
00087
00088 return obj;
00089 }
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099 static VALUE
00100 ossl_cipher_initialize(VALUE self, VALUE str)
00101 {
00102 EVP_CIPHER_CTX *ctx;
00103 const EVP_CIPHER *cipher;
00104 char *name;
00105 unsigned char key[EVP_MAX_KEY_LENGTH];
00106
00107 name = StringValuePtr(str);
00108 GetCipherInit(self, ctx);
00109 if (ctx) {
00110 ossl_raise(rb_eRuntimeError, "Cipher already inititalized!");
00111 }
00112 AllocCipher(self, ctx);
00113 EVP_CIPHER_CTX_init(ctx);
00114 if (!(cipher = EVP_get_cipherbyname(name))) {
00115 ossl_raise(rb_eRuntimeError, "unsupported cipher algorithm (%s)", name);
00116 }
00117
00118
00119
00120
00121
00122
00123 memset(key, 0, EVP_MAX_KEY_LENGTH);
00124 if (EVP_CipherInit_ex(ctx, cipher, NULL, key, NULL, -1) != 1)
00125 ossl_raise(eCipherError, NULL);
00126
00127 return self;
00128 }
00129
00130 static VALUE
00131 ossl_cipher_copy(VALUE self, VALUE other)
00132 {
00133 EVP_CIPHER_CTX *ctx1, *ctx2;
00134
00135 rb_check_frozen(self);
00136 if (self == other) return self;
00137
00138 GetCipherInit(self, ctx1);
00139 if (!ctx1) {
00140 AllocCipher(self, ctx1);
00141 }
00142 SafeGetCipher(other, ctx2);
00143 if (EVP_CIPHER_CTX_copy(ctx1, ctx2) != 1)
00144 ossl_raise(eCipherError, NULL);
00145
00146 return self;
00147 }
00148
00149 #ifdef HAVE_OBJ_NAME_DO_ALL_SORTED
00150 static void*
00151 add_cipher_name_to_ary(const OBJ_NAME *name, VALUE ary)
00152 {
00153 rb_ary_push(ary, rb_str_new2(name->name));
00154 return NULL;
00155 }
00156 #endif
00157
00158 #ifdef HAVE_OBJ_NAME_DO_ALL_SORTED
00159
00160
00161
00162
00163
00164
00165 static VALUE
00166 ossl_s_ciphers(VALUE self)
00167 {
00168 VALUE ary;
00169
00170 ary = rb_ary_new();
00171 OBJ_NAME_do_all_sorted(OBJ_NAME_TYPE_CIPHER_METH,
00172 (void(*)(const OBJ_NAME*,void*))add_cipher_name_to_ary,
00173 (void*)ary);
00174
00175 return ary;
00176 }
00177 #else
00178 #define ossl_s_ciphers rb_f_notimplement
00179 #endif
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190 static VALUE
00191 ossl_cipher_reset(VALUE self)
00192 {
00193 EVP_CIPHER_CTX *ctx;
00194
00195 GetCipher(self, ctx);
00196 if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, NULL, -1) != 1)
00197 ossl_raise(eCipherError, NULL);
00198
00199 return self;
00200 }
00201
00202 static VALUE
00203 ossl_cipher_init(int argc, VALUE *argv, VALUE self, int mode)
00204 {
00205 EVP_CIPHER_CTX *ctx;
00206 unsigned char key[EVP_MAX_KEY_LENGTH], *p_key = NULL;
00207 unsigned char iv[EVP_MAX_IV_LENGTH], *p_iv = NULL;
00208 VALUE pass, init_v;
00209
00210 if(rb_scan_args(argc, argv, "02", &pass, &init_v) > 0){
00211
00212
00213
00214
00215
00216 VALUE cname = rb_class_path(rb_obj_class(self));
00217 rb_warn("arguments for %"PRIsVALUE"#encrypt and %"PRIsVALUE"#decrypt were deprecated; "
00218 "use %"PRIsVALUE"#pkcs5_keyivgen to derive key and IV",
00219 cname, cname, cname);
00220 StringValue(pass);
00221 GetCipher(self, ctx);
00222 if (NIL_P(init_v)) memcpy(iv, "OpenSSL for Ruby rulez!", sizeof(iv));
00223 else{
00224 StringValue(init_v);
00225 if (EVP_MAX_IV_LENGTH > RSTRING_LEN(init_v)) {
00226 memset(iv, 0, EVP_MAX_IV_LENGTH);
00227 memcpy(iv, RSTRING_PTR(init_v), RSTRING_LEN(init_v));
00228 }
00229 else memcpy(iv, RSTRING_PTR(init_v), sizeof(iv));
00230 }
00231 EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), EVP_md5(), iv,
00232 (unsigned char *)RSTRING_PTR(pass), RSTRING_LENINT(pass), 1, key, NULL);
00233 p_key = key;
00234 p_iv = iv;
00235 }
00236 else {
00237 GetCipher(self, ctx);
00238 }
00239 if (EVP_CipherInit_ex(ctx, NULL, NULL, p_key, p_iv, mode) != 1) {
00240 ossl_raise(eCipherError, NULL);
00241 }
00242
00243 return self;
00244 }
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258 static VALUE
00259 ossl_cipher_encrypt(int argc, VALUE *argv, VALUE self)
00260 {
00261 return ossl_cipher_init(argc, argv, self, 1);
00262 }
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276 static VALUE
00277 ossl_cipher_decrypt(int argc, VALUE *argv, VALUE self)
00278 {
00279 return ossl_cipher_init(argc, argv, self, 0);
00280 }
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302 static VALUE
00303 ossl_cipher_pkcs5_keyivgen(int argc, VALUE *argv, VALUE self)
00304 {
00305 EVP_CIPHER_CTX *ctx;
00306 const EVP_MD *digest;
00307 VALUE vpass, vsalt, viter, vdigest;
00308 unsigned char key[EVP_MAX_KEY_LENGTH], iv[EVP_MAX_IV_LENGTH], *salt = NULL;
00309 int iter;
00310
00311 rb_scan_args(argc, argv, "13", &vpass, &vsalt, &viter, &vdigest);
00312 StringValue(vpass);
00313 if(!NIL_P(vsalt)){
00314 StringValue(vsalt);
00315 if(RSTRING_LEN(vsalt) != PKCS5_SALT_LEN)
00316 ossl_raise(eCipherError, "salt must be an 8-octet string");
00317 salt = (unsigned char *)RSTRING_PTR(vsalt);
00318 }
00319 iter = NIL_P(viter) ? 2048 : NUM2INT(viter);
00320 digest = NIL_P(vdigest) ? EVP_md5() : GetDigestPtr(vdigest);
00321 GetCipher(self, ctx);
00322 EVP_BytesToKey(EVP_CIPHER_CTX_cipher(ctx), digest, salt,
00323 (unsigned char *)RSTRING_PTR(vpass), RSTRING_LENINT(vpass), iter, key, iv);
00324 if (EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, -1) != 1)
00325 ossl_raise(eCipherError, NULL);
00326 OPENSSL_cleanse(key, sizeof key);
00327 OPENSSL_cleanse(iv, sizeof iv);
00328
00329 return Qnil;
00330 }
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345 static VALUE
00346 ossl_cipher_update(int argc, VALUE *argv, VALUE self)
00347 {
00348 EVP_CIPHER_CTX *ctx;
00349 unsigned char *in;
00350 int in_len, out_len;
00351 VALUE data, str;
00352
00353 rb_scan_args(argc, argv, "11", &data, &str);
00354
00355 StringValue(data);
00356 in = (unsigned char *)RSTRING_PTR(data);
00357 if ((in_len = RSTRING_LENINT(data)) == 0)
00358 ossl_raise(rb_eArgError, "data must not be empty");
00359 GetCipher(self, ctx);
00360 out_len = in_len+EVP_CIPHER_CTX_block_size(ctx);
00361
00362 if (NIL_P(str)) {
00363 str = rb_str_new(0, out_len);
00364 } else {
00365 StringValue(str);
00366 rb_str_resize(str, out_len);
00367 }
00368
00369 if (!EVP_CipherUpdate(ctx, (unsigned char *)RSTRING_PTR(str), &out_len, in, in_len))
00370 ossl_raise(eCipherError, NULL);
00371 assert(out_len < RSTRING_LEN(str));
00372 rb_str_set_len(str, out_len);
00373
00374 return str;
00375 }
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390
00391 static VALUE
00392 ossl_cipher_final(VALUE self)
00393 {
00394 EVP_CIPHER_CTX *ctx;
00395 int out_len;
00396 VALUE str;
00397
00398 GetCipher(self, ctx);
00399 str = rb_str_new(0, EVP_CIPHER_CTX_block_size(ctx));
00400 if (!EVP_CipherFinal_ex(ctx, (unsigned char *)RSTRING_PTR(str), &out_len))
00401 ossl_raise(eCipherError, NULL);
00402 assert(out_len <= RSTRING_LEN(str));
00403 rb_str_set_len(str, out_len);
00404
00405 return str;
00406 }
00407
00408
00409
00410
00411
00412
00413
00414
00415 static VALUE
00416 ossl_cipher_name(VALUE self)
00417 {
00418 EVP_CIPHER_CTX *ctx;
00419
00420 GetCipher(self, ctx);
00421
00422 return rb_str_new2(EVP_CIPHER_name(EVP_CIPHER_CTX_cipher(ctx)));
00423 }
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436 static VALUE
00437 ossl_cipher_set_key(VALUE self, VALUE key)
00438 {
00439 EVP_CIPHER_CTX *ctx;
00440
00441 StringValue(key);
00442 GetCipher(self, ctx);
00443
00444 if (RSTRING_LEN(key) < EVP_CIPHER_CTX_key_length(ctx))
00445 ossl_raise(eCipherError, "key length too short");
00446
00447 if (EVP_CipherInit_ex(ctx, NULL, NULL, (unsigned char *)RSTRING_PTR(key), NULL, -1) != 1)
00448 ossl_raise(eCipherError, NULL);
00449
00450 return key;
00451 }
00452
00453
00454
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466
00467
00468 static VALUE
00469 ossl_cipher_set_iv(VALUE self, VALUE iv)
00470 {
00471 EVP_CIPHER_CTX *ctx;
00472
00473 StringValue(iv);
00474 GetCipher(self, ctx);
00475
00476 if (RSTRING_LEN(iv) < EVP_CIPHER_CTX_iv_length(ctx))
00477 ossl_raise(eCipherError, "iv length too short");
00478
00479 if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, (unsigned char *)RSTRING_PTR(iv), -1) != 1)
00480 ossl_raise(eCipherError, NULL);
00481
00482 return iv;
00483 }
00484
00485 #ifdef HAVE_AUTHENTICATED_ENCRYPTION
00486
00487
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498
00499
00500
00501
00502
00503
00504 static VALUE
00505 ossl_cipher_set_auth_data(VALUE self, VALUE data)
00506 {
00507 EVP_CIPHER_CTX *ctx;
00508 unsigned char *in;
00509 int in_len;
00510 int out_len;
00511
00512 StringValue(data);
00513
00514 in = (unsigned char *) RSTRING_PTR(data);
00515 in_len = RSTRING_LENINT(data);
00516
00517 GetCipher(self, ctx);
00518
00519 if (!EVP_CipherUpdate(ctx, NULL, &out_len, in, in_len))
00520 ossl_raise(eCipherError, "couldn't set additional authenticated data");
00521
00522 return data;
00523 }
00524
00525 #define ossl_is_gcm(nid) (nid) == NID_aes_128_gcm || \
00526 (nid) == NID_aes_192_gcm || \
00527 (nid) == NID_aes_256_gcm
00528
00529 static VALUE
00530 ossl_get_gcm_auth_tag(EVP_CIPHER_CTX *ctx, int len)
00531 {
00532 unsigned char *tag;
00533 VALUE ret;
00534
00535 tag = ALLOC_N(unsigned char, len);
00536
00537 if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, len, tag))
00538 ossl_raise(eCipherError, "retrieving the authentication tag failed");
00539
00540 ret = rb_str_new((const char *) tag, len);
00541 xfree(tag);
00542 return ret;
00543 }
00544
00545
00546
00547
00548
00549
00550
00551
00552
00553
00554
00555
00556
00557
00558
00559 static VALUE
00560 ossl_cipher_get_auth_tag(int argc, VALUE *argv, VALUE self)
00561 {
00562 VALUE vtag_len;
00563 EVP_CIPHER_CTX *ctx;
00564 int nid, tag_len;
00565
00566 if (rb_scan_args(argc, argv, "01", &vtag_len) == 0) {
00567 tag_len = 16;
00568 } else {
00569 tag_len = NUM2INT(vtag_len);
00570 }
00571
00572 GetCipher(self, ctx);
00573 nid = EVP_CIPHER_CTX_nid(ctx);
00574
00575 if (ossl_is_gcm(nid)) {
00576 return ossl_get_gcm_auth_tag(ctx, tag_len);
00577 } else {
00578 ossl_raise(eCipherError, "authentication tag not supported by this cipher");
00579 return Qnil;
00580 }
00581 }
00582
00583 static inline void
00584 ossl_set_gcm_auth_tag(EVP_CIPHER_CTX *ctx, unsigned char *tag, int tag_len)
00585 {
00586 if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag_len, tag))
00587 ossl_raise(eCipherError, "unable to set GCM tag");
00588 }
00589
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600
00601
00602 static VALUE
00603 ossl_cipher_set_auth_tag(VALUE self, VALUE vtag)
00604 {
00605 EVP_CIPHER_CTX *ctx;
00606 int nid;
00607 unsigned char *tag;
00608 int tag_len;
00609
00610 StringValue(vtag);
00611 tag = (unsigned char *) RSTRING_PTR(vtag);
00612 tag_len = RSTRING_LENINT(vtag);
00613
00614 GetCipher(self, ctx);
00615 nid = EVP_CIPHER_CTX_nid(ctx);
00616
00617 if (ossl_is_gcm(nid)) {
00618 ossl_set_gcm_auth_tag(ctx, tag, tag_len);
00619 } else {
00620 ossl_raise(eCipherError, "authentication tag not supported by this cipher");
00621 }
00622
00623 return vtag;
00624 }
00625
00626
00627
00628
00629
00630
00631
00632
00633 static VALUE
00634 ossl_cipher_is_authenticated(VALUE self)
00635 {
00636 EVP_CIPHER_CTX *ctx;
00637 int nid;
00638
00639 GetCipher(self, ctx);
00640 nid = EVP_CIPHER_CTX_nid(ctx);
00641
00642 if (ossl_is_gcm(nid)) {
00643 return Qtrue;
00644 } else {
00645 return Qfalse;
00646 }
00647 }
00648 #else
00649 #define ossl_cipher_set_auth_data rb_f_notimplement
00650 #define ossl_cipher_get_auth_tag rb_f_notimplement
00651 #define ossl_cipher_set_auth_tag rb_f_notimplement
00652 #define ossl_cipher_is_authenticated rb_f_notimplement
00653 #endif
00654
00655
00656
00657
00658
00659
00660
00661
00662
00663
00664
00665
00666
00667 static VALUE
00668 ossl_cipher_set_key_length(VALUE self, VALUE key_length)
00669 {
00670 int len = NUM2INT(key_length);
00671 EVP_CIPHER_CTX *ctx;
00672
00673 GetCipher(self, ctx);
00674 if (EVP_CIPHER_CTX_set_key_length(ctx, len) != 1)
00675 ossl_raise(eCipherError, NULL);
00676
00677 return key_length;
00678 }
00679
00680 #if defined(HAVE_EVP_CIPHER_CTX_SET_PADDING)
00681
00682
00683
00684
00685
00686
00687
00688
00689
00690
00691 static VALUE
00692 ossl_cipher_set_padding(VALUE self, VALUE padding)
00693 {
00694 EVP_CIPHER_CTX *ctx;
00695 int pad = NUM2INT(padding);
00696
00697 GetCipher(self, ctx);
00698 if (EVP_CIPHER_CTX_set_padding(ctx, pad) != 1)
00699 ossl_raise(eCipherError, NULL);
00700 return padding;
00701 }
00702 #else
00703 #define ossl_cipher_set_padding rb_f_notimplement
00704 #endif
00705
00706 #define CIPHER_0ARG_INT(func) \
00707 static VALUE \
00708 ossl_cipher_##func(VALUE self) \
00709 { \
00710 EVP_CIPHER_CTX *ctx; \
00711 GetCipher(self, ctx); \
00712 return INT2NUM(EVP_CIPHER_##func(EVP_CIPHER_CTX_cipher(ctx))); \
00713 }
00714
00715
00716
00717
00718
00719
00720
00721 CIPHER_0ARG_INT(key_length)
00722
00723
00724
00725
00726
00727
00728 CIPHER_0ARG_INT(iv_length)
00729
00730
00731
00732
00733
00734
00735 CIPHER_0ARG_INT(block_size)
00736
00737
00738
00739
00740 void
00741 Init_ossl_cipher(void)
00742 {
00743 #if 0
00744 mOSSL = rb_define_module("OpenSSL");
00745 #endif
00746
00747
00748
00749
00750
00751
00752
00753
00754
00755
00756
00757
00758
00759
00760
00761
00762
00763
00764
00765
00766
00767
00768
00769
00770
00771
00772
00773
00774
00775
00776
00777
00778
00779
00780
00781
00782
00783
00784
00785
00786
00787
00788
00789
00790
00791
00792
00793
00794
00795
00796
00797
00798
00799
00800
00801
00802
00803
00804
00805
00806
00807
00808
00809
00810
00811
00812
00813
00814
00815
00816
00817
00818
00819
00820
00821
00822
00823
00824
00825
00826
00827
00828
00829
00830
00831
00832
00833
00834
00835
00836
00837
00838
00839
00840
00841
00842
00843
00844
00845
00846
00847
00848
00849
00850
00851
00852
00853
00854
00855
00856
00857
00858
00859
00860
00861
00862
00863
00864
00865
00866
00867
00868
00869
00870
00871
00872
00873
00874
00875
00876
00877
00878
00879
00880
00881
00882
00883
00884
00885
00886
00887
00888
00889
00890
00891
00892
00893
00894
00895
00896
00897
00898
00899
00900
00901
00902
00903
00904
00905
00906
00907
00908
00909
00910
00911
00912
00913
00914
00915
00916
00917
00918
00919
00920
00921
00922
00923
00924
00925
00926
00927
00928
00929
00930
00931
00932
00933
00934
00935
00936
00937
00938
00939
00940
00941
00942
00943
00944 cCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject);
00945 eCipherError = rb_define_class_under(cCipher, "CipherError", eOSSLError);
00946
00947 rb_define_alloc_func(cCipher, ossl_cipher_alloc);
00948 rb_define_copy_func(cCipher, ossl_cipher_copy);
00949 rb_define_module_function(cCipher, "ciphers", ossl_s_ciphers, 0);
00950 rb_define_method(cCipher, "initialize", ossl_cipher_initialize, 1);
00951 rb_define_method(cCipher, "reset", ossl_cipher_reset, 0);
00952 rb_define_method(cCipher, "encrypt", ossl_cipher_encrypt, -1);
00953 rb_define_method(cCipher, "decrypt", ossl_cipher_decrypt, -1);
00954 rb_define_method(cCipher, "pkcs5_keyivgen", ossl_cipher_pkcs5_keyivgen, -1);
00955 rb_define_method(cCipher, "update", ossl_cipher_update, -1);
00956 rb_define_method(cCipher, "final", ossl_cipher_final, 0);
00957 rb_define_method(cCipher, "name", ossl_cipher_name, 0);
00958 rb_define_method(cCipher, "key=", ossl_cipher_set_key, 1);
00959 rb_define_method(cCipher, "auth_data=", ossl_cipher_set_auth_data, 1);
00960 rb_define_method(cCipher, "auth_tag=", ossl_cipher_set_auth_tag, 1);
00961 rb_define_method(cCipher, "auth_tag", ossl_cipher_get_auth_tag, -1);
00962 rb_define_method(cCipher, "authenticated?", ossl_cipher_is_authenticated, 0);
00963 rb_define_method(cCipher, "key_len=", ossl_cipher_set_key_length, 1);
00964 rb_define_method(cCipher, "key_len", ossl_cipher_key_length, 0);
00965 rb_define_method(cCipher, "iv=", ossl_cipher_set_iv, 1);
00966 rb_define_method(cCipher, "iv_len", ossl_cipher_iv_length, 0);
00967 rb_define_method(cCipher, "block_size", ossl_cipher_block_size, 0);
00968 rb_define_method(cCipher, "padding=", ossl_cipher_set_padding, 1);
00969 }
00970
00971