fbPGP - #Facebook with #GnuPG

Beschreibung:

Gestern ging eine Schlagzeile durch die Medien: Ein reutlinger Richter will einen Facebook-Account beschlagnahmen, um Zugriff
auf die vom Nutzer versendeten Nachrichten zu erhalten. In meinen Augen ist es erstaunlich, wie schnell das Postgeheimnis ausgehebelt
werden kann, sobald das Objekt der Begierde nicht mehr in Papier eingewickelt ist. Wir sind noch weit davon entfernt, dass digitale
Kommunikation so geschützt ist, wie es derzeit bei der Briefpost der Fall ist. Um diesen Fortschritt ein bisschen zu unterstützen,
habe ich #fbPGP entwickelt. Dabei handelt es sich um ein Greasemonkey Script, das mit Hilfe von 2 PHP Scripts in Facebook
verschickte Nachrichten via GnuPG verschlüsselt. Die Idee ist, das eines Tages jeder Facebook-Nutzer ein Schlüsselpaar für die
Verwendung mit Facebook besitzt.

Die gezeigte Arbeit ist ein gerade einmal zwei Tage alter Proof-of-Concept. So ist derzeit z.B. die Passphrase für den Keystore von
GnuPG im Greasemonkey Script hinterlegt. Hier würde man natürlich im echten Leben einen anderen, sicheren Weg wählen. Zudem wird
derzeit nur die Nachrichtenzentrale unter facebook.com/messages/ unterstützt.

Damit die Technik funktioniert, muss auf einem separaten Server, der mit PHP ausgestattet ist, ein GnuPG Keystore angelegt werden. In
diesem muss das eigene Schlüsselpaar abgelegt werden. Der Name und die E-Mail-Adresse für das Schlüsselpaar wird wie folgt abgeleitet:
Leute, deren Profil-URL die Form facebook.com/profile.php?id=XYZ hat, merken sich XYZ. Leute, deren Profil-URL die Form facebook.com/ABC
hat, merken sich ABC. XYZ bzw. ABC werden mit SHA-1 gehasht und dem Hash werden die Buchstaben "fb" vorangestellt. Dies ergibt den
Realnamen für das Schlüsselpaar. Um die E-Mail-Adresse für das Schlüsselpaar zu erhalten, wird ein @localhost angefügt.

Beispiel:
* Profil-URL: facebook.com/zuck
* zu merken: zuck
* SHA-1 Hash: eac313a175c88cdf4f38f77526538493693e832e
* Realname: fbeac313a175c88cdf4f38f77526538493693e832e
* E-Mail-Adresse: fbeac313a175c88cdf4f38f77526538493693e832e@localhost

Für jeden Facebook-Nutzer, dessen öffentlicher Schlüssel nach dem gleichen Schema gebildet wurde und der sich im Keystore des eigenen
Servers befindet, können nun via GnuPG verschlüsselte Nachrichten verschickt werden. Der nächste Schritt wäre, das Profil des
Chat-Partners nach einem öffentlichen Schlüssel zu durchforsten, um den Prozess der Schlüsselverteilung zu automatisieren. Außerdem
gibt es noch viele weitere Stellen, an denen man mit anderen Nutzern kommunizieren kann und die ebenfalls abgesichert werden müssten.

OHNE fbPGP: 

MIT fbPGP: 
    

fbPGP.user.js (Greasemonkey Script):

// ==UserScript==
// @name           fbPGP
// @namespace      http://fbpgp.nix.li/
// @include        https://www.facebook.com/messages/*
// ==/UserScript==

var FBPGP_DECRYPT = "https://localhost/decrypt.php";
var FBPGP_ENCRYPT = "https://localhost/encrypt.php";

var FBPGP_DECRYPT_PASSWORD = "PassphraseOfKeystore"; //!!! CHANGE THIS

// DO NOT EDIT BELOW THIS LINE

String.prototype.contains   = function(sub) { return this.indexOf(sub) !== -1; };
String.prototype.startsWith = function(sub) { return this.indexOf(sub) == 0; };

// fbPGP message identifier
var FB_FBPGP_MESSAGE_IDENT = "<b>fbPGP encrypted:</b><br/>";

