git.fiddlerwoaroof.com
Browse code

In-progress OTP code

Ed Langley authored on 26/05/2017 19:10:44
Showing 8 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,11 @@
1
+root = true
2
+
3
+[*]
4
+charset = utf-8
5
+end_of_line = lf
6
+insert_final_newline = true
7
+trim_trailing_whitespace = true
8
+
9
+[*.{cc,h}]
10
+indent_style = space
11
+indent_size = 4
... ...
@@ -1,10 +1,10 @@
1
-CXXFLAGS += -fPIC -fno-stack-protector -std=c++11
2
-CFLAGS += -fPIC -fno-stack-protector
3
-LDFLAGS = -lpam
1
+CXXFLAGS += -fPIC -fno-stack-protector -std=c++14  -g
2
+CFLAGS += -fPIC -fno-stack-protector -g
3
+LDFLAGS = -lcryptopp -lpam
4 4
 
5 5
 INTEGRATION_OBJS = sys_syslog.o sys_fstream.o sys_unistd.o sys_pwd.o sys_pam.o \
6 6
                    sys_stdlib.o sys_time.o
7
-OBJS = dual_control.o request.o validator.o conversation.o user.o token.o logger.o session.o installer.o system.o
7
+OBJS = dual_control.o request.o validator.o conversation.o user.o token.o logger.o session.o installer.o system.o generator.o
8 8
 TEST_SOURCES := $(wildcard *_test.cc)
9 9
 TESTS := $(patsubst %.cc,%.out,$(TEST_SOURCES))
10 10
 TESTRUNS := $(patsubst %.out,RUN_%,$(TESTS))
... ...
@@ -46,7 +46,7 @@ distclean: clean
46 46
 -include .depend
47 47
 
48 48
 %_test.out: %_test.o $(OBJS)
49
-	$(CXX) $(CXXFLAGS) $(CPPFLAGS)  -o $@ $^
49
+	$(CXX) $(CXXFLAGS) $(CPPFLAGS) -o $@ $^ -lcryptopp
50 50
 
51 51
 RUN_%: %.out
52 52
 	@echo running $<
... ...
@@ -70,5 +70,3 @@ endif
70 70
 .PHONY: format
71 71
 format:
72 72
 	@./format.sh *.cc *.h
73
-
74
-
... ...
@@ -16,20 +16,22 @@
16 16
 
17 17
 #include "conversation.h"
18 18
 
19
-
20 19
 namespace
