zindosteg 1.0.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,704 @@
1
+ #include <rice/rice.hpp>
2
+ #include <rice/stl.hpp>
3
+ #include "device.h"
4
+ #include "hmac.h"
5
+ #include "key_generator.h"
6
+ #include "aes.h"
7
+ #include <memory>
8
+
9
+ using namespace Rice;
10
+ using namespace zindorsky;
11
+ using namespace std::string_literals;
12
+
13
+ namespace {
14
+ class rubyError : public std::runtime_error {
15
+ public:
16
+ rubyError(const char* msg = "") : std::runtime_error(msg) {}
17
+ rubyError(std::string const& msg) : std::runtime_error(msg.c_str()) {}
18
+ virtual VALUE error_type() const = 0;
19
+ };
20
+
21
+ class ioError : public rubyError {
22
+ public:
23
+ using rubyError::rubyError;
24
+ VALUE error_type() const override { return rb_eIOError; }
25
+ };
26
+
27
+ class eofError : public rubyError {
28
+ public:
29
+ eofError() : rubyError("EOF") {}
30
+ VALUE error_type() const override { return rb_eEOFError; }
31
+ };
32
+
33
+ class argumentError : public rubyError {
34
+ public:
35
+ using rubyError::rubyError;
36
+ VALUE error_type() const override { return rb_eArgError; }
37
+ };
38
+
39
+ [[noreturn]] void handle_ruby_error(rubyError const& e)
40
+ {
41
+ throw Exception(e.error_type(), e.what());
42
+ }
43
+
44
+ //Class representing file open mode
45
+ struct mode {
46
+ //Mode flags:
47
+ bool create = false, read = true, write = false, append = false, binary = false;
48
+
49
+ mode() = default;
50
+
51
+ mode(std::string mode_str)
52
+ {
53
+ std::string m{std::move(mode_str)};
54
+
55
+ if (m.empty()) {
56
+ m = "r";
57
+ }
58
+ if (m.back() == 'b') {
59
+ m = m.substr(0, m.size() - 1);
60
+ binary = true;
61
+ } else if (m.back() == 't') {
62
+ m = m.substr(0, m.size() - 1);
63
+ }
64
+
65
+ if (m == "r") {
66
+ create = false;
67
+ read = true;
68
+ write = false;
69
+ } else if (m == "r+") {
70
+ create = false;
71
+ read = true;
72
+ write = true;
73
+ } else if (m == "w") {
74
+ create = true;
75
+ read = false;
76
+ write = true;
77
+ } else if (m == "w+") {
78
+ create = true;
79
+ read = true;
80
+ write = true;
81
+ } else if (m == "a") {
82
+ create = false;
83
+ read = false;
84
+ write = true;
85
+ append = true;
86
+ } else if (m == "a+") {
87
+ create = false;
88
+ read = true;
89
+ write = true;
90
+ append = true;
91
+ } else {
92
+ throw ioError("invalid mode: "s + m);
93
+ }
94
+ }
95
+
96
+ std::string to_string() const
97
+ {
98
+ std::string m;
99
+ if (append) {
100
+ m = read ? "a+" : "a";
101
+ } else {
102
+ if (read && write) {
103
+ m = create ? "w+" : "r+";
104
+ } else if (read) {
105
+ m = "r";
106
+ } else {
107
+ m = "w";
108
+ }
109
+ }
110
+ if (binary) {
111
+ m += 'b';
112
+ }
113
+ return m;
114
+ }
115
+ };
116
+
117
+ struct key_cstr_helper {
118
+ explicit key_cstr_helper(zindorsky::crypto::key_generator const& generator) { generator.generate(data,sizeof(data)); }
119
+ byte data[32+AES_BLOCK_SIZE];
120
+ };
121
+
122
+ class device_interface {
123
+ public:
124
+ device_interface(std::string const& carrier_file, std::string const& password, mode const& mode = "r"s)
125
+ : device_interface{steganography::device_t{filesystem::path{carrier_file}, password, !mode.create, !mode.append}, password, mode}
126
+ {
127
+ if (!mode_.create) {
128
+ //Check hmac to make sure password is correct, payload hasn't been tampered with, etc.
129
+ if (sz_ >= crypto::hmac::digest_sz) {
130
+ //We don't include the HMAC in the logical size.
131
+ sz_ -= crypto::hmac::digest_sz;
132
+ //Calculate HMAC
133
+ unsigned char calculated_hmac[crypto::hmac::digest_sz], stored_hmac[crypto::hmac::digest_sz];
134
+ compute_hmac(calculated_hmac);
135
+ //Read stored HMAC after payload.
136
+ if (crypto::hmac::digest_sz != device_.read(reinterpret_cast<char *>(stored_hmac), sizeof(stored_hmac))) {
137
+ throw crypto::hmac_verification_failure{};
138
+ }
139
+ encryptor_.crypt(stored_hmac, stored_hmac, sizeof(stored_hmac));
140
+ //Compare
141
+ if (0 == memcmp(stored_hmac, calculated_hmac, sizeof(stored_hmac))) {
142
+ //Seek back to beginning.
143
+ seek(0, mode_.append ? std::ios::end : std::ios::beg);
144
+ return;
145
+ }
146
+ }
147
+ if (mode_.append) {
148
+ //Append mode means we should create a new payload instead of failing.
149
+ device_.seek(0, std::ios::beg);
150
+ sz_ = device_.truncate();
151
+ seek(0, std::ios::end);
152
+ return;
153
+ }
154
+ throw crypto::hmac_verification_failure{};
155
+ }
156
+ }
157
+
158
+ device_interface(String carrier_file, String password, String mode_str)
159
+ : device_interface{carrier_file.str(), password.str(), mode{mode_str.str()}}
160
+ {
161
+ }
162
+
163
+ ~device_interface()
164
+ {
165
+ try {
166
+ close();
167
+ } catch(...) { }
168
+ }
169
+
170
+ //non-copyable
171
+ device_interface(device_interface const&) = delete;
172
+ device_interface & operator = (device_interface const&) = delete;
173
+
174
+ //movable
175
+ device_interface(device_interface &&) = default;
176
+ device_interface & operator = (device_interface &&) = default;
177
+
178
+ bool autoclose() const { return true; }
179
+ void enable_binmode() { mode_.binary = true; }
180
+ bool binmode() const { return mode_.binary; }
181
+ long capacity() const { return max_sz_; }
182
+
183
+ void close()
184
+ {
185
+ if (closed_) {
186
+ return;
187
+ }
188
+ flush();
189
+ device_.close();
190
+ closed_ = true;
191
+ }
192
+
193
+ bool closed() const
194
+ {
195
+ return closed_;
196
+ }
197
+
198
+ void each(Object sep, Object limit)
199
+ {
200
+ rb_need_block();
201
+ while(!eof()) {
202
+ auto line = gets(sep, limit);
203
+ if (line.is_nil()) {
204
+ break;
205
+ }
206
+ rb_yield(line.value());
207
+ }
208
+ }
209
+
210
+ void each_byte()
211
+ {
212
+ rb_need_block();
213
+ while(!eof()) {
214
+ auto b = getbyte();
215
+ if (b.is_nil()) {
216
+ break;
217
+ }
218
+ rb_yield(b.value());
219
+ }
220
+ }
221
+
222
+ void each_char()
223
+ {
224
+ rb_need_block();
225
+ while(!eof()) {
226
+ auto c = getc();
227
+ if (c.is_nil()) {
228
+ break;
229
+ }
230
+ rb_yield(c.value());
231
+ }
232
+ }
233
+
234
+ bool eof() const
235
+ {
236
+ return pos_ >= sz_;
237
+ }
238
+
239
+ void flush()
240
+ {
241
+ check_closed();
242
+ //remember where we are so we can seek back after updating the HMAC:
243
+ long orig = pos_;
244
+ if (dirty_) {
245
+ //Write HMAC after the payload
246
+ unsigned char hmac[crypto::hmac::digest_sz];
247
+ compute_hmac(hmac);
248
+ encryptor_.crypt(hmac, hmac, sizeof(hmac));
249
+ device_.write(reinterpret_cast<char const *>(hmac), sizeof(hmac));
250
+ }
251
+ device_.flush();
252
+ dirty_ = false;
253
+ seek(orig);
254
+ }
255
+
256
+ Object getbyte()
257
+ {
258
+ check_closed();
259
+ check_read();
260
+
261
+ if (eof()) {
262
+ return {};
263
+ }
264
+
265
+ char c;
266
+ auto r = device_.read(&c, 1);
267
+ if (r <= 0) {
268
+ return {};
269
+ }
270
+ encryptor_.crypt(&c, &c, 1);
271
+ pos_++;
272
+ return detail::To_Ruby<int>().convert(static_cast<unsigned char>(c));
273
+ }
274
+
275
+ Object getc()
276
+ {
277
+ check_closed();
278
+ check_read();
279
+ if (eof()) {
280
+ return {};
281
+ }
282
+
283
+ char c;
284
+ auto r = device_.read(&c, 1);
285
+ if (r <= 0) {
286
+ return {};
287
+ }
288
+ encryptor_.crypt(&c, &c, 1);
289
+ pos_++;
290
+ return String(std::string(&c, 1));
291
+ }
292
+
293
+ Object gets(Object sep, Object limit)
294
+ {
295
+ check_closed();
296
+ check_read();
297
+ if (eof()) {
298
+ return {};
299
+ }
300
+
301
+ std::string separator{"\n"};
302
+ long lim = -1;
303
+ if (limit.is_nil()) {
304
+ if (!sep.is_nil()) {
305
+ if (sep.rb_type() == T_FIXNUM) {
306
+ lim = detail::From_Ruby<long>().convert(sep);
307
+ } else {
308
+ separator = detail::From_Ruby<std::string>().convert(sep);
309
+ }
310
+ }
311
+ } else {
312
+ separator = detail::From_Ruby<std::string>().convert(sep);
313
+ lim = detail::From_Ruby<long>().convert(limit);
314
+ }
315
+
316
+ if (lim == 0) {
317
+ return String{};
318
+ }
319
+ std::string str;
320
+ if (lim > 0) {
321
+ str.reserve(static_cast<std::string::size_type>(lim));
322
+ }
323
+ while(
324
+ !eof()
325
+ && (lim < 0 || str.size() < static_cast<std::string::size_type>(lim))
326
+ && (separator.empty() || (str.size() < separator.size() || str.substr(str.size() - separator.size()) != separator))
327
+ )
328
+ {
329
+ char c;
330
+ auto r = device_.read(&c, 1);
331
+ if (r <= 0) {
332
+ break;
333
+ }
334
+ encryptor_.crypt(&c, &c, 1);
335
+ pos_++;
336
+ str += c;
337
+ }
338
+ return detail::To_Ruby<std::string>().convert(str);
339
+ }
340
+
341
+ bool isatty() const { return false; }
342
+
343
+ String get_mode() const { return mode_.to_string(); }
344
+
345
+ long set_pos(long pos)
346
+ {
347
+ return seek(pos);
348
+ }
349
+
350
+ void putc(Object obj)
351
+ {
352
+ switch(obj.rb_type()) {
353
+ case T_FIXNUM:
354
+ {
355
+ char c[2] = {0};
356
+ c[0] = detail::From_Ruby<char>().convert(obj);
357
+ write(String(c));
358
+ }
359
+ break;
360
+ case T_STRING:
361
+ write(obj);
362
+ break;
363
+ default:
364
+ throw argumentError("Argument must be String or Numeric");
365
+ }
366
+ }
367
+
368
+ String read(Object length, Object outbuf)
369
+ {
370
+ check_closed();
371
+ check_read();
372
+
373
+
374
+ long n;
375
+ if (length.is_nil()) {
376
+ n = sz_ - pos_;
377
+ } else {
378
+ n = detail::From_Ruby<long>().convert(length);
379
+ }
380
+ if (n == 0) {
381
+ return String();
382
+ }
383
+ //Make sure pos_ is valid.
384
+ if (pos_ > sz_) {
385
+ pos_ = sz_;
386
+ }
387
+ if (pos_ < 0) {
388
+ pos_ = 0;
389
+ }
390
+ if (n < 0) {
391
+ throw ioError("negative length");
392
+ }
393
+ //don't try to read past eof
394
+ long toread = std::min(n, sz_ - pos_);
395
+
396
+ VALUE out;
397
+ if (outbuf.is_nil()) {
398
+ out = rb_str_new(nullptr, toread);
399
+ } else if (outbuf.rb_type() == T_STRING) {
400
+ out = outbuf.value();
401
+ rb_str_resize(out, toread);
402
+ } else {
403
+ throw argumentError("outbuf must be String");
404
+ }
405
+
406
+ auto ptr = StringValuePtr(out);
407
+ auto r = device_.read(ptr, toread);
408
+ encryptor_.crypt(ptr, ptr, static_cast<size_t>(r));
409
+ pos_ += static_cast<long>(r);
410
+ rb_str_resize(out, static_cast<long>(r));
411
+ return out;
412
+ }
413
+
414
+ Object readbyte()
415
+ {
416
+ auto retval = getbyte();
417
+ if (retval.is_nil()) {
418
+ throw eofError();
419
+ }
420
+ return retval;
421
+ }
422
+
423
+ Object readchar()
424
+ {
425
+ auto retval = getc();
426
+ if (retval.is_nil()) {
427
+ throw eofError();
428
+ }
429
+ return retval;
430
+ }
431
+
432
+ Object readline(Object sep, Object limit)
433
+ {
434
+ auto retval = gets(sep, limit);
435
+ if (retval.is_nil()) {
436
+ throw eofError();
437
+ }
438
+ return retval;
439
+ }
440
+
441
+ Array readlines(Object sep, Object limit)
442
+ {
443
+ check_closed();
444
+ check_read();
445
+ if (eof()) {
446
+ return Array{};
447
+ }
448
+
449
+ std::string separator{"\n"};
450
+ long lim = -1;
451
+ if (limit.is_nil()) {
452
+ if (!sep.is_nil()) {
453
+ if (sep.rb_type() == T_FIXNUM) {
454
+ lim = detail::From_Ruby<long>().convert(sep);
455
+ } else {
456
+ separator = detail::From_Ruby<std::string>().convert(sep);
457
+ }
458
+ }
459
+ } else {
460
+ separator = detail::From_Ruby<std::string>().convert(sep);
461
+ lim = detail::From_Ruby<long>().convert(limit);
462
+ }
463
+
464
+ if (lim == 0) {
465
+ return Array{};
466
+ }
467
+ std::string str;
468
+ if (lim > 0) {
469
+ str.reserve(static_cast<std::string::size_type>(lim));
470
+ }
471
+ Array arr;
472
+ while(!eof()) {
473
+ str.clear();
474
+ while(
475
+ !eof()
476
+ && (lim < 0 || str.size() < static_cast<std::string::size_type>(lim))
477
+ && (separator.empty() || (str.size() < separator.size() || str.substr(str.size() - separator.size()) != separator))
478
+ )
479
+ {
480
+ char c;
481
+ auto r = device_.read(&c, 1);
482
+ if (r <= 0) {
483
+ break;
484
+ }
485
+ encryptor_.crypt(&c, &c, 1);
486
+ pos_++;
487
+ str += c;
488
+ }
489
+ arr.push(String(detail::To_Ruby<std::string>().convert(str)));
490
+ }
491
+ return arr;
492
+ }
493
+ long rewind()
494
+ {
495
+ return seek(0);
496
+ }
497
+
498
+ long seek(long off, int way = std::ios::beg)
499
+ {
500
+ check_closed();
501
+
502
+ std::streampos newpos = device_.seek(off, static_cast<std::ios::seekdir>(way));
503
+ //No seeking past EOF. (To resize the file, use write or truncate.)
504
+ if (newpos > sz_) {
505
+ newpos = device_.seek(sz_, std::ios::beg);
506
+ }
507
+ encryptor_.seek(newpos);
508
+ pos_ = static_cast<long>(newpos);
509
+ return pos_;
510
+ }
511
+
512
+ long size() const { return sz_; }
513
+
514
+ long tell() const
515
+ {
516
+ check_closed();
517
+ return pos_;
518
+ }
519
+
520
+ void truncate()
521
+ {
522
+ truncate_size(pos_);
523
+ }
524
+
525
+ void truncate_size(long size)
526
+ {
527
+ check_closed();
528
+ check_write();
529
+
530
+ if (size == sz_) {
531
+ return;
532
+ }
533
+ if (size > max_sz_) {
534
+ size = max_sz_;
535
+ }
536
+
537
+ if (size != pos_) {
538
+ device_.seek(size, std::ios::beg);
539
+ sz_ = static_cast<long>(device_.truncate());
540
+ //Make sure pos_ doesn't point past eof:
541
+ if (pos_ > sz_) {
542
+ pos_ = sz_;
543
+ }
544
+ //Put current location back to pos_:
545
+ seek(pos_, std::ios::beg);
546
+ } else {
547
+ sz_ = static_cast<long>(device_.truncate());
548
+ }
549
+ dirty_ = true;
550
+ }
551
+
552
+ long write(String s)
553
+ {
554
+ check_closed();
555
+ check_write();
556
+ check_append();
557
+
558
+ auto len = static_cast<long>(s.length());
559
+ if (pos_ > sz_) {
560
+ pos_ = sz_;
561
+ }
562
+ if (pos_ + len > max_sz_) {
563
+ len = max_sz_ - pos_;
564
+ }
565
+
566
+ //Encrypt and write to device
567
+ char const *d = s.c_str();
568
+ long sz = len, start = pos_;
569
+ char c;
570
+ while (sz-- > 0)
571
+ {
572
+ encryptor_.crypt(d++, &c, 1);
573
+ if (device_.write(&c, 1) != 1) {
574
+ break;
575
+ }
576
+ ++pos_;
577
+ }
578
+ //Update size if we wrote past current end
579
+ if (pos_ > sz_) {
580
+ sz_ = pos_;
581
+ }
582
+ dirty_ = true;
583
+
584
+ return pos_ - start;
585
+ }
586
+
587
+ private:
588
+ //Data members
589
+ steganography::device_t device_;
590
+ long pos_, sz_, max_sz_;
591
+ crypto::aes_ctr_mode encryptor_;
592
+ crypto::hmac hmac_;
593
+ mode mode_;
594
+ bool closed_, dirty_;
595
+
596
+ //delegate constructors
597
+ device_interface( steganography::device_t && device, std::string const& password, mode const& mode )
598
+ : device_interface{std::move(device), key_cstr_helper(crypto::key_generator(password,device.salt_for_encryption())), password, mode}
599
+ {
600
+ }
601
+
602
+ device_interface( steganography::device_t && device, key_cstr_helper const& helper, std::string const& password, mode const& mode )
603
+ : device_{std::move(device)}
604
+ , pos_{0}
605
+ , sz_{static_cast<long>(device_.size())}
606
+ , max_sz_{ std::max<long>(0, static_cast<long>(device_.capacity() - crypto::hmac::digest_sz)) }
607
+ , encryptor_{helper.data, 32, helper.data+32}
608
+ , hmac_{password.c_str(), static_cast<int>(password.size())}
609
+ , mode_{mode}
610
+ , closed_{false}
611
+ , dirty_{false}
612
+ {
613
+ }
614
+
615
+ //Computes HMAC of payload. File pointer will be at EOF afterwards.
616
+ void compute_hmac(unsigned char * hmac)
617
+ {
618
+ unsigned char buff[0x1000];
619
+ seek(0);
620
+ hmac_.reset();
621
+ while(pos_ < sz_) {
622
+ size_t toread = std::min(static_cast<size_t>(sz_ - pos_), sizeof(buff));
623
+ device_.read(reinterpret_cast<char*>(buff), toread);
624
+ pos_ += static_cast<long>(toread);
625
+ encryptor_.crypt(buff, buff, toread);
626
+ hmac_.update(buff, toread);
627
+ }
628
+ hmac_.final(hmac);
629
+ }
630
+
631
+ void check_read() const
632
+ {
633
+ if (!mode_.read) {
634
+ throw ioError("File not open for reading");
635
+ }
636
+ }
637
+
638
+ void check_write() const
639
+ {
640
+ if (!mode_.write) {
641
+ throw ioError("File not open for writing");
642
+ }
643
+ }
644
+
645
+ void check_closed() const
646
+ {
647
+ if (closed_) {
648
+ throw ioError("I/O operation on closed file");
649
+ }
650
+ }
651
+
652
+ void check_append()
653
+ {
654
+ if (mode_.append) {
655
+ seek(0, std::ios::end);
656
+ }
657
+ }
658
+ };
659
+ }
660
+
661
+ extern "C" void Init_zindosteg()
662
+ {
663
+ Module rb_cModule = define_module("Zindosteg");
664
+ Data_Type<device_interface> rb_cZindosteg =
665
+ define_class_under<device_interface>(rb_cModule, "File")
666
+ .add_handler<rubyError>(handle_ruby_error)
667
+ .define_constructor(Constructor<device_interface, std::string, std::string, std::string>(), Arg("carrier"), Arg("password"), Arg("mode") = "r"s)
668
+ .define_method("<<", &device_interface::write)
669
+ .define_method("autoclose?", &device_interface::autoclose)
670
+ .define_method("binmode", &device_interface::enable_binmode)
671
+ .define_method("binmode?", &device_interface::binmode)
672
+ .define_method("capacity", &device_interface::capacity)
673
+ .define_method("closed?", &device_interface::closed)
674
+ .define_method("close", &device_interface::close)
675
+ .define_method("each", &device_interface::each, Arg("sep") = Object(), Arg("limit") = Object())
676
+ .define_method("each_byte", &device_interface::each_byte)
677
+ .define_method("each_char", &device_interface::each_char)
678
+ .define_method("each_line", &device_interface::each, Arg("sep") = Object(), Arg("limit") = Object())
679
+ .define_method("eof", &device_interface::eof)
680
+ .define_method("eof?", &device_interface::eof)
681
+ .define_method("flush", &device_interface::flush)
682
+ .define_method("getbyte", &device_interface::getbyte)
683
+ .define_method("getc", &device_interface::getc)
684
+ .define_method("gets", &device_interface::gets, Arg("sep") = Object(), Arg("limit") = Object())
685
+ .define_method("isatty", &device_interface::isatty)
686
+ .define_method("mode", &device_interface::get_mode)
687
+ .define_method("pos", &device_interface::tell)
688
+ .define_method("pos=", &device_interface::set_pos)
689
+ .define_method("print", &device_interface::write)
690
+ .define_method("putc", &device_interface::putc)
691
+ .define_method("puts", &device_interface::write)
692
+ .define_method("read", &device_interface::read, Arg("length") = Object(), Arg("outbuf") = Object())
693
+ .define_method("readbyte", &device_interface::readbyte)
694
+ .define_method("readchar", &device_interface::readchar)
695
+ .define_method("readline", &device_interface::readline, Arg("sep") = Object(), Arg("limit") = Object())
696
+ .define_method("readlines", &device_interface::readlines, Arg("sep") = Object(), Arg("limit") = Object())
697
+ .define_method("rewind", &device_interface::rewind)
698
+ .define_method("seek", &device_interface::seek, Arg("amount"), Arg("whence") = (int)std::ios::beg)
699
+ .define_method("size", &device_interface::size)
700
+ .define_method("tell", &device_interface::tell)
701
+ .define_method("tty?", &device_interface::isatty)
702
+ .define_method("write", &device_interface::write)
703
+ ;
704
+ }
@@ -0,0 +1,3 @@
1
+ module Zindosteg
2
+ VERSION = "1.0.0"
3
+ end
data/lib/zindosteg.rb ADDED
@@ -0,0 +1,18 @@
1
+ require "zindosteg/version"
2
+ require "zindosteg/zindosteg"
3
+
4
+ module Zindosteg
5
+ class File
6
+ class << self
7
+ alias_method :open, :new
8
+ end
9
+ end
10
+
11
+ def self.insert(carrier, password, payload)
12
+ ::Zindosteg::File.open(carrier, password, "w").write(::File.open(payload).read)
13
+ end
14
+
15
+ def self.extract(carrier, password, payload)
16
+ ::File.open(payload, "w").write(::Zindosteg::File.open(carrier, password).read)
17
+ end
18
+ end