コミットを比較

...

11 コミット

13個のファイルの変更345行の追加86行の削除

ファイルの表示

@ -1,3 +1,18 @@
# 1.2.0
* やっとTOTP機能性を修正した
* makeを実行したら、バイナリがもっと小さくなる
* パスワードの長さの延長
* パスワード追加機能性で、パスワードが既に存在するかどうか確認
* パスワード削除機能性で、パスワードが存在ないかどうか確認
* パスワード変更機能性の追加
* zsh対応の修正
# 1.1.2
* OpenBSDでのコンパイラーが発生された問題を修正した
# 1.1.1
* make install-zsh部分を修正
# 1.1.0
* TOTP対応
* READMEファイルで使い方を詳しく説明する

ファイルの表示

@ -1,14 +1,25 @@
NAME=sp
VERSION=1.1.0
# Linux、Haiku、かIllumos = /usr、FreeBSDかOpenBSD = /usr/local、NetBSD = /usr/pkg
UNAME_S := $(shell uname -s)
NAME := $(shell cat main.c | grep "const char\* sofname" | awk '{print $$5}' | sed "s/\"//g" | sed "s/;//" )
VERSION := $(shell cat main.c | grep "const char\* version" | awk '{print $$5}' | sed "s/\"//g" | sed "s/;//" )
PREFIX=/usr
ifeq ($(UNAME_S),FreeBSD)
PREFIX=/usr/local
endif
ifeq ($(UNAME_S),OpenBSD)
PREFIX=/usr/local
endif
ifeq ($(UNAME_S),NetBSD)
PREFIX=/usr/pkg
endif
CC=cc
FILES=main.c showpass.c yankpass.c addpass.c delpass.c listpass.c genpass.c initpass.c otppass.c
CFLAGS=-Wall -Wextra -g
FILES=main.c showpass.c yankpass.c addpass.c delpass.c listpass.c genpass.c initpass.c otppass.c base32.c
CFLAGS=-Wall -Wextra -O3 -I${PREFIX}/include -L${PREFIX}/lib
LDFLAGS=-lgpgme -lcrypto
all:
${CC} ${CFLAGS} -o ${NAME} ${FILES} ${LDFLAGS}
strip ${NAME}
clean:
rm -f ${NAME}
@ -27,7 +38,7 @@ install: all
chmod 755 ${DESTDIR}${PREFIX}/bin/${NAME}
install-zsh:
cp sp-completion.zsh /usr/share/zsh/site-functions/_sp
cp sp-completion.zsh ${DESTDIR}${PREFIX}/share/zsh/site-functions/_sp
uninstall:
rm -f ${DESTDIR}${PREFIX}/bin/${NAME}

ファイルの表示

@ -6,6 +6,7 @@
```sh
doas prt-get depinst gpgme gnupg pinentry
doas make install
doas make install-zsh
```
又は
@ -23,18 +24,21 @@ prt-get depinst sp
```sh
doas pacman -S base-devel gpgme gnupg pinentry
doas make install
doas make install-zsh
```
### OpenBSD
```sh
doas pkg_add gmake gpgme gnupg pinentry
doas gmake install PREFIX=/usr/local
doas gmake install-zsh PREFIX=/usr/local
```
### FreeBSD
```sh
doas pkg install gmake gpgme gnupg pinentry
doas gmake install PREFIX=/usr/local
doas gmake install-zsh PREFIX=/usr/local
```
## 初期設定

ファイルの表示

