A => 2fa.sh +35 -0
@@ 0,0 1,35 @@
+#!/bin/sh
+
+FILE="$HOME/.2fa"
+HOTP="hotp"
+
+name=$1
+
+l=$(cat "$FILE" | grep "$name")
+digits=$(echo "$l" | cut -d' ' -f2)
+key=$(echo "$l" | cut -d' ' -f3)
+counter=$(echo "$l" | cut -d' ' -f4)
+
+case "$digits" in
+ 6)
+ darg=""
+ ;;
+ 7)
+ ;&
+ 8)
+ darg="-$digits"
+ ;;
+ *)
+ echo "usage: $0 name"
+ exit 1
+esac
+
+if [ x"$counter" != x ]; then
+ counter=$(printf "%020d" $(("counter" + 1)))
+ printf "$key" | $HOTP $darg -c $counter
+ sed -i "s/^$name $digits $key.*/$name $digits $key $counter/" $FILE
+else
+ printf "$key" | $HOTP $darg
+fi
+
+
A => LICENCE +22 -0
@@ 0,0 1,22 @@
+Copyright (c) 2021 Pouya Tafti. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
A => Makefile +23 -0
@@ 0,0 1,23 @@
+CC=pcc
+LD=pcc
+
+CCFLAGS=-O
+LDFLAGS=-static
+
+TARG=hotp
+OFILES=main.o sha.o base32.o
+
+all: ${TARG}
+
+clean:
+ rm -f *.o ${TARG}
+
+.SUFFIXES: .c .o
+
+.c.o:
+ ${CC} ${CCFLAGS} -c ${.IMPSRC} -o ${.TARGET}
+
+${TARG}: ${OFILES}
+ ${LD} ${LDFLAGS} ${.ALLSRC} -o ${.TARGET}
+
+.PHONY: all clean
A => README +5 -0
@@ 0,0 1,5 @@
+C99 implementation of HOTP (and TOTP) for 2fa on NetBSD/OpenBSD.
+Includes a helper script (2fa.sh) to use with ~/.2fa files created by
+rsc's 2fa programme.
+
+No warranties or guarantees of any kind.
A => base32.c +74 -0
@@ 0,0 1,74 @@
+#include <stdint.h>
+
+#include "dat.h"
+#include "fns.h"
+
+#define dec(c) ((c)>='A' && (c)<='Z' ? (c)-'A' : (c)>='a' && (c)<='z' ? (c)-'a' : (c)>='2' && (c)<='7' ? (c)-'2'+26 : -1)
+
+int
+b32dec(char *in, int ni, char *out, int no) {
+ int ii, io, n;
+ char *pi, *po;
+ int8_t v;
+
+ if(no<(ni+7)/8*5) return -1; /* out is not long enough */
+
+ memset(out, 0, no);
+ n = 0;
+ for(po=out, pi=in, io=ii=0; ii<ni && io<no; pi+=8, po+=5, ii+=8, io+=5) switch(ni-ii) {
+ default:
+ if((v = dec(pi[7]))>=0) {
+ po[4] &= 0xe0; // 0b11100000
+ po[4] |= v;
+ }
+ case 7:
+ if((v = dec(pi[6]))>=0) {
+ po[4] &= 0x1f; // 0b00011111
+ po[4] |= (v&0x07)<<5;
+ po[3] &= 0xf6; // 0b11111100
+ po[3] |= v>>3;
+ n++;
+ }
+ case 6:
+ if((v = dec(pi[5]))>=0) {
+ po[3] &= 0x43; // 0b10000011
+ po[3] |= v<<2;
+ n++;
+ }
+ case 5:
+ if((v = dec(pi[4]))>=0) {
+ po[3] &= 0x7f; // 0b01111111
+ po[3] |= (v&0x01)<<7;
+ po[2] &= 0xf0; // 0b11110000
+ po[2] |= v>>1;
+ n++;
+ }
+ case 4:
+ if((v = dec(pi[3]))>=0) {
+ po[2] &= 0x0f; // 0b00001111
+ po[2] |= (v&0x0f)<<4;
+ po[1] &= 0xfe; // 0b11111110
+ po[1] |= v>>4;
+ }
+ case 3:
+ if((v = dec(pi[2]))>=0) {
+ po[1] &= 0x61; // 0b11000001
+ po[1] |= v<<1;
+ n++;
+ }
+ case 2:
+ if((v = dec(pi[1]))>=0) {
+ po[1] &= 0x3f; // 0b00111111
+ po[1] |= (v&0x03)<<6;
+ po[0] &= 0xf4; // 0b11111000
+ po[0] |= v>>2;
+ }
+ case 1:
+ if((v = dec(pi[0]))>=0) {
+ po[0] &= 0x07; // 0b00000111
+ po[0] |= v<<3;
+ n++;
+ }
+ }
+ return n;
+}
No newline at end of file
A => dat.h +1 -0
@@ 0,0 1,1 @@
+#define nil ((void *)0)
A => fns.h +13 -0
@@ 0,0 1,13 @@
+#define dprint(...) {printf(__VA_ARGS__); printf("\n"); fflush(stdout);}
+#define xprint(b, n) {int _i; for(_i=0; _i<(n); _i++) printf("%02x", ((uint8_t*)(b))[_i]); printf("\t"); for(_i=0; _i<(n); _i++) printf("%c", (b)[_i]>=32?(b)[_i]:'?'); printf("\n"); fflush(stdout);}
+
+#define nelem(a) (sizeof (a) / sizeof (*(a)))
+#define htonll(i) (htonl(1)==1 ? (i) : (uint64_t)htonl((i)&0xffffffff)<<32 | htonl((i)>>32))
+
+/* sha.c */
+void sha1(uint8_t *buf, uint8_t *data, int n);
+void hmac1(uint8_t *buf, uint8_t *k, int nk, uint8_t *m, int nm);
+int hotp(uint64_t c, int len, uint8_t *buf, int n);
+
+/* base32.c */
+int b32dec(char *in, int ni, char *out, int no);
A => main.c +63 -0
@@ 0,0 1,63 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <time.h>
+#include <sha1.h>
+
+#include "dat.h"
+#include "fns.h"
+
+#ifndef BUF_LENGTH
+#define BUF_LENGTH 256
+#endif
+
+typedef struct Ctxt Ctxt;
+
+struct Ctxt {
+ int len;
+ uint64_t c;
+};
+
+void
+init(Ctxt *ctx, int argc, char **argv) {
+ ctx->len = 6;
+ ctx->c = time(nil)/30;
+ while(--argc) {
+ if(strlen(*++argv)<2 || **argv != '-') goto usage;
+ switch((*argv)[1]) {
+ case '7':
+ ctx->len = 7;
+ break;
+ case '8':
+ ctx->len = 8;
+ break;
+ case 'c':
+ if(!--argc) goto usage;
+ ctx->c = atol(*++argv);
+ break;
+ default:
+ goto usage;
+ }
+ }
+ return;
+usage:
+ fprintf(stderr, "hmac-based one-time password\n");
+ fprintf(stderr, "usage: hotp [-7] [-8] [-c counter]\n");
+ fprintf(stderr, "\tdefault counter is unix epoch divided by 30s (totp)\n");
+ fprintf(stderr, "\tbase32 key is read from stdin\n");
+ exit(1);
+}
+
+int
+main(int argc, char **argv) {
+ Ctxt ctx;
+ char buf[BUF_LENGTH], dbuf[BUF_LENGTH], *b;
+ int c, i, n;
+
+ init(&ctx, argc, argv);
+
+ for(b=buf,i=0; (c=getchar()) != EOF && c!='\n' && c!='\r' && i++<nelem(buf);) *b++ = c;
+ n=b32dec(buf, i, dbuf, nelem(buf));
+ printf("%0*d\n", ctx.len, hotp(ctx.c, ctx.len, dbuf, n));
+
+ return 0;
+}
A => sha.c +65 -0
@@ 0,0 1,65 @@
+#include <stdint.h>
+#include <sha1.h>
+
+#include "dat.h"
+#include "fns.h"
+
+/* buf must be of length SHA1_DIGEST_LENGTH */
+void
+sha1(uint8_t *buf, uint8_t *data, int n) {
+ SHA1_CTX sha;
+
+ SHA1Init(&sha);
+ SHA1Update(&sha, data, n);
+ SHA1Final(buf, &sha);
+}
+
+/* buf must be of length SHA1_DIGEST_LENGTH */
+void
+hmac1(uint8_t *buf, uint8_t *k, int nk, uint8_t *m, int nm) {
+ uint8_t ibuf[SHA1_DIGEST_LENGTH], kbuf[SHA1_DIGEST_LENGTH];
+ uint8_t bbuf[SHA1_BLOCK_LENGTH], *bp, *kp;
+ int i;
+ SHA1_CTX isha, osha;
+
+ if(nk>SHA1_BLOCK_LENGTH) {
+ sha1(kbuf, k, nk);
+ k = kbuf;
+ nk = nelem(kbuf);
+ }
+
+ for(bp=bbuf, kp=k, i=0; i<nk; i++) *bp++ = *kp++^0x36;
+ for(i=nk; i<SHA1_BLOCK_LENGTH; i++) *bp++ = 0x36;
+ SHA1Init(&isha);
+ SHA1Update(&isha, bbuf, nelem(bbuf));
+ SHA1Update(&isha, m, nm);
+ SHA1Final(ibuf, &isha);
+
+ for(bp=bbuf, kp=k, i=0; i<nk; i++) *bp++ = *kp++^0x5c;
+ for(i=nk; i<SHA1_BLOCK_LENGTH; i++) *bp++ = 0x5c;
+ SHA1Init(&osha);
+ SHA1Update(&osha, bbuf, nelem(bbuf));
+ SHA1Update(&osha, ibuf, nelem(ibuf));
+ SHA1Final(buf, &osha);
+}
+
+int
+hotp(uint64_t c, int len, uint8_t *k, int n) {
+ uint8_t buf[SHA1_DIGEST_LENGTH], *vp;
+ int i, off;
+ uint32_t v, d;
+
+ c = htonll(c);
+ hmac1(buf, k, n, &c, sizeof c);
+
+ vp=&v;
+ off=buf[nelem(buf)-1]&0x0f;
+ for(i=off; i<off+4; i++) *vp++=buf[i];
+ v &= htonl(0x7fffffff);
+ v = ntohl(v);
+
+ d=1;
+ while(len-- > 0) d *= 10;
+
+ return v%d;
+}