diff --git a/lib/mk/vfs_gtotp.mk b/lib/mk/vfs_gtotp.mk
deleted file mode 100644
index c5ac6bd..0000000
--- a/lib/mk/vfs_gtotp.mk
+++ /dev/null
@@ -1,7 +0,0 @@
-SRC_CC = vfs.cc
-vpath %.cc $(REP_DIR)/src/lib/vfs/gtotp
-SHARED_LIB = yes
-
-LIBS += cryptopp stdcxx
-
-CC_CXX_WARN_STRICT =
diff --git a/src/app/gtotp_report/README b/src/app/gtotp_report/README
new file mode 100644
index 0000000..7541faf
--- /dev/null
+++ b/src/app/gtotp_report/README
@@ -0,0 +1,9 @@
+This component generates reports containing
+Time-based One-time Passwords generated
+using the common Google Authenticator algorithm.
+
+It is configured by a single XML attribute, the shared secret
+encoded in base32.
+! ...
+!
+! ...
diff --git a/src/app/gtotp_report/component.cc b/src/app/gtotp_report/component.cc
new file mode 100644
index 0000000..c9ce7ad
--- /dev/null
+++ b/src/app/gtotp_report/component.cc
@@ -0,0 +1,193 @@
+/*
+ * \brief Google Time-based One-time Password Algorithm filesystem
+ * \author Emery Hemingway
+ * \date 2016-09-07
+ *
+ * https://en.wikipedia.org/wiki/Google_Authenticator
+ */
+
+/*
+ * Copyright (C) 2016-2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+#include
+
+/* Crypto++ includes */
+#include
+#include
+
+
+struct Main
+{
+ Genode::Env &env;
+
+ Timer::Connection timer { env };
+ Genode::Reporter reporter { env, "otp" };
+
+ CryptoPP::HMAC hmac;
+
+ Timer::Periodic_timeout update_timeout {
+ timer, *this, &Main::update, Genode::Microseconds(30*1000*1000) };
+
+ static Genode::uint64_t rtc_nonce(Genode::Env &env)
+ {
+ using namespace Genode;
+ typedef Genode::uint64_t uint64_t;
+
+ Rtc::Timestamp const ts = Rtc::Connection(env).current_time();;
+
+ enum {
+ PERIOD = 30 /* seconds */,
+ PERIODS_PER_MINUTE = 60 / PERIOD,
+ PERIODS_PER_HOUR = PERIODS_PER_MINUTE*60,
+ PERIODS_PER_DAY = PERIODS_PER_HOUR*24
+ };
+
+ unsigned const m = (ts.month + 9) % 12;
+ unsigned const y = ts.year - m/10;
+ unsigned const days =
+ 365*(y) + y/4 - y/100 + y/400 +
+ (m*306 + 5)/10 + (ts.day - 1) - 719468;
+
+ return
+ (uint64_t)days*PERIODS_PER_DAY +
+ (uint64_t)ts.hour*PERIODS_PER_HOUR +
+ (uint64_t)ts.minute*PERIODS_PER_MINUTE +
+ (uint64_t)ts.second/PERIOD;
+ }
+
+ uint64_t const initial_nonce = rtc_nonce(env);
+
+ void update(Genode::Duration dur)
+ {
+ uint64_t const nonce =
+ initial_nonce + dur.trunc_to_plain_ms().value / (30 * 1000);
+
+ uint8_t nonce_digest[hmac.DigestSize()];
+ uint8_t nonce_buf[8];
+
+ nonce_buf[0] = nonce >> (7 * 8) & 0xff;
+ nonce_buf[1] = nonce >> (6 * 8) & 0xff;
+ nonce_buf[2] = nonce >> (5 * 8) & 0xff;
+ nonce_buf[3] = nonce >> (4 * 8) & 0xff;
+ nonce_buf[4] = nonce >> (3 * 8) & 0xff;
+ nonce_buf[5] = nonce >> (2 * 8) & 0xff;
+ nonce_buf[6] = nonce >> (1 * 8) & 0xff;
+ nonce_buf[7] = nonce >> (0 * 8) & 0xff;
+
+ hmac.Update(nonce_buf, sizeof(nonce_buf));
+ hmac.Final(nonce_digest);
+ hmac.Restart();
+
+ unsigned const offset =
+ nonce_digest[sizeof(nonce_digest)-1] & 0x0F;
+
+ uint32_t code;
+ code = (nonce_digest[offset+0]&0x7F) << (3 * 8);
+ code += nonce_digest[offset+1] << (2 * 8);
+ code += nonce_digest[offset+2] << (1 * 8);
+ code += nonce_digest[offset+3] << (0 * 8);
+
+ code = code % 1000000;
+
+ char buf[7] { 0 };
+ buf[0] = 0x30 | (code/100000);
+ buf[1] = 0x30 | ((code/10000) % 10);
+ buf[2] = 0x30 | ((code/1000) % 10);
+ buf[3] = 0x30 | ((code/100) % 10);
+ buf[4] = 0x30 | ((code/10) % 10);
+ buf[5] = 0x30 | (code%10);
+
+ {
+ using namespace Genode;
+
+ Reporter::Xml_generator gen(reporter, [&] () {
+ gen.attribute("value", (char const *)buf);
+ });
+ }
+ }
+
+ Main(Genode::Env &env, uint8_t *secret, size_t secret_len)
+ : env(env), hmac(secret, secret_len)
+ {
+ reporter.enabled(true);
+ }
+};
+
+
+void Libc::Component::construct(Libc::Env &env)
+{
+ using namespace Genode;
+
+ enum {
+ BASE32_FACTOR = 256 / 32,
+ MAX_SECRET_BIN_LEN = 20,
+ MAX_SECRET_STR_LEN = MAX_SECRET_BIN_LEN * BASE32_FACTOR
+ };
+
+ size_t i = 0;
+ uint8_t binsec[MAX_SECRET_BIN_LEN];
+
+ {
+ Genode::uint8_t base32_table[256];
+
+ char const *upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+ char const *lower = "abcdefghijklmnopqrstuvwxyz";
+
+ for (int i = 0; i < 256; ++i)
+ base32_table[i] = 0xFF;
+
+ for (int i = 0; i < 32; ++i)
+ base32_table[(uint8_t)upper[i]] = i;
+
+ for (int i = 0; i < 26; ++i)
+ base32_table[(uint8_t)lower[i]] = i;
+
+ typedef String Secret_string;
+
+ Secret_string secret_string;
+
+ env.config([&] (Xml_node const &node) {
+ try {
+ node.attribute("secret").value(&secret_string); }
+ catch (Xml_node::Nonexistent_attribute) {
+ error("'secret' attribute missing from gtotp VFS node");
+ throw;
+ }
+ });
+
+ if ((secret_string.length()-1) % 8) {
+ error("gtotp secret has a strange length ", secret_string.length()-1);
+ throw Exception();
+ }
+
+ char const *b32str = secret_string.string();
+
+ uint8_t buf[8];
+ size_t j = 0;
+ while (j < secret_string.length()-1) {
+ for (size_t k = 0; k < 8; ++k)
+ buf[k] = base32_table[(uint8_t)b32str[j+k]];
+
+ binsec[i+0] = buf[0]<<3 | buf[1]>>2;
+ binsec[i+1] = buf[1]<<6 | buf[2]<<1 | buf[3] >>4;
+ binsec[i+2] = buf[3]<<4 | buf[4]>>1;
+ binsec[i+3] = buf[4]<<7 | buf[5]<<2 | buf[6]>>3;
+ binsec[i+4] = buf[6]<<5 | buf[7];
+
+ i += 5;
+ j += 8;
+ }
+ }
+
+ static Main inst(env, binsec, i);
+};
diff --git a/src/app/gtotp_report/target.mk b/src/app/gtotp_report/target.mk
new file mode 100644
index 0000000..29acfe5
--- /dev/null
+++ b/src/app/gtotp_report/target.mk
@@ -0,0 +1,6 @@
+TARGET = gtotp_report
+LIBS += base cryptopp stdcxx
+SRC_CC += component.cc
+
+# Effc++ not compatible with CryptoPP headers
+CC_CXX_WARN_STRICT =
diff --git a/src/lib/vfs/gtotp/README b/src/lib/vfs/gtotp/README
deleted file mode 100644
index eb655b9..0000000
--- a/src/lib/vfs/gtotp/README
+++ /dev/null
@@ -1,15 +0,0 @@
-This is a VFS plugin that generates Time-based One-time Passwords
-using the common Google Authenticator algorithms.
-
-It is configured with two XML attributes, name and secret,
-that is the file name and a base32 encoded shared secret.
-! ...
-!
-! ...
-!
-!
-!
-!
-! ...
-!
-! ...
diff --git a/src/lib/vfs/gtotp/target.mk b/src/lib/vfs/gtotp/target.mk
deleted file mode 100644
index 763cb22..0000000
--- a/src/lib/vfs/gtotp/target.mk
+++ /dev/null
@@ -1,4 +0,0 @@
-TARGET = gtotp_dummy
-LIBS = vfs_gtotp
-
-CC_CXX_WARN_STRICT =
diff --git a/src/lib/vfs/gtotp/vfs.cc b/src/lib/vfs/gtotp/vfs.cc
deleted file mode 100644
index 749cf55..0000000
--- a/src/lib/vfs/gtotp/vfs.cc
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * \brief Google Time-based One-time Password Algorithm filesystem
- * \author Emery Hemingway
- * \date 2016-09-07
- *
- * https://en.wikipedia.org/wiki/Google_Authenticator
- */
-
-/*
- * Copyright (C) 2016 Genode Labs GmbH
- *
- * This file is part of the Genode OS framework, which is distributed
- * under the terms of the GNU General Public License version 2.
- */
-
-
-/* Genode includes */
-#include
-#include
-#include
-#include
-#include
-
-/* Crypto++ includes */
-#include
-#include
-
-
-namespace Vfs { class Gtotp_file_system; };
-
-class Vfs::Gtotp_file_system : public Single_file_system
-{
- private:
-
- CryptoPP::HMAC _hmac;
-
- Rtc::Connection &_rtc;
-
- public:
-
- Gtotp_file_system(Genode::Xml_node &node,
- Rtc::Connection &rtc,
- Genode::uint8_t *secret,
- Genode::size_t secret_len)
- :
- Vfs::Single_file_system(NODE_TYPE_CHAR_DEVICE, "gtotp", node),
- _hmac(secret, secret_len), _rtc(rtc) { }
-
- Stat_result stat(char const *path, Stat &out) override
- {
- Stat_result result = Single_file_system::stat(path, out);
- out.size = 7; /* six decimal digits and newline */
-
- return result;
- }
-
- Read_result read(Vfs_handle *vfs_handle,
- char *buf, file_size buf_size,
- file_size &out) override
- {
- if ((vfs_handle->seek() != 0) || (buf_size < 7)) {
- out = 0;
- return READ_OK;
- }
-
- using namespace Genode;
-
- enum {
- PERIOD = 30 /* seconds */,
- PERIODS_PER_MINUTE = 60 / PERIOD,
- PERIODS_PER_HOUR = PERIODS_PER_MINUTE*60,
- PERIODS_PER_DAY = PERIODS_PER_HOUR*24
- };
-
- Rtc::Timestamp const ts = _rtc.current_time();
-
- unsigned const m = (ts.month + 9) % 12;
- unsigned const y = ts.year - m/10;
- unsigned const days =
- 365*(y) + y/4 - y/100 + y/400 +
- (m*306 + 5)/10 + (ts.day - 1) - 719468;
-
- Genode::uint64_t const nonce =
- days*PERIODS_PER_DAY +
- ts.hour*PERIODS_PER_HOUR +
- ts.minute*PERIODS_PER_MINUTE +
- ts.second/PERIOD;
-
- uint8_t nonce_digest[_hmac.DigestSize()];
- uint8_t nonce_buf[8];
-
- nonce_buf[0] = nonce >> (7 * 8) & 0xff;
- nonce_buf[1] = nonce >> (6 * 8) & 0xff;
- nonce_buf[2] = nonce >> (5 * 8) & 0xff;
- nonce_buf[3] = nonce >> (4 * 8) & 0xff;
- nonce_buf[4] = nonce >> (3 * 8) & 0xff;
- nonce_buf[5] = nonce >> (2 * 8) & 0xff;
- nonce_buf[6] = nonce >> (1 * 8) & 0xff;
- nonce_buf[7] = nonce >> (0 * 8) & 0xff;
-
- _hmac.Update(nonce_buf, sizeof(nonce_buf));
- _hmac.Final(nonce_digest);
- _hmac.Restart();
-
- unsigned const offset =
- nonce_digest[sizeof(nonce_digest)-1] & 0x0F;
-
- uint32_t code;
- code = (nonce_digest[offset+0]&0x7F) << (3 * 8);
- code += nonce_digest[offset+1] << (2 * 8);
- code += nonce_digest[offset+2] << (1 * 8);
- code += nonce_digest[offset+3] << (0 * 8);
-
- code = code % 1000000;
-
- buf[0] = 0x30 | (code/100000);
- buf[1] = 0x30 | ((code/10000) % 10);
- buf[2] = 0x30 | ((code/1000) % 10);
- buf[3] = 0x30 | ((code/100) % 10);
- buf[4] = 0x30 | ((code/10) % 10);
- buf[5] = 0x30 | (code%10);
- buf[6] = '\n';
- out = 7;
-
- return Read_result::READ_OK;
- }
-
- Write_result write(Vfs_handle *vfs_handle,
- char const *buf, file_size buf_size,
- file_size &out_count) override {
- return WRITE_ERR_INVALID; }
-};
-
-
-struct Gtotp_file_system_factory : Vfs::File_system_factory
-{
- Genode::uint8_t base32_table[256];
-
- Gtotp_file_system_factory()
- {
- char const *upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
- char const *lower = "abcdefghijklmnopqrstuvwxyz";
-
- for (int i = 0; i < 256; ++i)
- base32_table[i] = 0xFF;
-
- for (int i = 0; i < 32; ++i)
- base32_table[(uint8_t)upper[i]] = i;
-
- for (int i = 0; i < 26; ++i)
- base32_table[(uint8_t)lower[i]] = i;
- }
-
- Vfs::File_system *create(Genode::Env &env,
- Genode::Allocator &alloc,
- Genode::Xml_node node) override
- {
- static Rtc::Connection rtc(env);
-
- using namespace Genode;
- typedef Genode::size_t size_t;
-
- enum {
- BASE32_FACTOR = 256 / 32,
- MAX_SECRET_BIN_LEN = 20,
- MAX_SECRET_STR_LEN = MAX_SECRET_BIN_LEN * BASE32_FACTOR
- };
-
- typedef String Secret_string;
-
- Secret_string secret_string;
- uint8_t binsec[MAX_SECRET_BIN_LEN];
-
- try { node.attribute("secret").value(&secret_string); }
- catch (Xml_node::Nonexistent_attribute) {
- error("'secret' attribute missing from gtotp VFS node");
- throw;
- }
-
- if ((secret_string.length()-1) % 8) {
- error("gtotp secret has a strange length ", secret_string.length()-1);
- throw Exception();
- }
-
- char const *b32str = secret_string.string();
-
- uint8_t buf[8];
- size_t i = 0;
- size_t j = 0;
- while (j < secret_string.length()-1) {
- for (size_t k = 0; k < 8; ++k)
- buf[k] = base32_table[(uint8_t)b32str[j+k]];
-
- binsec[i+0] = buf[0]<<3 | buf[1]>>2;
- binsec[i+1] = buf[1]<<6 | buf[2]<<1 | buf[3] >>4;
- binsec[i+2] = buf[3]<<4 | buf[4]>>1;
- binsec[i+3] = buf[4]<<7 | buf[5]<<2 | buf[6]>>3;
- binsec[i+4] = buf[6]<<5 | buf[7];
-
- i += 5;
- j += 8;
- }
-
- return new (Genode::env()->heap())
- Vfs::Gtotp_file_system(node, rtc, binsec, i);
- }
-};
-
-
-extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
-{
- static Gtotp_file_system_factory factory;
- return &factory;
-}