diff --git a/include/class.i18n.php b/include/class.i18n.php
index 34e873ef93ee1a4e4131f629abc93c25932a2f44..c023566d90f0fc7ad154c91d004f113c232c7c51 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -217,6 +217,7 @@ class Internationalization {
                     'lang' => $code,
                     'locale' => $locale,
                     'path' => $f,
+                    'phar' => substr($f, -5) == '.phar',
                     'code' => $base,
                 );
             }
diff --git a/include/class.plugin.php b/include/class.plugin.php
index ffa86b614161cb15b9ea9b418155b1d7f2c0e208..8d09c7713f425446cd389fefd775e3c20103630d 100644
--- a/include/class.plugin.php
+++ b/include/class.plugin.php
@@ -309,6 +309,15 @@ abstract class Plugin {
     var $id;
     var $info;
 
+    const VERIFIED = 1;             // Thumbs up
+    const VERIFY_EXT_MISSING = 2;   // PHP extension missing
+    const VERIFY_FAILED = 3;        // Bad signature data
+    const VERIFY_ERROR = 4;         // Unable to verify (unexpected error)
+    const VERIFY_NO_KEY = 5;        // Public key missing
+    const VERIFY_DNS_PASS = 6;      // DNS check passes, cannot verify sig
+
+    static $verify_domain = 'updates.osticket.com';
+
     function Plugin($id) {
         $this->id = $id;
         $this->load();
@@ -422,6 +431,129 @@ abstract class Plugin {
         if ($path)
            return PluginManager::getInstance($path);
     }
+
+    /**
+     * Function: isVerified
+     *
+     * This will help verify the content, integrity, oversight, and origin
+     * of plugins, language packs and other modules distributed for
+     * osTicket.
+     *
+     * This idea is that the signature of the PHAR file will be registered
+     * in DNS, for instance,
+     * `7afc8bf80b0555bed88823306744258d6030f0d9.updates.osticket.com`, for
+     * a PHAR file with a SHA1 signature of
+     * `7afc8bf80b0555bed88823306744258d6030f0d9 `, which will resolve to a
+     * string like the following:
+     * ```
+     * "v=1; i=storage:s3; s=MEUCIFw6A489eX4Oq17BflxCZ8+MH6miNjtcpScUoKDjmblsAiEAjiBo9FzYtV3WQtW6sbhPlJXcoPpDfYyQB+BFVBMps4c=; V=0.1;"
+     * ```
+     * Which is a simple semicolon separated key-value pair string with the
+     * following keys
+     *
+     *   Key | Description
+     *  :----|:---------------------------------------------------
+     *   v   | Algorithm version
+     *   i   | Plugin 'id' registered in plugin.php['id']
+     *   V   | Plugin 'version' registered in plugin.php['version']
+     *   s   | OpenSSL signature of the PHAR SHA1 signature using a
+     *       | private key (specified on the command line)
+     *
+     * The public key, which will be distributed with osTicket, can be used
+     * to verify the signature of the PHAR file from the data received from
+     * DNS.
+     *
+     * Parameters:
+     * $phar - (string) filename of phar file to verify
+     *
+     * Returns:
+     * (int) -
+     *      Plugin::VERIFIED upon success
+     *      Plugin::VERIFY_DNS_PASS if found in DNS but cannot verify sig
+     *      Plugin::VERIFY_NO_KEY if public key not found in include/plugins
+     *      Plugin::VERIFY_FAILED if the plugin fails validation
+     *      Plugin::VERIFY_EXT_MISSING if a PHP extension is required
+     *      Plugin::VERIFY_ERROR if an unexpected error occurred
+     */
+    static function isVerified($phar) {
+        static $pubkey = null;
+
+        if (!class_exists('Phar'))
+            return self::VERIFY_EXT_MISSING;
+        elseif (!file_exists(INCLUDE_DIR . '/plugins/updates.pem'))
+            return self::VERIFY_NO_KEY;
+
+        if (!isset($pubkey)) {
+            $pubkey = openssl_pkey_get_public(
+                    file_get_contents(INCLUDE_DIR . 'plugins/updates.pem'));
+        }
+        if (!$pubkey) {
+            return self::VERIFY_ERROR;
+        }
+
+        require_once(PEAR_DIR.'Net/DNS2.php');
+        $P = new Phar($phar);
+        $sig = $P->getSignature();
+        $info = array();
+        try {
+            $q = new Net_DNS2_Resolver();
+            $r = $q->query(strtolower($sig['hash']) . '.' . self::$verify_domain, 'TXT');
+            foreach ($r->answer as $rec) {
+                foreach ($rec->text as $txt) {
+                    foreach (explode(';', $txt) as $kv) {
+                        list($k, $v) = explode('=', trim($kv));
+                        $info[$k] = trim($v);
+                    }
+                    if ($info['v'] && $info['s'])
+                        break;
+                }
+            }
+        }
+        catch (Net_DNS2_Exception $e) {
+            // TODO: Differenciate NXDOMAIN and DNS failure
+        }
+
+        if (is_array($info) && isset($info['v'])) {
+            switch ($info['v']) {
+            case '1':
+                if (!($signature = base64_decode($info['s'])))
+                    return self::VERIFY_FAILED;
+                elseif (!function_exists('openssl_verify'))
+                    return self::VERIFY_DNS_PASS;
+
+                $codes = array(
+                    -1 => self::VERIFY_ERROR,
+                    0 => self::VERIFY_FAILED,
+                    1 => self::VERIFIED,
+                );
+                $result = openssl_verify($sig['hash'], $signature, $pubkey,
+                    OPENSSL_ALGO_SHA1);
+                return $codes[$result];
+            }
+        }
+        return self::VERIFY_FAILED;
+    }
+
+    static function showVerificationBadge($phar) {
+        switch (self::isVerified($phar)) {
+        case self::VERIFIED:
+            $show_lock = true;
+        case self::VERIFY_DNS_PASS: ?>
+         
+        <span class="label label-verified" title="<?php
+            if ($show_lock) echo sprintf(__('Verified by %s'), self::$verify_domain);
+            ?>"> <?php
+            if ($show_lock) echo '<i class="icon icon-lock"></i>'; ?>
+            <?php echo $show_lock ? __('Verified') : __('Registered'); ?></span>
+<?php       break;
+        case self::VERIFY_FAILED: ?>
+        &nbsp;
+        <span class="label label-danger" title="<?php
+            echo __('The originator of this extension cannot be verified');
+            ?>"><i class="icon icon-warning-sign"></i></span>
+<?php       break;
+        }
+    }
 }
 
 ?>
diff --git a/include/plugins/updates.pem b/include/plugins/updates.pem
new file mode 100644
index 0000000000000000000000000000000000000000..9f4077be363d71b3615867622fd1cf1cf899499f
--- /dev/null
+++ b/include/plugins/updates.pem
@@ -0,0 +1,20 @@
+-----BEGIN PUBLIC KEY-----
+MIIDRjCCAjkGByqGSM44BAEwggIsAoIBAQCdMklcYXqGlNYXZ5bS808qOS6U9S5z
+IQcCrf2Hzs6OLmTUDkLxKuvmoBVMu7Tkbb6TY4ne+G9npWih4OfpVsvY22T13sf2
+EBcX0jOslcm+Bc5eN4dmgjs17iuf14oMkM8WdlVceT1tVqfKJnJm3i3U/+x5SDUY
+x6UhbOgMygemfIoFtqTbaMvAmype8HnflIxRoL25uZ44Hx7eef6zpOqYVXM8VQq3
+RNXXfNmoxiMNhVrSK18LE8D8h4ABMzDg/pxFt2fbf2IrNFAV+h2MSfF+ueLcrEfy
+XbtWLx7DdxqASEASwVLGm3vJslLBBvBDfGhTSNMVGk1XWBIeHfsALPV9AiEA39Qn
+ksDDQIL5Ed7Q9mYHDqM23mtJPxi2L478HmU3zY8CggEAE1UcB7QrR/bLWgzX/fbp
+xzVonCJElkxrx1rviKkjwAAAPurCFy2bQNRPMp/e7DFVwAtouQf3i2JWtRNeyHOC
+dxDKrspfCDOdovRHkWYOxXJCztesMGcUAHo/WmsM+Qb0WobAG9MnZ5AEDldSOBrM
+VyJfEuoF12EPsbOUYjVzJz1swIWgrqZlo1ZKD/oC4Wx0/zXz+5gWWbgXykTWE4wV
+PzU8r33qkgiEtXOjMc5YbvWmTcM0xw7OH34LPOtgUNZtcYSK2u4p1NQ+bDFpXar5
+MEmfmILYFBxGyoe1tCut0M6ulzzV8iBhWHecGEx09Ln3wfoJE+ba0PNn4bdJm6T6
+QQOCAQUAAoIBADPF6xGfYIrIPqiJaeHzTU/q4zpKRCGcjw1chtsNn+oZQzNqvWbI
+XNu7E+MBGimgYerJzyx7lE5bfyu+C4CS6acOutX3ujYfHRVkkkyJedv8q5Ky8kJk
+OjyyhS+cAszbQdh/zvBu6SoDa50mcmk/jfgiRZT0FiSNBJD5nlgjyo2cTEK7e2oR
+GD2N7l43M9BuNjUjQqgeRO9RMt6g4iRO/+KlC/yJrSy/PrLARatk/21ZbCn8jofi
+WR3uNkh7bT7dIfJDDmLsRuQ5fegdQ9mQ/7nLvMZha4pitwTlaI6P0c76fRN1Al27
+6LpcuPd1iHi4UjnvGR5nRwVN68igLNp2tGY=
+-----END PUBLIC KEY-----
diff --git a/include/staff/system.inc.php b/include/staff/system.inc.php
index 89f3bd498c80eb355f0d455af192701ec02e9a76..6ca862558ff275812c6210921498a034bca7d925 100644
--- a/include/staff/system.inc.php
+++ b/include/staff/system.inc.php
@@ -82,3 +82,24 @@ $commit = GIT_VERSION != '$git' ? GIT_VERSION : (
         echo sprintf('%.2f MiB', $space); ?></td>
 </tbody>
 </table>
+<br/>
+<h2><?php echo __('Installed Language Packs'); ?></h2>
+<div style="margin: 0 20px">
+<?php
+    foreach (Internationalization::availableLanguages() as $info) {
+        $p = $info['path'];
+        if ($info['phar']) $p = 'phar://' . $p;
+        if (file_exists($p . '/MANIFEST.php')) {
+            $manifest = (include $p . '/MANIFEST.php'); ?>
+    <h3><strong><?php echo Internationalization::getLanguageDescription($info['code']); ?></strong>
+        &mdash; <?php echo $manifest['Language']; ?>
+<?php       if ($info['phar'])
+                Plugin::showVerificationBadge($info['path']);
+            ?>
+        </h3>
+        <div><?php echo __('Version'); ?>: <?php echo $manifest['Version']; ?>,
+            <?php echo __('Built'); ?>: <?php echo $manifest['Build-Date']; ?>
+        </div>
+<?php }
+    } ?>
+</div>
diff --git a/js/osticket.js b/js/osticket.js
index ce9d8d7fbdf0aee54198addf39c81b395238e8ad..ae166d4e3a2d9dadb8747639ed99f388491a7174 100644
--- a/js/osticket.js
+++ b/js/osticket.js
@@ -216,7 +216,7 @@ showImagesInline = function(urls, thread_id) {
 }
 
 function __(s) {
-  if ($.strings && $.strings[s])
-    return $.strings[s];
+  if ($.oststrings && $.oststrings[s])
+    return $.oststrings[s];
   return s;
 }
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 8e9f190386f3bdefcf799299b9ac419ddcb1c9bc..11a647993974a6617a2a0bf760426c94405843ee 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -1804,6 +1804,20 @@ tr.disabled th {
 .label-info {
   background-color: #3a87ad;
 }
+.label-verified {
+    border:1px solid green;
+    background-color:transparent;
+    background-color:rgba(0,0,0,0);
+    color:green;
+    text-shadow:none;
+}
+.label-danger {
+    border:1px solid red;
+    background-color:transparent;
+    background-color:rgba(0,0,0,0);
+    color:red;
+    text-shadow:none;
+}
 
 .tab_content {
     position: relative;
diff --git a/scp/js/scp.js b/scp/js/scp.js
index 2a7a08fcfb924e416018610d37704f2b9162b2b0..df8a867564757f14544ec1742b74a1a039948694 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -771,7 +771,7 @@ $('#new-note').live('click', function() {
 });
 
 function __(s) {
-  if ($.strings && $.strings[s])
-    return $.strings[s];
+  if ($.oststrings && $.oststrings[s])
+    return $.oststrings[s];
   return s;
 }
diff --git a/setup/cli/modules/i18n.php b/setup/cli/modules/i18n.php
index 9f2d90b3eb3e7a06663059d9e8d9a08ba070fc0f..e4084500d25c398327f3c76e0bfc4b4f5a6822e5 100644
--- a/setup/cli/modules/i18n.php
+++ b/setup/cli/modules/i18n.php
@@ -15,6 +15,7 @@ class i18n_Compiler extends Module {
                 'list' =>       'Show list of available translations',
                 'build' =>      'Compile a language pack',
                 'make-pot' =>   'Build the PO file for gettext translations',
+                'sign' =>       'Sign a language pack',
             ),
         ),
     );
@@ -25,6 +26,10 @@ class i18n_Compiler extends Module {
             CROWDIN_API_KEY is defined in the ost-config.php file'),
         "lang" => array('-L', '--lang', 'metavar'=>'code',
             'help'=>'Language code (used for building)'),
+        'file' => array('-f', '--file', 'metavar'=>'FILE',
+            'help' => "Language pack to be signed"),
+        'pkey' => array('-P', '--pkey', 'metavar'=>'key-file',
+            'help' => 'Private key for signing'),
     );
 
     static $crowdin_api_url = 'http://i18n.osticket.com/api/project/osticket-official/{command}';
@@ -81,6 +86,11 @@ class i18n_Compiler extends Module {
         case 'make-pot':
             $this->_make_pot();
             break;
+        case 'sign':
+            if (!$options['file'] || !file_exists($options['file']))
+                $this->fail('Specify a language pack to sign with --file=');
+            $this->_sign($options['file'], $options);
+            break;
         }
     }
 
@@ -211,18 +221,67 @@ class i18n_Compiler extends Module {
             }
             $phar->addFromString(
                 'js/osticket-strings.js',
-                sprintf('(function($){$.strings=%s;})(jQuery);',
+                sprintf('(function($){$.oststrings=%s;})(jQuery);',
                     JsonDataEncoder::encode($phrases))
             );
         }
 
+        list($code, $zip) = $this->_request("download/$lang.zip");
+
+        // Include a manifest
+        include_once INCLUDE_DIR . 'class.mailfetch.php';
+
+        $po_header = Mail_Parse::splitHeaders($mo['']);
+        $info = array(
+            'Build-Date' => date(DATE_RFC822),
+            'Build-Version' => trim(`git describe`),
+            'Language' => $po_header['Language'],
+            #'Phrases' =>
+            #'Translated' =>
+            #'Approved' =>
+            'Id' => 'lang:' . $lang,
+            'Version' => strtotime($po_header['PO-Revision-Date']),
+        );
+        $phar->addFromString(
+            'MANIFEST.php',
+            sprintf('<?php return %s;', var_export($info, true)));
+
         // TODO: Sign files
 
         // Use a very small stub
         $phar->setStub('<?php __HALT_COMPILER();');
+        $phar->setSignatureAlgorithm(Phar::SHA1);
         $phar->stopBuffering();
     }
 
+    function _sign($plugin, $options) {
+        if (!file_exists($plugin))
+            $this->fail($plugin.': Cannot find file');
+        elseif (!file_exists("phar://$plugin/MANIFEST.php"))
+            $this->fail($plugin.': Should be a plugin PHAR file');
+        $info = (include "phar://$plugin/MANIFEST.php");
+        $phar = new Phar($plugin);
+
+        if (!function_exists('openssl_get_privatekey'))
+            $this->fail('OpenSSL extension required for signing');
+        $private = openssl_get_privatekey(
+                file_get_contents($options['pkey']));
+        if (!$private)
+            $this->fail('Unable to read private key');
+        $signature = $phar->getSignature();
+        $seal = '';
+        openssl_sign($signature['hash'], $seal, $private,
+            OPENSSL_ALGO_SHA1);
+        if (!$seal)
+            $this->fail('Unable to generate verify signature');
+
+        $this->stdout->write(sprintf("Signature: %s\n",
+            strtolower($signature['hash'])));
+        $this->stdout->write(
+            sprintf("Seal: \"v=1; i=%s; s=%s; V=%s;\"\n",
+            $info['Id'], base64_encode($seal), $info['Version']));
+    }
+
     function __read_next_string($tokens) {
         $string = array();