blather 0.5.4 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'blather/client'
5
+
6
+ setup 'chris@vines.local', 'test', 'vines.local', 5222, "./certs"
7
+
8
+ when_ready { puts "Connected ! send messages to #{jid.stripped}." }
9
+
10
+ subscription :request? do |s|
11
+ write_to_stream s.approve!
12
+ end
13
+
14
+ message :chat?, :body => 'exit' do |m|
15
+ say m.from, 'Exiting ...'
16
+ shutdown
17
+ end
18
+
19
+ message :chat?, :body do |m|
20
+ say m.from, "You sent: #{m.body}"
21
+ end
@@ -7,6 +7,7 @@
7
7
  digest/md5
8
8
  digest/sha1
9
9
  logger
10
+ openssl
10
11
 
11
12
  active_support/core_ext/class/attribute
12
13
  active_support/core_ext/object/blank
@@ -14,6 +15,7 @@
14
15
  blather/core_ext/eventmachine
15
16
  blather/core_ext/ipaddr
16
17
 
18
+ blather/cert_store
17
19
  blather/errors
18
20
  blather/errors/sasl_error
19
21
  blather/errors/stanza_error
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+
3
+ module Blather
4
+
5
+ # An X509 certificate store that validates certificate trust chains.
6
+ # This uses the #{cert_directory}/*.crt files as the list of trusted root
7
+ # CA certificates.
8
+ class CertStore
9
+ @@certs = nil
10
+ @cert_directory = nil
11
+
12
+ def initialize(cert_directory)
13
+ @cert_directory = cert_directory
14
+ @store = OpenSSL::X509::Store.new
15
+ certs.each {|c| @store.add_cert(c) }
16
+ end
17
+
18
+ # Return true if the certificate is signed by a CA certificate in the
19
+ # store. If the certificate can be trusted, it's added to the store so
20
+ # it can be used to trust other certs.
21
+ def trusted?(pem)
22
+ if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
23
+ @store.verify(cert).tap do |trusted|
24
+ @store.add_cert(cert) if trusted rescue nil
25
+ end
26
+ end
27
+ end
28
+
29
+ # Return true if the domain name matches one of the names in the
30
+ # certificate. In other words, is the certificate provided to us really
31
+ # for the domain to which we think we're connected?
32
+ def domain?(pem, domain)
33
+ if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
34
+ OpenSSL::SSL.verify_certificate_identity(cert, domain) rescue false
35
+ end
36
+ end
37
+
38
+ # Return the trusted root CA certificates installed in the @cert_directory. These
39
+ # certificates are used to start the trust chain needed to validate certs
40
+ # we receive from clients and servers.
41
+ def certs
42
+ unless @@certs
43
+ pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
44
+ dir = @cert_directory
45
+ certs = Dir[File.join(dir, '*.crt')].map {|f| File.read(f) }
46
+ certs = certs.map {|c| c.scan(pattern) }.flatten
47
+ certs.map! {|c| OpenSSL::X509::Certificate.new(c) }
48
+ @@certs = certs.reject {|c| c.not_after < Time.now }
49
+ end
50
+ @@certs
51
+ end
52
+ end
53
+ end
@@ -30,7 +30,15 @@ optparse = OptionParser.new do |opts|
30
30
  end
31
31
  options[:log] = log
32
32
  end
33
-
33
+
34
+ opts.on('--certs=[CERTS DIRECTORY]', 'The directory path where the trusted certificates are stored') do |certs|
35
+ if !File.directory?(certs)
36
+ $stderr.puts "The certs directory path (#{certs}) is no good."
37
+ exit 1
38
+ end
39
+ options[:certs] = certs
40
+ end
41
+
34
42
  opts.on_tail('-h', '--help', 'Show this message') do
35
43
  puts opts
36
44
  exit
@@ -45,8 +45,8 @@ module Blather
45
45
  # @param [Fixnum, String] port the port to connect to.
46
46
  #
47
47
  # @return [Blather::Client]
48
- def self.setup(jid, password, host = nil, port = nil)
49
- self.new.setup(jid, password, host, port)
48
+ def self.setup(jid, password, host = nil, port = nil, certs = nil)
49
+ self.new.setup(jid, password, host, port, certs)
50
50
  end
51
51
 
52
52
  def initialize # @private
@@ -194,11 +194,12 @@ module Blather
194
194
  end
195
195
 
196
196
  # @private
