730d492d70aa — pouya@nohup.io 6 months ago
first commit
9 files changed, 301 insertions(+), 0 deletions(-)

A => 2fa.sh
A => LICENCE
A => Makefile
A => README
A => base32.c
A => dat.h
A => fns.h
A => main.c
A => sha.c
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;
+}