dualcone 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b2cefd1451fcbb7e091ab48f74b5cbca1eb8a585e3cc28edb34ff5a6f04beaac
4
+ data.tar.gz: 5ba6021faa19db6e737f669459f86c464fc6878bfad7250bead60004f0f60c60
5
+ SHA512:
6
+ metadata.gz: 5b1a2bc0687e734ec4b7a103825a2b698f15ae01a3d8efca9a0fd27b6b8d41e42b66b103c93770b7fc2df46ce5deccdce81723eb0cb406578ae8a52b6ebf5c3e
7
+ data.tar.gz: 98f13c8cc8c35459f34698d84f119fb7e5550128c76af207151ba3438b6ac056f5c199b17a54c8f95848e427ec3dcd71e05c5c58ba84e5f1ef4b486e95f8ad68
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Tom Richards
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,116 @@
1
+ # Dualcone
2
+
3
+ [![CircleCI](https://circleci.com/gh/t-richards/dualcone.svg?style=svg)](https://circleci.com/gh/t-richards/dualcone)
4
+
5
+ Dualcone is a Ruby source code protection system. Dualcone uses symmetric encryption to protect your source code.
6
+
7
+ Dualcone is a self-contained gem. It brings along its own copy of the lightweight cryptographic library, [libhydrogen][libhydrogen].
8
+
9
+ Dualcone supports GNU + Linux and other Unix-like operating systems. Windows is not supported.
10
+
11
+ ## Roadmap
12
+
13
+ ### Part 1
14
+ - [x] Key generation: `Dualcone.generate_key`
15
+ - [x] Encrypted code running: `Dualcone.run(code)`
16
+ - [x] Encrypted code generation: `Dualcone.encrypt(path)`
17
+ - [x] Specs passing
18
+
19
+ ### Part 2
20
+ - [x] Runnable trivial ruby script
21
+ - [ ] Runnable non-trivial ruby script
22
+ - [ ] Runnable sinatra app
23
+ - [ ] Runnable rails app
24
+
25
+ ## Installation
26
+
27
+ Add this gem to your application's Gemfile:
28
+
29
+ ```ruby
30
+ gem 'dualcone'
31
+ ```
32
+
33
+ And then execute:
34
+
35
+ ```bash
36
+ $ bundle install
37
+ ```
38
+
39
+ Or install it yourself as:
40
+
41
+ ```bash
42
+ $ gem install dualcone
43
+ ```
44
+
45
+ You need to have a C compiler and `make` installed on your system to be able to build this gem's native code and the included version of `libhydrogen`.
46
+
47
+ ## Usage
48
+
49
+ 1. Generate a secret encryption key.
50
+
51
+ ```ruby
52
+ require 'dualcone'
53
+ Dualcone.generate_key
54
+ => "764888c92f3059c88524225b622cd178856877cf3537230a9d7f5b5b6d8850c5"
55
+ ```
56
+
57
+ 2. Place your encryption key in the `DUALCONE_HEX_KEY` environment variable:
58
+
59
+ ```bash
60
+ $ export DUALCONE_HEX_KEY="8c8f91f84d8e554dc03277ce2f038af95cd932e2b65011969e77d3ac18d7bdd9"
61
+ ```
62
+
63
+ This environment variable is required for both encrypting files as well as running already-encrypted files.
64
+
65
+ 3. Encrypt your Ruby source code file(s).
66
+
67
+ :warning: Your source code file will be modified in-place. This is a one-way operation! :warning:
68
+
69
+ For example, let's say we have a file named `hello.rb` with the following contents:
70
+
71
+ ```ruby
72
+ puts 'Hello, world!'
73
+ ```
74
+
75
+ You can encrypt this file using `Dualcone.encrypt(path)`
76
+
77
+ ```ruby
78
+ Dualcone.encrypt('hello.rb')
79
+ ```
80
+
81
+ The entire contents of the file `hello.rb` will be replaced with a call to `Dualcone.run(code)`:
82
+
83
+ ```ruby
84
+ require 'dualcone'
85
+ Dualcone.run('7f1a1b6a047aee2403e415044b72f2ac2997cef689960df46afdfe6d7c657e18dbae1bea3bbe33ae9157cb2f7b22f34db69b2eb41e05aa512151')
86
+ ```
87
+
88
+ 4. Finally, test your encrypted code by running it.
89
+
90
+ ```bash
91
+ $ ruby hello.rb
92
+ ```
93
+
94
+ ## Development
95
+
96
+ 1. `git clone git@github.com:t-richards/dualcone.git` to clone the repo.
97
+ 2. `bin/setup` to install dependencies and fetch git submodules.
98
+ 3. `bin/rake compile` to build the gem's native extensions.
99
+ 4. `bin/rspec` to run the tests.
100
+ 5. `bin/rubocop` to check code style.
101
+
102
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
103
+
104
+ To install this gem onto your local machine, run `bin/rake install`. To release a new version, update the version number in `version.rb`, and then run `bin/rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org][rubygems].
105
+
106
+ ## Contributing
107
+
108
+ Bug reports and pull requests are welcome on GitHub at https://github.com/t-richards/dualcone.
109
+
110
+ ## License
111
+
112
+ The gem is available as open source under the terms of the [MIT License][mit-license].
113
+
114
+ [libhydrogen]: https://github.com/jedisct1/libhydrogen
115
+ [mit-license]: https://opensource.org/licenses/MIT
116
+ [rubygems]: https://rubygems.org
@@ -0,0 +1,288 @@
1
+ #include "dualcone.h"
2
+
3
+ VALUE rb_mDualcone;
4
+
5
+ void rb_dualcone_cleanup(DualconeContext *ctx) {
6
+ if (ctx->input_path != NULL) {
7
+ free(ctx->input_path);
8
+ }
9
+
10
+ if (ctx->output_path != NULL) {
11
+ unlink(ctx->output_path); /* temporary file */
12
+ hydro_memzero(ctx->output_path, PATH_MAX);
13
+ free(ctx->output_path);
14
+ }
15
+
16
+ if (ctx->plaintext != NULL) {
17
+ hydro_memzero(ctx->plaintext, ctx->plaintext_len);
18
+ free(ctx->plaintext);
19
+ }
20
+
21
+ if (ctx->ciphertext_hex != NULL) {
22
+ hydro_memzero(ctx->ciphertext_hex, ctx->ciphertext_hex_len);
23
+ free(ctx->ciphertext_hex);
24
+ }
25
+
26
+ if (ctx->ciphertext != NULL) {
27
+ hydro_memzero(ctx->ciphertext, ctx->ciphertext_len);
28
+ free(ctx->ciphertext);
29
+ }
30
+
31
+ hydro_memzero(ctx, sizeof(DualconeContext));
32
+ }
33
+
34
+ void rb_dualcone_get_key(DualconeContext *ctx) {
35
+ int result = 0;
36
+ int errno_sv = 0;
37
+
38
+ /* Hex-encoded encryption key from environment */
39
+ char *hex_key = getenv(DUALCONE_HEX_KEY);
40
+ if (hex_key == NULL) {
41
+ rb_dualcone_cleanup(ctx);
42
+ rb_raise(rb_eKeyError, "environment variable not found: " DUALCONE_HEX_KEY);
43
+ }
44
+
45
+ /* Convert encryption key (hex encoded) to binary */
46
+ result = hydro_hex2bin(ctx->binary_key, hydro_secretbox_KEYBYTES, hex_key, strlen(hex_key), NULL, NULL);
47
+ if (result == -1) {
48
+ errno_sv = errno;
49
+ rb_dualcone_cleanup(ctx);
50
+ rb_raise(rb_eFatal, "unable to hex-decode encryption key: %s", strerror(errno_sv));
51
+ }
52
+ }
53
+
54
+ VALUE rb_dualcone_run(VALUE _self, VALUE code) {
55
+ int result = 0;
56
+ int errno_sv = 0;
57
+
58
+ /* Check args */
59
+ rb_check_type(code, T_STRING);
60
+
61
+ /* Dualcone private memory allocations */
62
+ DualconeContext ctx = {0};
63
+ rb_dualcone_get_key(&ctx);
64
+
65
+ /* Encrypted ruby code (hex-encoded) */
66
+ char *hex_code = StringValuePtr(code);
67
+ long hex_code_len = RSTRING_LEN(code);
68
+
69
+ /* Check message length */
70
+ if (hex_code_len < DUALCONE_MIN_HEX_LEN) {
71
+ rb_dualcone_cleanup(&ctx);
72
+ rb_raise(rb_eFatal, "unable to run code: too short (got %ld chars, expected at least %d chars)", hex_code_len, DUALCONE_MIN_HEX_LEN);
73
+ }
74
+
75
+ /* Allocate memory for ciphertext */
76
+ ctx.ciphertext_len = hex_code_len / 2;
77
+ ctx.ciphertext = calloc(1, ctx.ciphertext_len);
78
+ if (RB_UNLIKELY(ctx.ciphertext == NULL)) {
79
+ errno_sv = errno;
80
+ rb_dualcone_cleanup(&ctx);
81
+ rb_raise(rb_eFatal, "unable to allocate memory for ciphertext: %s", strerror(errno_sv));
82
+ }
83
+
84
+ /* Convert code (hex encoded) to binary */
85
+ result = hydro_hex2bin(ctx.ciphertext, ctx.ciphertext_len, hex_code, hex_code_len, NULL, NULL);
86
+ if (result == -1) {
87
+ errno_sv = errno;
88
+ rb_dualcone_cleanup(&ctx);
89
+ rb_raise(rb_eFatal, "unable to hex-decode ruby code: %s", strerror(errno_sv));
90
+ }
91
+
92
+ /* Allocate memory for plaintext */
93
+ ctx.plaintext_len = ctx.ciphertext_len - hydro_secretbox_HEADERBYTES + 1; // Null byte
94
+ ctx.plaintext = calloc(1, ctx.plaintext_len);
95
+ if (RB_UNLIKELY(ctx.plaintext == NULL)) {
96
+ errno_sv = errno;
97
+ rb_dualcone_cleanup(&ctx);
98
+ rb_raise(rb_eFatal, "unable to allocate memory for plaintext: %s", strerror(errno_sv));
99
+ }
100
+
101
+ /* Decrypt binary code to plaintext code */
102
+ result = hydro_secretbox_decrypt(ctx.plaintext, ctx.ciphertext, ctx.ciphertext_len, 0, DUALCONE_CONTEXT, ctx.binary_key);
103
+ if (result == -1) {
104
+ rb_dualcone_cleanup(&ctx);
105
+ rb_raise(rb_eFatal, "unable to decrypt ruby code");
106
+ }
107
+
108
+ /* Evaluate the plaintext code */
109
+ rb_eval_string_protect(ctx.plaintext, &result);
110
+ if (result != 0) {
111
+ rb_dualcone_cleanup(&ctx);
112
+ rb_raise(rb_eFatal, "unable to evaluate ruby code");
113
+ }
114
+
115
+ /* Done */
116
+ rb_dualcone_cleanup(&ctx);
117
+ return Qnil;
118
+ }
119
+
120
+ VALUE rb_dualcone_generate_key(VALUE _self) {
121
+ uint8_t key[hydro_secretbox_KEYBYTES];
122
+ char hex[hydro_secretbox_KEYBYTES * 2 + 1];
123
+
124
+ /* Generate key */
125
+ hydro_secretbox_keygen(key);
126
+ char *retval = hydro_bin2hex(hex, hydro_secretbox_KEYBYTES * 2 + 1, key, hydro_secretbox_KEYBYTES);
127
+ if (retval == NULL) {
128
+ rb_raise(rb_eFatal, "unable to generate key");
129
+ }
130
+
131
+ return rb_str_new_cstr(hex);
132
+ }
133
+
134
+ VALUE rb_dualcone_encrypt(VALUE _self, VALUE path) {
135
+ int result = 0;
136
+ int errno_sv = 0;
137
+
138
+ /* Check args */
139
+ rb_check_type(path, T_STRING);
140
+
141
+ /* Dualcone private memory allocations */
142
+ DualconeContext ctx = {0};
143
+ rb_dualcone_get_key(&ctx);
144
+
145
+ /* The path of the input file (ruby code, plaintext) */
146
+ /* This value is modified by dirname(), so strdup() is necessary */
147
+ ctx.input_path = strdup(StringValuePtr(path));
148
+ if (RB_UNLIKELY(ctx.input_path == NULL)) {
149
+ errno_sv = errno;
150
+ rb_dualcone_cleanup(&ctx);
151
+ rb_raise(rb_eFatal, "unable to allocate memory for input path: %s", strerror(errno_sv));
152
+ }
153
+
154
+ /* Check if input file is readable */
155
+ int plaintext_fd = open(ctx.input_path, O_RDONLY);
156
+ if (plaintext_fd == -1) {
157
+ errno_sv = errno;
158
+ rb_dualcone_cleanup(&ctx);
159
+ rb_raise(rb_eFatal, "unable to read input file '%s': %s", ctx.input_path, strerror(errno_sv));
160
+ }
161
+
162
+ /* Allocate memory for temporary path template */
163
+ ctx.output_path = calloc(1, PATH_MAX);
164
+ if (RB_UNLIKELY(ctx.output_path == NULL)) {
165
+ errno_sv = errno;
166
+ rb_dualcone_cleanup(&ctx);
167
+ rb_raise(rb_eFatal, "unable to allocate memory for output path: %s", strerror(errno_sv));
168
+ }
169
+
170
+ /* Construct path template for temporary file */
171
+ char *output_dir = dirname(ctx.input_path);
172
+ strncat(ctx.output_path, output_dir, PATH_MAX - strlen(ctx.output_path) - 1);
173
+ strncat(ctx.output_path, "/.dualcone.XXXXXX", PATH_MAX - strlen(ctx.output_path) - 1);
174
+
175
+ /* Create temporary file */
176
+ int output_fd = mkstemp(ctx.output_path);
177
+ if (RB_UNLIKELY(output_fd == -1)) {
178
+ errno_sv = errno;
179
+ rb_dualcone_cleanup(&ctx);
180
+ rb_raise(rb_eFatal, "unable to create temporary file: %s", strerror(errno_sv));
181
+ }
182
+
183
+ /* Get input file length */
184
+ struct stat plaintext_stat = {0};
185
+ result = fstat(plaintext_fd, &plaintext_stat);
186
+ if (result == -1) {
187
+ errno_sv = errno;
188
+ rb_dualcone_cleanup(&ctx);
189
+ rb_raise(rb_eFatal, "unable to determine length of input file: %s", strerror(errno_sv));
190
+ }
191
+
192
+ /* Allocate buffer for input file */
193
+ ctx.plaintext_len = plaintext_stat.st_size;
194
+ ctx.plaintext = calloc(1, ctx.plaintext_len);
195
+ if (RB_UNLIKELY(ctx.plaintext == NULL)) {
196
+ errno_sv = errno;
197
+ rb_dualcone_cleanup(&ctx);
198
+ rb_raise(rb_eFatal, "unable to allocate memory for input data: %s", strerror(errno_sv));
199
+ }
200
+
201
+ /* Read entire input file */
202
+ ssize_t read_result = read(plaintext_fd, ctx.plaintext, ctx.plaintext_len);
203
+ if (read_result == -1) {
204
+ errno_sv = errno;
205
+ rb_dualcone_cleanup(&ctx);
206
+ rb_raise(rb_eFatal, "unable to read ruby code: %s", strerror(errno_sv));
207
+ }
208
+ close(plaintext_fd);
209
+
210
+ /* Allocate memory for encryption result */
211
+ ctx.ciphertext_len = hydro_secretbox_HEADERBYTES + ctx.plaintext_len;
212
+ ctx.ciphertext = calloc(1, ctx.ciphertext_len);
213
+ if (RB_UNLIKELY(ctx.ciphertext == NULL)) {
214
+ errno_sv = errno;
215
+ rb_dualcone_cleanup(&ctx);
216
+ rb_raise(rb_eFatal, "unable to allocate memory for encryption: %s", strerror(errno_sv));
217
+ }
218
+
219
+ /* Encrypt data! */
220
+ result = hydro_secretbox_encrypt(ctx.ciphertext, ctx.plaintext, ctx.plaintext_len, 0, DUALCONE_CONTEXT, ctx.binary_key);
221
+ if (result != 0) {
222
+ rb_dualcone_cleanup(&ctx);
223
+ rb_raise(rb_eFatal, "unable to encrypt ruby code");
224
+ }
225
+
226
+ /* Allocate memory for hex-encoded encrypted data */
227
+ ctx.ciphertext_hex_len = ctx.ciphertext_len * 2 + 1;
228
+ ctx.ciphertext_hex = calloc(1, ctx.ciphertext_hex_len);
229
+ if (RB_UNLIKELY(ctx.ciphertext_hex == NULL)) {
230
+ errno_sv = errno;
231
+ rb_dualcone_cleanup(&ctx);
232
+ rb_raise(rb_eFatal, "unable to allocate memory for hex-encoding: %s", strerror(errno_sv));
233
+ }
234
+
235
+ /* Hex encode encrypted data */
236
+ hydro_bin2hex(ctx.ciphertext_hex, ctx.ciphertext_hex_len, ctx.ciphertext, ctx.ciphertext_len);
237
+
238
+ /* Write preamble */
239
+ ssize_t write_result = 0;
240
+ write_result = write(output_fd, DUALCONE_PREAMBLE, sizeof(DUALCONE_PREAMBLE) - 1);
241
+ if (write_result == -1) {
242
+ errno_sv = errno;
243
+ rb_dualcone_cleanup(&ctx);
244
+ rb_raise(rb_eFatal, "unable to write to temporary file: %s", strerror(errno_sv));
245
+ }
246
+
247
+ /* Write hex-encoded data */
248
+ write_result = write(output_fd, ctx.ciphertext_hex, ctx.ciphertext_hex_len - 1);
249
+ if (write_result == -1) {
250
+ errno_sv = errno;
251
+ rb_dualcone_cleanup(&ctx);
252
+ rb_raise(rb_eFatal, "unable to write to temporary file: %s", strerror(errno_sv));
253
+ }
254
+
255
+ /* Write postamble */
256
+ write_result = write(output_fd, DUALCONE_POSTAMBLE, sizeof(DUALCONE_POSTAMBLE) - 1);
257
+ if (write_result == -1) {
258
+ errno_sv = errno;
259
+ rb_dualcone_cleanup(&ctx);
260
+ rb_raise(rb_eFatal, "unable to write to temporary file: %s", strerror(errno_sv));
261
+ }
262
+
263
+ /* Rename tempfile over original */
264
+ close(output_fd);
265
+ char *plaintext_path = StringValuePtr(path);
266
+ result = rename(ctx.output_path, plaintext_path);
267
+ if (result == -1) {
268
+ errno_sv = errno;
269
+ rb_dualcone_cleanup(&ctx);
270
+ rb_raise(rb_eFatal, "unable to rename temporary file: %s", strerror(errno_sv));
271
+ }
272
+
273
+ /* Done */
274
+ rb_dualcone_cleanup(&ctx);
275
+ return Qnil;
276
+ }
277
+
278
+ void Init_dualcone(void) {
279
+ if (RB_UNLIKELY(hydro_init() != 0)) {
280
+ rb_raise(rb_eFatal, "unable to initialize libhydrogen");
281
+ return;
282
+ }
283
+
284
+ rb_mDualcone = rb_define_module("Dualcone");
285
+ rb_define_module_function(rb_mDualcone, "encrypt", rb_dualcone_encrypt, 1);
286
+ rb_define_module_function(rb_mDualcone, "generate_key", rb_dualcone_generate_key, 0);
287
+ rb_define_module_function(rb_mDualcone, "run", rb_dualcone_run, 1);
288
+ }
@@ -0,0 +1,49 @@
1
+ #ifndef __DUALCONE_H
2
+ #define __DUALCONE_H
3
+
4
+ #include <stdio.h>
5
+ #include <errno.h>
6
+ #include <limits.h>
7
+ #include <libgen.h>
8
+ #include <sys/types.h>
9
+ #include <sys/stat.h>
10
+ #include <fcntl.h>
11
+ #include <unistd.h>
12
+
13
+ #include <ruby.h>
14
+ #include <hydrogen.h>
15
+
16
+ #define DUALCONE_CONTEXT "DUALCONE"
17
+ #define DUALCONE_HEX_KEY "DUALCONE_HEX_KEY"
18
+ #define DUALCONE_MIN_HEX_LEN hydro_secretbox_HEADERBYTES * 2
19
+ #define DUALCONE_PREAMBLE "require 'dualcone'\nDualcone.run('"
20
+ #define DUALCONE_POSTAMBLE "')\n"
21
+
22
+ #ifndef PATH_MAX
23
+ #define PATH_MAX 4096
24
+ #endif
25
+
26
+ typedef struct {
27
+ /* Symmetric key */
28
+ uint8_t binary_key[hydro_secretbox_KEYBYTES];
29
+
30
+ /* Input file path */
31
+ char *input_path;
32
+
33
+ /* Temporary output file path */
34
+ char *output_path;
35
+
36
+ /* Encrypted code (hex-encoded) */
37
+ char *ciphertext_hex;
38
+ size_t ciphertext_hex_len;
39
+
40
+ /* Encrypted code (binary) */
41
+ uint8_t *ciphertext;
42
+ size_t ciphertext_len;
43
+
44
+ /* Plaintext ruby code */
45
+ char *plaintext;
46
+ size_t plaintext_len;
47
+ } DualconeContext;
48
+
49
+ #endif /* __DUALCONE_H */