Skip to content

Commit

Permalink
Stop using base64 library in column_encryption plugin
Browse files Browse the repository at this point in the history
base64 will move out of the standard library in Ruby 3.4.0. As
the column_encryption plugin was the only part of Sequel that
used it, and base64 is a fairly simple wrapper around pack and
unpack, inline the necessary code.
  • Loading branch information
jeremyevans committed Sep 14, 2023
1 parent cc7afda commit 5ca610a
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
=== master

* Stop using base64 library in column_encryption plugin (jeremyevans)

=== 5.72.0 (2023-09-01)

* Sort caches before marshalling when using schema_caching, index_caching, static_cache_cache, and pg_auto_constraint_validations (jeremyevans)
Expand Down
38 changes: 33 additions & 5 deletions lib/sequel/plugins/column_encryption.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
# :nocov:
end

require 'base64'
require 'securerandom'

module Sequel
Expand Down Expand Up @@ -375,7 +374,7 @@ def initialize(keys)
# Decrypt using any supported format and any available key.
def decrypt(data)
begin
data = Base64.urlsafe_decode64(data)
data = urlsafe_decode64(data)
rescue ArgumentError
raise Error, "Unable to decode encrypted column: invalid base64"
end
Expand Down Expand Up @@ -448,7 +447,7 @@ def case_insensitive_searchable_encrypt(data)
# The prefix string of columns for the given search type and the first configured encryption key.
# Used to find values that do not use this prefix in order to perform reencryption.
def current_key_prefix(search_type)
Base64.urlsafe_encode64("#{search_type.chr}\0#{@key_id.chr}")
urlsafe_encode64("#{search_type.chr}\0#{@key_id.chr}")
end

# The prefix values to search for the given data (an array of strings), assuming the column uses
Expand All @@ -472,11 +471,40 @@ def regular_and_lowercase_search_prefixes(data)

private

if RUBY_VERSION >= '2.4'
def decode64(str)
str.unpack1("m0")
end
# :nocov:
else
def decode64(str)
str.unpack("m0")[0]
end
# :nocov:
end

def urlsafe_encode64(bin)
str = [bin].pack("m0")
str.chomp!("=") unless str.chomp!("==")
str.tr!("+/", "-_")
str
end

def urlsafe_decode64(str)
if str.length % 4 == 0
str = str.tr("-_", "+/")
else
str = str.ljust((str.length + 3) & ~3, "=")
str.tr!("-_", "+/")
end
decode64(str)
end

# An array of strings, one for each configured encryption key, to find encypted values matching
# the given data and search format.
def _search_prefixes(data, search_type)
@key_map.map do |key_id, (key, _)|
Base64.urlsafe_encode64(_search_prefix(data, search_type, key_id, key))
urlsafe_encode64(_search_prefix(data, search_type, key_id, key))
end
end

Expand Down Expand Up @@ -509,7 +537,7 @@ def _encrypt(data, prefix)
cipher_text << cipher.update(data) if data_size > 0
cipher_text << cipher.final

Base64.urlsafe_encode64("#{prefix}#{random_data}#{cipher_iv}#{cipher.auth_tag}#{cipher_text}")
urlsafe_encode64("#{prefix}#{random_data}#{cipher_iv}#{cipher.auth_tag}#{cipher_text}")
end
end

Expand Down
8 changes: 4 additions & 4 deletions spec/extensions/column_encryption_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -395,16 +395,16 @@ def obj.save(*); end
@obj[:enc] = enc.dup.tap{|x| x[0] = '%'}
proc{@obj.enc}.must_raise Sequel::Error # invalid base-64

@obj[:enc] = enc.dup.tap{|x| x[0,4] = Base64.urlsafe_encode64("\4\0\0")}
@obj[:enc] = enc.dup.tap{|x| x[0,4] = "BAAA"} # "\4\0\0" base64
proc{@obj.enc}.must_raise Sequel::Error # invalid flags

@obj[:enc] = enc.dup.tap{|x| x[0,4] = Base64.urlsafe_encode64("\0\1\0")}
@obj[:enc] = enc.dup.tap{|x| x[0,4] = "AAEA"} # "\0\1\0" base64
proc{@obj.enc}.must_raise Sequel::Error # invalid reserved byte

@obj[:enc] = enc.dup.tap{|x| x[0,4] = Base64.urlsafe_encode64("\0\0\1")}
@obj[:enc] = enc.dup.tap{|x| x[0,4] = "AAAB"} # "\0\0\1" base64
proc{@obj.enc}.must_raise Sequel::Error # invalid key id

@obj[:enc] = enc.dup.tap{|x| x[0,4] = Base64.urlsafe_encode64("\1\0\0")}
@obj[:enc] = enc.dup.tap{|x| x[0,4] = "AQAA"} # "\1\0\0" base64
proc{@obj.enc}.must_raise Sequel::Error # invalid minimum size for searchable

@obj[:enc] = enc.dup.tap{|x| x.slice!(60, 1000)}
Expand Down

0 comments on commit 5ca610a

Please sign in to comment.