197
- def setup(jid, password, host = nil, port = nil)
197
+ def setup(jid, password, host = nil, port = nil, certs = nil)
198
198
  @jid = JID.new(jid)
199
199
  @setup = [@jid, password]
200
200
  @setup << host if host
201
201
  @setup << port if port
202
+ @setup << certs if certs
202
203
  self
203
204
  end
204
205
 
@@ -83,6 +83,13 @@ module Blather
83
83
 
84
84
  autoload :PubSub, File.expand_path(File.join(File.dirname(__FILE__), *%w[dsl pubsub]))
85
85
 
86
+ def self.append_features(o)
87
+ Blather::Stanza.handler_list.each do |handler_name|
88
+ o.__send__ :remove_method, handler_name if !o.is_a?(Class) && o.method_defined?(handler_name)
89
+ end
90
+ super
91
+ end
92
+
86
93
  # The actual client connection
87
94
  #
88
95
  # @return [Blather::Client]
@@ -116,8 +123,8 @@ module Blather
116
123
  # @param [String] host (optional) the host to connect to (can be an IP). If
117
124
  # this is `nil` the domain on the JID will be used
118
125
  # @param [Fixnum, String] (optional) port the port to connect on
119
- def setup(jid, password, host = nil, port = nil)
120
- client.setup(jid, password, host, port)
126
+ def setup(jid, password, host = nil, port = nil, certs = nil)
127
+ client.setup(jid, password, host, port, certs)
121
128
  end
122
129
 
123
130
  # Shutdown the connection.
@@ -55,6 +55,7 @@ module Blather
55
55
  STREAM_NS = 'http://etherx.jabber.org/streams'
56
56
  attr_accessor :password
57
57
  attr_reader :jid
58
+ @@store = nil
58
59
 
59
60
  # Start the stream between client and server
60
61
  #
@@ -66,8 +67,13 @@ module Blather
66
67
  # to use the domain on the JID
67
68
  # @param [Fixnum, nil] port the port to connect on. Default is the XMPP
68
69
  # default of 5222
69
- def self.start(client, jid, pass, host = nil, port = 5222)
70
+ # @param [String, nil] certs the trusted cert store in pem format to verify
71
+ # communication with the server is trusted.
72
+ def self.start(client, jid, pass, host = nil, port = 5222, certs_directory = nil)
70
73
  jid = JID.new jid
74
+ if certs_directory
75
+ @@store = CertStore.new(certs_directory)
76
+ end
71
77
  if host
72
78
  connect host, port, self, client, jid, pass
73
79
  else
@@ -153,6 +159,21 @@ module Blather
153
159
  send "<stream:error><xml-not-well-formed xmlns='#{StreamError::STREAM_ERR_NS}'/></stream:error>"
154
160
  stop
155
161
  end
162
+
163
+ # Called by EM to verify the peer certificate. If a certificate store directory
164
+ # has not been configured don't worry about peer verification. At least it is encrypted
165
+ # We Log the certificate so that you can add it to the trusted store easily if desired
166
+ # @private
167
+ def ssl_verify_peer(pem)
168
+ # EM is supposed to close the connection when this returns false,
169
+ # but it only does that for inbound connections, not when we
170
+ # make a connection to another server.
171
+ Blather.logger.debug("Checking SSL cert: #{pem}")
172
+ return true if !@@store
173
+ @@store.trusted?(pem).tap do |trusted|
174
+ close_connection unless trusted
175
+ end
176
+ end
156
177
 
157
178
  # Called by EM after the connection has started
158
179
  # @private
@@ -15,7 +15,7 @@ class Stream
15
15
  when 'starttls'
16
16
  @stream.send "<starttls xmlns='#{TLS_NS}'/>"
17
17
  when 'proceed'
18
- @stream.start_tls
18
+ @stream.start_tls(:verify_peer => true)
19
19
  @stream.start
20
20
  # succeed!
21
21
  else
@@ -1,4 +1,4 @@
1
1
  module Blather
2
2
  # Blather version number
3
- VERSION = '0.5.4'
3
+ VERSION = '0.5.6'
4
4
  end
@@ -12,23 +12,35 @@ describe Blather::DSL do
12
12
  end
13
13
 
14
14
  it 'wraps the setup' do
15
- args = ['jid', 'pass', 'host', 0000]
15
+ args = ['jid', 'pass', 'host', 0000, nil]
16
16
  @client.expects(:setup).with *args
17
17
  @dsl.setup *args
18
18
  end
19
19
 
