zindosteg 1.0.0

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