[ Posted by James Harton
Sun, 23 Aug 2009 22:34:32 GMT ]
After yesterday's post about using Lockbox to secure your models, I have implemented an acts_as_lockbox extension to ActiveRecord::Base. Here's a quick tutorial on how to make it work:
Installing Lockbox
From within your Rails application I would suggest using the plugin script to install the latest version of Lockbox directly from GitHub and checking for updates regularly, as this is a piece of security infrastructure you need to be able to rely on.
# ./script/plugin install git://github.com/jamesotron/Lockbox.git
Configuring your public and private keys
In order to securely encrypt secrets we need both a public and private key, and a pass phrase. We will just use the openssl command-line tool to generate these keys, first the private key. Make sure you select a pass phrase which would be non-trivial to guess.
# openssl genrsa -des3 -out private.pem 2048
..................................................................................................................+++
..........+++
e is 65537 (0x10001)
Enter pass phrase for private.pem:
Verifying - Enter pass phrase for private.pem:
Next we will extract the public key from the private key so that we can use it:
# openssl rsa -in private.pem -out public.pem -outform PEM -pubout
Enter pass phrase for private.pem:
writing RSA key
Move these files into your Rails application path, (I'd suggest putting them under config/) and make sure that they are only readable from by your rails application.
The next step is to configure your config/lockbox.yml to tell Lockbox where to access the public and private keys, and optionally the pass phrase (I would not recommend this, except for your test environment or if you are absolutely certain that the pass phrase cannot be compromised).
# cat <<EOF > config/lockbox.yml
development:
public_key_path: config/public.pem
private_key_path: config/private.pem
testing:
public_key_path: vendor/plugins/lockbox/test/public.pem
private_key_path: vendor/plugins/lockbox/test/private.pem
pass_phrase: test
production:
public_key_path: config/public.pem
private_key_path: config/private.pem
EOF
Using the Lockbox API
Lockbox is a generic PK en/decryption system, meaning you can use it anywhere in your Rails app, not just in your models. The Lockbox API is very simple:
Lockbox.locked? tests if the Lockbox is still locked (ie: no correct pass phrase has been supplied yet)
Lockbox.unlocked? the inverse of the above.
Lockbox.try? (pass_phrase) attempt to unlock the Lockbox using the supplied pass phrase. Returns true or false depending on success.
Lockbox.lock! abandons access to the public and private keys, stopping your application from being able to retrieve encrypted secrets.
Lockbox.encrypt (val) encrypts a string using the public key, if the lockbox is unlocked.
Lockbox.decrypt (val) decrypts an encrypted string using the private key, provided the lockbox is unlocked.
Using Lockbox in your Models
Using Lockbox in your models is as simple as using acts_as_lockbox into your model's class. acts_as_lockbox adds the locked?, unlocked?, try? and lock! methods to your model's class and creates accessors for fields supplied in the an array to the :for argument:
class User < ActiveRecord::Base
acts_as_lockbox :for => [ :password, :credit_card ]
end
In the above example if you try and access the password or credit_card fields without providing Lockbox with the pass phrase (via the Lockbox.try? or User.try? methods) then Lockbox will raise a StillLockedException.
Stopping your application from running until the Lockbox is unlocked
Because you haven't used an easily guessable pass phrase (right?) I don't see any problem with hijacking any web requests using a before_filter on your ApplicationController class:
class ApplicationController < ActionController::Base
before_filter :check_unlocked
def check_unlocked
if Lockbox.locked?
if !Lockbox.try? params[:pass_phrase]
render :partial => 'unlock'
end
end
end
Alternatively you could redirect requests to a "down for maintenance" page.
Tags cryptography, des3, encryption, key, lockbox, plugin, private, public, rails, rsa | no comments
[ Posted by James Harton
Sun, 23 Aug 2009 05:23:34 GMT ]
After a short discussion I had at work about storing secrets on a web application I threw together this Rails plugin which handles public key cryptography using RSA key pairs. In my particular use case I wanted to stop the web application from running at all if the passphrase hasn't been provided.
Lockbox is very simple to use, you can test if the Lockbox is locked using Lockbox.locked? or Lockbox.unlocked?, try unlocking the private key using Lockbox.try? 'my passphrase', lock it again using Lockbox.lock! and of course encryption is done using Lockbox.encrypt(str) and Lockbox.decrypt(str).
I plan to add acts_as_lockbox functionality for ActiveRecord models in the future.
Lockbox is available at Github, you can install it with the following:
./script/plugin install git://github.com/jamesotron/Lockbox.git
The README file contains documentation on configuring your keys, etc.
Tags cryptography, des3, encryption, key, lockbox, plugin, private, public, rails, rsa | no comments
[ Posted by James Harton
Thu, 13 Aug 2009 22:54:42 GMT ]
Another instalment in my series of IPAddr extensions for Ruby, today I show you how to calculate the EUI-64 address for a given network and MAC address:
class IPAddr
def self.eui_64(network, mac)
if !network.is_a? IPAddr
raise ArgumentError, "Network must be an IPAddr type."
elsif network.ipv4?
raise ArgumentError, "Network must be an IPv6 network."
elsif network.length != 64
raise ArgumentError, "Network must have a 64 bit mask."
end
if mac.is_a? Integer
mac = "%:012x" % mac
end
if mac.is_a? String
mac = mac.split(":").join.downcase
if mac.match(/^[0-9a-f]{12}/).nil?
raise ArgumentError, "Second argument must be a valid MAC address."
end
e64 = (mac[0..5] + "fffe" + mac[6..11]).to_i(16) ^ 0x0200000000000000
IPAddr.from_i(network.first.to_i + e64, Socket::AF_INET6)
end
end
end
Tags class, extend, integer, ipaddr, patch, range, ruby, work | no comments
[ Posted by James Harton
Wed, 12 Aug 2009 23:50:17 GMT ]
WRT yesterday's blog entry, Ruby's IPAddr class is missing a lot of useful methods. Today I'll show you how to add methods to return the network address and the last address (the broadcast address in IPv4 parlance).
class IPAddr
def length
# nasty hack, but works well enough.
@mask_addr.to_s(2).count("1")
end
def first
IPAddr.from_i(@addr & @mask_addr, @family)
end
def last
if @family == Socket::AF_INET
IPAddr.from_i(first.to_i | (@mask_addr ^ IN4MASK), @family)
elsif @family == Socket::AF_INET6
IPAddr.from_i(first.to_i | (@mask_addr ^ IN6MASK), @family)
else
raise "unsupported address family."
end
end
end
I hope you find these useful.
Tags class, extend, integer, ipaddr, patch, range, ruby, work | no comments
[ Posted by James Harton
Wed, 12 Aug 2009 01:00:38 GMT ]
There are a number of really dumb things wrong with Ruby's IPAddr class, but the two most annoying is no way to convert an integer to an IPv4 or IPv6 address and you can't use IPAddr's in ranges.
I've added a from_i() Class method that clones a new IPAddr instance by converting the integer into the string representation of an IP address. You can optionally also pass in an address family (Socket::AF_INET or Socket::AF_INET6) otherwise it will try and work it out from the size of the integer. I was lucky because I was able to take the bulk of the code from Culvert and convert it to rubyisms.
I've also added the succ() and <=>() methods to IPAddr so that you can use it in ranges, eg:
(IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.1")
=> false
(IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.17")
=> true
(IPAddr.new("192.0.2.13")..IPAddr.new("192.0.2.21")) === IPAddr.new("192.0.2.43")
=> false
I need this because I'm writing some code for work which abstracts DHCP ranges (which don't always match subnet boundaries).
Here's the code:
require 'ipaddr'
class IPAddr
include Comparable
def self.from_i(i = 0, family = Socket::AF_UNSPEC)
if i < 0
raise ArgumentError, "Cannot convert negative integer to an IP address."
elsif family == Socket::AF_INET && i > IN4MASK
raise ArgumentError, "Cannot convert integer > 0xffffffff to an IPv4 address."
elsif family == Socket::AF_INET6 && i > IN6MASK
raise ArgumentError, "Cannot convert integer > 0xffffffffffffffffffffffffffffffff to an IPv6 address."
elsif i > IN6MASK
raise ArgumentError, "Cannot convert integer > 0xffffffffffffffffffffffffffffffff to IP address."
end
if family == Socket::AF_UNSPEC
if (0..4) === i.size
family = Socket::AF_INET
elsif (5..16) === i.size
family = Socket::AF_INET6
end
end
if family == Socket::AF_INET
r = []
(0..3).each do |byte|
x = (3 - byte) * 8
y = (4 - byte) * 8
r << ((i >> x) - ((i >> y) * 256 )).to_s
end
IPAddr.new(r * ".")
elsif family == Socket::AF_INET6
r = []
(0..7).each do |byte|
x = (7 - byte) * 16
y = (8 - byte) * 16
r << ((i >> x) - ((i >> y) * 65536)).to_s(16)
end
IPAddr.new(r * ":")
end
end
def succ
i = to_i + 1
if ipv4? and (i <= IN4MASK)
IPAddr.from_i(i)
elsif ipv6? and (i <= IN6MASK)
IPAddr.from_i(i)
end
end
def <=>(other)
to_i <=> other.to_i
end
end
I hope you find this code useful.
Tags class, extend, integer, ipaddr, patch, range, ruby, work | no comments
[ Posted by James Harton
Tue, 04 Aug 2009 04:03:00 GMT ]
Mainly because I liked it better. GitHub project page.
Posted in Projects, Meta, Culvert, Pike | Tags culvert, rename | no comments
[ Posted by James Harton
Mon, 03 Aug 2009 02:11:35 GMT ]
I've spent the last wee while hacking on PikeFlows to fix some of the locking issues present in the initial version. I've made use of Pike 7.8's new getter/setter syntax to give much finer grained locking, meaning that it now runs faster because it's locking each object only for a single atomic transaction.
There are still some unusual behaviours using Public.Network.Pcap (it seems to randomly capture packets on Mac OS X for example), however appears to be working nicely on Linux.
I've rewritten the demo app flow.pike to take command line options, check the top of the file for more information.
Check out the latest source from GitHub.
Posted in Projects, Culvert, Pike | Tags flow, linux, locking, mutex, osx, pike, pike7.8 | 1 comment