From 6c66cf6873e023e54ab4ece566abf6845e49522b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com>
Date: Thu, 29 Dec 2016 16:17:26 -0500
Subject: [PATCH] ringaccount: support device revocation

Add support for Ring device revocation.
Each device manages a local revocation list, synced with
other devices linked with the account.
When a device is revoked, its certificate is added to the
local certificate revocation list (standard x509 CRL).
CRL support from the trust store and OpenDHT is used.

Tuleap: #1457
Change-Id: I227e21afb3234e70ad562a5a4d0cf1084d61f174
---
 .../cx.ring.Ring.ConfigurationManager.xml     | 17 +++++
 bin/dbus/dbusconfigurationmanager.cpp         |  6 ++
 bin/dbus/dbusconfigurationmanager.h           |  1 +
 src/client/configurationmanager.cpp           |  9 +++
 src/dring/configurationmanager_interface.h    |  1 +
 src/ringdht/ringaccount.cpp                   | 70 +++++++++++++++++--
 src/ringdht/ringaccount.h                     | 12 +++-
 7 files changed, 110 insertions(+), 6 deletions(-)

diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 76c36ca753..1cda603b11 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -192,6 +192,23 @@
            </arg>
        </signal>
 
+       <method name="revokeDevice" tp:name-for-bindings="revokeDevice">
+           <tp:docstring>
+               Revoke device attached to the given Ring account, and publish the new revocation list.
+           </tp:docstring>
+           <arg type="s" name="accountID" direction="in">
+           </arg>
+           <arg type="s" name="password" direction="in">
+           </arg>
+           <arg type="s" name="deviceId" direction="in">
+           </arg>
+           <arg type="b" name="success" direction="out">
+               <tp:docstring>
+                   True if the operation was performed successfully.
+               </tp:docstring>
+           </arg>
+       </method>
+
        <signal name="knownDevicesChanged" tp:name-for-bindings="knownDevicesChanged">
            <tp:docstring>
                Notify clients when a new device linked to this account is found.
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index 43169ae24a..a782e90172 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -80,6 +80,12 @@ DBusConfigurationManager::exportOnRing(const std::string& accountID, const std::
     return DRing::exportOnRing(accountID, password);
 }
 
+auto
+DBusConfigurationManager::revokeDevice(const std::string& accountID, const std::string& password, const std::string& device) -> decltype(DRing::revokeDevice(accountID, password, device))
+{
+    return DRing::revokeDevice(accountID, password, device);
+}
+
 auto
 DBusConfigurationManager::getKnownRingDevices(const std::string& accountID) -> decltype(DRing::getKnownRingDevices(accountID))
 {
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 2c530f8596..7b6573c7f5 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -64,6 +64,7 @@ class DBusConfigurationManager :
         std::map<std::string, std::string> getAccountTemplate(const std::string& accountType);
         std::string addAccount(const std::map<std::string, std::string>& details);
         bool exportOnRing(const std::string& accountID, const std::string& password);
+        bool revokeDevice(const std::string& accountID, const std::string& password, const std::string& device);
         std::map<std::string, std::string> getKnownRingDevices(const std::string& accountID);
         bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name);
         bool lookupAddress(const std::string& account, const std::string& nameserver, const std::string& address);
diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp
index c235888b36..36324bf8a8 100644
--- a/src/client/configurationmanager.cpp
+++ b/src/client/configurationmanager.cpp
@@ -287,6 +287,15 @@ exportOnRing(const std::string& accountID, const std::string& password)
     return false;
 }
 
+bool
+revokeDevice(const std::string& accountID, const std::string& password, const std::string& deviceID)
+{
+    if (const auto account = ring::Manager::instance().getAccount<ring::RingAccount>(accountID)) {
+        return account->revokeDevice(password, deviceID);
+    }
+    return false;
+}
+
 std::map<std::string, std::string>
 getKnownRingDevices(const std::string& accountId)
 {
diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h
index ad396ca40f..02db6f7403 100644
--- a/src/dring/configurationmanager_interface.h
+++ b/src/dring/configurationmanager_interface.h
@@ -46,6 +46,7 @@ void setAccountActive(const std::string& accountID, bool active);
 std::map<std::string, std::string> getAccountTemplate(const std::string& accountType);
 std::string addAccount(const std::map<std::string, std::string>& details);
 bool exportOnRing(const std::string& accountID, const std::string& password);
+bool revokeDevice(const std::string& accountID, const std::string& password, const std::string& deviceID);
 std::map<std::string, std::string> getKnownRingDevices(const std::string& accountID);
 
 bool lookupName(const std::string& account, const std::string& nameserver, const std::string& name);
diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp
index 22f90f4586..f4305badb5 100644
--- a/src/ringdht/ringaccount.cpp
+++ b/src/ringdht/ringaccount.cpp
@@ -166,6 +166,9 @@ struct RingAccount::ArchiveContent
     /** Generated CA key (for self-signed certificates) */
     std::shared_ptr<dht::crypto::PrivateKey> ca_key;
 
+    /** Revoked devices */
+    std::shared_ptr<dht::crypto::RevocationList> revoked;
+
     /** Ethereum private key */
     std::vector<uint8_t> eth_key;
 
@@ -763,10 +766,16 @@ RingAccount::hasSignedReceipt()
         return false;
     }
 
