namespace OTP { abstract class Base : Object { private GLib.Bytes _secret; public string secret { get { // TODO: Terminate it with a zero! return (string)_secret.get_data(); } set { _secret = Base32.b32decode(new GLib.Bytes(value.data), true, 0); } } public int digits {get; set; default = 6;} public GLib.ChecksumType digest {get; set; default = GLib.ChecksumType.SHA1;} public Base(string secret_key, int digits, GLib.ChecksumType digest) { Object(secret: secret_key, digits: digits, digest: digest); } private static GLib.Bytes int_to_bytestring(uint64 val) { var size = (int) sizeof(uint64); var result = new uint8[size]; for (int i = size - 1; i >= 0; i--) { result[i] = (uint8)(val & 0xFF); val >>= 8; } return new GLib.Bytes(result); } public uint64 generate_otp(uint64 input) { // TODO: Make this dynamic based on the digest uint8[256] result = {0}; size_t result_len = 256; var hasher = new GLib.Hmac(digest, _secret.get_data()); hasher.update(int_to_bytestring(input).get_data()); hasher.get_digest(result, ref result_len); var offset = result[result_len - 1] & 0xf; uint64 code = ((uint64)(result[offset] & 0x7f) << 24 | (uint64)(result[offset + 1] & 0xff) << 16 | (uint64)(result[offset + 2] & 0xff) << 8 | (uint64)(result[offset + 3] & 0xff)); return Math.llrint(code % GLib.Math.pow(10, digits)); } } class TOTP : Base { public int interval {get; set; default = 30;} public TOTP(string secret_key, int digits, GLib.ChecksumType digest, int interval) { Object(secret: secret_key, digits: digits, digest: digest); } public uint64 at(GLib.DateTime for_time, int counter_offset) { return generate_otp(timecode(for_time) + counter_offset); } public uint64 now() { return generate_otp(timecode(new GLib.DateTime.now_utc())); } public uint64 timecode(GLib.DateTime for_time) { return Math.llrint(Math.ceil(for_time.to_unix() / interval)); } } }