21 20
 {
22 21
 class impl : public conversation_ifc
23 22
 {
24 23
 private:
25 24
     pam pam_;
26
-    pam_conv_result conversation(const std::string &msg, int msg_style, const pam_request &request) {
25
+    pam_conv_result conversation (const std::string &msg, int msg_style,
26
+                                  const pam_request &request)
27
+    {
27 28
         const pam_conv *conv;
28 29
         int get_conv_result = pam_.get_conv (request.handle(), &conv);
30
+
29 31
         if (get_conv_result != PAM_SUCCESS) {
30 32
             return pam_conv_result {get_conv_result, PAM_CONV_ERR, std::vector<pam_response *>()};
31 33
         }
32
-        
34
+
33 35
         pam_message message;
34 36
         message.msg = const_cast<char *> (msg.c_str());
35 37
         message.msg_style = msg_style;
... ...
@@ -39,8 +41,8 @@ private:
39 41
         std::vector<pam_response *> responses (1);
40 42
 
41 43
         int conv_result = conv->conv (1, messages.data(),
42
-                                             responses.data(),
43
-                                             conv->appdata_ptr);
44
+                                      responses.data(),
45
+                                      conv->appdata_ptr);
44 46
 
45 47
         return pam_conv_result {get_conv_result, conv_result, responses};
46 48
     }
... ...
@@ -51,12 +53,16 @@ public:
51 53
     {
52 54
         conversation_result result;
53 55
 
54
-        pam_conv_result token_result (conversation("Dual control token: ", PAM_PROMPT_ECHO_OFF, request));
56
+        pam_conv_result token_result (conversation ("Dual control token: ",
57
+                                      PAM_PROMPT_ECHO_OFF, request));
58
+
55 59
         if (token_result.get_conv_result != PAM_SUCCESS) {
56 60
             return result;
57 61
         }
58 62
 
59
-        pam_conv_result reason_result (conversation("Reason: ", PAM_PROMPT_ECHO_ON, request));
63
+        pam_conv_result reason_result (conversation ("Reason: ", PAM_PROMPT_ECHO_ON,
64
+                                       request));
65
+
60 66
         if (reason_result.get_conv_result != PAM_SUCCESS) {
61 67
             return result;
62 68
         }
... ...
@@ -18,6 +18,7 @@
18 18
 #include "user.h"
19 19
 #include "sys_unistd.h"
20 20
 #include "sys_pwd.h"
21
+#include "sys_time.h"
21 22
 #include "token.h"
22 23
 #include "system.h"
23 24
 #include "sys_fstream.h"
... ...
@@ -39,9 +40,14 @@ installer init_installer()
39 40
     unistd unistd (unistd::create());
40 41
     directory directory (directory::create (unistd, pwd));
41 42
     stdlib stdlib (stdlib::get());
42
-    generator generator{make_generator (stdlib)};
43
-    installer installer (installer::create (tokens, unistd,
44
-                                            directory, generator));
43
+    sys_time timer (sys_time::get());
44
+    int code_digits = 6;
45
+    std::shared_ptr<totp_generator> totp_generator =
46
+        std::make_shared<totp_generator> (timer, "\x00", code_digits);
47
+    generator generator = std::bind (&TOTPGenerator::generate_token,
48
+                                     totp_generator);
49
+    installer installer (installer::create (tokens, unistd, directory,
50
+                                            generator));
45 51
 
46 52
     return installer;
47 53
 }
48 54
new file mode 100644
... ...
@@ -0,0 +1,126 @@
1
+/* Copyright (C) CJ Affiliate
2
+ *
3
+ * You may use, distribute and modify this code under  the
4
+ * terms of the  GNU General Public License  version 2  or
5
+ * later.
6
+ *
7
+ * You should have received a copy of the license with this
8
+ * file. If not, you will find a copy in the "LICENSE" file
9
+ * at https://github.com/cjdev/dual-control.
10
+ */
11
+
12
+#include "generator.h"
13
+#include <iostream>
14
+
15
+int ipow (int base, int exp)
16
+{
17
+    int result = 1;
18
+
19
+    while (exp) {
20
+        if (exp & 1) {
21
+            result *= base;
22
+        }
23
+
24
+        exp >>= 1;
25
+        base *= base;
26
+    }
27
+
28
+    return result;
29
+}
30
+
31
+unsigned long bytesToInt (const std::string &bytes)
32
+{
33
+    unsigned long result = 0;
34
+    auto          byteCount = bytes.size() - 1;
35
+
36
+    for (auto byte = bytes.cbegin(); byte < bytes.cend(); byte++, byteCount--) {
37
+        const uint8_t val = static_cast<uint8_t> (*byte);
38
+        result |= val << (byteCount * 8);
39
+    }
40
+
41
+    return result;
42
+}
43
+
44
+time_t time_step (const time_t time, const int step)
45
+{
46
+    // Time is > 0 so division produces the result we want.
47
+    return time / step;
48
+}
49
+
50
+class impl : public token_generator_ifc
51
+{
52
+private:
53
+    const sys_time &sys_time;
54
+    unsigned int code_digits;
55
+    const std::shared_ptr<CryptoPP::SecByteBlock> key;
56
+
57
+    unsigned long truncate (const std::string &mac) const;
58
+
59
+    unsigned long hotp (const CryptoPP::SecByteBlock &key,
60
+                        const CryptoPP::Integer &counter) const;
61
+
62
+    // TODO: move elsewhere
63
+    CryptoPP::SecByteBlock generate_key (unsigned int size) const;
64
+
65
+    unsigned long totp_generator::truncate (const std::string &mac) const
66
+    {
67
+        uint8_t offset = static_cast<uint8_t > (mac[19]) & static_cast<uint8_t>
68
+                         (0x0f);
69
+        std::string  offsetBytes = mac.substr (offset, 4);
70
+        return bytesToInt (offsetBytes) & 0x7fffffff;
71
+    }
72
+
73
+    unsigned long totp_generator::hotp (const CryptoPP::SecByteBlock &key,
74
+                                        const CryptoPP::Integer &counter) const
75
+    {
76
+        std::string mac;
77
+
78
+        CryptoPP::SecByteBlock counter_bytes (8);
79
+        // Do I know that 8 is sufficient here? . . .
80
+        counter.Encode (counter_bytes.BytePtr(), 8, CryptoPP::Integer::UNSIGNED);
81
+
82
+        CryptoPP::HMAC<CryptoPP::SHA1> hmac (key, key.size());
83
+
84
+        CryptoPP::StringSink  *stringSink = new CryptoPP::StringSink (mac);
85
+        CryptoPP::HashFilter  *hashFilter = new CryptoPP::HashFilter (hmac,
86
+                stringSink);
87
+        CryptoPP::StringSource ss2 (counter_bytes, counter_bytes.size(), true,
88
+                                    hashFilter);
89
+
90
+        unsigned long result = truncate (mac);
91
+
92
+        result = result % ipow (10, code_digits);
93
+        return result;
94
+    }
95
+
96
+    CryptoPP::SecByteBlock totp_generator::generate_key (unsigned int size)
97
+    const
98
+    {
99
+        CryptoPP::AutoSeededRandomPool prng;
100
+
101
+        CryptoPP::SecByteBlock key (size);
102
+        prng.GenerateBlock (key, key.size());
103
+        return key;
104
+    }
105
+
106
+public:
107
+    std::string generate_token () const override
108
+    {
109
+        time_t foo = 111;
110
+        const CryptoPP::Integer &time = sys_time.time (&foo);
111
+        std::cout << "At the tone, the time is: " << foo << " or " << time << "\a"
112
+                  << std::endl;
113
+        int time_step_size = 30;
114
+        CryptoPP::Integer current_step = time_step (time.ConvertToLong(),
115
+                                         time_step_size);
116
+        long otp = hotp (*key, current_step);
117
+        std::ostringstream is;
118
+        is << std::setfill ('0') << std::setw (6)<< otp;
119
+        return is.str();
120
+    }
121
+};
122
+
123
+// Generator goes here....
124
+std::string totp_generator::generate_token () const
125
+;
126
+
... ...
@@ -9,6 +9,7 @@
9 9
  * at https://github.com/cjdev/dual-control.
10 10
  */
11 11
 
12
+#pragma once
12 13
 #ifndef GENERATOR_H_
13 14
 #define GENERATOR_H_
14 15
 
... ...
@@ -17,25 +18,39 @@
17 18
 #include <sstream>
18 19
 #include <iomanip>
19 20
 #include <cmath>
21
+#include <ctime>
22
+
23
+#include <cryptopp/base32.h>
24
+#include <cryptopp/hex.h>
25
+#include <cryptopp/hmac.h>
26
+#include <cryptopp/osrng.h>
20 27
 
21 28
 #include "sys_stdlib.h"
29
+#include "sys_time.h"
22 30
 
23 31
 using generator = std::function<std::string()>;
32
+int ipow (int base, int exp);
33
+time_t time_step (const time_t time, const int step);
24 34
 
25
-inline    std::string token_from_int (int x)
35
+class token_generator_ifc
26 36
 {
27
-    int v = std::abs (x % 1000000);
28
-    std::ostringstream is;
29
-    is << std::setfill ('0') << std::setw (6)<< v;
30
-    return is.str();
31
-}
37
+public:
38
+    virtual std::string generate_token () const;
39
+};
32 40
 
33
-inline generator make_generator (const stdlib &stdlib)
41
+class totp_generator : public token_generator
34 42
 {
35
-    return [stdlib] {
36
-        return token_from_int (stdlib.rand());
37
-    };
38
-}
43
+public:
44
+    totp_generator (const class sys_time &sys_time,
45
+                    const std::string &key_c,
46
+                    const int code_digits) :
47
+        sys_time (sys_time), code_digits (code_digits),
48
+        key (std::make_shared<CryptoPP::SecByteBlock> (CryptoPP::SecByteBlock (
49
+                    reinterpret_cast<const unsigned char *> (key_c.c_str()), key_c.size())))
50
+    {};
51
+
52
+    std::string generate_token () const;
53
+};
39 54
 