-    auto pk = identity_.second->issuer->getPublicKey();
-    RING_WARN("hasSignedReceipt() with %s", pk.getId().toString().c_str());
+    auto accountCertificate = identity_.second->issuer;
+    if (not accountCertificate) {
+        RING_WARN("Device certificate must be signed by the account certificate");
+        return false;
+    }
+
+    auto pk = accountCertificate->getPublicKey();
+    RING_WARN("Checking device receipt for account %s", pk.getId().toString().c_str());
     if (!pk.checkSignature({receipt_.begin(), receipt_.end()}, receiptSignature_)) {
-        RING_WARN("hasSignedReceipt() signature check failed");
+        RING_WARN("Device receipt signature check failed");
         return false;
     }
 
@@ -836,6 +845,13 @@ RingAccount::loadIdentity()
         if (crt_id != dht_key.getPublicKey().getId())
             return {};
 
+        if (not dht_cert.issuer) {
+            RING_ERR("Device certificate has no issuer");
+            return {};
+        }
+        // load revocation lists for device authority (account certificate).
+        tls::CertificateStore::instance().loadRevocations(*dht_cert.issuer);
+
         identity_ = {
             std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)),
             std::make_shared<dht::crypto::Certificate>(std::move(dht_cert))
@@ -907,6 +923,8 @@ RingAccount::loadArchive(const std::vector<uint8_t>& dat)
                     c.id.second = std::make_shared<dht::crypto::Certificate>(base64::decode(itr->asString()));
                 } else if (itr.key().asString().compare(Conf::ETH_KEY) == 0) {
                     c.eth_key = base64::decode(itr->asString());
+                } else if (itr.key().asString().compare(Conf::RING_ACCOUNT_CRL) == 0) {
+                    c.revoked = std::make_shared<dht::crypto::RevocationList>(base64::decode(itr->asString()));
                 } else
                     c.config[itr.key().asString()] = itr->asString();
             } catch (const std::exception& ex) {
@@ -950,6 +968,8 @@ RingAccount::makeArchive(const ArchiveContent& archive) const
     root[Conf::RING_ACCOUNT_KEY] = base64::encode(archive.id.first->serialize());
     root[Conf::RING_ACCOUNT_CERT] = base64::encode(archive.id.second->getPacked());
     root[Conf::ETH_KEY] = base64::encode(archive.eth_key);
+    if (archive.revoked)
+        root[Conf::RING_ACCOUNT_CRL] = base64::encode(archive.revoked->getPacked());
 
     Json::FastWriter fastWriter;
     std::string output = fastWriter.write(root);
@@ -1058,6 +1078,31 @@ RingAccount::addDevice(const std::string& password)
     });
 }
 
