nokogiri-xmlsec-me-harder 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +13 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +123 -0
  9. data/Rakefile +30 -0
  10. data/ext/nokogiri_ext_xmlsec/common.h +13 -0
  11. data/ext/nokogiri_ext_xmlsec/extconf.rb +27 -0
  12. data/ext/nokogiri_ext_xmlsec/init.c +76 -0
  13. data/ext/nokogiri_ext_xmlsec/nokogiri_decrypt_with_key.c +82 -0
  14. data/ext/nokogiri_ext_xmlsec/nokogiri_encrypt_with_key.c +169 -0
  15. data/ext/nokogiri_ext_xmlsec/nokogiri_helpers_set_attribute_id.c +76 -0
  16. data/ext/nokogiri_ext_xmlsec/nokogiri_init.c +32 -0
  17. data/ext/nokogiri_ext_xmlsec/nokogiri_sign_certificate.c +186 -0
  18. data/ext/nokogiri_ext_xmlsec/nokogiri_sign_rsa.c +167 -0
  19. data/ext/nokogiri_ext_xmlsec/nokogiri_verify_signature_certificates.c +138 -0
  20. data/ext/nokogiri_ext_xmlsec/nokogiri_verify_signature_named_keys.c +133 -0
  21. data/ext/nokogiri_ext_xmlsec/nokogiri_verify_signature_rsa.c +76 -0
  22. data/ext/nokogiri_ext_xmlsec/options.c +166 -0
  23. data/ext/nokogiri_ext_xmlsec/options.h +36 -0
  24. data/ext/nokogiri_ext_xmlsec/shutdown.c +12 -0
  25. data/ext/nokogiri_ext_xmlsec/util.c +139 -0
  26. data/ext/nokogiri_ext_xmlsec/util.h +42 -0
  27. data/ext/nokogiri_ext_xmlsec/xmlsecrb.h +44 -0
  28. data/lib/nokogiri-xmlsec.rb +1 -0
  29. data/lib/xmlsec.rb +104 -0
  30. data/lib/xmlsec/version.rb +3 -0
  31. data/nokogiri-xmlsec-me-harder.gemspec +39 -0
  32. data/spec/fixtures/cert/server.crt +14 -0
  33. data/spec/fixtures/cert/server.csr +11 -0
  34. data/spec/fixtures/cert/server.key.decrypted +15 -0
  35. data/spec/fixtures/cert/server.key.encrypted +18 -0
  36. data/spec/fixtures/hate.xml +7 -0
  37. data/spec/fixtures/pwned.xml +1 -0
  38. data/spec/fixtures/rsa.pem +15 -0
  39. data/spec/fixtures/rsa.pub +6 -0
  40. data/spec/fixtures/sign2-doc.xml +6 -0
  41. data/spec/fixtures/sign2-result.xml +25 -0
  42. data/spec/fixtures/sign3-result.xml +38 -0
  43. data/spec/lib/nokogiri/xml/document/encryption_and_decryption_spec.rb +34 -0
  44. data/spec/lib/nokogiri/xml/document/signing_and_verifying_spec.rb +123 -0
  45. data/spec/lib/nokogiri/xml/document/unsafe_xml_spec.rb +61 -0
  46. data/spec/spec_helper.rb +10 -0
  47. metadata +213 -0
