ore-rs 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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