1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
# stdlib
require "openssl"
require "securerandom"
require "stringio"
# modules
require_relative "lockbox/aes_gcm"
require_relative "lockbox/box"
require_relative "lockbox/calculations"
require_relative "lockbox/encryptor"
require_relative "lockbox/key_generator"
require_relative "lockbox/io"
require_relative "lockbox/migrator"
require_relative "lockbox/model"
require_relative "lockbox/padding"
require_relative "lockbox/utils"
require_relative "lockbox/version"
module Lockbox
class Error < StandardError; end
class DecryptionError < Error; end
class PaddingError < Error; end
autoload :Audit, "lockbox/audit"
extend Padding
class << self
attr_accessor :default_options, :encode_attributes
attr_writer :master_key
end
self.default_options = {}
self.encode_attributes = true
def self.master_key
@master_key ||= ENV["LOCKBOX_MASTER_KEY"]
end
def self.migrate(relation, batch_size: 1000, restart: false)
Migrator.new(relation, batch_size: batch_size).migrate(restart: restart)
end
def self.rotate(relation, batch_size: 1000, attributes:)
Migrator.new(relation, batch_size: batch_size).rotate(attributes: attributes)
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: 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.unpack1("H*")
end
def self.new(**options)
Encryptor.new(**options)
end
def self.encrypts_action_text_body(**options)
ActiveSupport.on_load(:action_text_rich_text) do
ActionText::RichText.has_encrypted :body, **options
end
end
end
# integrations
require_relative "lockbox/carrier_wave_extensions" if defined?(CarrierWave)
require_relative "lockbox/railtie" if defined?(Rails)
if defined?(ActiveSupport::LogSubscriber)
require_relative "lockbox/log_subscriber"
Lockbox::LogSubscriber.attach_to :lockbox
end
if defined?(ActiveSupport.on_load)
ActiveSupport.on_load(:active_record) do
ar_version = ActiveRecord::VERSION::STRING.to_f
if ar_version < 7.1
if ar_version >= 7.0
raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} requires Lockbox < 2.1"
elsif ar_version >= 5.2
raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} requires Lockbox < 2"
elsif ar_version >= 5
raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} requires Lockbox < 0.7"
else
raise Lockbox::Error, "Active Record #{ActiveRecord::VERSION::STRING} not supported"
end
end
extend Lockbox::Model
extend Lockbox::Model::Attached
ActiveRecord::Relation.prepend Lockbox::Calculations
end
ActiveSupport.on_load(:mongoid) do
mongoid_version = Mongoid::VERSION.to_i
if mongoid_version < 8
if mongoid_version >= 6
raise Lockbox::Error, "Mongoid #{Mongoid::VERSION} requires Lockbox < 2"
else
raise Lockbox::Error, "Mongoid #{Mongoid::VERSION} not supported"
end
end
Mongoid::Document::ClassMethods.include(Lockbox::Model)
end
end
|