20
20
  it 'allows host to be nil in setup' do
21
21
  args = ['jid', 'pass']
22
- @client.expects(:setup).with *(args + [nil, nil])
22
+ @client.expects(:setup).with *(args + [nil, nil, nil])
23
23
  @dsl.setup *args
24
24
  end
25
25
 
26
26
  it 'allows port to be nil in setup' do
27
27
  args = ['jid', 'pass', 'host']
28
- @client.expects(:setup).with *(args + [nil])
28
+ @client.expects(:setup).with *(args + [nil, nil])
29
29
  @dsl.setup *args
30
30
  end
31
31
 
32
+ it 'allows certs to be nil in setup' do
33
+ args = ['jid', 'pass', 'host', 'port']
34
+ @client.expects(:setup).with *(args + [nil])
35
+ @dsl.setup *args
36
+ end
37
+
38
+ it 'accepts certs in setup' do
39
+ args = ['jid', 'pass', 'host', 'port', 'certs']
40
+ @client.expects(:setup).with *(args)
41
+ @dsl.setup *args
42
+ end
43
+
32
44
  it 'stops when shutdown is called' do
33
45
  @client.expects(:close)
34
46
  @dsl.shutdown
@@ -61,6 +73,11 @@ describe Blather::DSL do
61
73
  @dsl.handle type, *guards
62
74
  end
63
75
 
76
+ it 'sets up handler methods' do
77
+ @client.expects(:register_handler).with :presence, :unavailable?
78
+ @dsl.presence :unavailable?
79
+ end
80
+
64
81
  it 'provides a helper for ready state' do
65
82
  @client.expects(:register_handler).with :ready
66
83
  @dsl.when_ready
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Blather::CertStore do
4
+ before do
5
+ @pem = "-----BEGIN CERTIFICATE-----\nMIIDszCCApugAwIBAgIETiZC5TANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJV\nUzERMA8GA1UECAwIQ29sb3JhZG8xDzANBgNVBAcMBkRlbnZlcjEaMBgGA1UECgwR\nVmluZXMgWE1QUCBTZXJ2ZXIxFDASBgNVBAMMC3ZpbmVzLmxvY2FsMB4XDTExMDcx\nOTAyNTIyMVoXDTEyMDcxOTAyNTIyMVowYzELMAkGA1UEBhMCVVMxETAPBgNVBAgM\nCENvbG9yYWRvMQ8wDQYDVQQHDAZEZW52ZXIxGjAYBgNVBAoMEVZpbmVzIFhNUFAg\nU2VydmVyMRQwEgYDVQQDDAt2aW5lcy5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAMyqj4UyXIpLYyIDaqDoN/8yZHDv281lz1UzuhPZ5r4S6teA\n90dXT6MxEoQ5vpRV2lVU21mDOoRPk9qjgGA01zimrX/YvPf2BBedFkvU18ZKiOMD\n7D89Ej2oIPLc6dJMiIx1SbfpdvUtVZFn1/jGvQPv5iajHW5n/zn1KrHOvVa6R5eY\nVGEH3DD3RkzSxWHyiNN8R5SQzyOVX9F4DVFAffPOLbkFsCi2POy3dp+ZWuYKEjBd\nMuRibrt87PCESnyXZx/Y+GBG856wQT8Ny6mmnh5z5YtopvAJh16ps2p6DFgyDtF+\nhaW3WMlStXYQPqSTrreD7qdAxi3rVft2OUJLTJkCAwEAAaNvMG0wDAYDVR0TBAUw\nAwEB/zAdBgNVHQ4EFgQUUCSlixByIPK3s20w4xHhcMax7igwPgYDVR0RBDcwNYIL\ndmluZXMubG9jYWyCJmNocmlzdG9waGVyLWpvaG5zb25zLW1hY2Jvb2stcHJvLmxv\nY2FsMA0GCSqGSIb3DQEBBQUAA4IBAQBOy1nI7H8XpnpVzpRK5RN/MzelQUl1Efo0\nl9wZ73E6EgJinl2AUp1/sYMUWkVlZ4DSflRBxBEp0CAJNoUydBh8O1xEKGTyqlLy\n/daqvNFLnYwFluAWi1xJQZv4AE62ua5wjsrhPuu3aMvPt9hx1X3CVh+8aA24/gAo\nAPJYsfT3T8GCD+MU3Uc2yADnLSUJ6Jal56/okOJA2Pfkr/K4zj1CyfAEWlpgo2Pv\nyrpv4V2WP1SL5fOONNGfOzio1LD6seAl+8SjiCefnMan2aXmna6SpMDzXB8vTDUE\nWmsD9g0621WqNz2x6lY5IYr7azE2C46Tpb9FOeSAAd83Zka4acaL\n-----END CERTIFICATE-----\n"
6
+ @cert = File.open("./test.crt", 'w') {|f| f.write(@pem) }
7
+ @store = Blather::CertStore.new("./")
8
+ end
9
+
10
+ after do
11
+ File.delete("./test.crt")
12
+ end
13
+
14
+ it 'can verify valid cert' do
15
+ @store.trusted?(@pem).tap do |trusted|
16
+ trusted.must_equal true
17
+ end
18
+ end
19
+
20
+ it 'can verify invalid cert' do
21
+ @store.trusted?(@pem.gsub("L", "a")).tap do |trusted|
22
+ trusted.must_equal nil
23
+ end
24
+ end
25
+
26
+ it 'can verify without a cert store' do
27
+ @store = Blather::CertStore.new("../")
28
+ @store.trusted?(@pem).tap do |trusted|
29
+ trusted.must_equal true
30
+ end
31
+ end
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blather
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.6
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,12 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-08-11 00:00:00.000000000 +01:00
12
+ date: 2011-09-23 00:00:00.000000000 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: eventmachine
17
- requirement: &2153050540 !ruby/object:Gem::Requirement
17
+ requirement: &2153627300 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 0.12.6
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2153050540
25
+ version_requirements: *2153627300
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: nokogiri
28
- requirement: &2156390300 !ruby/object:Gem::Requirement
28
+ requirement: &2153626740 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 1.4.0
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *2156390300
36
+ version_requirements: *2153626740
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: niceogiri
39
- requirement: &2156389820 !ruby/object:Gem::Requirement
39
+ requirement: &2153626180 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,21 +44,10 @@ dependencies:
44
44
  version: 0.0.4
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *2156389820
48
- - !ruby/object:Gem::Dependency
49
- name: minitest
50
- requirement: &2156389340 !ruby/object:Gem::Requirement
51
- none: false
52
- requirements:
53
- - - ! '>='
54
- - !ruby/object:Gem::Version
55
- version: 1.7.1
56
- type: :runtime
57
- prerelease: false
58
- version_requirements: *2156389340
47
+ version_requirements: *2153626180
59
48
  - !ruby/object:Gem::Dependency
