· 5 years ago · Nov 23, 2019, 08:52 AM
1//
2// Created by omerh on 22/11/2019.
3//
4
5#include <gtest/gtest.h>
6#include <utils/random.h>
7#include <model/hash.h>
8#include <breaks/sha1.h>
9
10class set_4_5 : public testing::Test
11{
12public:
13
14 struct protocol_message
15 {
16 static const size_t signature_length = model::sha1::hash_size;
17
18 unsigned char signature[signature_length];
19 unsigned char message[];
20 };
21
22 set_4_5() : _secret_key(std::move(utils::random::instance().buffer<16>()))
23 {}
24
25 size_t sign(const unsigned char message[], size_t message_length, unsigned char buffer[], size_t buffer_length)
26 {
27 if (buffer_length < model::sha1::hash_size)
28 {
29 throw std::length_error("Not enough space in the buffer provided");
30 }
31
32 auto msg = (protocol_message *) buffer;
33
34 model::sha1 hash;
35
36 hash.update(_secret_key.data(), _secret_key.size());
37 hash.update(message, message_length);
38
39 hash.digest(msg->signature, sizeof(msg->signature));
40 std::copy_n(message, message_length, msg->message);
41
42 return protocol_message::signature_length + message_length;
43 }
44
45 bool validate(const unsigned char signed_message[], size_t length)
46 {
47 if (length < model::sha1::hash_size)
48 {
49 throw std::length_error("Not enough space in the buffer provided");
50 }
51
52 auto msg = (protocol_message *) signed_message;
53
54 unsigned char result[model::sha1::hash_size];
55
56 model::sha1 hash;
57
58 hash.update(_secret_key.data(), _secret_key.size());
59 hash.update(msg->message, length - protocol_message::signature_length);
60
61 hash.digest(result, sizeof(result));
62
63 return std::equal(result, result + sizeof(result), msg->signature);
64 }
65
66 union sha1_result
67 {
68 unsigned char raw[model::sha1::hash_size];
69 uint32_t h[model::sha1::hash_size / sizeof(uint32_t)];
70 };
71
72 struct sha1_intenral_state
73 {
74 uint64_t _message_length;
75 size_t _buffer_index;
76 unsigned char _buffer[64];
77 sha1_result h;
78 };
79
80 std::array<unsigned char, 16> _secret_key;
81};
82
83TEST_F(set_4_5, run)
84{
85 std::cout << "-----------" << std::endl;
86
87 unsigned char buffer[100];
88 const char *msg1 = "yyyy";
89 const size_t msg1_length = strlen(msg1);
90
91 auto l = sign((const unsigned char *) msg1, msg1_length, buffer, sizeof(buffer));
92
93 ASSERT_EQ(l, model::sha1::hash_size + msg1_length);
94
95 ASSERT_TRUE(validate(buffer, l));
96
97 /**
98 * Now the break will generate a new message and without knowing the key used to calculate the signature with
99 * we will create a message that will be validated correctly:
100 *
101 * S1 = sha1(key, m1, (padding1))
102 * M1 = (S1, m1)
103 *
104 * Now lets create a malicious message M2:
105 *
106 * S2 = sha1[initialized with S1](m1, padding1, m2, (padding2))
107 * M2 = (S2, m1, padding1, m2)
108 *
109 * because when calculating sha we eventually just return the internal state, the following principle is valid:
110 *
111 * (ignoring padding for simplicity)
112 * S1 = sha1(x)
113 * S2 = sha1[initialized with S1](y) = sha1(x, y)
114 */
115 const char *msg2 = "xxxx";
116 const size_t msg2_length = strlen(msg2);
117 const size_t key_size = 16;
118
119 unsigned char new_buffer[100] = {0};
120 unsigned char *data = new_buffer + 20;
121
122 //
123 // The hash object extracted contains the hash of [(secret, msg1, pad1), len: secret+msg]
124 // That means, in order to sign a new message, we need to
125 // 1) create the message msg1, pad1, msg2
126 // 2) update with the value msg2
127 //
128 // The hash object of the hash already digested the key, the msg1 and the padding; the exact calculation
129 // will be provided in the next comments, but eventually we get that it has digested 64 bytes.
130 //
131 auto hash = breaks::sha1::generate_sha1_from_result(buffer, model::sha1::hash_size, 64);
132
133 hash.update((unsigned char*)msg2, msg2_length);
134 //
135 // After the update and the digest, the hash digested:
136 // PREVIOUSLY ATTACK
137 // [key, msg1, padding1] + [msg2, padding2]
138 //
139 // With:
140 // padding1 for [key, msg1](len: 64 - (16 + 4) bytes)
141 // padding2 for [key, msg1, padding1, msg2](len: 64 - (4) bytes, total length of 4 + 16 + (64-4-16) + 4 = 64 + 4 = 68)
142 //
143 hash.digest(new_buffer, 20);
144
145 //
146 // For building the message, lets first calculate some lengths:
147 // pad_length + len(key) + len(msg1) = 64 ->
148 // pad_length + 16 + 4 = 64 ->
149 // pad_length = 48
150 //
151 // So, if the hash was calculated like this:
152 // hash(key | msg1 | pad)
153 //
154 // And the new hash will be calculated like this
155 // hash(key | M | msg2)
156 //
157 // That means that for this attack to work we will need that:
158 // M = msg1 | pad ->
159 // len(M) = len(msg1) + pad_length ->
160 // len(M) = 4 + 48
161 //
162
163 std::copy_n(msg1, msg1_length, data);
164
165 //
166 // Add the padding as if the has was updated with (key, msg1)
167 //
168 breaks::sha1::complete_padding(data - key_size, msg1_length + key_size, msg1_length + key_size);
169
170 //
171 // The padding must have added 44 bytes (chunk - key - msg1 -> 64-16-4=44). msg2 need to be written in +(44+4)
172 //
173 std::copy_n(msg2, msg2_length, data + 48);
174
175 ASSERT_TRUE(validate(new_buffer, 20 + 48 + 4));
176}