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.
 
 

163 lines
4.3 KiB

/* otp.vala
*
* Written in 2022 by Lucas
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
namespace Otp {
public abstract class Otp : GLib.Object {
protected uint8[] key;
protected int _digits = 6;
public int digits {
get { return _digits; }
set {
return_if_fail(value >= 6 && value <= 10);
_digits = value;
}
}
private ChecksumType[] ALLOWED_ALGORITHMS = {
ChecksumType.SHA1,
ChecksumType.SHA256,
ChecksumType.SHA512,
};
protected ChecksumType _algorithm = ChecksumType.SHA1;
public ChecksumType algorithm {
get { return _algorithm; }
set {
return_if_fail(value in ALLOWED_ALGORITHMS);
_algorithm = value;
}
}
protected int32 _compute_value(uint64 counter) {
uint digits = this._digits, modulo = 0;
if (digits < 10)
for (modulo = 1000000; digits > 6; digits--)
modulo *= 10;
Hmac hmac = new Hmac(this.algorithm, this.key);
uint8 buf[8];
buf[0] = (uint8)(counter >> 56);
buf[1] = (uint8)((counter >> 48) & 0xff);
buf[2] = (uint8)((counter >> 40) & 0xff);
buf[3] = (uint8)((counter >> 32) & 0xff);
buf[4] = (uint8)((counter >> 24) & 0xff);
buf[5] = (uint8)((counter >> 16) & 0xff);
buf[6] = (uint8)((counter >> 8) & 0xff);
buf[7] = (uint8)(counter & 0xff);
hmac.update(buf);
uint8[] digest = new uint8[64];
size_t digest_len = digest.length;
hmac.get_digest(digest, ref digest_len);
uint offset = digest[digest_len - 1] & 0xf;
uint res = (digest[offset] << 24) |
(digest[offset + 1] << 16) |
(digest[offset + 2] << 8) |
digest[offset + 3];
res &= 0x7fffffff;
return digits == 10 ? (int32)res :
(int32)(res % modulo);
}
}
public class Hotp : Otp {
public Hotp(uint8[] key, int digits = 6,
ChecksumType algorithm = ChecksumType.SHA1) {
this.key = key;
this.digits = digits;
this.algorithm = algorithm;
}
public string get_value_at(uint64 counter) {
int32 result = this._compute_value(counter);
return ("%0*" + int32.FORMAT).printf(this.digits,
result);
}
public static string compute(uint8[] key, uint counter = 0,
int digits = 6,
ChecksumType algorithm = ChecksumType.SHA1)
requires (digits >= 6 && digits <= 10)
ensures (result.length == digits)
{
Hotp hotp = new Hotp(key, digits, algorithm);
return hotp.get_value_at(counter);
}
}
public class Totp : Otp {
private uint _period = 30;
public uint period {
get { return _period; }
set {
return_if_fail(value != 0);
_period = value;
}
}
public Totp(uint8[] key, uint period = 30, int digits = 6,
ChecksumType algorithm = ChecksumType.SHA1) {
this.key = key;
this.period = period;
this.digits = digits;
this.algorithm = algorithm;
}
public static string compute_at_timestamp(uint8[] key,
int64 time, uint period = 30, int digits = 6,
ChecksumType algorithm = ChecksumType.SHA1)
requires (digits >= 6 && digits <= 10)
requires (period != 0)
ensures (result.length == digits)
{
Totp totp = new Totp(key, period, digits, algorithm);
return totp.get_value_at_timestamp(time);
}
public static string compute_at_datetime(uint8[] key,
DateTime dt, uint period = 30, int digits = 6,
ChecksumType algorithm = ChecksumType.SHA1)
{
return Totp.compute_at_timestamp(key, dt.to_unix(),
period, digits, algorithm);
}
public static string compute_at_now(uint8[] key,
uint period = 30, int digits = 6,
ChecksumType algorithm = ChecksumType.SHA1)
{
return Totp.compute_at_timestamp(key,
new DateTime.now_utc().to_unix(), period, digits,
algorithm);
}
public string get_value_at_timestamp(int64 time) {
int32 result = this._compute_value(time / this.period);
return ("%0*" + int32.FORMAT).printf(this.digits,
result);
}
public string get_value_at_datetime(DateTime dt) {
return get_value_at_timestamp(dt.to_unix());
}
public string get_value_at_now() {
return get_value_at_timestamp(
new DateTime.now_utc().to_unix());
}
}
}