@@ -0,0 +1,36 @@
1
+ #ifndef NOKOGIRI_EXT_XMLSEC_OPTIONS_H
2
+ #define NOKOGIRI_EXT_XMLSEC_OPTIONS_H
3
+
4
+ #include "common.h"
5
+
6
+ #include <ruby.h>
7
+ #include <xmlsec/crypto.h>
8
+
9
+ typedef struct {
10
+ // From :block_encryption
11
+ xmlSecTransformId block_encryption;
12
+ const char* key_type;
13
+ int key_bits;
14
+
15
+ // From :key_transport
16
+ xmlSecTransformId key_transport;
17
+ } XmlEncOptions;
18
+
19
+ // Supported algorithms taken from #5.1 of
20
+ // http://www.w3.org/TR/xmlenc-core
21
+ //
22
+ // For options, only use the URL fragment (stuff post #)
23
+ // since that's unique enough and it removes a lot of typing.
24
+ BOOL GetXmlEncOptions(VALUE rb_opts, XmlEncOptions* options,
25
+ VALUE* rb_exception_result,
26
+ const char** exception_message);
27
+
28
+ // XML DSIG helpers.
29
+ xmlSecTransformId GetSignatureMethod(VALUE rb_method,
30
+ VALUE* rb_exception_result,
31
+ const char** exception_message);
32
+ xmlSecTransformId GetDigestMethod(VALUE rb_digest_method,
33
+ VALUE* rb_exception_result,
34
+ const char** exception_message);
35
+
36
+ #endif // NOKOGIRI_EXT_XMLSEC_OPTIONS_H
@@ -0,0 +1,12 @@
1
+ #include "xmlsecrb.h"
2
+
3
+ /* not actually called anywhere right now, but here for posterity */
4
+ void Shutdown_xmlsecrb() {
5
+ xmlSecCryptoShutdown();
6
+ xmlSecCryptoAppShutdown();
7
+ xmlSecShutdown();
8
+ xsltCleanupGlobals();
9
+ #ifndef XMLSEC_NO_XSLT
10
+ xsltCleanupGlobals();
11
+ #endif /* XMLSEC_NO_XSLT */
12
+ }
@@ -0,0 +1,139 @@
1
+ #include "util.h"
2
+
3
+ #include <xmlsec/errors.h>
4
+
5
+ xmlSecKeysMngrPtr createKeyManagerWithSingleKey(
6
+ char* keyStr,
7
+ unsigned int keyLength,
8
+ char *keyName,
9
+ VALUE* rb_exception_result_out,
10
+ const char** exception_message_out) {
11
+ VALUE rb_exception_result = Qnil;
12
+ const char* exception_message = NULL;
13
+ xmlSecKeysMngrPtr mngr = NULL;
14
+ xmlSecKeyPtr key = NULL;
15
+
16
+ /* create and initialize keys manager, we use a simple list based
17
+ * keys manager, implement your own xmlSecKeysStore klass if you need
18
+ * something more sophisticated
19
+ */
20
+ mngr = xmlSecKeysMngrCreate();
21
+ if(mngr == NULL) {
22
+ rb_exception_result = rb_eDecryptionError;
23
+ exception_message = "failed to create keys manager.";
24
+ goto done;
25
+ }
26
+ if(xmlSecCryptoAppDefaultKeysMngrInit(mngr) < 0) {
27
+ rb_exception_result = rb_eDecryptionError;
28
+ exception_message = "failed to initialize keys manager.";
29
+ goto done;
30
+ }
31
+
32
+ /* load private RSA key */
33
+ key = xmlSecCryptoAppKeyLoadMemory((xmlSecByte *)keyStr,
34
+ keyLength,
35
+ xmlSecKeyDataFormatPem,
36
+ NULL, // the key file password
37
+ NULL, // the key password callback
38
+ NULL);// the user context for password callback
39
+ if(key == NULL) {
40
+ rb_exception_result = rb_eDecryptionError;
41
+ exception_message = "failed to load rsa key";
42
+ goto done;
43
+ }
44
+
45
+ if(xmlSecKeySetName(key, BAD_CAST keyName) < 0) {
46
+ rb_exception_result = rb_eDecryptionError;
47
+ exception_message = "failed to set key name";
48
+ goto done;
49
+ }
50
+
51
+ /* add key to keys manager, from now on keys manager is responsible
52
+ * for destroying key
53
+ */
54
+ if(xmlSecCryptoAppDefaultKeysMngrAdoptKey(mngr, key) < 0) {
55
+ rb_exception_result = rb_eDecryptionError;
56
+ exception_message = "failed to add key to keys manager";
57
+ goto done;
58
+ }
59
+
60
+ done:
61
+ if(rb_exception_result != Qnil) {
62
+ if (key) {
63
+ xmlSecKeyDestroy(key);
64
+ }
65
+
66
+ if (mngr) {
67
+ xmlSecKeysMngrDestroy(mngr);
68
+ mngr = NULL;
69
+ }
70
+ }
71
+
72
+ *rb_exception_result_out = rb_exception_result;
73
+ *exception_message_out = exception_message;
74
+ return mngr;
75
+ }
76
+
77
+ xmlSecDSigCtxPtr createDSigContext(xmlSecKeysMngrPtr keyManager) {
78
+ xmlSecDSigCtxPtr dsigCtx = xmlSecDSigCtxCreate(keyManager);
79
+ if (!dsigCtx) {
80
+ return NULL;
81
+ }
82
+
83
+ // Restrict ReferenceUris to same document or empty to avoid XXE attacks.
84
+ dsigCtx->enabledReferenceUris = xmlSecTransformUriTypeEmpty |
85
+ xmlSecTransformUriTypeSameDocument;
86
+
87
+ return dsigCtx;
88
+ }
89
+
90
+ #define ERROR_STACK_SIZE 4096
91
+ static char g_errorStack[ERROR_STACK_SIZE];
92
+ static size_t g_errorStackPos;
93
+
94
+ char* getXmlSecLastError() {
95
+ return g_errorStack;
96
+ }
97
+
98
+ int hasXmlSecLastError() {
99
+ return g_errorStack[0] != '\0';
100
+ }
101
+
102
+ void resetXmlSecError() {
103
+ g_errorStack[0] = '\0';
104
+ g_errorStackPos = 0;
105
+ }
106
+
107
+ void storeErrorCallback(const char *file,
108
+ int line,
109
+ const char *func,
110
+ const char *errorObject,
111
+ const char *errorSubject,
112
+ int reason,
113
+ const char *msg) {
114
+ int i = 0;
115
+ const char* error_msg = NULL;
116
+ int amt = 0;
117
+ if (g_errorStackPos >= ERROR_STACK_SIZE) {
118
+ // Just bail. Earlier errors are more interesting usually anyway.
119
+ return;
120
+ }
121
+
122
+ for(i = 0; (i < XMLSEC_ERRORS_MAX_NUMBER) && (xmlSecErrorsGetMsg(i) != NULL); ++i) {
123
+ if(xmlSecErrorsGetCode(i) == reason) {
124
+ error_msg = xmlSecErrorsGetMsg(i);
125
+ break;
126
+ }
127
+ }
128
+
129
+ amt = snprintf(
130
+ &g_errorStack[g_errorStackPos],
131
+ ERROR_STACK_SIZE - g_errorStackPos,
132
+ "func=%s:file=%s:line=%d:obj=%s:subj=%s:error=%d:%s:%s\n",
133
+ func, file, line, errorObject, errorSubject, reason,
134
+ error_msg ? error_msg : "", msg);
135
+
136
+ if (amt > 0) {
137
+ g_errorStackPos += amt;
138
+ }
139
+ }
@@ -0,0 +1,42 @@
1
+ #ifndef NOKOGIRI_EXT_XMLSEC_UTIL_H
2
+ #define NOKOGIRI_EXT_XMLSEC_UTIL_H
3
+
4
+ #include "xmlsecrb.h"
5
+
6
+ // Constructs a xmlSecKeysMngr and adds the given named key to the manager.
7
+ //
8
+ // Caller takes ownership. Free with xmlSecKeysMngrDestroy().
9
+ xmlSecKeysMngrPtr createKeyManagerWithSingleKey(
10
+ char* keyStr,
11
+ unsigned int keyLength,
12
+ char *keyName,
13
+ VALUE* rb_exception_result_out,
14
+ const char** exception_message_out);
15
+
16
+ // Creates a xmlSecDSigCtx with defaults locked down to prevent XXE.
17
+ //
18
+ // Caller takes ownership of the context. Free with xmlSecDSigCtxDestroy().
19
+ xmlSecDSigCtxPtr createDSigContext(xmlSecKeysMngrPtr keyManager);
20
+
21
+ // Retrieves the recorded error strings from libxmlsec1. Ensure resetXmlSecError()
22
+ // is called at the start of the range of error collection.
23
+ char* getXmlSecLastError();
24
+
25
+ // Reset the recording of errors. After this getXmlSecLastError() will return
26
+ // an empty string. Call at the start of a logical interaction with libxmlsec.
27
+ void resetXmlSecError();
28
+
29
+ // Return false if there are no errors. If false, getXmlSecLastError() will
30
+ // return an empty string.
31
+ int hasXmlSecLastError();
32
+
33
+ // Error reporting hooks to redirect Xmlsec1 library errors away from stdout.
34
+ void storeErrorCallback(const char *file,
35
+ int line,
36
+ const char *func,
37
+ const char *errorObject,
38
+ const char *errorSubject,
39
+ int reason,
40
+ const char *msg);
41
+
42
+ #endif // NOKOGIRI_EXT_XMLSEC_UTIL_H
@@ -0,0 +1,44 @@
1
+ #ifndef NOKOGIRI_EXT_XMLSEC_XMLSECRB_H
2
+ #define NOKOGIRI_EXT_XMLSEC_XMLSECRB_H
3
+
4
+ #include "common.h"
5
+
6
+ #include <ruby.h>
7
+
8
+ #include <libxml/tree.h>
9
+ #include <libxml/xmlmemory.h>
10
+ #include <libxml/parser.h>
11
+ #include <libxml/xmlstring.h>
12
+
13
+ #include <libxslt/xslt.h>
14
+
15
+ #include <xmlsec/xmlsec.h>
16
+ #include <xmlsec/xmltree.h>
17
+ #include <xmlsec/xmldsig.h>
18
+ #include <xmlsec/xmlenc.h>
19
+ #include <xmlsec/templates.h>
20
+ #include <xmlsec/crypto.h>
21
+
22
+ // TODO(awong): Support non-gcc and non-clang compilers.
23
+ #define EXTENSION_EXPORT __attribute__((visibility("default")))
24
+
25
+ VALUE sign_with_key(VALUE self, VALUE rb_opts);
26
+ VALUE sign_with_certificate(VALUE self, VALUE rb_opts);
27
+ VALUE verify_signature_with_rsa_key(VALUE self, VALUE rb_rsa_key);
28
+ VALUE verify_signature_with_named_keys(VALUE self, VALUE rb_keys);
29
+ VALUE verify_signature_with_certificates(VALUE self, VALUE rb_certs);
30
+ VALUE encrypt_with_key(VALUE self, VALUE rb_rsa_key_name, VALUE rb_rsa_key,
31
+ VALUE rb_opts);
32
+ VALUE decrypt_with_key(VALUE self, VALUE rb_key_name, VALUE rb_key);
33
+ VALUE set_id_attribute(VALUE self, VALUE rb_attr_name);
34
+
35
+ void Init_Nokogiri_ext(void);
36
+
37
+ extern VALUE rb_cNokogiri_XML_Document;
38
+ extern VALUE rb_eSigningError;
39
+ extern VALUE rb_eVerificationError;
40
+ extern VALUE rb_eKeystoreError;
41
+ extern VALUE rb_eEncryptionError;
42
+ extern VALUE rb_eDecryptionError;
43
+
44
+ #endif // NOKOGIRI_EXT_XMLSEC_XMLSECRB_H
@@ -0,0 +1 @@
1
+ require 'xmlsec'
data/lib/xmlsec.rb ADDED
@@ -0,0 +1,104 @@
1
+ require "xmlsec/version"
2
+ require 'nokogiri'
3
+ require 'nokogiri_ext_xmlsec'
4
+
5
+ class Nokogiri::XML::Document
6
+ # Signs this document, and then returns it.
7
+ #
8
+ # Examples:
9
+ #
10
+ # doc.sign! key: 'rsa-private-key'
11
+ # doc.sign! key: 'rsa-private-key', name: 'key-name'
12
+ # doc.sign! cert: 'x509 certificate', key: 'cert private key'
13
+ # doc.sign! cert: 'x509 certificate', key: 'cert private key',
14
+ # name: 'key-name'
15
+ def sign! opts
16
+ if opts.has_key? :cert
17
+ raise "need a private :key" unless opts[:key]
18
+ sign_with_certificate opts
19
+ elsif opts[:key]
20
+ sign_with_key opts
21
+ else
22
+ raise "No private :key was given"
23
+ end
24
+ self
25
+ end
26
+
27
+ # Verifies the signature on the current document.
28
+ #
29
+ # Returns `true` if the signature is valid, `false` otherwise.
30
+ #
31
+ # Examples:
32
+ #
33
+ # # Try to validate with the given public or private key
34
+ # doc.verify_with key: 'rsa-key'
35
+ #
36
+ # # Try to validate with a set of keys. It will try to match
37
+ # # based on the contents of the `KeyName` element.
38
+ # doc.verify_with({
39
+ # 'key-name' => 'x509 certificate',
40
+ # 'another-key-name' => 'rsa-public-key'
41
+ # })
42
+ #
43
+ # # Try to validate with a trusted certificate
44
+ # doc.verify_with(x509: 'certificate')
45
+ #
46
+ # # Try to validate with a set of certificates, any one of which
47
+ # # can match
48
+ # doc.verify_with(x509: ['cert1', 'cert2'])
49
+ #
50
+ # You can also use `:cert` or `:certificate` or `:certs` or
51
+ # `:certificates` as aliases for `:x509`.
52
+ #
53
+ def verify_with opts_or_keys
54
+ if (certs = opts_or_keys[:cert]) ||
55
+ (certs = opts_or_keys[:certs])
56
+ certs = [certs] unless certs.kind_of?(Array)
57
+ verify_with_certificates certs
58
+ elsif opts_or_keys[:key]
59
+ verify_with_rsa_key opts_or_keys[:key]
60
+ else
61
+ verify_with_named_keys opts_or_keys
62
+ end
63
+ end
64
+
65
+ # Attempts to verify the signature of this document using only certificates
66
+ # installed on the system. This is equivalent to calling
67
+ # `verify_with certificates: []` (that is, an empty array).
68
+ #
69
+ def verify_signature
70
+ verify_with_certificates []
71
+ end
72
+
73
+ # Encrypts the current document, then returns it.
74
+ #
75
+ # Examples:
76
+ #
77
+ # # encrypt with a public key and optional key name
78
+ # doc.encrypt! key: 'public-key', name: 'name'
79
+ #
80
+ def encrypt! opts
81
+ if opts[:key]
82
+ encrypt_with_key opts[:name].to_s, opts[:key], opts.select { |key, _| key != :key && key != :name }
83
+ else
84
+ raise "public :key is required for encryption"
85
+ end
86
+ self
87
+ end
88
+
89
+ # Decrypts the current document, then returns it.
90
+ #
91
+ # Examples:
92
+ #
93
+ # # decrypt with a specific private key
94
+ # doc.decrypt! key: 'private-key'
95
+ #
96
+ def decrypt! opts
97
+ if opts[:key]
98
+ decrypt_with_key opts[:name].to_s, opts[:key]
99
+ else
100
+ raise 'inadequate options specified for decryption'
101
+ end
102
+ self
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ module Xmlsec
2
+ VERSION = '0.9.0'
3
+ end
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'xmlsec/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "nokogiri-xmlsec-me-harder"
8
+ spec.version = Xmlsec::VERSION
9
+ spec.authors = ["Albert J. Wong"]
10
+ spec.email = ["awong.dev@gmail.com"]
11
+ spec.description = %q{Adds support to Ruby for encrypting, decrypting,
12
+ signing and validating the signatures of XML documents, according to the
13
+ [XML Encryption Syntax and Processing](http://www.w3.org/TR/xmlenc-core/)
14
+ standard, by wrapping around the [xmlsec](http://www.aleksey.com/xmlsec) C
15
+ library and adding relevant methods to `Nokogiri::XML::Document`.
16
+ Implementation is based off nokogiri-xmlsec by
17
+ "Colin MacKenzie IV" <inisterchipmunk@gmail.com> with heavy modifications
18
+ and some API changes.}
19
+ spec.summary = %q{Wrapper around http://www.aleksey.com/xmlsec to
20
+ support XML encryption, decryption, signing and signature validation in
21
+ Ruby}
22
+ spec.homepage = "https://github.com/omb-awong/xmlsec"
23
+ spec.license = "MIT"
24
+
25
+ spec.files = `git ls-files`.split($/)
26
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
27
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
28
+ spec.require_paths = ["lib"]
29
+ spec.extensions = %w{ext/nokogiri_ext_xmlsec/extconf.rb}
30
+
31
+ spec.add_dependency 'nokogiri'
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.3"
34
+ spec.add_development_dependency "rake"
35
+ spec.add_development_dependency "rake-compiler"
36
+ spec.add_development_dependency "rspec"
37
+ spec.add_development_dependency "guard-rspec"
38
+ spec.add_development_dependency "guard-rake"
39
+ end
@@ -0,0 +1,14 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICLzCCAZgCCQCVuhhQ38rw0TANBgkqhkiG9w0BAQUFADBbMQswCQYDVQQGEwJV
3
+ UzEQMA4GA1UECAwHR2VvcmdpYTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ
4
+ dHkgTHRkMRcwFQYDVQQDDA53d3cuZ29vZ2xlLmNvbTAgFw0xMzA1MjUxODQwMDRa
5
+ GA8zMDEyMDkyNTE4NDAwNFowWzELMAkGA1UEBhMCVVMxEDAOBgNVBAgMB0dlb3Jn
6
+ aWExITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEXMBUGA1UEAwwO
7
+ d3d3Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALE4oSql
8
+ eymfHtzOeY86WyvfsjZmaz2XnIo9dzZsK71yMEKkgvXQnnYy9pK0NaYcG0B0hcii
9
+ 3fqGBiHMkZY2BOGWwCC/wOmJCzLq9q6caPWUs71Zko+h59LaqV93vzDmZaXYfFoQ
10
+ gSVEWpEpCSo560x0mSuLnJYdQQzZ/L6xvxZ1AgMBAAEwDQYJKoZIhvcNAQEFBQAD
11
+ gYEATyK/RlfpohUVimgFkycTF2hyusjctseXoZDCctgg/STMsL8iA0P9YB6k91GC
12
+ kWpwevuiwarD1MfSUV6goPINFkIBvfK+5R9lpHaTqqs615z8T9R5VJgaLcFe3tWd
13
+ 7oq3V2q5Nl6MrZfXj2N07qe6/9zfdauxYO26vAEKCvIkbMo=
14
+ -----END CERTIFICATE-----