// access token
var FB_ACCESS_TOKEN_LINK   = "https://graph.facebook.com/me/home?access_token=";
var FB_ACCESS_TOKEN_TEXT   = "\">https://graph.facebook.com/me/";
var FB_GRAPH_API_REFERENCE = "https://developers.facebook.com/docs/reference/api/";

// FBPGP decrypt/encrypt link
var FB_FBPGP_ME_PARAM      = "me=";
var FB_FBPGP_MESSAGE_PARAM = "&message=";
var FB_FBPGP_PASS_PARAM    = "&pass=";
var FB_FBPGP_YOU_PARAM     = "&you=";

// FBPGP public key link
var FB_FBPGP_TOKEN_START = "#!FBPGP!#";
var FB_FBPGP_TOKEN_STOP  = "#!/FBPGP!#";
var FB_FBPGP_TOKEN_URL_A = "https://graph.facebook.com/";
var FB_FBPGP_TOKEN_URL_B = "?access_token=";

// my link
var FBXWELCOMEBOXNAME   = "fbxWelcomeBoxName";
var PAGELET_WELCOME_BOX = "pagelet_welcome_box";

// your link
var MESSAGINGREADPARTICIPANTS = "MessagingReadParticipants";

// user name
var FB_USERLINK_START_A = "https://www.facebook.com/profile.php?id=";
var FB_USERLINK_START_B = "http://www.facebook.com/profile.php?id=";
var FB_USERLINK_START_C = "https://www.facebook.com/";
var FB_USERLINK_START_D = "http://www.facebook.com/";

// messaging
var ID_PREFIX = "id.";

var MESSAGE_BODY             = "message_body";
var MESSAGINGCOMPOSERBODY    = "MessagingComposerBody";
var MESSAGINGCOMPOSERFORM    = "MessagingComposerForm";
var MESSAGINGINLINECOMPOSER  = "MessagingInlineComposer";
var MESSAGINGMESSAGES        = "MessagingMessages";
var MESSAGINGSENDREPLYBUTTON = "MessagingSendReplyButton";
var MESSAGINGSHELFCONTENT    = "MessagingShelfContent";

// decrypt message with remote GPG PHP script
function decryptMessage(message, myName, yourName) {
  var result = null;

  var decryptedMessage = GM_xmlhttpRequest({data        : FB_FBPGP_ME_PARAM + encodeURIComponent(myName) + FB_FBPGP_MESSAGE_PARAM + encodeURIComponent(message) + FB_FBPGP_PASS_PARAM + encodeURIComponent(FBPGP_DECRYPT_PASSWORD) + FB_FBPGP_YOU_PARAM + encodeURIComponent(yourName),
                                            headers     : {"Content-Type" : "application/x-www-form-urlencoded"},
                                            method      : "POST",
                                            synchronous : true,
                                            url         : FBPGP_DECRYPT});

  if (decryptedMessage != null) {
    decryptedMessage = decryptedMessage.responseText;
    if (decryptedMessage != null) {
      result = decryptedMessage;
    }
  }
  
  return result;
}

// encrypt message with remote GPG PHP script
function encryptMessage(message, myName, yourName) {
  var result = null;

  var encryptedMessage = GM_xmlhttpRequest({data        : FB_FBPGP_ME_PARAM + encodeURIComponent(myName) + FB_FBPGP_MESSAGE_PARAM + encodeURIComponent(message) + FB_FBPGP_YOU_PARAM + encodeURIComponent(yourName),
                                            headers     : {"Content-Type" : "application/x-www-form-urlencoded"},
                                            method      : "POST",
                                            synchronous : true,
                                            url         : FBPGP_ENCRYPT});

  if (encryptedMessage != null) {
    encryptedMessage = encryptedMessage.responseText;
    if (encryptedMessage != null) {
      result = encryptedMessage;
    }
  }
  
  return result;
}

function getFBPGPText(text) {
  var result = null;

  if ((text.contains(FB_FBPGP_TOKEN_START)) &&
      (text.contains(FB_FBPGP_TOKEN_STOP))) {
    result = text;

    result = result.substring(result.indexOf(FB_FBPGP_TOKEN_START) + FB_FBPGP_TOKEN_START.length);
    result = result.substring(0, result.indexOf(FB_FBPGP_TOKEN_STOP));
  }

  return result;
}

