diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ca75a..774f2af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.2.0 +* やっとTOTP機能性を修正した +* makeを実行したら、バイナリがもっと小さくなる + # 1.1.2 * OpenBSDでのコンパイラーが発生された問題を修正した diff --git a/Makefile b/Makefile index 6dd257c..dfd5bb3 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ NAME=sp -VERSION=1.1.2 +VERSION=1.2.0 # Linux、Haiku、かIllumos = /usr、FreeBSDかOpenBSD = /usr/local、NetBSD = /usr/pkg PREFIX=/usr CC=cc -FILES=main.c showpass.c yankpass.c addpass.c delpass.c listpass.c genpass.c initpass.c otppass.c +FILES=main.c showpass.c yankpass.c addpass.c delpass.c listpass.c genpass.c initpass.c otppass.c base32.c CFLAGS=-Wall -Wextra -g -I${PREFIX}/include -L${PREFIX}/lib LDFLAGS=-lgpgme -lcrypto diff --git a/base32.c b/base32.c new file mode 100644 index 0000000..6a8ce6d --- /dev/null +++ b/base32.c @@ -0,0 +1,42 @@ +#include "base32.h" + +int char_to_val(char c) { + const char *base32_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + char *ptr = strchr(base32_alphabet, c); + return ptr ? ptr - base32_alphabet : -1; +} + +unsigned char *base32_decode(const char *encoded, size_t *out_len) { + size_t encoded_len = strlen(encoded); + size_t padding = 0; + for (int i = encoded_len - 1; i >= 0 && encoded[i] == '='; --i) { + ++padding; + } + + *out_len = (encoded_len - padding) * 5 / 8; + if (*out_len == 0) return NULL; + + unsigned char *decoded = malloc(*out_len); + if (!decoded) return NULL; + + int buffer = 0, bits_left = 0, count = 0; + for (size_t i = 0; i < encoded_len - padding; ++i) { + int val = char_to_val(encoded[i]); + if (val < 0) { + free(decoded); + return NULL; + } + + buffer <<= 5; + buffer |= val; + bits_left += 5; + + if (bits_left >= 8) { + decoded[count++] = (unsigned char) (buffer >> (bits_left - 8)); + bits_left -= 8; + } + } + + *out_len = count; + return decoded; +} diff --git a/base32.h b/base32.h new file mode 100644 index 0000000..6e2561f --- /dev/null +++ b/base32.h @@ -0,0 +1,9 @@ +#ifndef BASE32_H +#define BASE32_H + +#include +#include + +unsigned char *base32_decode(const char *encoded, size_t *out_len); + +#endif diff --git a/main.c b/main.c index 5502a0d..535bd1f 100644 --- a/main.c +++ b/main.c @@ -16,7 +16,7 @@ void helpme(); const char* sofname = "sp"; -const char* version = "1.1.2"; +const char* version = "1.2.0"; void helpme() { printf("076 sp - シンプルなパスワードマネージャー\n"); @@ -64,7 +64,19 @@ int main (int argc, char* argv[]) { else if (argc == 4 && strcmp(argv[3], "secure") == 0) genpass(atoi(argv[2]), true); else helpme(); } - else if (argc == 3 && strcmp(argv[1], "-o") == 0) otppass(argv[2]); + else if (argc == 3 && strcmp(argv[1], "-o") == 0) { + char fullPath[512]; + char* homedir = getenv("HOME"); + if (homedir == NULL) { + perror("ホームディレクトリを受取に失敗。"); + return -1; + } + + char* basedir = "/.local/share/sp/"; + snprintf(fullPath, sizeof(fullPath), "%s%s%s.gpg", homedir, basedir, argv[2]); + + otppass(fullPath); + } else if (argc == 2 && strcmp(argv[1], "-v") == 0) printf("%s-%s\n", sofname, version); else helpme(); diff --git a/otppass.c b/otppass.c index 87c2b42..1630f47 100644 --- a/otppass.c +++ b/otppass.c @@ -1,26 +1,123 @@ #include -#include -#include #include +#include +#include -unsigned long generate_totp(const unsigned char* secret, size_t keylen) { - unsigned long timestep = time(NULL) / 30; - unsigned char hmacres[20]; +#include "base32.h" +#include "otppass.h" - HMAC(EVP_sha1(), secret, keylen, (unsigned char*)×tep, sizeof(timestep), hmacres, NULL); +unsigned char* extract_secret(const char* otpauth_url, size_t* decoded_len) { + const char* secret_start = strstr(otpauth_url, "secret="); + if (!secret_start) { + fprintf(stderr, "OTPAuth URLの中に、シークレットを見つけられませんでした。\n"); + return NULL; + } + secret_start += 7; - int offset = hmacres[19] & 0xF; - unsigned long trunhash = (hmacres[offset] & 0x7F) << 24 | - (hmacres[offset + 1] & 0xFF) << 16 | - (hmacres[offset + 2] & 0xFF) << 8 | - (hmacres[offset + 3] & 0xFF); - - unsigned long otp = trunhash % 1000000; - return otp; + const char* secret_end = strchr(secret_start, '&'); + if (!secret_end) { + if (secret_start[0] != '\0') { + secret_end = secret_start + strlen(secret_start); + } else { + secret_end = secret_start; + } + } + + if (secret_end < secret_start) { + fprintf(stderr, "不正なシークレットの距離。\n"); + return NULL; + } + + size_t secret_len = secret_end - secret_start; + char* secret_encoded = (char*)malloc(secret_len + 1); + + if (secret_encoded == NULL) { + fprintf(stderr, "メモリの役割に失敗。\n"); + return NULL; + } + + strncpy(secret_encoded, secret_start, secret_len); + secret_encoded[secret_len] = '\0'; + + unsigned char* secret_decoded = base32_decode(secret_encoded, decoded_len); + free(secret_encoded); + + if (!secret_decoded) { + fprintf(stderr, "BASE32の復号化に失敗。\n"); + return NULL; + } + + return secret_decoded; +} + +uint32_t generate_totp(const char *secret, uint64_t counter) { + counter = htobe64(counter); + + unsigned char hash[SHA_DIGEST_LENGTH]; + HMAC(EVP_sha1(), secret, strlen(secret), (unsigned char *)&counter, sizeof(counter), hash, NULL); + + int offset = hash[SHA_DIGEST_LENGTH - 1] & 0x0F; + uint32_t truncated_hash = + (hash[offset] & 0x7F) << 24 | + (hash[offset + 1] & 0xFF) << 16 | + (hash[offset + 2] & 0xFF) << 8 | + (hash[offset + 3] & 0xFF); + + return truncated_hash % 1000000; } void otppass(char* file) { - const char* secret = file; - unsigned long otp = generate_totp((unsigned char*)secret, strlen(secret)); - printf("%06lu\n", otp); + gpgme_ctx_t ctx; + gpgme_error_t err; + gpgme_data_t in, out; + size_t secret_len; + + gpgme_check_version(NULL); + err = gpgme_new(&ctx); + if (err) { + fprintf(stderr, "GPGMEを創作に失敗\n"); + exit(1); + } + + err = gpgme_data_new_from_file(&in, file, 1); + if (err) { + fprintf(stderr, "GPGファイルを読込に失敗\n"); + exit(1); + } + + err = gpgme_data_new(&out); + if (err) { + fprintf(stderr, "GPGデータを読込に失敗\n"); + exit(1); + } + + err = gpgme_op_decrypt(ctx, in, out); + if (err) { + fprintf(stderr, "GPGを復号化に失敗\n"); + exit(1); + } + + char* secret = gpgme_data_release_and_get_mem(out, &secret_len); + if (!secret) { + fprintf(stderr, "GPGを受取に失敗\n"); + exit(1); + } + + size_t decoded_len; + unsigned char* secret_decoded = extract_secret(secret, &decoded_len); + if (!secret_decoded) { + fprintf(stderr, "シークレットの抽出またはデコードに失敗しました\n"); + free(secret); + exit(1); + } + + time_t current_time = time(NULL); + uint64_t counter = current_time / 30; + uint32_t otp = generate_totp((const char*)secret_decoded, counter); + printf("%06d\n", otp); + + gpgme_data_release(in); + gpgme_release(ctx); + free(secret); + free(secret_decoded); }