164 lines
4.3 KiB
Vala
164 lines
4.3 KiB
Vala
|
/* 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());
|
||
|
}
|
||
|
}
|
||
|
}
|