40 55
 #endif
41 56
 
... ...
@@ -13,9 +13,12 @@
13 13
 #include <initializer_list>
14 14
 #include <vector>
15 15
 #include <climits>
16
+#include <ctime>
17
+#include <iostream>
16 18
 
17 19
 #include "generator.h"
18 20
 #include "sys_stdlib.h"
21
+#include "sys_time.h"
19 22
 #include "test_util.h"
20 23
 
21 24
 class fake_stdlib : public stdlib_ifc
... ...
@@ -40,16 +43,46 @@ public:
40 43
     }
41 44
 };
42 45
 
46
+class fake_time : public sys_time_ifc
47
+{
48
+
49
+private:
50
+    std::vector<time_t> samples_;
51
+    mutable std::vector<time_t>::iterator current_;
52
+public:
53
+    fake_time ( const std::initializer_list<time_t> &samples)
54
+        : samples_ (samples.begin(), samples.end()),
55
+          current_ (samples_.begin()) {}
56
+    time_t time (time_t *time_ptr) const override
57
+    {
58
+        if (current_ != samples_.end()) {
59
+            auto rval = *current_;
60
+
61
+            if (time_ptr != nullptr) {
62
+                *time_ptr = rval;
63
+            }
64
+
65
+            current_ += 1;
66
+            return rval;
67
+        }
68
+
69
+        return 0;
70
+    }
71
+};
72
+
43 73
 int six_digits()
