Skip to content

Commit c76cac2

Browse files
committed
Kdf::derivatePassWithSymbols given the command \
1 parent 28c29bb commit c76cac2

File tree

4 files changed

+237
-7
lines changed

4 files changed

+237
-7
lines changed

turtlpass-firmware/Base94.hpp

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// source: https://github.com/Holmojan/base94
2+
#pragma once
3+
4+
#ifndef BASE94_HPP
5+
#define BASE94_HPP
6+
7+
#include <stdint.h>
8+
#include <vector>
9+
#include <string>
10+
11+
class Base94 {
12+
/* 96 printable characters(include tab) */
13+
/* remove \ for compatibility */
14+
/* remove tab for uniformity */
15+
/* luckly, 11/9 > log(256)/log(94) */
16+
protected:
17+
enum {
18+
BASE94_SYMBOL_COUNT = 94,
19+
BASE94_INPUT_BLOCK_SIZE = 9,
20+
BASE94_OUTPUT_BLOCK_SIZE = 11,
21+
};
22+
static constexpr char encode_table[BASE94_SYMBOL_COUNT + 1] = {
23+
" !\"#$%&'()*+,-./"
24+
"0123456789"
25+
":;<=>?@"
26+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
27+
"[]^_`"
28+
"abcdefghijklmnopqrstuvwxyz"
29+
"{|}~"
30+
};
31+
static constexpr char first_symbol = encode_table[0];
32+
static constexpr char last_symbol = encode_table[BASE94_SYMBOL_COUNT - 1];
33+
34+
static constexpr uint32_t encode_tail_cut[BASE94_INPUT_BLOCK_SIZE] = {
35+
0, 9, 8, 7, 6, 4, 3, 2, 1 //11-ceil(input_tail*8/log2(94))
36+
};
37+
static constexpr uint32_t decode_tail_cut[BASE94_OUTPUT_BLOCK_SIZE] = {
38+
0, 0, 8, 7, 6, 5, 0, 4, 3, 2, 1 //9-input_tail
39+
};
40+
41+
typedef uint8_t base94_input_block[BASE94_INPUT_BLOCK_SIZE];
42+
typedef char base94_output_block[BASE94_OUTPUT_BLOCK_SIZE];
43+
44+
inline static bool encode_symbol(uint32_t x, char& y) {
45+
y = encode_table[x];
46+
return true;
47+
}
48+
49+
inline static bool decode_symbol(char x, uint32_t& y) {
50+
if (x < first_symbol || x > last_symbol || x == '\\')
51+
return false;
52+
if (x > '\\') x--;
53+
y = x - first_symbol;
54+
return true;
55+
}
56+
57+
static bool encode_block(const base94_input_block& x, base94_output_block& y) {
58+
enum {
59+
BASE94_ENCODE_MOD = (1 << 24) % BASE94_SYMBOL_COUNT,
60+
BASE94_ENCODE_MOD2 = (BASE94_ENCODE_MOD * BASE94_ENCODE_MOD) % BASE94_SYMBOL_COUNT,
61+
};
62+
uint32_t a = x[0] | (x[1] << 8) | (x[2] << 16);
63+
uint32_t b = x[3] | (x[4] << 8) | (x[5] << 16);
64+
uint32_t c = x[6] | (x[7] << 8) | (x[8] << 16);
65+
uint32_t d = 0;
66+
for (uint32_t i = 0; i < BASE94_OUTPUT_BLOCK_SIZE; i++) {
67+
d = (a + b * BASE94_ENCODE_MOD + c * BASE94_ENCODE_MOD2) % BASE94_SYMBOL_COUNT;
68+
if (!encode_symbol(d, y[i]))
69+
return false;
70+
b += c % BASE94_SYMBOL_COUNT << 24;
71+
a += b % BASE94_SYMBOL_COUNT << 24;
72+
c /= BASE94_SYMBOL_COUNT;
73+
b /= BASE94_SYMBOL_COUNT;
74+
a /= BASE94_SYMBOL_COUNT;
75+
}
76+
return true;
77+
}
78+
static bool decode_block(const base94_output_block& x, base94_input_block& y) {
79+
enum {
80+
BASE94_DECODE_MASK = (1 << 24) - 1,
81+
};
82+
uint32_t a = 0;
83+
uint32_t b = 0;
84+
uint32_t c = 0;
85+
uint32_t d = 0;
86+
for (uint32_t i = BASE94_OUTPUT_BLOCK_SIZE - 1; i != -1; i--) {
87+
if (!decode_symbol(x[i], d))
88+
return false;
89+
a *= BASE94_SYMBOL_COUNT;
90+
b *= BASE94_SYMBOL_COUNT;
91+
c *= BASE94_SYMBOL_COUNT;
92+
a += d;
93+
b += a >> 24;
94+
c += b >> 24;
95+
a &= BASE94_DECODE_MASK;
96+
b &= BASE94_DECODE_MASK;
97+
}
98+
y[0] = a & 0xFF;
99+
y[1] = (a >> 8) & 0xFF;
100+
y[2] = a >> 16;
101+
y[3] = b & 0xFF;
102+
y[4] = (b >> 8) & 0xFF;
103+
y[5] = b >> 16;
104+
y[6] = c & 0xFF;
105+
y[7] = (c >> 8) & 0xFF;
106+
y[8] = c >> 16;
107+
return true;
108+
}
109+
public:
110+
bool encode(const std::vector<uint8_t>& /*int*/ v, std::string& /*out*/ s) {
111+
uint32_t block_count = (v.size() + BASE94_INPUT_BLOCK_SIZE - 1) / BASE94_INPUT_BLOCK_SIZE;
112+
uint32_t buffer_size = block_count * BASE94_OUTPUT_BLOCK_SIZE;
113+
uint32_t tail = v.size() % BASE94_INPUT_BLOCK_SIZE;
114+
s.resize(buffer_size);
115+
uint32_t off_v = 0, off_s = 0;
116+
for (; off_v + BASE94_INPUT_BLOCK_SIZE <= v.size(); off_v += BASE94_INPUT_BLOCK_SIZE, off_s += BASE94_OUTPUT_BLOCK_SIZE) {
117+
if (!encode_block(*(base94_input_block*)(v.data() + off_v), *(base94_output_block*)(s.data() + off_s)))
118+
return false;
119+
}
120+
if (tail > 0) {
121+
base94_input_block buff = { 0 };
122+
for (uint32_t i = 0; i < tail; i++)
123+
buff[i] = v[off_v + i];
124+
if (!encode_block(buff, *(base94_output_block*)(s.data() + off_s)))
125+
return false;
126+
s.resize(buffer_size - encode_tail_cut[tail]);
127+
}
128+
return true;
129+
}
130+
bool decode(const std::string& /*in*/ s, std::vector<uint8_t>& /*out*/ v) {
131+
uint32_t block_count = (s.size() + BASE94_OUTPUT_BLOCK_SIZE - 1) / BASE94_OUTPUT_BLOCK_SIZE;
132+
uint32_t buffer_size = block_count * BASE94_INPUT_BLOCK_SIZE;
133+
uint32_t tail = s.size() % BASE94_OUTPUT_BLOCK_SIZE;
134+
135+
if (tail > 0 && decode_tail_cut[tail] == 0)
136+
return false;
137+
138+
v.resize(buffer_size);
139+
uint32_t off_s = 0, off_v = 0;
140+
for (; off_s + BASE94_OUTPUT_BLOCK_SIZE <= s.size(); off_s += BASE94_OUTPUT_BLOCK_SIZE, off_v += BASE94_INPUT_BLOCK_SIZE) {
141+
if (!decode_block(*(base94_output_block*)(s.data() + off_s), *(base94_input_block*)(v.data() + off_v)))
142+
return false;
143+
}
144+
if (tail > 0) {
145+
base94_output_block buff = { first_symbol, first_symbol, first_symbol, first_symbol, first_symbol,
146+
first_symbol, first_symbol, first_symbol, first_symbol, first_symbol, first_symbol };
147+
for (uint32_t i = 0; i < tail; i++)
148+
buff[i] = s[off_v + i];
149+
if (!decode_block(buff, *(base94_input_block*)(v.data() + off_v)))
150+
return false;
151+
v.resize(buffer_size - decode_tail_cut[tail]);
152+
}
153+
return true;
154+
}
155+
};
156+
157+
#endif // BASE94_HPP