@ -74,8 +74,34 @@ void getpasswd(char* prompt, char*pw, size_t pwlen) {
}
void addpass(char* file) {
char pass[100];
char knin[100];
// パスを準備
char* homedir = getenv("HOME");
if (homedir == NULL) {
perror("ホームディレクトリを受取に失敗。");
return;
}
char* basedir = "/.local/share/sp/";
char* ext = ".gpg";
char pass[256];
char knin[256];
int alllen = snprintf(NULL, 0, "%s%s%s%s", homedir, basedir, file, ext) + 1;
char* gpgpathchk = malloc(alllen);
if (gpgpathchk == NULL) {
perror("メモリを割当に失敗。");
return;
}
// ファイルが既に存在するかどうか確認
snprintf(gpgpathchk, alllen, "%s%s%s%s", homedir, basedir, file, ext);
if (access(gpgpathchk, F_OK) != -1) {
fprintf(stderr, "パスワードが既に存在しています。\n変更するには、「 sp -e %s 」を実行して下さい。\n", file);
free(gpgpathchk);
return;
}
free(gpgpathchk);
// パスワードを受け取る
getpasswd("パスワード: ", pass, sizeof(pass));
@ -126,16 +152,6 @@ void addpass(char* file) {
gpgme_data_new(&out);
// パスを準備
char* homedir = getenv("HOME");
if (homedir == NULL) {
perror("ホームディレクトリを受取に失敗。");
return;
}
char* basedir = "/.local/share/sp/";
char* ext = ".gpg";
// 鍵を受け取る
char keypath[256];
snprintf(keypath, sizeof(keypath), "%s%s%s", homedir, basedir, ".gpg-id");
@ -148,8 +164,8 @@ void addpass(char* file) {
return;
}
char* keyid = malloc(100);
if (!fgets(keyid, 100, keyfile)) {
char* keyid = malloc(256);
if (!fgets(keyid, 256, keyfile)) {
perror("鍵IDを読込に失敗。");
fclose(keyfile);
free(keyid);
@ -183,7 +199,6 @@ void addpass(char* file) {
}
// 暗号化したタイルを開く
int alllen = snprintf(NULL, 0, "%s%s%s%s", homedir, basedir, file, ext) + 1;
char* gpgpath = malloc(alllen);
if (gpgpath == NULL) {
cleanup(ctx, key[0], in, out);

42
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;
}

9
base32.h ノーマルファイル
ファイルの表示

@ -0,0 +1,9 @@
#ifndef BASE32_H
#define BASE32_H
#include <stdlib.h>
#include <string.h>
unsigned char *base32_decode(const char *encoded, size_t *out_len);
#endif

ファイルの表示

@ -4,7 +4,7 @@
#include "delpass.h"
int delpass(char* file) {
int delpass(char* file, int force) {
// パスを準備
char pwfile[512];
char* homedir = getenv("HOME");
@ -15,6 +15,23 @@ int delpass(char* file) {
char* basedir = "/.local/share/sp/";
char* ext = ".gpg";
int alllen = snprintf(NULL, 0, "%s%s%s%s", homedir, basedir, file, ext) + 1;
char* gpgpathchk = malloc(alllen);
if (gpgpathchk == NULL) {
perror("メモリを割当に失敗。");
return -1;
}
// ファイルが既に存在するかどうか確認
snprintf(gpgpathchk, alllen, "%s%s%s%s", homedir, basedir, file, ext);
if (access(gpgpathchk, F_OK) != 0) {
fprintf(stderr, "パスワードが存在しません。\n");
free(gpgpathchk);
return -1;
}
free(gpgpathchk);
int needed = snprintf(pwfile, sizeof(pwfile), "%s%s%s%s", homedir, basedir, file, ext);
if (needed >= (int)sizeof(pwfile)) {
fprintf(stderr, "エラー:パスが長すぎる。\n");
@ -22,21 +39,25 @@ int delpass(char* file) {
}
// 削除を確認する
printf("パスワード「%s」を本当に削除する事が宜しいでしょうか? (y/N): ", file);
int confirm = getchar();
if (confirm != 'y' && confirm != 'Y') {
printf("削除しませんでした。\n");
return -1;
}
if (force == 0) { // パスワードの変更のばあい、確認は不要
printf("パスワード「%s」を本当に削除する事が宜しいでしょうか? (y/N): ", file);
int confirm = getchar();
if (confirm != 'y' && confirm != 'Y') {
printf("削除しませんでした。\n");
return -1;
}
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
int ch;
while ((ch = getchar()) != '\n' && ch != EOF);
}
if (unlink(pwfile) == -1) {
perror("パスワードを削除出来ませんですた。");
return -1;
}
if (force == 1) return 0;
printf("パスワードを削除しました。\n");
return -1;
return 0;
}

ファイルの表示

@ -1,6 +1,6 @@
#ifndef DELPASS_H
#define DELPASS_H
int delpass(char* file);
int delpass(char* file, int force);
#endif

29
main.c
ファイルの表示

@ -16,18 +16,19 @@
void helpme();
const char* sofname = "sp";
const char* version = "1.1.0";
const char* version = "1.2.0";
void helpme() {
printf(" sp - シンプルなパスワードマネージャー\n");
printf("https://076.moe/ | https://gitler.moe/suwako/sp\n\n");
printf("使い方:\n");
printf(" %s %s - シンプルなパスワードマネージャー\n", sofname, version);
printf("https://076.moe/ | https://gitler.moe/suwako/%s\n\n", sofname);
puts ("使い方:\n");
printf("%s -i <gpg-id> GPGと使ってパスワードストレージを初期設定\n", sofname);
printf("%s -s <パスワード名> :パスワードを表示\n", sofname);
printf("%s -y <パスワード名> :パスワードを表示せずクリップボードにコピーする\n", sofname);
printf("%s -l :パスワード一覧を表示\n", sofname);
printf("%s -a <パスワード名> :パスワードを追加\n", sofname);
printf("%s -d <パスワード名> :パスワードを削除\n", sofname);
printf("%s -e <パスワード名> :パスワードを変更\n", sofname);
printf("%s -g <文字数> [risk|secure] 希望文字数でパスワードをランダムに作成する。risk英数字のみ(不安)、secure英数字特別文字(デフォルト)を使用\n", sofname);
printf("%s -o <パスワード名>\n ワンタイムパスワード(TOTP)を表示。存在しなければ、創作する\n", sofname);
printf("%s -h :ヘルプを表示\n", sofname);
@ -57,14 +58,30 @@ int main (int argc, char* argv[]) {
listpass(basePath, 0);
}
else if (argc == 3 && strcmp(argv[1], "-a") == 0) addpass(argv[2]);
else if (argc == 3 && strcmp(argv[1], "-d") == 0) delpass(argv[2]);
else if (argc == 3 && strcmp(argv[1], "-d") == 0) delpass(argv[2], 0);
else if (argc == 3 && strcmp(argv[1], "-e") == 0) {
delpass(argv[2], 1);
addpass(argv[2]);
}
else if (strcmp(argv[1], "-g") == 0) {
if (argc == 3) genpass(atoi(argv[2]), true);
else if (argc == 4 && strcmp(argv[3], "risk") == 0) genpass(atoi(argv[2]), false);
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();

131
otppass.c
ファイルの表示

@ -1,26 +1,123 @@
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <gpgme.h>
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*)&timestep, 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);
}

ファイルの表示

@ -18,7 +18,7 @@ void clean_up(gpgme_ctx_t ctx, gpgme_data_t in, gpgme_data_t out, FILE* gpgfile,
void showpass(char* file) {
gpgme_ctx_t ctx;
gpgme_error_t err;
gpgme_data_t in, out;
gpgme_data_t in = NULL, out = NULL;
FILE *gpgfile;
// GPGMEライブラリを設置
@ -52,7 +52,7 @@ void showpass(char* file) {
return;
}
sprintf(gpgpath, "%s%s%s%s", homedir, basedir, file, ext);
snprintf(gpgpath, alllen, "%s%s%s%s", homedir, basedir, file, ext);
gpgfile = fopen(gpgpath, "rb");
if (gpgfile == NULL) {
perror("ファイルを開くに失敗。");
@ -61,7 +61,15 @@ void showpass(char* file) {
return;
}
if (gpgme_data_new_from_stream(&in, gpgfile) != GPG_ERR_NO_ERROR || gpgme_data_new(&out) != GPG_ERR_NO_ERROR) {
// ファイルからinデータオブジェクトを創作
if (gpgme_data_new_from_stream(&in, gpgfile) != GPG_ERR_NO_ERROR) {
fprintf(stderr, "GPGMEデータオブジェクトを創作に失敗。\n");
clean_up(ctx, in, out, gpgfile, gpgpath);
return;
}
// outデータオブジェクトを創作
if (gpgme_data_new(&out) != GPG_ERR_NO_ERROR) {
fprintf(stderr, "GPGMEデータオブジェクトを創作に失敗。\n");
clean_up(ctx, in, out, gpgfile, gpgpath);
return;

ファイルの表示

@ -1,28 +1,48 @@
# compdef sp
#compdef sp
#autoload
_sp () {
local state
local -a options
local -a entries
_arguments -C \
'-i[GPGと使ってパスワードストレージを初期設定]: :_sp_complete_keys' \
'-s[パスワードを表示]: :_sp_complete_entries' \
'-y[パスワードを表示せずクリップボードにコピーする]: :_sp_complete_entries' \
'-l[パスワード一覧を表示]' \
'-a[パスワードを追加]: :_sp_complete_entries' \
'-d[パスワードを削除]: :_sp_complete_entries' \
'-g[希望文字数でパスワードをランダムに作成する]: :_sp_complete_entries' \
'-o[ワンタイムパスワード(TOTP)を表示]: :_sp_complete_entries' \
'-h[ヘルプを表示]' \
'-v[バージョンを表示]' \
'*:: :->subcmds'
case $state in
subcmds)
_message "no more arguments"
;;
esac
local cmd
if (( CURRENT > 2)); then
cmd=${words[2]}
curcontext="${curcontext%:*:*}:pass-$cmd"
(( CURRENT-- ))
shift words
case "${cmd}" in
-i)
_sp_complete_keys
;;
-i|-y|-d|-a|-e|-o)
_sp_complete_entries_helper
;;
-g)
local -a subcommands
subcommands=(
"secure:英数字+特別文字(デフォルト)"
"risk:英数字のみ(不安)"
)
;;
-s)
_sp_cmd_show
;;
esac
else
local -a subcommands
subcommands=(
"-i:GPGと使ってパスワードストレージを初期設定"
"-s:パスワードを表示"
"-y:パスワードを表示せずクリップボードにコピーする"
"-l:パスワード一覧を表示"
"-a:パスワードを追加"
"-d:パスワードを削除"
"-e:パスワードを変更"
"-g:希望文字数でパスワードをランダムに作成する。risk英数字のみ(不安)、secure英数字特別文字(デフォルト)を使用"
"-o:ワンタイムパスワード(TOTP)を表示。存在しなければ、創作する"
"-h:ヘルプを表示"
"-v:バージョンを表示"
)
_sp_cmd_show
fi
}
_sp_cmd_show () {
@ -35,9 +55,9 @@ _sp_complete_entries() {
_sp_complete_entries_helper () {
local IFS=$'\n'
local prefix="${SP_DIR:-$HOME/.local/share/sp}"
entries=("${(f)$(find -L "$prefix" \( -name .git -o -name .gpg-id \) -prune -o -type f -print 2>/dev/null | sed -e "s#${prefix}/##" -e 's#\.gpg$##')}")
_describe 'password entries' entries
local prefix
zstyle -s ":completion:${curcontext}:" prefix prefix || prefix="${SP_DIR:-$HOME/.local/share/sp}"
_values -C 'passwords' ${$(find -L "$prefix" \( -name .git -o -name .gpg-id \) -prune -o $@ -print 2>/dev/null | sed -e "s#${prefix}/\{0,1\}##" -e 's#\.gpg##' -e 's#\\#\\\\#g' -e 's#:#\\:#g' | sort):-""}
}
_sp_complete_keys () {
@ -45,4 +65,4 @@ _sp_complete_keys () {
_values 'gpg keys' $(gpg2 --list-secret-keys --with-colons | cut -d : -f 10 | sort -u | sed '/^$/d')
}
compdef _sp sp
_sp

ファイルの表示

@ -52,7 +52,7 @@ void yankpass(char* file) {
return;
}
sprintf(gpgpath, "%s%s%s%s", homedir, basedir, file, ext);
snprintf(gpgpath, alllen, "%s%s%s%s", homedir, basedir, file, ext);
gpgfile = fopen(gpgpath, "rb");
if (gpgfile == NULL) {
perror("ファイルを開くに失敗。");