44 74
 {
45 75
     // given
46
-    std::initializer_list<int> samples { 1 };
47
-    auto test_stdlib = std::make_shared<fake_stdlib> (samples);
48
-    stdlib stdlib (test_stdlib);
49
-    generator generator = make_generator (stdlib);
76
+    std::initializer_list<time_t> samples { 1 };
77
+    auto test_stdtime = std::make_shared<fake_time> (samples);
78
+
79
+    sys_time stdtime (test_stdtime);
80
+    // Fake the Key
81
+    std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`";
82
+    auto generator = totp_generator (stdtime, key, 6);
50 83
 
51 84
     // when
52
-    auto actual = generator();
85
+    auto actual = generator.generate_token();
53 86
 
54 87
     // then
55 88
     check (actual.size() == 6, "size is wrong");
... ...
@@ -62,30 +95,36 @@ int six_digits()
62 95
 int modulated_source_modulates_tokens()
63 96
 {
64 97
     // given
65
-    std::initializer_list<int> samples { 1, 2, 3 };
66
-    auto test_stdlib = std::make_shared<fake_stdlib> (samples);
67
-    stdlib stdlib (test_stdlib);
68
-    generator generator = make_generator (stdlib);
98
+    std::initializer_list<time_t> samples { 1, 31 };
99
+    auto test_stdtime = std::make_shared<fake_time> (samples);
100
+
101
+    sys_time stdtime (test_stdtime);
102
+    // Fake the Key
103
+    std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`";
104
+    auto generator = totp_generator (stdtime, key, 6);
69 105
 
70 106
     // when
71
-    auto actual1 = generator();
72
-    auto actual2 = generator();
107
+    auto actual1 = generator.generate_token();
108
+    auto actual2 = generator.generate_token();
73 109
 
74 110
     // then
75
-    check (actual1 != actual2, "samples should be different");
111
+    check (actual1 != actual2, "tokens should be different");
76 112
     succeed();
77 113
 }
78 114
 
79 115
 int int_max()
