nokogiri-xmlsec 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +13 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +121 -0
  8. data/Rakefile +30 -0
  9. data/ext/nokogiri_ext_xmlsec/extconf.rb +20 -0
  10. data/ext/nokogiri_ext_xmlsec/init.c +46 -0
  11. data/ext/nokogiri_ext_xmlsec/nokogiri_decrypt_with_key.c +124 -0
  12. data/ext/nokogiri_ext_xmlsec/nokogiri_encrypt_with_key.c +182 -0
  13. data/ext/nokogiri_ext_xmlsec/nokogiri_init.c +29 -0
  14. data/ext/nokogiri_ext_xmlsec/nokogiri_sign_certificate.c +104 -0
  15. data/ext/nokogiri_ext_xmlsec/nokogiri_sign_rsa.c +95 -0
  16. data/ext/nokogiri_ext_xmlsec/nokogiri_verify_signature_certificates.c +96 -0
  17. data/ext/nokogiri_ext_xmlsec/nokogiri_verify_signature_named_keys.c +106 -0
  18. data/ext/nokogiri_ext_xmlsec/nokogiri_verify_signature_rsa.c +56 -0
  19. data/ext/nokogiri_ext_xmlsec/shutdown.c +12 -0
  20. data/ext/nokogiri_ext_xmlsec/xmlsecrb.h +38 -0
  21. data/lib/nokogiri-xmlsec.rb +1 -0
  22. data/lib/xmlsec.rb +110 -0
  23. data/lib/xmlsec/version.rb +3 -0
  24. data/nokogiri-xmlsec.gemspec +36 -0
  25. data/spec/fixtures/cert/server.crt +14 -0
  26. data/spec/fixtures/cert/server.csr +11 -0
  27. data/spec/fixtures/cert/server.key.decrypted +15 -0
  28. data/spec/fixtures/cert/server.key.encrypted +18 -0
  29. data/spec/fixtures/rsa.pem +15 -0
  30. data/spec/fixtures/rsa.pub +6 -0
  31. data/spec/fixtures/sign2-doc.xml +6 -0
  32. data/spec/fixtures/sign2-result.xml +24 -0
  33. data/spec/fixtures/sign3-result.xml +37 -0
  34. data/spec/lib/nokogiri/xml/document/encryption_and_decryption_spec.rb +28 -0
  35. data/spec/lib/nokogiri/xml/document/signing_and_verifying_spec.rb +70 -0
  36. data/spec/spec_helper.rb +10 -0
  37. metadata +196 -0