60
49
  name: activesupport
61
- requirement: &2156388860 !ruby/object:Gem::Requirement
50
+ requirement: &2153625680 !ruby/object:Gem::Requirement
62
51
  none: false
63
52
  requirements:
64
53
  - - ! '>='
@@ -66,10 +55,10 @@ dependencies:
66
55
  version: 3.0.7
67
56
  type: :runtime
68
57
  prerelease: false
69
- version_requirements: *2156388860
58
+ version_requirements: *2153625680
70
59
  - !ruby/object:Gem::Dependency
71
60
  name: minitest
72
- requirement: &2156388380 !ruby/object:Gem::Requirement
61
+ requirement: &2153625200 !ruby/object:Gem::Requirement
73
62
  none: false
74
63
  requirements:
75
64
  - - ~>
@@ -77,10 +66,10 @@ dependencies:
77
66
  version: 1.7.1
78
67
  type: :development
79
68
  prerelease: false
80
- version_requirements: *2156388380
69
+ version_requirements: *2153625200
81
70
  - !ruby/object:Gem::Dependency
82
71
  name: mocha
83
- requirement: &2156387900 !ruby/object:Gem::Requirement
72
+ requirement: &2153624720 !ruby/object:Gem::Requirement
84
73
  none: false
85
74
  requirements:
86
75
  - - ~>
@@ -88,10 +77,10 @@ dependencies:
88
77
  version: 0.9.12
89
78
  type: :development
90
79
  prerelease: false
91
- version_requirements: *2156387900
80
+ version_requirements: *2153624720
92
81
  - !ruby/object:Gem::Dependency
93
82
  name: bundler
94
- requirement: &2156387420 !ruby/object:Gem::Requirement
83
+ requirement: &2153624180 !ruby/object:Gem::Requirement
95
84
  none: false
96
85
  requirements:
97
86
  - - ~>
@@ -99,10 +88,10 @@ dependencies:
99
88
  version: 1.0.0
100
89
  type: :development