80 116
 {
81 117
     // given
82
-    std::initializer_list<int> samples { INT_MAX };
83
-    auto test_stdlib = std::make_shared<fake_stdlib> (samples);
84
-    stdlib stdlib (test_stdlib);
85
-    generator generator = make_generator (stdlib);
118
+    std::initializer_list<time_t> samples { INT_MAX };
119
+    auto test_stdtime = std::make_shared<fake_time> (samples);
120
+
121
+    sys_time stdtime (test_stdtime);
122
+    // Fake the Key
123
+    std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`";
124
+    auto generator = totp_generator (stdtime, key, 6);
86 125
 
87 126
     // when
88
-    auto actual = generator();
127
+    auto actual = generator.generate_token();
89 128
 
90 129
     // then
91 130
     check (actual.size() == 6, "size is wrong");
... ...
@@ -98,13 +137,16 @@ int int_max()
98 137
 int int_min()
99 138
 {
100 139
     // given
101
-    std::initializer_list<int> samples { INT_MIN };
102
-    auto test_stdlib = std::make_shared<fake_stdlib> (samples);
103
-    stdlib stdlib (test_stdlib);
104
-    generator generator = make_generator (stdlib);
140
+    std::initializer_list<time_t> samples { INT_MIN };
141
+    auto test_stdtime = std::make_shared<fake_time> (samples);
142
+
143
+    sys_time stdtime (test_stdtime);
144
+    // Fake the Key
145
+    std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`";
146
+    auto generator = totp_generator (stdtime, key, 6);
105 147
 
106 148
     // when
107
-    auto actual = generator();
149
+    auto actual = generator.generate_token();
108 150
 
109 151
     // then
110 152
     check (actual.size() == 6, "size is wrong");
... ...
@@ -114,12 +156,39 @@ int int_min()
114 156
     succeed();
115 157
 }
116 158
 
159
+// totp test
160
+int int_precomputed()
161
+{
162
+    // given
163
+    // The token for key 76I6WTYEUTNCJUREMGKVM45PMA and time '2017/01/01 00:00:00' is 258675
164
+    time_t theTime = 1483257600;
165
+    /// TODO: int -> time_t
166
+    std::initializer_list<time_t> samples { theTime }; //
167
+    auto test_stdtime = std::make_shared<fake_time> (samples);
168
+
169
+    sys_time stdtime (test_stdtime);
170
+    // Fake the Key
171
+    std::string key = "\xff\x91\xebO\x04\xa4\xda$\xd2$a\x95Vs\xaf`";
172
+    auto generator = totp_generator (stdtime, key, 6);
173
+    std::string expected = "258675";
174
+
175
+    // when
176
+    auto actual = generator.generate_token();
177
+
178
+    // then
179
+    check (actual.size() == 6, "size is wrong");
180
+    check (actual == expected,
181
+           "precomputed value failed to match"); // TODO: Does == work for std::string like I want it to?
182
+    succeed();
183
+}
184
+
117 185
 int run_tests()
118 186
 {
119 187
     test (six_digits);
120 188
     test (modulated_source_modulates_tokens);
121 189
     test (int_max);
122 190
     test (int_min);
191
+    test (int_precomputed);
123 192
     succeed();
124 193
 }
125 194
 
... ...
@@ -127,4 +196,3 @@ int main (int argc, char *argv[])
127 196
 {
128 197
     return !run_tests();
129 198
 }
130
-
... ...
@@ -16,7 +16,7 @@
16 16
 #include <cstdio>
17 17
 #define check(assertion, msg) \
18 18
     if (!(assertion)) { \
19
-      fprintf(stderr, "assertion failed: %s\n", msg); \
19
+      fprintf(stderr, "> assertion failed: %s\n", msg); \
20 20
       return 0; \
21 21
     }
22 22
 
... ...
@@ -32,11 +32,13 @@
32 32
 
33 33
 #define test(NAME) \
34 34
     { \
35
-      int result = NAME (); \
36
-      if (!result) { \
37
-          fprintf(stderr, "test failed: %s\n", #NAME); \
38
-          return 0; \
39
-      } \
35
+        int result = NAME (); \
36
+        if (!result) { \
37
+            fprintf(stderr, "> test failed: %s\n", #NAME); \
38
+            return 0; \
39
+        } else { \
40
+            fprintf (stderr, "> test passed: %s\n", #NAME); \
41
+        } \
40 42
     }
41 43
 
42 44
 #define succeed() return 1