@@ -0,0 +1,29 @@
1
+ #include "xmlsecrb.h"
2
+
3
+ VALUE rb_cNokogiri_XML_Document = T_NIL;
4
+ VALUE rb_eSigningError = T_NIL;
5
+ VALUE rb_eVerificationError = T_NIL;
6
+ VALUE rb_eKeystoreError = T_NIL;
7
+ VALUE rb_eEncryptionError = T_NIL;
8
+ VALUE rb_eDecryptionError = T_NIL;
9
+
10
+ void Init_Nokogiri_ext() {
11
+ VALUE XMLSec = rb_define_module("XMLSec");
12
+ VALUE Nokogiri = rb_define_module("Nokogiri");
13
+ VALUE Nokogiri_XML = rb_define_module_under(Nokogiri, "XML");
14
+ rb_cNokogiri_XML_Document = rb_const_get(Nokogiri_XML, rb_intern("Document"));
15
+
16
+ rb_define_method(rb_cNokogiri_XML_Document, "sign_with_key", sign_with_key, 2);
17
+ rb_define_method(rb_cNokogiri_XML_Document, "sign_with_certificate", sign_with_certificate, 3);
18
+ rb_define_method(rb_cNokogiri_XML_Document, "verify_with_rsa_key", verify_signature_with_rsa_key, 1);
19
+ rb_define_method(rb_cNokogiri_XML_Document, "verify_with_named_keys", verify_signature_with_named_keys, 1);
20
+ rb_define_method(rb_cNokogiri_XML_Document, "verify_with_certificates", verify_signature_with_certificates, 1);
21
+ rb_define_method(rb_cNokogiri_XML_Document, "encrypt_with_key", encrypt_with_key, 2);
22
+ rb_define_method(rb_cNokogiri_XML_Document, "decrypt_with_key", decrypt_with_key, 2);
23
+
24
+ rb_eSigningError = rb_define_class_under(XMLSec, "SigningError", rb_eRuntimeError);
25
+ rb_eVerificationError = rb_define_class_under(XMLSec, "VerificationError", rb_eRuntimeError);
26
+ rb_eKeystoreError = rb_define_class_under(XMLSec, "KeystoreError", rb_eRuntimeError);
27
+ rb_eEncryptionError = rb_define_class_under(XMLSec, "EncryptionError", rb_eRuntimeError);
28
+ rb_eDecryptionError = rb_define_class_under(XMLSec, "DecryptionError", rb_eRuntimeError);
29
+ }
@@ -0,0 +1,104 @@
1
+ #include "xmlsecrb.h"
2
+
3
+ VALUE sign_with_certificate(VALUE self, VALUE rb_key_name, VALUE rb_rsa_key, VALUE rb_cert) {
4
+ xmlDocPtr doc;
5
+ xmlNodePtr signNode = NULL;
6
+ xmlNodePtr refNode = NULL;
7
+ xmlNodePtr keyInfoNode = NULL;
8
+ xmlSecDSigCtxPtr dsigCtx = NULL;
9
+ char *keyName;
10
+ char *certificate;
11
+ char *rsaKey;
12
+ unsigned int rsaKeyLength, certificateLength;
13
+
14
+ Data_Get_Struct(self, xmlDoc, doc);
15
+ rsaKey = RSTRING_PTR(rb_rsa_key);
16
+ rsaKeyLength = RSTRING_LEN(rb_rsa_key);
17
+ keyName = RSTRING_PTR(rb_key_name);
18
+ certificate = RSTRING_PTR(rb_cert);
19
+ certificateLength = RSTRING_LEN(rb_cert);
20
+
21
+ // create signature template for RSA-SHA1 enveloped signature
22
+ signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformExclC14NId,
23
+ xmlSecTransformRsaSha1Id, NULL);
24
+ if (signNode == NULL) {
25
+ rb_raise(rb_eSigningError, "failed to create signature template");
26
+ goto done;
27
+ }
28
+
29
+ // add <dsig:Signature/> node to the doc
30
+ xmlAddChild(xmlDocGetRootElement(doc), signNode);
31
+
32
+ // add reference
33
+ refNode = xmlSecTmplSignatureAddReference(signNode, xmlSecTransformSha1Id,
34
+ NULL, NULL, NULL);
35
+ if(refNode == NULL) {
36
+ rb_raise(rb_eSigningError, "failed to add reference to signature template");
37
+ goto done;
38
+ }
39
+
40
+ // add enveloped transform
41
+ if(xmlSecTmplReferenceAddTransform(refNode, xmlSecTransformEnvelopedId) == NULL) {
42
+ rb_raise(rb_eSigningError, "failed to add enveloped transform to reference");
43
+ goto done;
44
+ }
45
+
46
+ // add <dsig:KeyInfo/> and <dsig:X509Data/>
47
+ keyInfoNode = xmlSecTmplSignatureEnsureKeyInfo(signNode, NULL);
48
+ if(keyInfoNode == NULL) {
49
+ rb_raise(rb_eSigningError, "failed to add key info");
50
+ goto done;
51
+ }
52
+
53
+ if(xmlSecTmplKeyInfoAddX509Data(keyInfoNode) == NULL) {
54
+ rb_raise(rb_eSigningError, "failed to add X509Data node");
55
+ goto done;
56
+ }
57
+
58
+ // create signature context, we don't need keys manager in this example
59
+ dsigCtx = xmlSecDSigCtxCreate(NULL);
60
+ if(dsigCtx == NULL) {
61
+ rb_raise(rb_eSigningError, "failed to create signature context");
62
+ goto done;
63
+ }
64
+
65
+ // load private key, assuming that there is not password
66
+ dsigCtx->signKey = xmlSecCryptoAppKeyLoadMemory((xmlSecByte *)rsaKey,
67
+ rsaKeyLength,
68
+ xmlSecKeyDataFormatPem,
69
+ NULL, // password
70
+ NULL,
71
+ NULL);
72
+ if(dsigCtx->signKey == NULL) {
73
+ rb_raise(rb_eSigningError, "failed to load private key");
74
+ goto done;
75
+ }
76
+
77
+ // load certificate and add to the key
78
+ if(xmlSecCryptoAppKeyCertLoadMemory(dsigCtx->signKey,
79
+ (xmlSecByte *)certificate,
80
+ certificateLength,
81
+ xmlSecKeyDataFormatPem) < 0) {
82
+ rb_raise(rb_eSigningError, "failed to load certificate");
83
+ goto done;
84
+ }
85
+
86
+ // set key name
87
+ if(xmlSecKeySetName(dsigCtx->signKey, (xmlSecByte *)keyName) < 0) {
88
+ rb_raise(rb_eSigningError, "failed to set key name");
89
+ goto done;
90
+ }
91
+
92
+ // sign the template
93
+ if(xmlSecDSigCtxSign(dsigCtx, signNode) < 0) {
94
+ rb_raise(rb_eSigningError, "signature failed");
95
+ goto done;
96
+ }
97
+
98
+ done:
99
+ if(dsigCtx != NULL) {
100
+ xmlSecDSigCtxDestroy(dsigCtx);
101
+ }
102
+
103
+ return T_NIL;
104
+ }
@@ -0,0 +1,95 @@
1
+ #include "xmlsecrb.h"
2
+
3
+ // TODO the signature context probably should be a ruby instance variable
4
+ // and separate object, instead of being allocated/freed in each method.
5
+
6
+ VALUE sign_with_key(VALUE self, VALUE rb_key_name, VALUE rb_rsa_key) {
7
+ xmlDocPtr doc;
8
+ xmlNodePtr signNode = NULL;
9
+ xmlNodePtr refNode = NULL;
10
+ xmlNodePtr keyInfoNode = NULL;
11
+ xmlSecDSigCtxPtr dsigCtx = NULL;
12
+ char *keyName;
13
+ char *rsaKey;
14
+ unsigned int rsaKeyLength;
15
+
16
+ Data_Get_Struct(self, xmlDoc, doc);
17
+ rsaKey = RSTRING_PTR(rb_rsa_key);
18
+ rsaKeyLength = RSTRING_LEN(rb_rsa_key);
19
+ keyName = RSTRING_PTR(rb_key_name);
20
+
21
+ // create signature template for RSA-SHA1 enveloped signature
22
+ signNode = xmlSecTmplSignatureCreate(doc, xmlSecTransformExclC14NId,
23
+ xmlSecTransformRsaSha1Id, NULL);
24
+ if (signNode == NULL) {
25
+ rb_raise(rb_eSigningError, "failed to create signature template");
26
+ goto done;
27
+ }
28
+
29
+ // add <dsig:Signature/> node to the doc
30
+ xmlAddChild(xmlDocGetRootElement(doc), signNode);
31
+
32
+ // add reference
33
+ refNode = xmlSecTmplSignatureAddReference(signNode, xmlSecTransformSha1Id,
34
+ NULL, NULL, NULL);
35
+ if(refNode == NULL) {
36
+ rb_raise(rb_eSigningError, "failed to add reference to signature template");
37
+ goto done;
38
+ }
39
+
40
+ // add enveloped transform
41
+ if(xmlSecTmplReferenceAddTransform(refNode, xmlSecTransformEnvelopedId) == NULL) {
42
+ rb_raise(rb_eSigningError, "failed to add enveloped transform to reference");
43
+ goto done;
44
+ }
45
+
46
+ // add <dsig:KeyInfo/> and <dsig:KeyName/> nodes to put key name in the signed
47
+ // document
48
+ keyInfoNode = xmlSecTmplSignatureEnsureKeyInfo(signNode, NULL);
49
+ if(keyInfoNode == NULL) {
50
+ rb_raise(rb_eSigningError, "failed to add key info");
51
+ goto done;
52
+ }
53
+ if(xmlSecTmplKeyInfoAddKeyName(keyInfoNode, NULL) == NULL) {
54
+ rb_raise(rb_eSigningError, "failed to add key name");
55
+ goto done;
56
+ }
57
+
58
+ // create signature context, we don't need keys manager in this example
59
+ dsigCtx = xmlSecDSigCtxCreate(NULL);
60
+ if(dsigCtx == NULL) {
61
+ rb_raise(rb_eSigningError, "failed to create signature context");
62
+ goto done;
63
+ }
64
+
65
+ // load private key, assuming that there is no password
66
+ dsigCtx->signKey = xmlSecCryptoAppKeyLoadMemory((xmlSecByte *)rsaKey,
67
+ rsaKeyLength,
68
+ xmlSecKeyDataFormatPem,
69
+ NULL, // password
70
+ NULL,
71
+ NULL);
72
+ if(dsigCtx->signKey == NULL) {
73
+ rb_raise(rb_eSigningError, "failed to load private pem key");
74
+ goto done;
75
+ }
76
+
77
+ // set key name
78
+ if(xmlSecKeySetName(dsigCtx->signKey, (xmlSecByte *)keyName) < 0) {
79
+ rb_raise(rb_eSigningError, "failed to set key name");
80
+ goto done;
81
+ }
82
+
83
+ // sign the template
84
+ if(xmlSecDSigCtxSign(dsigCtx, signNode) < 0) {
85
+ rb_raise(rb_eSigningError, "signature failed");
86
+ goto done;
87
+ }
88
+
89
+ done:
90
+ if(dsigCtx != NULL) {
91
+ xmlSecDSigCtxDestroy(dsigCtx);
92
+ }
93
+
94
+ return T_NIL;
95
+ }
@@ -0,0 +1,96 @@
1
+ #include "xmlsecrb.h"
2
+
3
+ static xmlSecKeysMngrPtr getKeyManager(VALUE rb_certs) {
4
+ int i, numCerts = RARRAY_LEN(rb_certs);
5
+ xmlSecKeysMngrPtr keyManager = xmlSecKeysMngrCreate();
6
+ VALUE rb_cert;
7
+ char *cert;
8
+ unsigned int certLength;
9
+ int numSuccessful = 0;
10
+
11
+ if (keyManager == NULL) return NULL;
12
+
13
+ if (xmlSecCryptoAppDefaultKeysMngrInit(keyManager) < 0) {
14
+ rb_raise(rb_eKeystoreError, "could not initialize key manager");
15
+ xmlSecKeysMngrDestroy(keyManager);
16
+ return NULL;
17
+ }
18
+
19
+ for (i = 0; i < numCerts; i++) {
20
+ rb_cert = RARRAY_PTR(rb_certs)[i];
21
+ Check_Type(rb_cert, T_STRING);
22
+ cert = RSTRING_PTR(rb_cert);
23
+ certLength = RSTRING_LEN(rb_cert);
24
+
25
+ if(xmlSecCryptoAppKeysMngrCertLoadMemory(keyManager,
26
+ (xmlSecByte *)cert,
27
+ certLength,
28
+ xmlSecKeyDataFormatPem,
29
+ xmlSecKeyDataTypeTrusted) < 0) {
30
+ rb_warn("failed to load certificate at index %d", i);
31
+ } else {
32
+ numSuccessful++;
33
+ }
34
+ }
35
+
36
+ // note, numCerts could be zero, meaning that we should use system SSL certs
37
+ if (numSuccessful == 0 && numCerts != 0) {
38
+ rb_raise(rb_eKeystoreError, "Could not load any of the specified certificates for signature verification");
39
+ xmlSecKeysMngrDestroy(keyManager);
40
+ return NULL;
41
+ }
42
+
43
+ return keyManager;
44
+ }
45
+
46
+ VALUE verify_signature_with_certificates(VALUE self, VALUE rb_certs) {
47
+ xmlDocPtr doc;
48
+ xmlNodePtr node = NULL;
49
+ xmlSecDSigCtxPtr dsigCtx = NULL;
50
+ xmlSecKeysMngrPtr keyManager = NULL;
51
+ VALUE result = Qfalse;
52
+
53
+ Check_Type(rb_certs, T_ARRAY);
54
+ Data_Get_Struct(self, xmlDoc, doc);
55
+
56
+ // find start node
57
+ node = xmlSecFindNode(xmlDocGetRootElement(doc), xmlSecNodeSignature, xmlSecDSigNs);
58
+ if(node == NULL) {
59
+ rb_raise(rb_eVerificationError, "start node not found");
60
+ goto done;
61
+ }
62
+
63
+ keyManager = getKeyManager(rb_certs);
64
+ if (keyManager == NULL) {
65
+ rb_raise(rb_eKeystoreError, "failed to create key manager");
66
+ goto done;
67
+ }
68
+
69
+ // create signature context, we don't need keys manager in this example
70
+ dsigCtx = xmlSecDSigCtxCreate(keyManager);
71
+ if(dsigCtx == NULL) {
72
+ rb_raise(rb_eVerificationError, "failed to create signature context");
73
+ goto done;
74
+ }
75
+
76
+ // verify signature
77
+ if(xmlSecDSigCtxVerify(dsigCtx, node) < 0) {
78
+ rb_raise(rb_eVerificationError, "signature could not be verified");
79
+ goto done;
80
+ }
81
+
82
+ if(dsigCtx->status == xmlSecDSigStatusSucceeded) {
83
+ result = Qtrue;
84
+ }
85
+
86
+ done:
87
+ if(dsigCtx != NULL) {
88
+ xmlSecDSigCtxDestroy(dsigCtx);
89
+ }
90
+
91
+ if (keyManager != NULL) {
92
+ xmlSecKeysMngrDestroy(keyManager);
93
+ }
94
+
95
+ return result;
96
+ }
@@ -0,0 +1,106 @@
1
+ #include "xmlsecrb.h"
2
+
3
+ static int addRubyKeyToManager(VALUE rb_key, VALUE rb_value, VALUE rb_manager) {
4
+ xmlSecKeysMngrPtr keyManager = (xmlSecKeysMngrPtr)rb_manager;
5
+ char *keyName, *keyData;
6
+ unsigned int keyDataLength;
7
+ xmlSecKeyPtr key;
8
+
9
+ Check_Type(rb_key, T_STRING);
10
+ Check_Type(rb_value, T_STRING);
11
+ keyName = RSTRING_PTR(rb_key);
12
+ keyData = RSTRING_PTR(rb_value);
13
+ keyDataLength = RSTRING_LEN(rb_value);
14
+
15
+ // load key
16
+ key = xmlSecCryptoAppKeyLoadMemory((xmlSecByte *)keyData,
17
+ keyDataLength,
18
+ xmlSecKeyDataFormatPem,
19
+ NULL, // password
20
+ NULL, NULL);
21
+ if (key == NULL) {
22
+ rb_warn("failed to load '%s' public or private pem key", keyName);
23
+ return ST_CONTINUE;
24
+ }
25
+
26
+ // set key name
27
+ if (xmlSecKeySetName(key, BAD_CAST keyName) < 0) {
28
+ rb_warn("failed to set key name for key '%s'", keyName);
29
+ return ST_CONTINUE;
30
+ }
31
+
32
+ // add key to key manager; from now on the manager is responsible for
33
+ // destroying the key
34
+ if (xmlSecCryptoAppDefaultKeysMngrAdoptKey(keyManager, key) < 0) {
35
+ rb_warn("failed to add key '%s' to key manager", keyName);
36
+ return ST_CONTINUE;
37
+ }
38
+
39
+ return ST_CONTINUE;
40
+ }
41
+
42
+ static xmlSecKeysMngrPtr getKeyManager(VALUE rb_hash) {
43
+ xmlSecKeysMngrPtr keyManager = xmlSecKeysMngrCreate();
44
+ if (keyManager == NULL) return NULL;
45
+ if (xmlSecCryptoAppDefaultKeysMngrInit(keyManager) < 0) {
46
+ rb_raise(rb_eKeystoreError, "could not initialize key manager");
47
+ xmlSecKeysMngrDestroy(keyManager);
48
+ return NULL;
49
+ }
50
+
51
+ rb_hash_foreach(rb_hash, addRubyKeyToManager, (VALUE)keyManager);
52
+
53
+ return keyManager;
54
+ }
55
+
56
+ VALUE verify_signature_with_named_keys(VALUE self, VALUE rb_hash) {
57
+ xmlDocPtr doc;
58
+ xmlNodePtr node = NULL;
59
+ xmlSecDSigCtxPtr dsigCtx = NULL;
60
+ xmlSecKeysMngrPtr keyManager = NULL;
61
+ VALUE result = Qfalse;
62
+
63
+ Check_Type(rb_hash, T_HASH);
64
+ Data_Get_Struct(self, xmlDoc, doc);
65
+
66
+ // find start node
67
+ node = xmlSecFindNode(xmlDocGetRootElement(doc), xmlSecNodeSignature, xmlSecDSigNs);
68
+ if(node == NULL) {
69
+ rb_raise(rb_eVerificationError, "start node not found");
70
+ goto done;
71
+ }
72
+
73
+ keyManager = getKeyManager(rb_hash);
74
+ if (keyManager == NULL) {
75
+ rb_raise(rb_eKeystoreError, "failed to create key manager");
76
+ goto done;
77
+ }
78
+
79
+ // create signature context, we don't need keys manager in this example
80
+ dsigCtx = xmlSecDSigCtxCreate(keyManager);
81
+ if(dsigCtx == NULL) {
82
+ rb_raise(rb_eVerificationError, "failed to create signature context");
83
+ goto done;
84
+ }
85
+
86
+ // verify signature
87
+ if(xmlSecDSigCtxVerify(dsigCtx, node) < 0) {
88
+ rb_raise(rb_eVerificationError, "signature could not be verified");
89
+ goto done;
90
+ }
91
+
92
+ if(dsigCtx->status == xmlSecDSigStatusSucceeded) {
93
+ result = Qtrue;
94
+ }
95
+
96
+ done:
97
+ if(dsigCtx != NULL) {
98
+ xmlSecDSigCtxDestroy(dsigCtx);
99
+ }
100
+
101
+ if (keyManager != NULL) {
102
+ xmlSecKeysMngrDestroy(keyManager);
103
+ }
104
+
105
+ return result;
106
+ }
@@ -0,0 +1,56 @@
1
+ #include "xmlsecrb.h"
2
+
3
+ VALUE verify_signature_with_rsa_key(VALUE self, VALUE rb_rsa_key) {
4
+ xmlDocPtr doc;
5
+ xmlNodePtr node = NULL;
6
+ xmlSecDSigCtxPtr dsigCtx = NULL;
7
+ char *rsaKey;
8
+ unsigned int rsaKeyLength;
9
+ VALUE result = Qfalse;
10
+
11
+ Data_Get_Struct(self, xmlDoc, doc);
12
+ rsaKey = RSTRING_PTR(rb_rsa_key);
13
+ rsaKeyLength = RSTRING_LEN(rb_rsa_key);
14
+
15
+ // find start node
16
+ node = xmlSecFindNode(xmlDocGetRootElement(doc), xmlSecNodeSignature, xmlSecDSigNs);
17
+ if(node == NULL) {
18
+ rb_raise(rb_eVerificationError, "start node not found");
19
+ goto done;
20
+ }
21
+
22
+ // create signature context, we don't need keys manager in this example
23
+ dsigCtx = xmlSecDSigCtxCreate(NULL);
24
+ if(dsigCtx == NULL) {
25
+ rb_raise(rb_eVerificationError, "failed to create signature context");
26
+ goto done;
27
+ }
28
+
29
+ // load public key
30
+ dsigCtx->signKey = xmlSecCryptoAppKeyLoadMemory((xmlSecByte *)rsaKey,
31
+ rsaKeyLength,
32
+ xmlSecKeyDataFormatPem,
33
+ NULL, // password
34
+ NULL, NULL);
35
+ if(dsigCtx->signKey == NULL) {
36
+ rb_raise(rb_eVerificationError, "failed to load public pem key");
37
+ goto done;
38
+ }
39
+
40
+ // verify signature
41
+ if(xmlSecDSigCtxVerify(dsigCtx, node) < 0) {
42
+ rb_raise(rb_eVerificationError, "signature could not be verified");
43
+ goto done;
44
+ }
45
+
46
+ if(dsigCtx->status == xmlSecDSigStatusSucceeded) {
47
+ result = Qtrue;
48
+ }
49
+
50
+ done:
51
+ if(dsigCtx != NULL) {
52
+ xmlSecDSigCtxDestroy(dsigCtx);
53
+ }
54
+
55
+ return result;
56
+ }