ore-rs 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,14 @@
1
+ [package]
2
+ name = "ore-rs"
3
+ version = "0.0.0"
4
+ edition = "2021"
5
+
6
+ [dependencies]
7
+ lazy_static = "^0.2.2"
8
+ ore-rs = "^0.6.0"
9
+ ore-encoding-rs = { git = "https://github.com/cipherstash/ore_encoding.rs" }
10
+ rb-sys = "0.8.0"
11
+ rutie = { git = "https://github.com/mpalmer/rutie", branch = "rb_sys" }
12
+
13
+ [lib]
14
+ crate-type = ["cdylib"]
@@ -0,0 +1,4 @@
1
+ require "mkmf"
2
+ require "rb_sys/mkmf"
3
+
4
+ create_rust_makefile("ore_rs/ore_rs")
@@ -0,0 +1,142 @@
1
+ #[macro_use]
2
+ extern crate rutie;
3
+
4
+ #[macro_use]
5
+ extern crate lazy_static;
6
+
7
+ use ore_encoding_rs::OrePlaintext;
8
+ use ore_rs::{CipherText, ORECipher, OREEncrypt};
9
+ use ore_rs::scheme::bit2::OREAES128;
10
+ use rutie::{Boolean, Class, Encoding, Float, Integer, Module, Object, RString, VerifiedObject, VM};
11
+ use std::cmp::Ordering;
12
+
13
+ module!(RbORE);
14
+ class!(RbOREAES128);
15
+ class!(RbOREAES128Ciphertext);
16
+
17
+ impl VerifiedObject for RbOREAES128Ciphertext {
18
+ fn is_correct_type<T: Object>(object: &T) -> bool {
19
+ let klass = Module::from_existing("ORE").get_nested_class("AES128").get_nested_class("Ciphertext");
20
+ klass.case_equals(object)
21
+ }
22
+
23
+ fn error_message() -> &'static str {
24
+ "Error converting to ORE::AES128::Ciphertext"
25
+ }
26
+ }
27
+
28
+ impl From<CipherText<OREAES128, 8>> for RbOREAES128Ciphertext {
29
+ fn from(ct: CipherText<OREAES128, 8>) -> Self {
30
+ let klass = Module::from_existing("ORE").get_nested_class("AES128").get_nested_class("Ciphertext");
31
+ klass.wrap_data(OreAes128Ciphertext { ct: ct.to_bytes(), n: 8 }, &*OREAES128_CIPHERTEXT_WRAPPER)
32
+ }
33
+ }
34
+
35
+ pub struct OreAes128 {
36
+ cipher: OREAES128
37
+ }
38
+
39
+ wrappable_struct!(OreAes128, OreAes128Wrapper, OREAES128_WRAPPER);
40
+
41
+ pub struct OreAes128Ciphertext {
42
+ #[allow(dead_code)]
43
+ n: u32,
44
+ ct: Vec<u8>
45
+ }
46
+
47
+ wrappable_struct!(OreAes128Ciphertext, OreAes128CiphertextWrapper, OREAES128_CIPHERTEXT_WRAPPER);
48
+
49
+ methods!(
50
+ RbOREAES128,
51
+ rbself,
52
+
53
+ fn ore_aes128_new(k1string: RString, k2string: RString) -> RbOREAES128 {
54
+ let mut k1: [u8; 16] = Default::default();
55
+ let mut k2: [u8; 16] = Default::default();
56
+
57
+ k1.clone_from_slice(k1string.unwrap().to_bytes_unchecked());
58
+ k2.clone_from_slice(k2string.unwrap().to_bytes_unchecked());
59
+
60
+ let cipher: OREAES128 = ORECipher::init(&k1, &k2).unwrap();
61
+
62
+ let klass = Module::from_existing("ORE").get_nested_class("AES128");
63
+ return klass.wrap_data(OreAes128 { cipher: cipher }, &*OREAES128_WRAPPER);
64
+ }
65
+
66
+ fn ore_aes128_encrypt_u64(plaintext: Integer) -> RbOREAES128Ciphertext {
67
+ let ore = rbself.get_data_mut(&*OREAES128_WRAPPER);
68
+ plaintext.unwrap().to_u64().encrypt(&mut ore.cipher).map_err(|e| VM::raise(Class::from_existing("RuntimeError"), &format!("Failed to encrypt ORE plaintext: {:?}", e))).unwrap().into()
69
+ }
70
+
71
+ fn ore_aes128_encrypt_f64(plaintext: Float) -> RbOREAES128Ciphertext {
72
+ let ore = rbself.get_data_mut(&*OREAES128_WRAPPER);
73
+ OrePlaintext::<u64>::from(plaintext.unwrap().to_f64()).0.encrypt(&mut ore.cipher).map_err(|e| VM::raise(Class::from_existing("RuntimeError"), &format!("Failed to encrypt ORE plaintext: {:?}", e))).unwrap().into()
74
+ }
75
+
76
+ fn ore_aes128_encrypt_string(plaintext: RString) -> RbOREAES128Ciphertext {
77
+ let ore = rbself.get_data_mut(&*OREAES128_WRAPPER);
78
+ OrePlaintext::<u64>::from(plaintext.unwrap().to_string_unchecked()).0.encrypt(&mut ore.cipher).map_err(|e| VM::raise(Class::from_existing("RuntimeError"), &format!("Failed to encrypt ORE plaintext: {:?}", e))).unwrap().into()
79
+ }
80
+
81
+ fn ore_aes128_encrypt_bool(plaintext: Boolean) -> RbOREAES128Ciphertext {
82
+ let ore = rbself.get_data_mut(&*OREAES128_WRAPPER);
83
+ OrePlaintext::<u64>::from(plaintext.unwrap().to_bool()).0.encrypt(&mut ore.cipher).map_err(|e| VM::raise(Class::from_existing("RuntimeError"), &format!("Failed to encrypt ORE plaintext: {:?}", e))).unwrap().into()
84
+ }
85
+ );
86
+
87
+ methods!(
88
+ RbOREAES128Ciphertext,
89
+ rbself,
90
+
91
+ fn ore_aes128_ciphertext_new(serialized_ciphertext: RString, n: Integer) -> RbOREAES128Ciphertext {
92
+ let ct = CipherText::<OREAES128, 8>::from_bytes(&serialized_ciphertext.unwrap().to_vec_u8_unchecked()).map_err(|e| VM::raise(Class::from_existing("ArgumentError"), &format!("Failed to deserialize ORE ciphertext: {:?}", e))).unwrap();
93
+
94
+ let klass = Module::from_existing("ORE").get_nested_class("AES128").get_nested_class("Ciphertext");
95
+ return klass.wrap_data(OreAes128Ciphertext { ct: ct.to_bytes(), n: n.unwrap().to_u32() }, &*OREAES128_CIPHERTEXT_WRAPPER);
96
+ }
97
+
98
+ fn ore_aes128_ciphertext_serialize() -> RString {
99
+ let obj = rbself.get_data(&*OREAES128_CIPHERTEXT_WRAPPER);
100
+
101
+ return RString::from_bytes(&obj.ct, &Encoding::find("BINARY").unwrap());
102
+ }
103
+
104
+ fn ore_aes128_ciphertext_cmp(other: RbOREAES128Ciphertext) -> Integer {
105
+ let obj = rbself.get_data(&*OREAES128_CIPHERTEXT_WRAPPER);
106
+ let real_other = other.unwrap();
107
+ let oth = real_other.get_data(&*OREAES128_CIPHERTEXT_WRAPPER);
108
+
109
+ match OREAES128::compare_raw_slices(&obj.ct, &oth.ct) {
110
+ Some(Ordering::Equal) => Integer::from(0),
111
+ Some(Ordering::Less) => Integer::from(-1),
112
+ Some(Ordering::Greater) => Integer::from(1),
113
+ None => {
114
+ VM::raise(Class::from_existing("RuntimeError"), "Comparison failed");
115
+ Integer::from(0)
116
+ }
117
+ }
118
+ }
119
+ );
120
+
121
+
122
+
123
+
124
+ #[allow(non_snake_case)]
125
+ #[no_mangle]
126
+ pub extern "C" fn Init_ore_rs() {
127
+ Module::from_existing("ORE").define(|oremod| {
128
+ oremod.define_nested_class("AES128", None).define(|cipher_class| {
129
+ cipher_class.singleton_class().def_private("_new", ore_aes128_new);
130
+ cipher_class.def_private("_encrypt_u64", ore_aes128_encrypt_u64);
131
+ cipher_class.def_private("_encrypt_f64", ore_aes128_encrypt_f64);
132
+ cipher_class.def_private("_encrypt_string", ore_aes128_encrypt_string);
133
+ cipher_class.def_private("_encrypt_bool", ore_aes128_encrypt_bool);
134
+
135
+ cipher_class.define_nested_class("Ciphertext", None).define(|ciphertext_class| {
136
+ ciphertext_class.singleton_class().def_private("_new", ore_aes128_ciphertext_new);
137
+ ciphertext_class.def_private("_serialize", ore_aes128_ciphertext_serialize);
138
+ ciphertext_class.def_private("_cmp", ore_aes128_ciphertext_cmp);
139
+ });
140
+ });
141
+ });
142
+ }
@@ -0,0 +1,47 @@
1
+ module ORE
2
+ class AES128
3
+ # An ORE ciphertext produced by an AES128 cipher.
4
+ class Ciphertext
5
+ include Comparable
6
+
7
+ # Create a ciphertext object from a serialized form.
8
+ #
9
+ # ORE ciphertexts can be serialized (using #to_s), and then deserialized by
10
+ # passing them into this constructor.
11
+ #
12
+ # @param ct [String] the serialized ciphertext. This must be a `BINARY` encoded
13
+ # string.
14
+ #
15
+ # @param n [Integer] the number of ORE blocks contained in the ciphertext. Each
16
+ # ORE block represents one octet of the plaintext. At present, only 64-bit
17
+ # plaintexts are supported, so this must always be `8`.
18
+ #
19
+ # @raises [ArgumentError] if the string passed as the ciphertext is not valid,
20
+ # or `n` is not a supported value.
21
+ #
22
+ def self.new(ct, n)
23
+ unless ct.is_a?(String)
24
+ raise ArgumentError, "Ciphertext must be a string"
25
+ end
26
+
27
+ unless n == 8
28
+ raise ArgumentError, "Only a block count of 8 is currently supported"
29
+ end
30
+
31
+ _new(ct, n)
32
+ end
33
+
34
+ def to_s
35
+ _serialize
36
+ end
37
+
38
+ def <=>(other)
39
+ unless other.is_a?(ORE::AES128::Ciphertext)
40
+ raise ArgumentError, "Cannot compare an ORE ciphertext to anything other than another ciphertext"
41
+ end
42
+
43
+ _cmp(other)
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/ore/aes128.rb ADDED
@@ -0,0 +1,204 @@
1
+ require "date"
2
+
3
+ require_relative "./aes128/ciphertext"
4
+
5
+ module ORE
6
+ class AES128
7
+ VALID_STRING_ENCODINGS = [Encoding.find("UTF-8"), Encoding.find("US-ASCII")]
8
+ private_constant :VALID_STRING_ENCODINGS
9
+
10
+ # Create a new ORE::AES128 "cipher" -- an object capable of encrypting plaintexts into ORE ciphertexts.
11
+ #
12
+ # @param k1 [String] also known as the "PRF key", this is one of the two keys required
13
+ # to encrypt with ORE. This key must be a 16 octet string in the `BINARY` encoding.
14
+ #
15
+ # @param k2 [String] also known as the "PRP key", this is the other of the two keys
16
+ # required to encrypt with ORE. This key must also be a 16 octet string in the `BINARY`
17
+ # encoding.
18
+ #
19
+ # @param b [Integer] the number of bits in each plaintext. At present, the only supported
20
+ # value for this parameter is `64`.
21
+ #
22
+ # @param n [Integer] the number of blocks to generate in the ORE ciphertext. Each block
23
+ # is derived from 8 bits of plaintext, and thus at present the only supported value
24
+ # for this parameter is `8`.
25
+ #
26
+ # @raises [ArgumentError] if any of the parameters do not meet the requirements.
27
+ #
28
+ def self.new(k1, k2, b, n)
29
+ validate_key(k1, "k1")
30
+ validate_key(k2, "k2")
31
+
32
+ unless b == 64
33
+ raise ArgumentError, "Only 64 bit ORE plaintexts are supported at present"
34
+ end
35
+
36
+ unless n == 8
37
+ raise ArgumentError, "Only 8 bit ORE blocks are supported at present"
38
+ end
39
+
40
+ _new(k1, k2, b, n)
41
+ end
42
+
43
+ # Encrypt a plaintext into an ORE::AES128::Ciphertext.
44
+ #
45
+ # @note the type of the object you pass in as the plaintext is crucially important.
46
+ # Different types of object are encoded incompatibly, and comparisons between
47
+ # ciphertexts generated from objects of different types is not guaranteed.
48
+ # For example, `#encrypt(-3.0) < #encrypt(1)` will not necessarily be true,
49
+ # even though `#encrypt(-3.0) < #encrypt(1.0)` is guaranteed.
50
+ #
51
+ # The currently supported types are:
52
+ #
53
+ # * `Integer` -- must be in the range 0..2**64-1 (inclusive). Will be encoded as
54
+ # an ORE `uint64`.
55
+ #
56
+ # * `Float` -- can be any double precision floating-point number, except for a NaN.
57
+ #
58
+ # * `String` -- any valid UTF-8 string. Will be hashed into a 64-bit value for storage.
59
+ #
60
+ # * `Range` -- a range of Integers, Floats, Dates, or Times; both ends must be of the
61
+ # same type. Indefinite beginnings *or* ends are supported, but not both.
62
+ #
63
+ # * `Date`, `Time` -- will be converted into milliseconds relative to the UTC epoch.
64
+ #
65
+ # @param plaintext [Integer, Float] the plaintext to encrypt.
66
+ #
67
+ # @raises [ArgumentError] if the type of the plaintext is not one currently supported,
68
+ # or the value of the plaintext is not one which is valid.
69
+ #
70
+ # @raises [RuntimeError] if a float is encrypted on a platform that doesn't use
71
+ # IEEE754 double-precision floating-point numbers as its representation of a
72
+ # `Float`, or if something spectacularly wrong happens in the ORE encryption process.
73
+ #
74
+ def encrypt(plaintext)
75
+ case plaintext
76
+ when Integer
77
+ encrypt_u64(plaintext)
78
+ when Float
79
+ encrypt_f64(plaintext)
80
+ when String
81
+ encrypt_string(plaintext)
82
+ when TrueClass, FalseClass
83
+ encrypt_bool(plaintext)
84
+ when Range
85
+ encrypt_range(plaintext)
86
+ when Date
87
+ encrypt(plaintext.to_time)
88
+ when Time
89
+ encrypt_time(plaintext)
90
+ else
91
+ raise ArgumentError, "Do not know how to ORE encrypt a #{plaintext.class}"
92
+ end
93
+ end
94
+
95
+ class << self
96
+ private
97
+
98
+ def validate_key(k, name)
99
+ unless k.is_a?(String) && k.encoding == Encoding::BINARY && k.bytesize == 16
100
+ raise ArgumentError, "#{name} must be a 16 octet binary string"
101
+ end
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def encrypt_u64(plaintext)
108
+ if plaintext < 0
109
+ raise ArgumentError, "Cannot encrypt integers less than zero"
110
+ end
111
+
112
+ if plaintext >= 2**64
113
+ raise ArgumentError, "Cannot encrypt integers greater than 2^64 - 1"
114
+ end
115
+
116
+ _encrypt_u64(plaintext)
117
+ end
118
+
119
+ def encrypt_f64(plaintext)
120
+ if Float::MAX_EXP != 1024 || Float::MIN_EXP != -1021
121
+ # This is a pure sanity check, so...
122
+ #:nocov:
123
+ raise RuntimeError, "This platform does not conform to our expectations for floating-point representations. Out of an abundance of caution, we will not encrypt floats on this platform."
124
+ #:nocov:
125
+ end
126
+
127
+ if plaintext.nan?
128
+ raise ArgumentError, "Cannot ORE encrypt NaN"
129
+ end
130
+
131
+ _encrypt_f64(plaintext)
132
+ end
133
+
134
+ def encrypt_string(plaintext)
135
+ unless VALID_STRING_ENCODINGS.include?(plaintext.encoding)
136
+ raise ArgumentError, "Cannot encrypt non-UTF-8 string"
137
+ end
138
+
139
+ if !plaintext.valid_encoding?
140
+ raise ArgumentError, "Cannot encrypt invalid UTF-8 string"
141
+ end
142
+
143
+ _encrypt_string(plaintext)
144
+ end
145
+
146
+ def encrypt_bool(plaintext)
147
+ _encrypt_bool(plaintext)
148
+ end
149
+
150
+ def encrypt_range(plaintext)
151
+ min, max = plaintext.begin, plaintext.end
152
+
153
+ case [min.class, max.class]
154
+ # Integer ranges
155
+ when [Integer, Integer]
156
+ if max < min
157
+ raise ArgumentError, "Cannot encrypt a non-ascending range"
158
+ end
159
+ (encrypt_u64(min)..encrypt_u64(max))
160
+ when [Integer, NilClass]
161
+ (encrypt_u64(min)..encrypt_u64(2**64-1))
162
+ when [NilClass, Integer]
163
+ (encrypt_u64(0)..encrypt_u64(max))
164
+
165
+ # Float ranges
166
+ when [Float, Float]
167
+ if max < min
168
+ raise ArgumentError, "Cannot encrypt a non-ascending range"
169
+ end
170
+ (encrypt_f64(min)..encrypt_f64(max))
171
+ when [Float, NilClass]
172
+ (encrypt_f64(min)..encrypt_f64(Float::INFINITY))
173
+ when [NilClass, Float]
174
+ (encrypt_f64(-Float::INFINITY)..encrypt_f64(max))
175
+
176
+ # Date ranges
177
+ when [Date, Date]
178
+ encrypt_range(min.to_time..max.to_time)
179
+ when [Date, NilClass]
180
+ encrypt_range(min.to_time..)
181
+ when [NilClass, Date]
182
+ encrypt_range(..max.to_time)
183
+
184
+ # Time ranges
185
+ when [Time, Time]
186
+ (encrypt_time(min)..encrypt_time(max))
187
+ when [Time, NilClass]
188
+ (encrypt_time(min)..encrypt_u64(2**64-1))
189
+ when [NilClass, Time]
190
+ (encrypt_u64(0)..encrypt_time(max))
191
+
192
+ else
193
+ raise ArgumentError, "Cannot encrypt a range over #{min.class}..#{max.class}"
194
+ end
195
+ end
196
+
197
+ def encrypt_time(plaintext)
198
+ # Get the time in integer milliseconds, and then "shift" it so
199
+ # that times before the epoch are still positive numbers, just
200
+ # smaller than times after the epoch
201
+ encrypt_u64((plaintext.to_r * 1000).to_i + 2**63)
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,3 @@
1
+ module ORE
2
+ VERSION = "0.7.0"
3
+ end
data/lib/ore-rs.rb ADDED
@@ -0,0 +1,15 @@
1
+ module ORE
2
+ end
3
+
4
+ begin
5
+ RUBY_VERSION =~ /(\d+\.\d+)/
6
+ require_relative "./#{$1}/ore_rs"
7
+ rescue LoadError
8
+ begin
9
+ require_relative "./ore_rs"
10
+ rescue LoadError
11
+ raise LoadError, "Could not load ore_rs binary library"
12
+ end
13
+ end
14
+
15
+ require_relative "./ore/aes128"
data/ore-rs.gemspec ADDED
@@ -0,0 +1,43 @@
1
+ require_relative './lib/ore/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "ore-rs"
5
+
6
+ s.version = ORE::VERSION
7
+ s.date = Time.now.strftime("%Y-%m-%d")
8
+
9
+ s.platform = Gem::Platform::RUBY
10
+
11
+ s.summary = "Ruby bindings for the ore.rs Order-Revealing Encryption library"
12
+
13
+ s.authors = ["James Sadler", "Bennett Hardwick", "Drew Thomas"]
14
+ s.email = ["james@cipherstash.com", "bennett@cipherstash.com", "drew@cipherstash.com"]
15
+ s.homepage = "https://cipherstash.com/protect"
16
+
17
+ s.files = `git ls-files -z`.split("\0").reject { |f| f =~ /^(\.|G|spec|Rakefile)/ }
18
+
19
+ s.extensions = ["ext/ore_rs/extconf.rb"]
20
+
21
+ s.required_ruby_version = ">= 2.7.0"
22
+
23
+ s.metadata["homepage_uri"] = s.homepage
24
+ s.metadata["source_code_uri"] = "https://github.com/cipherstash/ruby-ore-rs"
25
+ s.metadata["changelog_uri"] = "https://github.com/cipherstash/ruby-ore-rs/releases"
26
+ s.metadata["bug_tracker_uri"] = "https://github.com/cipherstash/ruby-ore-rs/issues"
27
+ s.metadata["documentation_uri"] = "https://rubydoc.info/gems/ore-rs"
28
+ s.metadata["mailing_list_uri"] = "https://discuss.cipherstash.com"
29
+
30
+ s.add_runtime_dependency 'rb_sys', '~> 0.1'
31
+
32
+ s.add_development_dependency 'bundler'
33
+ s.add_development_dependency 'guard-rspec'
34
+ s.add_development_dependency 'rake', '~> 13.0'
35
+ s.add_development_dependency 'rake-compiler', '~> 1.2'
36
+ s.add_development_dependency 'rake-compiler-dock', '~> 1.2'
37
+ s.add_development_dependency 'rb-inotify', '~> 0.9'
38
+ s.add_development_dependency 'rb_sys', '~> 0.1'
39
+ s.add_development_dependency 'redcarpet'
40
+ s.add_development_dependency 'rspec'
41
+ s.add_development_dependency 'simplecov'
42
+ s.add_development_dependency 'yard'
43
+ end