// extract link of you
function getMyLink() {
  var result = null;

  var myLink = document.getElementById(PAGELET_WELCOME_BOX);
  if (myLink != null) {
    var links = myLink.getElementsByTagName("a");
    for (var index = 0; index < links.length; index++) {  
      if (links[index].className.contains(FBXWELCOMEBOXNAME)) {
        result = links[index].href;
        
        break;
      }
    }
  }
  
  return result;
}

// extract link of user you're messaging with
function getYourLink() {
  var result = null;

  var yourLink = document.getElementById(MESSAGINGREADPARTICIPANTS);
  if (yourLink != null) {
    var links = yourLink.getElementsByTagName("a");
    if (links.length == 1) {
      if ((links[0].href.startsWith(FB_USERLINK_START_A)) ||
          (links[0].href.startsWith(FB_USERLINK_START_B))) {
        result = links[0].href;
      }
    }
  }
  
  return result;
}

// extract user name from facebook profile link
function getUserName(userLink) {
  var result = null;

  if (userLink != null) {
    if (userLink.toLowerCase().startsWith(FB_USERLINK_START_A)) {
      result = userLink.substring(FB_USERLINK_START_A.length);
    } else {
      if (userLink.toLowerCase().startsWith(FB_USERLINK_START_B)) {
        result = userLink.substring(FB_USERLINK_START_B.length);
      } else {
        if (userLink.toLowerCase().startsWith(FB_USERLINK_START_C)) {
          result = userLink.substring(FB_USERLINK_START_C.length);
        } else {
          if (userLink.toLowerCase().startsWith(FB_USERLINK_START_D)) {
            result = userLink.substring(FB_USERLINK_START_D.length);
          }
        }
      }
    }
  }
  
  return result;
}

function hookGUI(myName, yourName) {
  var shelfContent = document.getElementById(MESSAGINGSHELFCONTENT);
  if (shelfContent != null) {
    // show that hooking has worked
    shelfContent.style.backgroundColor = "#FBBFBF";
  }

  var replyButton = document.getElementById(MESSAGINGSENDREPLYBUTTON);
  if (replyButton != null) {
    // show that hooking has worked
    replyButton.style.backgroundColor = "#FB0000";
    replyButton.style.backgroundImage = "none";

    // get reference of textarea
    var messageBody = null;    
    var inlineComposer = document.getElementById(MESSAGINGINLINECOMPOSER);
    if (inlineComposer != null) {
      var textareas = inlineComposer.getElementsByTagName("textarea");
      if (textareas.length == 1) {
        if (textareas[0].className.contains(MESSAGINGCOMPOSERBODY)) {
          messageBody = textareas[0];
        }
      }
    }

    var replyInput = document.getElementById(replyButton.getAttribute("for"));
    if (replyInput != null) {
      // add fake reply button
      var newButton = document.createElement("input");
      newButton.setAttribute("type", "button");
      newButton.setAttribute("value", replyInput.value);
      replyInput.parentNode.appendChild(newButton);

      // hide real reply button
      replyInput.style.display    = "none";
      replyInput.style.visibility = "hidden";

      // set action handlers
      var newButtonClicked = function () { var result = encryptMessage(messageBody.value, myName, yourName); if ((result != null) && (result.length > 0)) { messageBody.value = FB_FBPGP_TOKEN_START + result + FB_FBPGP_TOKEN_STOP; } };
      newButton.addEventListener("click", newButtonClicked, true);
    }
  }

  // decrypt received messages
  var messaging = document.getElementById(MESSAGINGMESSAGES);
  if (messaging != null) {
    var messages = messaging.getElementsByTagName("div");
    for (var index = 0; index < messages.length; index++) {
      if (messages[index].id.startsWith(ID_PREFIX)) {
        var pgpText = getFBPGPText(messages[index].firstChild.innerHTML);
        if (pgpText != null) {
          messages[index].firstChild.innerHTML = FB_FBPGP_MESSAGE_IDENT + decryptMessage(pgpText, myName, yourName);
        }
      }
    }
  }
}

