Permalink
Browse files

Merge branch 'release/0.5.5'

  • Loading branch information...
benlangfeld committed Sep 23, 2011
2 parents 4c3d6c5 + 619e361 commit 456571f00ee8cdb40c601c6192f4d709fa9e24a2
View
@@ -1,3 +1,7 @@
+v0.5.5
+ Bugfix(benlangfeld/kibs): ActiveSupport was overriding the presence DSL method
+ Feature(fyafighter): Adds SSL peer verification to TLS
+
v0.5.4
Bugfix(fyafighter): Regression related to earlier refactoring: https://github.com/sprsquish/blather/issues/53
Feature(fyafighter): Make it much easier to allow private network addresses
View
@@ -2,3 +2,8 @@ source "http://rubygems.org"
# Specify your gem's dependencies in testgem.gemspec
gemspec
+
+if RUBY_PLATFORM =~ /darwin/
+ gem 'growl_notify'
+ gem 'rb-fsevent'
+end
View
@@ -0,0 +1,5 @@
+guard 'minitest' do
+ watch(%r|^spec/(.*)_spec\.rb|)
+ watch(%r|^lib/(.*)\.rb|) { |m| "spec/#{m[1]}_spec.rb" }
+ watch(%r|^spec/spec_helper\.rb|) { "spec" }
+end
View
@@ -20,17 +20,17 @@ Gem::Specification.new do |s|
s.rdoc_options = %w{--charset=UTF-8}
s.extra_rdoc_files = %w{LICENSE README.md}
- s.add_dependency("eventmachine", ["~> 0.12.6"])
- s.add_dependency("nokogiri", ["~> 1.4.0"])
- s.add_dependency("niceogiri", [">= 0.0.4"])
- s.add_dependency("minitest", [">= 1.7.1"])
- s.add_dependency("activesupport", [">= 3.0.7"])
+ s.add_dependency "eventmachine", ["~> 0.12.6"]
+ s.add_dependency "nokogiri", ["~> 1.4.0"]
+ s.add_dependency "niceogiri", [">= 0.0.4"]
+ s.add_dependency "activesupport", [">= 3.0.7"]
- s.add_development_dependency("minitest", ["~> 1.7.1"])
- s.add_development_dependency("mocha", ["~> 0.9.12"])
- s.add_development_dependency("bundler", ["~> 1.0.0"])
- s.add_development_dependency("rcov", ["~> 0.9.9"])
- s.add_development_dependency("yard", ["~> 0.6.1"])
- s.add_development_dependency("bluecloth", ["~> 2.1.0"])
- s.add_development_dependency("rake")
+ s.add_development_dependency "minitest", ["~> 1.7.1"]
+ s.add_development_dependency "mocha", ["~> 0.9.12"]
+ s.add_development_dependency "bundler", ["~> 1.0.0"]
+ s.add_development_dependency "rcov", ["~> 0.9.9"]
+ s.add_development_dependency "yard", ["~> 0.6.1"]
+ s.add_development_dependency "bluecloth", ["~> 2.1.0"]
+ s.add_development_dependency "rake"
+ s.add_development_dependency "guard-minitest"
end
View
@@ -0,0 +1,20 @@
+The certs/ directory contains the TLS certificates required for encrypting
+client to server XMPP connections.
+
+The ca-bundle.crt file contains root Certificate Authority (CA) certificates.
+These are used to validate certificates presented during TLS handshake
+negotiation. The source for this file is the cacert.pem file available
+at http://curl.haxx.se/docs/caextract.html.
+
+Any self-signed CA certificate placed in this directory will be considered
+a trusted certificate. For example, let's say you're running the wonderland.lit
+XMPP server and would like to and verify the ssl cert during TLS.
+The wonderland.lit server hasn't purchased a legitimate TLS certificate
+from a CA known in ca-bundle.crt. Instead, they've created a self-signed
+certificate and sent it to you. Place the certificate in this directory
+with a name of wonderland.lit.crt and it will be trusted. Trusted TLS connections
+from wonderland.lit will now work.
+
+Alternatively, you can purchase a TLS certificate from a CA (e.g. Go Daddy,
+VeriSign, etc.) and place it in this directory. This will avoid the hassles
+of managing self-signed certificates.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -0,0 +1,21 @@
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'blather/client'
+
+setup 'chris@vines.local', 'test', 'vines.local', 5222, "./certs"
+
+when_ready { puts "Connected ! send messages to #{jid.stripped}." }
+
+subscription :request? do |s|
+ write_to_stream s.approve!
+end
+
+message :chat?, :body => 'exit' do |m|
+ say m.from, 'Exiting ...'
+ shutdown
+end
+
+message :chat?, :body do |m|
+ say m.from, "You sent: #{m.body}"
+end
View
@@ -7,13 +7,15 @@
digest/md5
digest/sha1
logger
+ openssl
active_support/core_ext/class/attribute
active_support/core_ext/object/blank
blather/core_ext/eventmachine
blather/core_ext/ipaddr
+ blather/cert_store
blather/errors
blather/errors/sasl_error
blather/errors/stanza_error
View
@@ -0,0 +1,53 @@
+# encoding: UTF-8
+
+module Blather
+
+ # An X509 certificate store that validates certificate trust chains.
+ # This uses the #{cert_directory}/*.crt files as the list of trusted root
+ # CA certificates.
+ class CertStore
+ @@certs = nil
+ @cert_directory = nil
+
+ def initialize(cert_directory)
+ @cert_directory = cert_directory
+ @store = OpenSSL::X509::Store.new
+ certs.each {|c| @store.add_cert(c) }
+ end
+
+ # Return true if the certificate is signed by a CA certificate in the
+ # store. If the certificate can be trusted, it's added to the store so
+ # it can be used to trust other certs.
+ def trusted?(pem)
+ if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
+ @store.verify(cert).tap do |trusted|
+ @store.add_cert(cert) if trusted rescue nil
+ end
+ end
+ end
+
+ # Return true if the domain name matches one of the names in the
+ # certificate. In other words, is the certificate provided to us really
+ # for the domain to which we think we're connected?
+ def domain?(pem, domain)
+ if cert = OpenSSL::X509::Certificate.new(pem) rescue nil
+ OpenSSL::SSL.verify_certificate_identity(cert, domain) rescue false
+ end
+ end
+
+ # Return the trusted root CA certificates installed in the @cert_directory. These
+ # certificates are used to start the trust chain needed to validate certs
+ # we receive from clients and servers.
+ def certs
+ unless @@certs
+ pattern = /-{5}BEGIN CERTIFICATE-{5}\n.*?-{5}END CERTIFICATE-{5}\n/m
+ dir = @cert_directory
+ certs = Dir[File.join(dir, '*.crt')].map {|f| File.read(f) }
+ certs = certs.map {|c| c.scan(pattern) }.flatten
+ certs.map! {|c| OpenSSL::X509::Certificate.new(c) }
+ @@certs = certs.reject {|c| c.not_after < Time.now }
+ end
+ @@certs
+ end
+ end
+end
View
@@ -30,7 +30,15 @@
end
options[:log] = log
end
-
+
+ opts.on('--certs=[CERTS DIRECTORY]', 'The directory path where the trusted certificates are stored') do |certs|
+ if !File.directory?(certs)
+ $stderr.puts "The certs directory path (#{certs}) is no good."
+ exit 1
+ end
+ options[:certs] = certs
+ end
+
opts.on_tail('-h', '--help', 'Show this message') do
puts opts
exit
@@ -45,8 +45,8 @@ class Client
# @param [Fixnum, String] port the port to connect to.
#
# @return [Blather::Client]
- def self.setup(jid, password, host = nil, port = nil)
- self.new.setup(jid, password, host, port)
+ def self.setup(jid, password, host = nil, port = nil, certs = nil)
+ self.new.setup(jid, password, host, port, certs)
end
def initialize # @private
@@ -194,11 +194,12 @@ def setup?
end
# @private
- def setup(jid, password, host = nil, port = nil)
+ def setup(jid, password, host = nil, port = nil, certs = nil)
@jid = JID.new(jid)
@setup = [@jid, password]
@setup << host if host
@setup << port if port
+ @setup << certs if certs
self
end
View
@@ -83,6 +83,13 @@ module DSL
autoload :PubSub, File.expand_path(File.join(File.dirname(__FILE__), *%w[dsl pubsub]))
+ def self.append_features(o)
+ Blather::Stanza.handler_list.each do |handler_name|
+ o.__send__ :remove_method, handler_name if o.method_defined? handler_name
+ end
+ super
+ end
+
# The actual client connection
#
# @return [Blather::Client]
@@ -116,8 +123,8 @@ def <<(stanza)
# @param [String] host (optional) the host to connect to (can be an IP). If
# this is `nil` the domain on the JID will be used
# @param [Fixnum, String] (optional) port the port to connect on
- def setup(jid, password, host = nil, port = nil)
- client.setup(jid, password, host, port)
+ def setup(jid, password, host = nil, port = nil, certs = nil)
+ client.setup(jid, password, host, port, certs)
end
# Shutdown the connection.
View
@@ -55,6 +55,7 @@ class NoConnection < RuntimeError; end
STREAM_NS = 'http://etherx.jabber.org/streams'
attr_accessor :password
attr_reader :jid
+ @@store = nil
# Start the stream between client and server
#
@@ -66,8 +67,13 @@ class NoConnection < RuntimeError; end
# to use the domain on the JID
# @param [Fixnum, nil] port the port to connect on. Default is the XMPP
# default of 5222
- def self.start(client, jid, pass, host = nil, port = 5222)
+ # @param [String, nil] certs the trusted cert store in pem format to verify
+ # communication with the server is trusted.
+ def self.start(client, jid, pass, host = nil, port = 5222, certs_directory = nil)
jid = JID.new jid
+ if certs_directory
+ @@store = CertStore.new(certs_directory)
+ end
if host
connect host, port, self, client, jid, pass
else
@@ -153,6 +159,21 @@ def receive_data(data)
send "<stream:error><xml-not-well-formed xmlns='#{StreamError::STREAM_ERR_NS}'/></stream:error>"
stop
end
+
+ # Called by EM to verify the peer certificate. If a certificate store directory
+ # has not been configured don't worry about peer verification. At least it is encrypted
+ # We Log the certificate so that you can add it to the trusted store easily if desired
+ # @private
+ def ssl_verify_peer(pem)
+ # EM is supposed to close the connection when this returns false,
+ # but it only does that for inbound connections, not when we
+ # make a connection to another server.
+ Blather.logger.debug("Checking SSL cert: #{pem}")
+ return true if !@@store
+ @@store.trusted?(pem).tap do |trusted|
+ close_connection unless trusted
+ end
+ end
# Called by EM after the connection has started
# @private
@@ -15,7 +15,7 @@ def receive_data(stanza)
when 'starttls'
@stream.send "<starttls xmlns='#{TLS_NS}'/>"
when 'proceed'
- @stream.start_tls
+ @stream.start_tls(:verify_peer => true)
@stream.start
# succeed!
else
View
@@ -1,4 +1,4 @@
module Blather
# Blather version number
- VERSION = '0.5.4'
+ VERSION = '0.5.5'
end
@@ -1,4 +1,4 @@
-require 'spec_helper'
+require '../../spec_helper'
require 'blather/client/dsl'
describe Blather::DSL do
@@ -12,23 +12,35 @@
end
it 'wraps the setup' do
- args = ['jid', 'pass', 'host', 0000]
+ args = ['jid', 'pass', 'host', 0000, nil]
@client.expects(:setup).with *args
@dsl.setup *args
end
it 'allows host to be nil in setup' do
args = ['jid', 'pass']
- @client.expects(:setup).with *(args + [nil, nil])
+ @client.expects(:setup).with *(args + [nil, nil, nil])
@dsl.setup *args
end
it 'allows port to be nil in setup' do
args = ['jid', 'pass', 'host']
- @client.expects(:setup).with *(args + [nil])
+ @client.expects(:setup).with *(args + [nil, nil])
@dsl.setup *args
end
+ it 'allows certs to be nil in setup' do
+ args = ['jid', 'pass', 'host', 'port']
+ @client.expects(:setup).with *(args + [nil])
+ @dsl.setup *args
+ end
+
+ it 'accepts certs in setup' do
+ args = ['jid', 'pass', 'host', 'port', 'certs']
+ @client.expects(:setup).with *(args)
+ @dsl.setup *args
+ end
+
it 'stops when shutdown is called' do
@client.expects(:close)
@dsl.shutdown
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Blather::CertStore do
+ before do
+ @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"
+ @cert = File.open("./test.crt", 'w') {|f| f.write(@pem) }
+ @store = Blather::CertStore.new("./")
+ end
+
+ after do
+ File.delete("./test.crt")
+ end
+
+ it 'can verify valid cert' do
+ @store.trusted?(@pem).tap do |trusted|
+ trusted.must_equal true
+ end
+ end
+
+ it 'can verify invalid cert' do
+ @store.trusted?(@pem.gsub("L", "a")).tap do |trusted|
+ trusted.must_equal nil
+ end
+ end
+
+ it 'can verify without a cert store' do
+ @store = Blather::CertStore.new("../")
+ @store.trusted?(@pem).tap do |trusted|
+ trusted.must_equal true
+ end
+ end
+end

0 comments on commit 456571f

Please sign in to comment.