101
90
  prerelease: false
102
- version_requirements: *2156387420
91
+ version_requirements: *2153624180
103
92
  - !ruby/object:Gem::Dependency
104
93
  name: rcov
105
- requirement: &2156386940 !ruby/object:Gem::Requirement
94
+ requirement: &2153623680 !ruby/object:Gem::Requirement
106
95
  none: false
107
96
  requirements:
108
97
  - - ~>
@@ -110,10 +99,10 @@ dependencies:
110
99
  version: 0.9.9
111
100
  type: :development
112
101
  prerelease: false
113
- version_requirements: *2156386940
102
+ version_requirements: *2153623680
114
103
  - !ruby/object:Gem::Dependency
115
104
  name: yard
116
- requirement: &2156386460 !ruby/object:Gem::Requirement
105
+ requirement: &2153623200 !ruby/object:Gem::Requirement
117
106
  none: false
118
107
  requirements:
119
108
  - - ~>
@@ -121,10 +110,10 @@ dependencies:
121
110
  version: 0.6.1
122
111
  type: :development
123
112
  prerelease: false
124
- version_requirements: *2156386460
113
+ version_requirements: *2153623200
125
114
  - !ruby/object:Gem::Dependency
126
115
  name: bluecloth
127
- requirement: &2156385980 !ruby/object:Gem::Requirement
116
+ requirement: &2153622680 !ruby/object:Gem::Requirement
128
117
  none: false
129
118
  requirements:
130
119
  - - ~>
@@ -132,10 +121,21 @@ dependencies:
132
121
  version: 2.1.0
133
122
  type: :development
134
123
  prerelease: false
135
- version_requirements: *2156385980
124
+ version_requirements: *2153622680
136
125
  - !ruby/object:Gem::Dependency
137
126
  name: rake
138
- requirement: &2156385600 !ruby/object:Gem::Requirement
127
+ requirement: &2153609960 !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: *2153609960
136
+ - !ruby/object:Gem::Dependency
137
+ name: guard-minitest
138
+ requirement: &2153609500 !ruby/object:Gem::Requirement
139
139
  none: false
140
140
  requirements:
141
141
  - - ! '>='
@@ -143,7 +143,7 @@ dependencies:
143
143
  version: '0'
144
144
  type: :development
145
145
  prerelease: false
146
- version_requirements: *2156385600
146
+ version_requirements: *2153609500
147
147
  description: An XMPP DSL for Ruby written on top of EventMachine and Nokogiri
148
148
  email: sprsquish@gmail.com
149
149
  executables: []
@@ -158,19 +158,24 @@ files:
158
158
  - .travis.yml
159
159
  - CHANGELOG
160
160
  - Gemfile
161
+ - Guardfile
161
162
  - LICENSE
162
163
  - README.md
163
164
  - Rakefile
164
165
  - TODO.md
165
166
  - blather.gemspec
167
+ - examples/certs/README
168
+ - examples/certs/ca-bundle.crt
166
169
  - examples/echo.rb
167
170
  - examples/execute.rb
168
171
  - examples/ping_pong.rb
169
172
  - examples/print_hierarchy.rb
170
173
  - examples/rosterprint.rb
171
174
  - examples/stream_only.rb
175
+ - examples/trusted_echo.rb
172
176
  - examples/xmpp4r/echo.rb
173
177
  - lib/blather.rb
178
+ - lib/blather/cert_store.rb
174
179
  - lib/blather/client.rb
175
180
  - lib/blather/client/client.rb
176
181
  - lib/blather/client/dsl.rb
@@ -279,6 +284,7 @@ files:
279
284
  - spec/blather/stream/client_spec.rb
280
285
  - spec/blather/stream/component_spec.rb
281
286
  - spec/blather/stream/parser_spec.rb
287
+ - spec/blather/stream/ssl_spec.rb
282
288
  - spec/blather/xmpp_node_spec.rb
283
289
  - spec/fixtures/pubsub.rb
284
290
  - spec/spec_helper.rb
@@ -358,6 +364,7 @@ test_files:
358
364
  - spec/blather/stream/client_spec.rb
359
365
  - spec/blather/stream/component_spec.rb
360
366
  - spec/blather/stream/parser_spec.rb
367
+ - spec/blather/stream/ssl_spec.rb
361
368
  - spec/blather/xmpp_node_spec.rb
362
369
  - spec/fixtures/pubsub.rb
363
370
  - spec/spec_helper.rb