function main() {
  var myLink = getMyLink();
  if (myLink != null) {
    var myName = getUserName(myLink);
    if (myName != null) {
      var yourLink = getYourLink();
      if (yourLink != null) {
        var yourName = getUserName(yourLink);
        if (yourName != null) {
          hookGUI(myName, yourName);
        }
      }
    }
  }
}
setTimeout(main, 5000);

/*
//!!! quick and dirty hack to get a valid access token
function getAccessToken() {
  var result = null;

  var accessToken = GM_xmlhttpRequest({method      : "GET",
                                       synchronous : true,
                                       url         : FB_GRAPH_API_REFERENCE});

  if (accessToken != null) {
    accessToken = accessToken.responseText;
    if (accessToken != null) {
      if (accessToken.contains(FB_ACCESS_TOKEN_LINK)) {
        accessToken = accessToken.substring(accessToken.indexOf(FB_ACCESS_TOKEN_LINK) + FB_ACCESS_TOKEN_LINK.length);
        accessToken = accessToken.substring(0, accessToken.indexOf(FB_ACCESS_TOKEN_TEXT));
        
        result = accessToken;
      }
    }
  }

  return result;
}

// extract link to someone's PGP public key
function getFBPGPLink(yourName, accessToken) {
  var result = null;

  var fbpgpLink = GM_xmlhttpRequest({method      : "GET",
                                     synchronous : true,
                                     url         : FB_FBPGP_TOKEN_URL_A + encodeURIComponent(yourName) + FB_FBPGP_TOKEN_URL_B + encodeURIComponent(accessToken)});

  if (fbpgpLink != null) {
    fbpgpLink = fbpgpLink.responseText;
    if (fbpgpLink != null) {
      result = getFBPGPText(fbpgpLink);
    }
  }
  
  return result;
}

// find public key
var accessToken = getAccessToken();
if (accessToken != null) {
  alert("accessToken = " + accessToken);

  var fbpgpLink = getFBPGPLink(yourName, accessToken);
  if (fbpgpLink != null) {
    alert("fbpgpLink = " + fbpgpLink);
  }
}
*/
    

encrypt.php (GPG Encrypt PHP Script):

<?php
  $gpg  = "/usr/bin/gpg";
  $tmp  = "/tmp/fbpgp_";

  $recipient = null;
  if (isset($_POST["you"])) {
    $recipient = "fb" . sha1(rawurldecode($_POST["you"])) . "@localhost";
  }
  $message = null;
  if (isset($_POST["message"])) {
    $message = rawurldecode($_POST["message"]);
  }

  if (($recipient != null) && ($message != null)) {
    $file_in  = uniqid($tmp, true);
    $file_out = uniqid($tmp, true);

    file_put_contents($file_in, $message);

    exec("$gpg --recipient " . escapeshellarg($recipient) . " --output " . escapeshellarg($file_out) . " --encrypt " . escapeshellarg($file_in));

    $result = file_get_contents($file_out);
    $result = base64_encode($result);

    print($result);

    unlink($file_in);
    unlink($file_out);
  }
?>
    

decrypt.php (GPG Decrypt PHP Script):

<?php
  $echo = "/bin/echo";
  $gpg  = "/usr/bin/gpg";
  $tmp  = "/tmp/fbpgp_";

  $recipient = null;
  if (isset($_POST["me"])) {
    $recipient = "fb" . sha1(rawurldecode($_POST["me"])) . "@localhost";
  }
  $password = null;
  if (isset($_POST["pass"])) {
    $password = rawurldecode($_POST["pass"]);
  }
  $message = null;
  if (isset($_POST["message"])) {
    $message = base64_decode(rawurldecode($_POST["message"]));
  }

  if (($recipient != null) && ($password != null) && ($message != null)) {
    $file_in  = uniqid($tmp, true);
    $file_out = uniqid($tmp, true);

    file_put_contents($file_in, $message);

    exec("$echo " . escapeshellarg($password) . " | $gpg --batch --passphrase-fd=0 --local-user " . escapeshellarg($recipient) . " --output " . escapeshellarg($file_out) . " --decrypt " . escapeshellarg($file_in));

    $result = file_get_contents($file_out);
    $result = str_replace("<", "&lt;", $result);
    $result = str_replace(">", "&gt;", $result);

    print($result);

    unlink($file_in);
    unlink($file_out);
  }
?>
    
Idea, Concept & Code by WeizenSpr.eu