/* 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 * . */ 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()); } } }