Some tools to play with GnuPG’s smartcard daemon and OpenPGP smartcards.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
scdtools/src/otpauth.c

318 lines
6.6 KiB

/*
* scdtools - Tools for Scdaemon and OpenPGP smartcards
* Copyright (C) 2015 Damien Goutte-Gattat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "otpauth.h"
#include <stdio.h>
#include <string.h>
#define skip_delimiter(p) \
do { \
if ( *(p) ) \
(p) += 1; \
} while ( 0 )
static const char *
unescape(otp_t *otp, const char **uri, char delim)
{
const char *p;
char *q;
p = *uri;
q = otp->buffer;
while ( *p && *p != delim ) {
if ( *p == '%' ) {
int byte;
if ( sscanf(++p, "%02X", &byte) == 1 ) {
*(otp->buffer)++ = (char)byte;
p += 2;
}
}
else
*(otp->buffer++) = *p++;
}
*(otp->buffer++) = '\0';
skip_delimiter(p);
*uri = p;
return q;
}
static char *
copy(otp_t *otp, const char **uri, char delim)
{
const char *p;
char *q;
p = *uri;
q = otp->buffer;
while ( *p && *p != delim )
*(otp->buffer++) = *p++;
*(otp->buffer++) = '\0';
skip_delimiter(p);
*uri = p;
return q;
}
static size_t
base32_decode(const char *src, size_t slen, char *dst, size_t dlen)
{
unsigned char a, b, c;
unsigned int car, i, j, n;
for ( i = j = n = b = c = 0; i < slen && j < dlen; i++ ) {
car = src[i];
if ( car >= 'A' && car <= 'Z' )
a = car - 'A';
else if ( car >= '2' && car <= '7' )
a = car - '2' + 26;
else if ( car == '=' ) {
n = 0;
continue;
}
else
continue;
switch ( n++ ) {
case 0:
b = a;
break;
case 1:
dst[j++] = (b << 3) | (a >> 2);
c = a;
break;
case 2:
b = a;
break;
case 3:
dst[j++] = (c << 6) | (b << 1) | (a >> 4);
b = a;
break;
case 4:
dst[j++] = (b << 4) | (a >> 1);
c = a;
break;
case 5:
b = a;
break;
case 6:
dst[j++] = (c << 7) | (b << 2) | (a >> 3);
b = a;
break;
case 7:
dst[j++] = (b << 5) | a;
n = 0;
break;
}
}
return j;
}
static void
parse_label(otp_t *otp, const char **uri)
{
char *colon;
otp->label = unescape(otp, uri, '?');
if ( (colon = strchr(otp->label, ':')) ) {
*colon++ = '\0';
otp->issuer = otp->label;
while ( *colon == ' ' )
colon++;
otp->label = colon;
}
}
static int
parse_number(const char **uri)
{
unsigned n;
int i;
if ( sscanf(*uri, "%u%n", &n, &i) < 1 )
return -1;
*uri += i;
skip_delimiter(*uri);
return n;
}
static int
parse_parameter(otp_t *otp, const char **uri)
{
const char *p;
char *name;
p = *uri;
name = copy(otp, &p, '=');
if ( ! *p )
return -1;
if ( ! strcmp(name, "secret") ) {
size_t len;
const char *secret;
secret = p;
while ( *p && *p != '&' )
p += 1;
len = p - secret;
skip_delimiter(p);
otp->secret = otp->buffer = name;
otp->length = base32_decode(secret, len, otp->buffer, len);
otp->buffer += otp->length;
}
else if ( ! strcmp(name, "issuer") ) {
otp->buffer = name;
otp->issuer = unescape(otp, &p, '&');
}
else if ( ! strcmp(name, "algorithm") ) {
char *alg_name;
otp->buffer = name;
alg_name = copy(otp, &p, '&');
if ( ! strcmp(alg_name, "sha1") )
otp->algo = OTP_ALGO_SHA1;
else if ( ! strcmp(alg_name, "sha256") )
otp->algo = OTP_ALGO_SHA256;
else if ( ! strcmp(alg_name, "sha512") )
otp->algo = OTP_ALGO_SHA512;
else
return -1;
otp->buffer = name;
}
else if ( ! strcmp(name, "period") ) {
int n;
otp->buffer = name;
if ( otp->type != OTP_TYPE_TOTP )
return -1;
if ( (n = parse_number(&p)) == -1 )
return -1;
otp->period = n;
}
else if ( ! strcmp(name, "counter") ) {
int n;
otp->buffer = name;
if ( otp->type != OTP_TYPE_HOTP )
return -1;
if ( (n = parse_number(&p)) == -1 )
return -1;
otp->counter = n;
}
else if ( ! strcmp(name, "digits") ) {
int n;
otp->buffer = name;
if ( (n = parse_number(&p)) == -1 )
return -1;
otp->digits = n;
}
else {
/* Skip unknown parameter. */
while ( *p && *p != '&' )
p += 1;
skip_delimiter(p);
}
*uri = p;
return 0;
}
otp_t *
otp_parse_uri(const char *uri)
{
otp_t *otp;
const char *p;
p = uri;
if ( strncmp(p, "otpauth://", 10) )
return NULL;
p += 10;
/* By allocating a buffer at least the size of the input URI,
* we are sure to have enough space to store all parameters. */
if ( ! (otp = malloc(sizeof(otp_t) + strlen(uri))) )
return NULL;
otp->buffer = (char *)otp + sizeof(otp_t);
otp->algo = OTP_ALGO_SHA1;
otp->digits = 6;
otp->label = otp->buffer;
otp->issuer = NULL;
otp->secret = NULL;
if ( strncmp(p, "totp/", 5) == 0 && (p += 5) ) {
otp->type = OTP_TYPE_TOTP;
otp->period = 30;
}
else if ( strncmp(p, "hotp", 5) == 0 && (p += 5) ) {
otp->type = OTP_TYPE_HOTP;
otp->counter = 0;
}
else
goto bad_uri;
parse_label(otp, &p);
while ( *p ) {
if ( parse_parameter(otp, &p) == -1 )
goto bad_uri;
}
if ( ! otp->secret )
goto bad_uri;
return otp;
bad_uri:
free(otp);
return NULL;
}