aead 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ /Gemfile.lock
2
+
3
+ /.yardoc
4
+ /bin
5
+ /coverage
6
+ /doc
7
+
8
+ *.bundle
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ - 1.9.2
3
+ - 1.9.3
@@ -0,0 +1,2 @@
1
+ --markup-provider=redcarpet
2
+ --markup=markdown
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'terminal-notifier-guard'
6
+ gem 'pry'
7
+ gem 'pry-doc'
@@ -0,0 +1,10 @@
1
+ guard 'yard' do
2
+ watch(%r{lib/.+\.rb})
3
+ watch(%r{ext/.+\.c})
4
+ end
5
+
6
+ guard 'minitest' do
7
+ watch(%r|^spec/(.*)_spec\.rb|)
8
+ watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
9
+ watch(%r|^spec/spec_helper\.rb|) { "spec" }
10
+ end
@@ -0,0 +1,22 @@
1
+ The MIT License
2
+ ===============
3
+
4
+ Copyright (c) 2012 OneLogin, Inc.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in
14
+ all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ THE SOFTWARE.
@@ -0,0 +1,57 @@
1
+ # aead #
2
+
3
+ Ruby library for generating AEAD (authenticated encryption with
4
+ associated data) ciphertexts.
5
+
6
+ ## Description ##
7
+
8
+ Modern encryption best practices encourage the use of authenticated
9
+ encryption: ciphertext contents should be authenticated during the
10
+ decryption process, preventing either malicious or unintentional
11
+ silent corruption.
12
+
13
+ This library provides an extension to the Ruby OpenSSL bindings that
14
+ allows access to the GCM mode supported by OpenSSL (in versions higher
15
+ than 1.0.0). For Rubies linked against older versions of OpenSSL, a
16
+ mode is provided to perform AES-256-CTR with HMAC-SHA-256, as
17
+ [encouraged by Colin Percival](http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html).
18
+
19
+ ## Getting Started ##
20
+
21
+ ```ruby
22
+ require 'aead'
23
+
24
+ # currently, AES-256-GCM and AES-256-CTR-HMAC-SHA-256 are supported
25
+ mode = AEAD::Cipher.new('AES-256-GCM')
26
+ key = cipher.generate_key
27
+ nonce = cipher.generate_nonce
28
+
29
+ cipher = mode.new(key)
30
+ aead = cipher.encrypt(nonce, 'authentication data', 'plaintext')
31
+ plaintext = cipher.decrypt(nonce, 'authentication data', aead)
32
+ ```
33
+
34
+ If any of the key, nonce, authentication data, or ciphertext has been
35
+ altered, the ciphertext will fail to decrypt and an exception will be
36
+ raised.
37
+
38
+ ```ruby
39
+ cipher.decrypt(nonce, 'authentication data', aead.succ) # => ArgumentError
40
+ ```
41
+
42
+ ## Security Guidelines ##
43
+
44
+ Nonces should *never* be used to encrypt more than one plaintext with
45
+ the same key.
46
+
47
+ Nonces generated through the API provided by this gem are guaranteed
48
+ to be unique as long as the state file is not corrupted or
49
+ removed. The state file is located in `/var/tmp/ruby-aead`.
50
+
51
+ Nonce generation is thread-safe and tolerates being performed
52
+ simultaneously in separate processes.
53
+
54
+ If you believe you have discovered a security vulnerability in this
55
+ gem, please email `security@onelogin.com` with a description. We
56
+ follow responsible disclosure guidelines, and will work with you to
57
+ quickly find a resolution.
@@ -0,0 +1,46 @@
1
+ require 'rake/testtask'
2
+ require 'rake/version_task'
3
+ require 'yard'
4
+
5
+ task :default => %w{ build test }
6
+
7
+ task :build do
8
+ Dir.chdir('ext/openssl/cipher/aead') do
9
+ system %{ruby extconf.rb}
10
+ system %{make}
11
+ system %{cp aead.#{RbConfig::CONFIG['DLEXT']} ../../../../lib/openssl/cipher}
12
+ end
13
+ end
14
+
15
+ Rake::TestTask.new do |t|
16
+ t.libs.push 'lib'
17
+ t.libs.push 'spec'
18
+
19
+ t.test_files = FileList['spec/**/*_spec.rb']
20
+ t.verbose = true
21
+ end
22
+
23
+ if defined?(RUBY_ENGINE) and RUBY_ENGINE == 'ruby'
24
+ require 'cane/rake_task'
25
+
26
+ task :default => :cane
27
+
28
+ Cane::RakeTask.new do |t|
29
+ t.add_threshold 'coverage/coverage.txt', :>=, 100
30
+ end
31
+ end
32
+
33
+ Rake::VersionTask.new do |t|
34
+ t.with_git_tag = true
35
+ end
36
+
37
+ YARD::Rake::YardocTask.new(:doc) do |t|
38
+ # --no-stats applies only to the `yard stats` command, so to include
39
+ # it we have to disable automatic stat generation and do it
40
+ # ourselves
41
+ t.options << '--no-stats'
42
+ t.after = lambda do
43
+ stats = YARD::CLI::Stats.new
44
+ stats.run '--list-undoc'
45
+ end
46
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.3.0
@@ -0,0 +1,34 @@
1
+ require 'pathname'
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'aead'
5
+ gem.version = Pathname.new(__FILE__).join('../VERSION').read.chomp
6
+
7
+ gem.author = 'Stephen Touset'
8
+ gem.email = 'stephen@touset.org'
9
+
10
+ gem.homepage = 'https://github.com/onelogin/aead'
11
+ gem.summary = %{Ruby library to generate AEADs}
12
+ gem.description = %{Ruby library to generate AEADs}
13
+
14
+ gem.bindir = 'script'
15
+ gem.files = `git ls-files` .split("\n")
16
+ gem.extensions = `git ls-files -- ext/*.rb`.split("\n")
17
+ gem.executables = `git ls-files -- script/*`.split("\n").map {|e| e[7..-1] }
18
+ gem.test_files = `git ls-files -- spec/*` .split("\n")
19
+
20
+ gem.add_dependency 'macaddr', '~> 1'
21
+
22
+ gem.add_development_dependency 'bundler'
23
+ gem.add_development_dependency 'cane'
24
+ gem.add_development_dependency 'guard'
25
+ gem.add_development_dependency 'guard-minitest'
26
+ gem.add_development_dependency 'guard-yard'
27
+ gem.add_development_dependency 'markdown'
28
+ gem.add_development_dependency 'minitest'
29
+ gem.add_development_dependency 'rake'
30
+ gem.add_development_dependency 'redcarpet'
31
+ gem.add_development_dependency 'simplecov'
32
+ gem.add_development_dependency 'yard'
33
+ gem.add_development_dependency 'version'
34
+ end
@@ -0,0 +1,4 @@
1
+ /Makefile
2
+ /*.bundle
3
+ /*.log
4
+ /*.o
@@ -0,0 +1,156 @@
1
+ #include <ruby.h>
2
+ #include <openssl/ssl.h>
3
+ #include <openssl/err.h>
4
+
5
+ VALUE dOSSL;
6
+ VALUE eCipherError;
7
+
8
+ #define GetCipherInit(obj, ctx) do { \
9
+ Data_Get_Struct((obj), EVP_CIPHER_CTX, (ctx)); \
10
+ } while (0)
11
+
12
+ #define GetCipher(obj, ctx) do { \
13
+ GetCipherInit((obj), (ctx)); \
14
+ if (!(ctx)) { \
15
+ ossl_raise(rb_eRuntimeError, "Cipher not inititalized!"); \
16
+ } \
17
+ } while (0)
18
+
19
+ static VALUE
20
+ ossl_make_error(VALUE exc, const char *fmt, va_list args)
21
+ {
22
+ char buf[BUFSIZ];
23
+ const char *msg;
24
+ long e;
25
+ int len = 0;
26
+
27
+ #ifdef HAVE_ERR_PEEK_LAST_ERROR
28
+ e = ERR_peek_last_error();
29
+ #else
30
+ e = ERR_peek_error();
31
+ #endif
32
+ if (fmt) {
33
+ len = vsnprintf(buf, BUFSIZ, fmt, args);
34
+ }
35
+ if (len < BUFSIZ && e) {
36
+ if (dOSSL == Qtrue) /* FULL INFO */
37
+ msg = ERR_error_string(e, NULL);
38
+ else
39
+ msg = ERR_reason_error_string(e);
40
+ len += snprintf(buf+len, BUFSIZ-len, "%s%s", (len ? ": " : ""), msg);
41
+ }
42
+ if (dOSSL == Qtrue){ /* show all errors on the stack */
43
+ while ((e = ERR_get_error()) != 0){
44
+ rb_warn("error on stack: %s", ERR_error_string(e, NULL));
45
+ }
46
+ }
47
+ ERR_clear_error();
48
+
49
+ if(len > BUFSIZ) len = rb_long2int(strlen(buf));
50
+ return rb_exc_new(exc, buf, len);
51
+ }
52
+
53
+ void
54
+ ossl_raise(VALUE exc, const char *fmt, ...)
55
+ {
56
+ va_list args;
57
+ VALUE err;
58
+ va_start(args, fmt);
59
+ err = ossl_make_error(exc, fmt, args);
60
+ va_end(args);
61
+ rb_exc_raise(err);
62
+ }
63
+
64
+ static VALUE
65
+ ossl_cipher_set_aad(VALUE self, VALUE data)
66
+ {
67
+ EVP_CIPHER_CTX *ctx;
68
+ char *in = NULL;
69
+ int in_len = 0;
70
+ int out_len = 0;
71
+
72
+ StringValue(data);
73
+
74
+ in = (unsigned char *) RSTRING_PTR(data);
75
+ in_len = RSTRING_LENINT(data);
76
+
77
+ GetCipher(self, ctx);
78
+
79
+ if (!EVP_CipherUpdate(ctx, NULL, &out_len, in, in_len))
80
+ ossl_raise(eCipherError, NULL);
81
+
82
+ return self;
83
+ }
84
+
85
+ static VALUE
86
+ ossl_cipher_get_tag(VALUE self)
87
+ {
88
+ EVP_CIPHER_CTX *ctx;
89
+ VALUE tag;
90
+
91
+ tag = rb_str_new(NULL, 16);
92
+
93
+ GetCipher(self, ctx);
94
+
95
+ #ifndef EVP_CTRL_GCM_GET_TAG
96
+ ossl_raise(eCipherError, "your version of OpenSSL doesn't support GCM");
97
+ #else
98
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, (unsigned char *)RSTRING_PTR(tag)))
99
+ ossl_raise(eCipherError, NULL);
100
+ #endif
101
+
102
+ return tag;
103
+ }
104
+
105
+ static VALUE
106
+ ossl_cipher_set_tag(VALUE self, VALUE data)
107
+ {
108
+ EVP_CIPHER_CTX *ctx;
109
+ char *in = NULL;
110
+ int in_len = 0;
111
+
112
+ StringValue(data);
113
+
114
+ in = (unsigned char *) RSTRING_PTR(data);
115
+ in_len = RSTRING_LENINT(data);
116
+
117
+ GetCipher(self, ctx);
118
+
119
+ #ifndef EVP_CTRL_GCM_SET_TAG
120
+ ossl_raise(eCipherError, "your version of OpenSSL doesn't support GCM");
121
+ #else
122
+ if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, in_len, in))
123
+ ossl_raise(eCipherError, NULL);
124
+ #endif
125
+
126
+ return data;
127
+ }
128
+
129
+ static VALUE
130
+ ossl_cipher_verify(VALUE self)
131
+ {
132
+ EVP_CIPHER_CTX *ctx;
133
+ int out_len = 0;
134
+
135
+ GetCipher(self, ctx);
136
+
137
+ if (!EVP_CipherUpdate(ctx, NULL, &out_len, NULL, 0))
138
+ ossl_raise(eCipherError, "ciphertext failed authentication step");
139
+
140
+ return rb_str_new(0, 0);
141
+ }
142
+
143
+ void
144
+ Init_aead(void)
145
+ {
146
+ VALUE mOSSL = rb_define_module("OpenSSL");
147
+ VALUE mOSSLCipher = rb_define_class_under(mOSSL, "Cipher", rb_cObject);
148
+ VALUE eOSSLError = rb_define_class_under(mOSSL,"OpenSSLError",rb_eStandardError);
149
+
150
+ eCipherError = rb_define_class_under(mOSSLCipher, "CipherError", eOSSLError);
151
+
152
+ rb_define_method(mOSSLCipher, "aad=", ossl_cipher_set_aad, 1);
153
+ rb_define_method(mOSSLCipher, "gcm_tag", ossl_cipher_get_tag, 0);
154
+ rb_define_method(mOSSLCipher, "gcm_tag=", ossl_cipher_set_tag, 1);
155
+ rb_define_method(mOSSLCipher, "verify", ossl_cipher_verify, 0);
156
+ }
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ have_header("openssl/ssl.h")
4
+ have_library("ssl", "SSLv23_method")
5
+
6
+ create_makefile('openssl/cipher/aead')
@@ -0,0 +1,4 @@
1
+ module AEAD
2
+ autoload :Cipher, 'aead/cipher'
3
+ autoload :Nonce, 'aead/nonce'
4
+ end
@@ -0,0 +1,221 @@
1
+ require 'aead'
2
+
3
+ require 'openssl'
4
+ require 'openssl/cipher/aead'
5
+ require 'securerandom'
6
+
7
+ #
8
+ # Wraps AEAD ciphers in a simplified interface.
9
+ #
10
+ class AEAD::Cipher
11
+ autoload :AES_256_GCM, 'aead/cipher/aes_256_gcm'
12
+ autoload :AES_256_CBC_HMAC_SHA_256, 'aead/cipher/aes_256_cbc_hmac_sha_256'
13
+ autoload :AES_256_CTR_HMAC_SHA_256, 'aead/cipher/aes_256_ctr_hmac_sha_256'
14
+
15
+ #
16
+ # Returns a particular Cipher implementation.
17
+ #
18
+ # @param [String] algorithm the AEAD implementation to use
19
+ # @return [Class] the cipher implementation
20
+ #
21
+ def self.new(algorithm)
22
+ # run normal Class#new if we're being called from a subclass
23
+ return super unless self == AEAD::Cipher
24
+
25
+ # TODO: ciphers should register themselves, as opposed to using a
26
+ # potentiall-unsafe const_get
27
+ self.const_get algorithm.tr('-', '_').upcase
28
+ end
29
+
30
+ #
31
+ # Returns a securely-generated key of appropriate length for the
32
+ # current Cipher.
33
+ #
34
+ # @return [String] a random key
35
+ #
36
+ def self.generate_key
37
+ SecureRandom.random_bytes(self.key_len)
38
+ end
39
+
40
+ #
41
+ # Returns a unique nonce for the current Cipher.
42
+ #
43
+ # @return [String] a random key
44
+ #
45
+ def self.generate_nonce
46
+ AEAD::Nonce.generate
47
+ end
48
+
49
+ #
50
+ # Does a constant-time comparison between two strings. Useful to
51
+ # avoid timing attacks when comparing a generated signature against
52
+ # a user-provided signature.
53
+ #
54
+ # @param [String] left any string
55
+ # @param [String] right any string
56
+ # @return [Boolean] whether or not the strings are equal
57
+ #
58
+ def self.signature_compare(left, right)
59
+ # short-circuit if the lengths are inequal
60
+ return false if left.to_s.bytesize != right.bytesize
61
+
62
+ # Constant-time string comparison algorithm:
63
+ # 1. Break both strings into bytes
64
+ # 2. Subtract the strings from one-another, byte by byte
65
+ # (any non-equal bytes will subtract to a nonzero value)
66
+ # 3. OR the XOR'd bytes together
67
+ # 4. If the result is nonzero, the strings differed.
68
+ left = left.bytes.to_a
69
+ right = right.bytes.to_a
70
+ result = 0
71
+
72
+ left.length.times do |i|
73
+ result |= left[i] - right[i]
74
+ end
75
+
76
+ result == 0
77
+ end
78
+
79
+ #
80
+ # The length of keys of encryption keys used by the current Cipher.
81
+ #
82
+ # @return [Integer] the length of keys in bytes
83
+ #
84
+ def key_len
85
+ self.class.key_len
86
+ end
87
+
88
+ #
89
+ # The length of nonces used by the current Cipher.
90
+ #
91
+ # @return [Integer] the length of nonces in bytes
92
+ #
93
+ def nonce_len
94
+ self.class.nonce_len
95
+ end
96
+
97
+ #
98
+ # Encrypts a plaintext using the current Cipher.
99
+ #
100
+ # IMPORTANT: Do not ever encrypt data using the same nonce more than
101
+ # once given a particular secret key. Doing so will violate the
102
+ # security guarantees of the AEAD cipher.
103
+ #
104
+ # @param [String] nonce a unique nonce, never before used with the
105
+ # current encryption key
106
+ # @param [String, nil] aad arbitrary additional authentication data that
107
+ # must later be provided to decrypt the aead
108
+ # @param [String] plaintext arbitrary plaintext
109
+ # @return [String] the generated AEAD
110
+ #
111
+ def encrypt(nonce, aad, plaintext)
112
+ _verify_nonce_bytesize(nonce, self.nonce_len)
113
+ _verify_plaintext_presence(plaintext)
114
+
115
+ self._encrypt(
116
+ _pad_nonce(nonce),
117
+ aad,
118
+ plaintext
119
+ )
120
+ end
121
+
122
+ #
123
+ # Decrypts a plaintext using the current Cipher.
124
+ #
125
+ # @param [String] nonce the nonce used when encrypting the AEAD
126
+ # @param [String] aad the additional authentication data used when
127
+ # encrypting the AEAD
128
+ # @param [String] aead the encrypted AEAD
129
+ # @return [String] the original plaintext
130
+ #
131
+ def decrypt(nonce, aad, aead)
132
+ _verify_nonce_bytesize(nonce, self.nonce_len)
133
+
134
+ self._decrypt(
135
+ _pad_nonce(nonce),
136
+ aad,
137
+ _extract_ciphertext(aead, self.tag_len),
138
+ _extract_tag(aead, self.tag_len)
139
+ )
140
+ end
141
+
142
+ protected
143
+
144
+ # The OpenSSL algorithm to be used by the cipher.
145
+ attr_accessor :algorithm
146
+
147
+ # The secret key provided by the user.
148
+ attr_accessor :key
149
+
150
+ #
151
+ # Initializes the cipher.
152
+ #
153
+ # @param [String] algorithm the full encryption mode to be used in
154
+ # calls to {#cipher}.
155
+ # @param [String] key the encryption key supplied by the user
156
+ # @return [Cipher] the initialized Cipher
157
+ #
158
+ def initialize(algorithm, key)
159
+ _verify_key_bytesize(key, self.key_len)
160
+
161
+ self.algorithm = algorithm.dup.freeze
162
+ self.key = key.dup.freeze
163
+
164
+ self.freeze
165
+ end
166
+
167
+
168
+ #
169
+ # Yields the {OpenSSL::Cipher} for the current {#algorithm}
170
+ #
171
+ def cipher(direction)
172
+ yield OpenSSL::Cipher.new(algorithm).send(direction)
173
+ end
174
+
175
+ #
176
+ # The length of initialization vectors used by the current Cipher.
177
+ #
178
+ # @return [Integer] the length of initialization vectors in bytes
179
+ #
180
+ def iv_len
181
+ self.class.iv_len
182
+ end
183
+
184
+ #
185
+ # The length of authentication tags generated by the current Cipher.
186
+ #
187
+ # @return [Integer] the length of authentication tags in bytes
188
+ #
189
+ def tag_len
190
+ self.class.tag_len
191
+ end
192
+
193
+ private
194
+
195
+ def _verify_key_bytesize(key, key_len)
196
+ raise ArgumentError, "key must be at least #{key_len} bytes" unless
197
+ key.bytesize >= key_len
198
+ end
199
+
200
+ def _verify_nonce_bytesize(nonce, nonce_len)
201
+ raise ArgumentError, "nonce must be at least #{nonce_len} bytes" unless
202
+ nonce.bytesize == nonce_len
203
+ end
204
+
205
+ def _verify_plaintext_presence(plaintext)
206
+ raise ArgumentError, 'plaintext must not be empty' unless
207
+ not plaintext.nil? and not plaintext.empty?
208
+ end
209
+
210
+ def _pad_nonce(nonce)
211
+ nonce.rjust(self.iv_len, "\0")
212
+ end
213
+
214
+ def _extract_ciphertext(ciphertext, tag_len)
215
+ ciphertext[ 0 .. -tag_len - 1 ].to_s
216
+ end
217
+
218
+ def _extract_tag(ciphertext, tag_len)
219
+ ciphertext[ -tag_len .. -1 ].to_s
220
+ end
221
+ end