turtlpass-firmware/Kdf.cpp

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,46 @@
11
#include "Kdf.h"
22

3+
// base94
4+
bool Kdf::derivatePassWithSymbols(uint8_t *dst, size_t dstLength, char *input, const char *seed) {
5+
// validate input pointers
6+
if (!dst || !input || !seed) {
7+
return false;
8+
}
9+
// calculate the length of the input string
10+
size_t inputLength = strlen(input);
11+
// allocate memory for the intermediate key
12+
size_t keyLength = base94InputLength(dstLength);
13+
std::vector<uint8_t> key(keyLength);
14+
if (key.empty()) {
15+
return false; // Memory allocation failed
16+
}
17+
// derive the key using the provided input and seed
18+
if (!derivateKey(key.data(), keyLength, input, seed)) {
19+
return false;
20+
}
21+
// encode the derived key using base94 encoding
22+
Base94 base94;
23+
std::string encodedKey;
24+
if (!base94.encode(key, encodedKey)) {
25+
return false;
26+
}
27+
// copy the encoded key to the destination buffer
28+
size_t copyLength = std::min(encodedKey.size(), dstLength);
29+
std::memcpy(dst, encodedKey.data(), copyLength);
30+
dst[copyLength] = '\0'; // ensure null-termination
31+
return true;
32+
}
33+
34+
uint32_t Kdf::base94InputLength(size_t encodedLength) {
35+
// calculate the number of output blocks
36+
uint32_t outputBlocks = (encodedLength + BASE94_OUTPUT_BLOCK_SIZE - 1) / BASE94_OUTPUT_BLOCK_SIZE;
37+
// calculate the number of input blocks
38+
uint32_t inputBlocks = outputBlocks * BASE94_INPUT_BLOCK_SIZE;
39+
// return the total input length
40+
return inputBlocks;
41+
}
42+
43+
// base62
344
bool Kdf::derivatePass(uint8_t *dst, size_t dstLength, char *input, const char *seed) {
445
// validate input pointers
546
if (!dst || !input || !seed) {
@@ -34,6 +75,11 @@ bool Kdf::derivatePass(uint8_t *dst, size_t dstLength, char *input, const char *
3475
return true;
3576
}
3677

78+
uint32_t Kdf::base62InputLength(size_t encodedLength) {
79+
// round down to the nearest whole number
80+
return (encodedLength * BASE62_ENCODED_BITS) / BITS_PER_BYTE;
81+
}
82+
3783
bool Kdf::derivateKey(uint8_t *dst, size_t dstLength, char *input, const char *seed) {
3884
// validate input pointers
3985
if (!dst || !input || !seed) {
@@ -91,8 +137,3 @@ void Kdf::hkdf(uint8_t *dst, size_t dstLength, const uint8_t *src, size_t srcLen
91137
// clear the HKDF instance to remove any sensitive information from memory
92138
hkdf.clear();
93139
}
94-
95-
uint32_t Kdf::base62InputLength(size_t encodedLength) {
96-
// round down to the nearest whole number
97-
return (encodedLength * BASE62_ENCODED_BITS) / BITS_PER_BYTE;
98-
}

turtlpass-firmware/Kdf.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
#include <cctype>
99
#include "SHA512.h"
1010
#include "HKDF.h"
11-
#include "base62.h"
11+
#include "Base62.h"
12+
#include "Base94.hpp"
1213

1314
#if defined(__TURTLPASS_PASS_SIZE__)
1415
#define PASS_SIZE __TURTLPASS_PASS_SIZE__
@@ -18,16 +19,19 @@
1819

1920
const size_t BITS_PER_BYTE = 8;
2021
const size_t BASE62_ENCODED_BITS = 6;
22+
const size_t BASE94_INPUT_BLOCK_SIZE = 9;
23+
const size_t BASE94_OUTPUT_BLOCK_SIZE = 11;
2124

2225
class Kdf {
2326
public:
2427
bool derivatePass(uint8_t *dst, size_t dstLength, char *input, const char *seed);
28+
bool derivatePassWithSymbols(uint8_t *dst, size_t dstLength, char *input, const char *seed);
2529
bool derivateKey(uint8_t *dst, size_t dstLength, char *input, const char *seed);
2630

2731
private:
2832
void hkdf(uint8_t *dst, size_t dstLength, const uint8_t *src, size_t srcLength, const uint8_t *salt, size_t saltLength);
29-
static void key2Charset(uint8_t *dst, const uint8_t *src, size_t srcLength);
3033
static uint32_t base62InputLength(size_t encodedLength);
34+
static uint32_t base94InputLength(size_t encodedLength);
3135
};
3236

3337
#endif // KDF_H

turtlpass-firmware/turtlpass-firmware.ino

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,33 @@ void handleCommand() {
246246
}
247247
break;
248248
}
249+
case '\\':
250+
{
251+
size_t inputLength = strlen(input);
252+
if (inputLength <= MIN_INPUT_SIZE || inputLength > MAX_INPUT_SIZE + 1) {
253+
Serial.println("<PASSWORD-INVALID-LENGTH>");
254+
internalState = IDLE;
255+
break;
256+
}
257+
for (size_t i = 1; i < inputLength; i++) {
258+
if (!isHexadecimalDigit(input[i])) {
259+
Serial.println("<PASSWORD-INVALID-INPUT>");
260+
internalState = IDLE;
261+
break;
262+
}
263+
}
264+
bool result = kdf.derivatePassWithSymbols(output, PASS_SIZE, input + 1, getSelectedSeed());
265+
if (result) {
266+
Serial.println("<PASSWORD-READY>");
267+
ledManager.setPulsing();
268+
internalState = PASSWORD_READY;
269+
} else {
270+
Serial.println("<PASSWORD-ERROR>");
271+
memset(output, 0, sizeof(output));
272+
internalState = IDLE;
273+
}
274+
break;
275+
}
249276
case '+':
250277
{
251278
bool result = otpManager.addOtpSecretToEEPROM(input + 1, getSelectedSeed());
@@ -279,6 +306,7 @@ void handleCommand() {
279306
case '*':
280307
{
281308
otpManager.factoryReset();
309+
Serial.println("<OTP-RESET>");
282310
internalState = IDLE;
283311
break;
284312
}

0 commit comments

Comments
 (0)