lib/lockbox.rb in lockbox-0.1.1 vs lib/lockbox.rb in lockbox-0.2.0

- old
+ new

@@ -1,24 +1,76 @@ +# dependencies +require "securerandom" + # modules require "lockbox/box" require "lockbox/encryptor" +require "lockbox/key_generator" require "lockbox/utils" require "lockbox/version" # integrations require "lockbox/carrier_wave_extensions" if defined?(CarrierWave) require "lockbox/railtie" if defined?(Rails) +if defined?(ActiveSupport) + ActiveSupport.on_load(:active_record) do + require "lockbox/model" + extend Lockbox::Model + end +end + class Lockbox class Error < StandardError; end class DecryptionError < Error; end class << self attr_accessor :default_options + attr_writer :master_key end - self.default_options = {algorithm: "aes-gcm"} + self.default_options = {} + def self.master_key + @master_key ||= ENV["LOCKBOX_MASTER_KEY"] + end + + def self.migrate(model, restart: false) + # get fields + fields = model.lockbox_attributes.select { |k, v| v[:migrating] } + + # get blind indexes + blind_indexes = model.respond_to?(:blind_indexes) ? model.blind_indexes.select { |k, v| v[:migrating] } : {} + + # build relation + relation = model.unscoped + + unless restart + attributes = fields.map { |_, v| v[:encrypted_attribute] } + attributes += blind_indexes.map { |_, v| v[:bidx_attribute] } + + attributes.each_with_index do |attribute, i| + relation = + if i == 0 + relation.where(attribute => nil) + else + relation.or(model.where(attribute => nil)) + end + end + end + + # migrate + relation.find_each do |record| + fields.each do |k, v| + record.send("#{v[:attribute]}=", record.send(k)) if restart || !record.send(v[:encrypted_attribute]) + end + blind_indexes.each do |k, v| + record.send("compute_#{k}_bidx") if restart || !record.send(v[:bidx_attribute]) + end + record.save(validate: false) if record.changed? + end + end + def initialize(**options) options = self.class.default_options.merge(options) previous_versions = options.delete(:previous_versions) @boxes = @@ -54,21 +106,38 @@ end end end end + def self.generate_key + SecureRandom.hex(32) + end + def self.generate_key_pair require "rbnacl" # encryption and decryption servers exchange public keys # this produces smaller ciphertext than sealed box alice = RbNaCl::PrivateKey.generate bob = RbNaCl::PrivateKey.generate # alice is sending message to bob # use bob first in both cases to prevent keys being swappable { - encryption_key: (bob.public_key.to_bytes + alice.to_bytes).unpack("H*").first, - decryption_key: (bob.to_bytes + alice.public_key.to_bytes).unpack("H*").first + encryption_key: to_hex(bob.public_key.to_bytes + alice.to_bytes), + decryption_key: to_hex(bob.to_bytes + alice.public_key.to_bytes) } + end + + def self.attribute_key(table:, attribute:, master_key: nil, encode: true) + master_key ||= Lockbox.master_key + raise ArgumentError, "Missing master key" unless master_key + + key = Lockbox::KeyGenerator.new(master_key).attribute_key(table: table, attribute: attribute) + key = to_hex(key) if encode + key + end + + def self.to_hex(str) + str.unpack("H*").first end private def check_string(str, name)