+bool
+RingAccount::revokeDevice(const std::string& password, const std::string& device)
+{
+    // shared_ptr of future
+    auto fa = ThreadPool::instance().getShared<ArchiveContent>(
+                    std::bind(&RingAccount::readArchive, this, password)
+                );
+    findCertificate(dht::InfoHash(device),
+                    [fa,sthis=shared(),password](const std::shared_ptr<dht::crypto::Certificate>& crt) mutable
+    {
+        sthis->foundAccountDevice(crt);
+        auto a = fa->get();
+        // Add revoked device to the revocation list and resign it
+        if (not a.revoked)
+            a.revoked = std::make_shared<decltype(a.revoked)::element_type>();
+        a.revoked->revoke(*crt);
+        a.revoked->sign(a.id);
+        // add to CRL cache
+        tls::CertificateStore::instance().pinRevocationList(a.id.second->getId().toString(), a.revoked);
+        tls::CertificateStore::instance().loadRevocations(*sthis->identity_.second->issuer);
+        sthis->saveArchive(a, password);
+    });
+    return true;
+}
+
 std::pair<std::string, std::string>
 RingAccount::saveIdentity(const dht::crypto::Identity id, const std::string& path) const
 {
@@ -1913,12 +1958,23 @@ RingAccount::doRegister_()
             auto h = dht::InfoHash(ringAccountId_);
             RING_WARN("Announcing device at %s: %s", h.toString().c_str(), announce_->toString().c_str());
             dht_.put(h, announce_, dht::DoneCallback{}, {}, true);
+            for (const auto& crl : identity_.second->issuer->getRevocationLists())
+                dht_.put(h, crl, dht::DoneCallback{}, {}, true);
             dht_.listen<DeviceAnnouncement>(h, [shared](DeviceAnnouncement&& dev) {
                 shared->findCertificate(dev.dev, [shared](const std::shared_ptr<dht::crypto::Certificate> crt) {
                     shared->foundAccountDevice(crt);
                 });
                 return true;
             });
+            dht_.listen<dht::crypto::RevocationList>(h, [shared](dht::crypto::RevocationList&& crl) {
+                if (crl.isSignedBy(*shared->identity_.second->issuer)) {
+                    RING_WARN("Found CRL for account.");
+                    tls::CertificateStore::instance().pinRevocationList(
+                        shared->ringAccountId_,
+                        std::make_shared<dht::crypto::RevocationList>(std::move(crl)));
+                }
+                return true;
+            });
             syncDevices();
         } else {
             RING_WARN("Can't announce device: no annoucement...");
@@ -2287,13 +2343,13 @@ RingAccount::connectivityChanged()
 }
 
 bool
-RingAccount::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>)> cb)
+RingAccount::findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb)
 {
     if (auto cert = tls::CertificateStore::instance().getCertificate(h.toString())) {
         if (cb)
             cb(cert);
     } else {
-        dht_.findCertificate(h, [=](const std::shared_ptr<dht::crypto::Certificate> crt) {
+        dht_.findCertificate(h, [cb{std::move(cb)}](const std::shared_ptr<dht::crypto::Certificate> crt) {
             if (crt)
                 tls::CertificateStore::instance().pinCertificate(std::move(crt));
             if (cb)
@@ -2804,6 +2860,10 @@ RingAccount::forEachDevice(const dht::InfoHash& to,
 {
     auto shared = std::static_pointer_cast<RingAccount>(shared_from_this());
     auto treatedDevices = std::make_shared<std::set<dht::InfoHash>>();
+    dht_.get<dht::crypto::RevocationList>(to, [to](dht::crypto::RevocationList&& crl){
+        tls::CertificateStore().instance().pinRevocationList(to.toString(), std::move(crl));
+        return true;
+    });
     dht_.get<DeviceAnnouncement>(to, [shared,to,treatedDevices,op](DeviceAnnouncement&& dev) {
         if (dev.from != to)
             return true;
diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h
index fcadf05f9f..0316799a78 100644
--- a/src/ringdht/ringaccount.h
+++ b/src/ringdht/ringaccount.h
@@ -84,6 +84,7 @@ constexpr const char* const RING_ACCOUNT_KEY = "ringAccountKey";
 constexpr const char* const RING_ACCOUNT_CERT = "ringAccountCert";
 constexpr const char* const RING_ACCOUNT_RECEIPT = "ringAccountReceipt";
 constexpr const char* const RING_ACCOUNT_RECEIPT_SIG = "ringAccountReceiptSignature";
+constexpr const char* const RING_ACCOUNT_CRL = "ringAccountCRL";
 }
 
 class IceTransport;
@@ -101,6 +102,13 @@ class RingAccount : public SIPAccountBase {
             return ACCOUNT_TYPE;
         }
 
+        std::shared_ptr<RingAccount> shared() {
+            return std::static_pointer_cast<RingAccount>(shared_from_this());
+        }
+        std::shared_ptr<RingAccount const> shared() const {
+            return std::static_pointer_cast<RingAccount const>(shared_from_this());
+        }
+
         /**
          * Constructor
          * @param accountID The account identifier
@@ -275,7 +283,7 @@ class RingAccount : public SIPAccountBase {
         std::vector<std::string> getCertificatesByStatus(tls::TrustStore::PermissionStatus status);
 
         bool findCertificate(const std::string& id);
-        bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>)> cb = {});
+        bool findCertificate(const dht::InfoHash& h, std::function<void(const std::shared_ptr<dht::crypto::Certificate>&)>&& cb = {});
 
         /* contact requests */
         std::map<std::string, std::string> getTrustRequests() const;
@@ -287,6 +295,8 @@ class RingAccount : public SIPAccountBase {
 
         void addDevice(const std::string& password);
 
+        bool revokeDevice(const std::string& password, const std::string& device);
+
         std::map<std::string, std::string> getKnownDevices() const;
 
         void connectivityChanged() override;
-- 
GitLab