Ejemplo n.º 1
0
 /**
  * Fire the add command!
  *
  * @param array $args
  * @return bool
  */
 public function fire(array $args = []) : bool
 {
     try {
         $this->getSession();
         $dir = $this->session['dir'] . $this->findRelativeDir();
     } catch (\Error $e) {
         echo $e->getMessage(), "\n";
         return false;
     }
     if (\count($args) === 0) {
         echo 'No file passed.', "\n";
         return false;
     }
     if (!isset($this->session['add'])) {
         echo 'Creating session data', "\n";
         $this->session['add'] = [];
     }
     $added = 0;
     foreach ($args as $file) {
         $l = Binary::safeStrlen($file) - 1;
         if ($file[$l] === DIRECTORY_SEPARATOR) {
             $file = Binary::safeSubstr($file, 0, -1);
         }
         $added += $this->addFile($file, $dir);
     }
     echo $added, ' file', $added === 1 ? '' : 's', ' added.', "\n";
     return true;
 }
Ejemplo n.º 2
0
 /**
  * SeedSpring constructor.
  *
  * @param string $seed
  * @param int $counter
  */
 public function __construct($seed = '', $counter = 0)
 {
     if (Binary::safeStrlen($seed) !== 16) {
         throw new \InvalidArgumentException('Seed must be 16 bytes');
     }
     $this->seed('set', $seed);
     $this->counter = 0;
 }
Ejemplo n.º 3
0
/**
 * Request a value.
 * @param string $text
 * @return string
 */
function prompt(string $text = '')
{
    static $fp = null;
    if ($fp === null) {
        $fp = \fopen('php://stdin', 'r');
    }
    echo $text;
    return Binary::safeSubstr(\fgets($fp), 0, -1);
}
Ejemplo n.º 4
0
 /**
  * 1. VerifyHMAC-then-Decrypt the ciphertext to get the hash
  * 2. Verify that the password matches the hash
  *
  * @param string $password
  * @param string $ciphertext
  * @param string $aesKey - must be exactly 16 bytes
  * @return bool
  * @throws \Exception
  * @throws \InvalidArgumentException
  */
 public static function decryptAndVerifyLegacy(string $password, string $ciphertext, string $aesKey) : bool
 {
     if (!\is_string($password)) {
         throw new \InvalidArgumentException('Password must be a string.');
     }
     if (Binary::safeStrlen($aesKey) !== 16) {
         throw new \Exception("Encryption keys must be 16 bytes long");
     }
     $hash = Crypto::legacyDecrypt($ciphertext, $aesKey);
     return \password_verify(Base64::encode(\hash('sha256', $password, true)), $hash);
 }
Ejemplo n.º 5
0
 /**
  * Work around poorly-configured web servers by parsing out the GET parameters
  *
  * Be forewarned: this will overwrite $lastPiece
  *
  * @param string& $lastPiece (optional)
  * @return array
  */
 protected function httpGetParams(string &$lastPiece = null) : array
 {
     if ($lastPiece === null) {
         return $_GET ?? [];
     }
     $p = \strpos($lastPiece, '?');
     if ($p !== false && empty($_GET)) {
         $_GET = \Airship\array_from_http_query(Binary::safeSubstr($lastPiece, $p + 1));
         $lastPiece = Binary::safeSubstr($lastPiece, 0, $p);
     }
     return $_GET;
 }
Ejemplo n.º 6
0
 /**
  * FileStore constructor.
  * @param string $baseDirectory
  * @param string $logfileFormat
  * @param string $timeFormat
  */
 public function __construct(string $baseDirectory = '', string $logfileFormat = self::FILE_FORMAT, string $timeFormat = self::TIME_FORMAT)
 {
     if (Binary::safeStrlen($baseDirectory) < 2) {
         $this->basedir = ROOT . '/tmp/logs/';
     } else {
         $this->basedir = $baseDirectory;
     }
     if (!\is_dir($this->basedir)) {
         \mkdir($this->basedir, 0775);
     }
     $this->fileFormat = $logfileFormat;
     $this->timeFormat = $timeFormat;
 }
Ejemplo n.º 7
0
 /**
  * If a file path is absolute, but still in the root, truncate it.
  * If a file path is relative to the root, return it.
  * Otherwise, thorw an error!
  *
  * @param string $file
  * @return string
  * @throws \Error
  */
 protected function getRealPath(string $file) : string
 {
     if (!\file_exists($file)) {
         throw new \Error('File not found: ' . $file);
     }
     if (\strpos($file, $this->session['dir']) === 0) {
         $x = Binary::safeStrlen($this->session['dir']);
         return Binary::safeSubstr($file, $x + 1);
     } elseif ($file[0] !== DIRECTORY_SEPARATOR) {
         return $file;
     } else {
         throw new \Error('File path is outside the root directory: ' . $file);
     }
 }
Ejemplo n.º 8
0
 /**
  * Our nonce logic needs to match OpenSSL's internals.
  */
 public function testCtrModeNonce()
 {
     $seed = random_bytes(16);
     $rnd1 = new SeedSpring($seed);
     $rnd2 = new SeedSpring($seed);
     foreach ([4096, 4097, 8192, 16384, 65536] as $test) {
         $buf1 = '';
         for ($i = 0; $i < $test; $i += 16) {
             $buf1 .= $rnd1->getBytes($i + 16 > $test ? $test - $i : 16);
         }
         $buf2 = $rnd2->getBytes($test);
         $this->assertSame(\ParagonIE\ConstantTime\Binary::safeStrlen($buf1), \ParagonIE\ConstantTime\Binary::safeStrlen($buf2), 'Not the same length - test ' . $test);
         $this->assertSame(bin2hex(substr($buf1, 0, 16)), bin2hex(substr($buf2, 0, 16)), 'AES CTR nonce isn\'t correct - first 16 - test ' . $test);
         $this->assertSame(bin2hex(substr($buf1, 16, 16)), bin2hex(substr($buf2, 16, 16)), 'AES CTR nonce isn\'t correct - next 16 - test ' . $test);
         $this->assertSame(bin2hex(substr($buf1, -16, 16)), bin2hex(substr($buf2, -16, 16)), 'AES CTR nonce isn\'t correct - last 16 - test ' . $test);
     }
 }
Ejemplo n.º 9
0
 /**
  * The homepage for an Airship.
  *
  * @route /
  */
 public function index()
 {
     $this->blog = $this->blueprint('Blog');
     if (!\file_exists(ROOT . '/public/robots.txt')) {
         // Default robots.txt
         \file_put_contents(ROOT . '/public/robots.txt', "User-agent: *\nAllow: /");
     }
     $blogRoll = $this->blog->recentFullPosts((int) ($this->config('homepage.blog-posts') ?? 5));
     $mathJAX = false;
     foreach ($blogRoll as $i => $blog) {
         $blogRoll[$i] = $this->blog->getSnippet($blog);
         if (Binary::safeStrlen($blogRoll[$i]['snippet']) !== Binary::safeStrlen($blog['body'])) {
             $blogRoll[$i]['snippet'] = \rtrim($blogRoll[$i]['snippet'], "\n");
         }
         $mathJAX |= \strpos($blog['body'], '$$') !== false;
     }
     $args = ['blogposts' => $blogRoll];
     $this->config('blog.cachelists') ? $this->stasis('index', $args) : $this->lens('index', $args);
 }
Ejemplo n.º 10
0
 /**
  * Load all of the supplier's Ed25519 public keys
  *
  * @param string $supplier
  * @param boolean $force_flush
  * @return SupplierObject|SupplierObject[]
  * @throws NoSupplier
  */
 public function getSupplier(string $supplier = '', bool $force_flush = false)
 {
     if (empty($supplier)) {
         // Fetch all suppliers
         if ($force_flush || empty($this->supplierCache)) {
             $supplierCache = [];
             $allSuppliers = \Airship\list_all_files(ROOT . '/config/supplier_keys', 'json');
             foreach ($allSuppliers as $supplierKeyFile) {
                 // We want everything except the .json
                 $supplier = $this->escapeSupplierName(Binary::safeSubstr($this->getEndPiece($supplierKeyFile), 0, -5));
                 try {
                     $data = \Airship\loadJSON($supplierKeyFile);
                 } catch (FileNotFound $ex) {
                     $data = [];
                 }
                 $supplierCache[$supplier] = new SupplierObject($supplier, $data);
             }
             $this->supplierCache = $supplierCache;
         }
         return $this->supplierCache;
     }
     // Otherwise, we're just fetching one supplier's keys
     if ($force_flush || empty($this->supplierCache[$supplier])) {
         try {
             $supplier = $this->escapeSupplierName($supplier);
             $supplierFile = ROOT . '/config/supplier_keys/' . $supplier . '.json';
             if (!\file_exists($supplierFile)) {
                 throw new NoSupplier(\__("Supplier file not found: %s", "default", $supplierFile));
             }
             $data = \Airship\loadJSON($supplierFile);
         } catch (FileNotFound $ex) {
             throw new NoSupplier(\__("Supplier not found: %s", "default", $supplier), 0, $ex);
         }
         $this->supplierCache[$supplier] = new SupplierObject($supplier, $data);
     }
     if (isset($this->supplierCache[$supplier])) {
         return $this->supplierCache[$supplier];
     }
     throw new NoSupplier();
 }
Ejemplo n.º 11
0
 /**
  * Blog post home
  *
  * @route /
  */
 public function index()
 {
     $blogRoll = $this->blog->recentFullPosts((int) $this->config('homepage.blog-posts') ?? 5);
     $mathJAX = false;
     foreach ($blogRoll as $i => $blog) {
         $blogRoll[$i] = $this->blog->getSnippet($blog);
         if (Binary::safeStrlen($blogRoll[$i]['snippet']) !== Binary::safeStrlen($blog['body'])) {
             $blogRoll[$i]['snippet'] = \rtrim($blogRoll[$i]['snippet'], "\n");
         }
         $mathJAX = $mathJAX || \strpos($blog['body'], '$$') !== false;
     }
     $args = ['pageTitle' => \__('Blog'), 'blogroll' => $blogRoll, 'mathjax' => $mathJAX];
     $this->config('blog.cachelists') ? $this->stasis('blog/index', $args) : $this->lens('blog/index', $args);
 }
Ejemplo n.º 12
0
 /**
  * WordPress's internal password hashing algorithm. Only used for migrations.
  * The actual security of CMS Airship doesn't depend on this algorithm.
  *
  * @internal
  * @param HiddenString $password
  * @param string $setting
  * @return string
  */
 private function wordPressCryptPrivate(HiddenString $password, string $setting) : string
 {
     $output = '*0';
     if (Binary::safeSubstr($setting, 0, 2) === $output) {
         $output = '*1';
     }
     $id = Binary::safeSubstr($setting, 0, 3);
     if ($id !== '$P$' && $id !== '$H$') {
         return $output;
     }
     // This is a really weird way to encode iteration count.
     $count_log2 = \strpos($this->itoa64, $setting[3]);
     if ($count_log2 < 7 || $count_log2 > 30) {
         return $output;
     }
     $count = 1 << $count_log2;
     $salt = Binary::safeSubstr($setting, 4, 8);
     if (Binary::safeStrlen($salt) !== 8) {
         return $output;
     }
     // And now we do our (default 8192) rounds of MD5...
     $hash = \md5($salt . $password->getString(), true);
     do {
         $hash = \md5($hash . $password->getString(), true);
     } while (--$count);
     $output = Binary::safeSubstr($setting, 0, 12);
     $output .= $this->encode64($hash, 16);
     return $output;
 }
Ejemplo n.º 13
0
 /**
  * Binary-safe strlen() implementation
  *
  * @param string $str
  * @return int
  */
 public static function stringLength(string $str) : int
 {
     return Binary::safeStrlen($str);
 }
Ejemplo n.º 14
0
/**
 * Configure the application event logger here
 */
$log_setup_closure = function () {
    $state = State::instance();
    $loggerClass = Gears::getName('Ledger');
    $args = [];
    /**
     * Here we build our logger storage class
     */
    switch ($state->universal['ledger']['driver']) {
        case 'file':
            $path = $state->universal['ledger']['path'];
            if (Binary::safeStrlen($path) >= 2) {
                if ($path[0] === '~' && $path[1] === '/') {
                    $path = ROOT . '/' . Binary::safeSubstr($path, 2);
                }
            }
            $storage = new FileStore($path, $state->universal['ledger']['file-format'] ?? FileStore::FILE_FORMAT, $state->universal['ledger']['time-format'] ?? FileStore::TIME_FORMAT);
            break;
        case 'database':
            $path = $state->universal['ledger']['connection'];
            try {
                $storage = new DBStore($path, $state->universal['ledger']['table'] ?? DBStore::DEFAULT_TABLE);
            } catch (\Throwable $ex) {
                \http_response_code(500);
                echo \file_get_contents(__DIR__ . '/error_pages/uncaught-exception.html');
                if ($state->universal['debug']) {
                    echo '<pre>', $ex->getTraceAsString(), '</pre>';
                }
                exit(1);
Ejemplo n.º 15
0
 /**
  * @covers \Airship\uniqueId()
  */
 public function testUniqueId()
 {
     $strings = [];
     for ($i = 0; $i < 1024; ++$i) {
         $strings[] = \Airship\uniqueId(33);
     }
     $this->assertSame($strings, \array_unique($strings), 'Collision!');
     for ($i = 18; $i < 30; ++$i) {
         $unique = \trim(\Airship\uniqueId($i), '=');
         $this->assertSame($i, Binary::safeStrlen($unique));
     }
 }
Ejemplo n.º 16
0
<?php

declare (strict_types=1);
use ParagonIE\ConstantTime\Binary;
/**
 * Paragon Initiative Enterprises
 * PSR-4 compatible autoloader
 */
\spl_autoload_register(function ($class) {
    // Project-specific namespace prefix
    $prefix = 'Airship\\Hangar';
    // Base directory for the namespace prefix
    $base_dir = __DIR__ . DIRECTORY_SEPARATOR;
    // Does the class use the namespace prefix?
    $len = \strlen($prefix);
    if (\strncmp($prefix, $class, $len) !== 0) {
        // no, move to the next registered autoloader
        return;
    }
    // Get the relative class name
    $relative_class = Binary::safeSubstr($class, $len);
    // Replace the namespace prefix with the base directory, replace namespace
    // separators with directory separators in the relative class name, append
    // with .php
    $file = $base_dir . \str_replace(['\\', '_'], DIRECTORY_SEPARATOR, $relative_class) . '.php';
    // If the file exists, require it
    if (\file_exists($file)) {
        require $file;
    }
});
Ejemplo n.º 17
0
}
/**
 * Let the user know precisely what's wrong, if anything is wrong.
 */
if (!Halite::isLibsodiumSetupCorrectly()) {
    // Easiest way to grab this info:
    \ob_start();
    \phpinfo();
    $data = \ob_get_clean();
    $version = '';
    foreach (\explode("\n", $data) as $line) {
        if (empty($line)) {
            continue;
        }
        if (\strpos($line, 'libsodium compiled version') !== false) {
            $version = \trim(Binary::safeSubstr(\trim($line), -6));
            break;
        }
    }
    die("Your libsodium is not setup correctly. Please make sure you have at least:\n\n" . "\tlibsodium     v1.0.10 (Installed: " . \Sodium\version_string() . ")\n" . "\tlibsodium-php v1.0.6  (Installed: " . $version . ")\n");
}
/**
 * 3. Process the CLI parameters
 */
$showAll = true;
if ($argc < 2) {
    // Default behavior: Display the help menu
    $argv[1] = 'help';
    $showAll = false;
    $argc = 2;
}
Ejemplo n.º 18
0
    $link = ROOT . '/public/static/' . $active['name'];
    if (!\is_link($link)) {
        // Remove copies, we only allow symlinks in static
        if (\is_dir($link)) {
            \rmdir($link);
        } elseif (\file_exists($link)) {
            \unlink($link);
        }
        // Create a symlink from public/static/* to Cabin/*/public
        /** @noinspection PhpUsageOfSilenceOperatorInspection */
        @\symlink(CABIN_DIR . '/public', ROOT . '/public/static/' . $active['name']);
    }
}
// Let's load the default cargo modules
if (\is_dir(CABIN_DIR . '/Lens/cargo')) {
    $cargoCacheFile = ROOT . '/tmp/cache/cargo-' . $active['name'] . '.cache.json';
    if (\file_exists($cargoCacheFile)) {
        $data = Airship\loadJSON($cargoCacheFile);
        $state->cargo = $data;
    } else {
        $dir = \getcwd();
        \chdir(CABIN_DIR . '/Lens');
        foreach (\Airship\list_all_files('cargo', 'twig') as $cargo) {
            $idx = \str_replace(['__', '/'], ['', '__'], Binary::safeSubstr($cargo, 6, -5));
            Gadgets::loadCargo($idx, $cargo);
        }
        \chdir($dir);
        // Store the cache file
        \Airship\saveJSON($cargoCacheFile, $state->cargo);
    }
}
Ejemplo n.º 19
0
 /**
  * Get a preview snippet of a blog post
  *
  * @param array $post Post data
  * @param boolean $after Do we want the content after the fold?
  * @return array
  */
 public function getSnippet(array $post, bool $after = false) : array
 {
     // First, let's try cutting it off by section...
     if (empty($post['body'])) {
         return $post;
     }
     $i = 0;
     if ($post['format'] === 'Rich Text') {
         $post['body'] = \str_replace('><', '>' . "\n" . '<', $post['body']);
     }
     // Just in case:
     $post['body'] = \str_replace("\r\n", "\n", $post['body']);
     $lines = \explode("\n", $post['body']);
     // If we find <!--FOLD--> in the body, split on that instead:
     $search = \array_search($this->defaultSeparator, $lines);
     if ($search !== false) {
         $post['snippet'] = \implode("\n", \array_slice($lines, 0, $search - 1));
         if ($after) {
             $post['after_fold'] = \implode("\n", \array_slice($lines, $search));
         }
         return $post;
     }
     // Short post? Just dump it as-is:
     if (count($lines) < 4 || Binary::safeStrlen($post['body']) < 200) {
         $post['snippet'] = $post['body'];
         $post['after_fold'] = '';
         return $post;
     }
     $cutoff = null;
     $regex = '#^([' . \preg_quote('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~', '#') . '])#';
     foreach ($lines as $i => $line) {
         if (empty($line)) {
             continue;
         }
         if ($post['format'] === 'RST') {
             if (\preg_match($regex, $line[0], $m)) {
                 if ($i > 2 && \trim($line) === \str_repeat($m[1], Binary::safeStrlen($line))) {
                     $cutoff = $i;
                     break;
                 }
             }
         } elseif ($post['format'] === 'Markdown') {
             if (\preg_match('#^([' . \preg_quote('#', '#') . ']{1,})#', $line[0], $m)) {
                 $cutoff = $i;
                 break;
             }
         } elseif ($post['format'] === 'HTML' || $post['format'] === 'Rich Text') {
             if (\preg_match('#^<' . 'h[1-7]>#', $line[0], $m)) {
                 $cutoff = $i;
                 break;
             }
         }
     }
     if ($cutoff !== null) {
         $post['snippet'] = \implode("\n", \array_slice($lines, 0, $i - 1));
         if ($after) {
             $post['after_fold'] = \implode("\n", \array_slice($lines, $i - 1));
         }
         return $post;
     }
     // Next, let's find the 37% mark for breaks
     $split = $post['format'] === 'Rich Text' || $post['format'] === 'HTML' ? "\n" : "\n\n";
     $sects = \explode($split, $post['body']);
     // 37% is approximately 1/e (the mathematical constant)
     $cut = (int) \ceil(0.37 * \count($sects));
     if ($sects < 2) {
         $post['snippet'] = $post['body'];
         $post['after_fold'] = '';
         return $post;
     }
     if (\preg_match('#^\\.\\. #', $sects[$cut - 1])) {
         --$cut;
     }
     $post['snippet'] = \implode("\n\n", \array_slice($sects, 0, $cut - 1)) . "\n";
     if ($after) {
         $post['after_fold'] = "\n" . \implode($split, \array_slice($sects, $cut - 1));
     }
     return $post;
 }
Ejemplo n.º 20
0
<?php

declare (strict_types=1);
use ParagonIE\ConstantTime\Binary;
require_once \dirname(__DIR__) . '/src/bootstrap.php';
/**
 * Grabs a random file and tells you to audit it.
 */
if ($argc > 1) {
    $extensions = \array_slice($argv, 1);
} else {
    $extensions = ['php', 'twig'];
}
$fileList = [];
foreach ($extensions as $ex) {
    foreach (\Airship\list_all_files(\dirname(__DIR__) . '/src/', $ex) as $file) {
        $fileList[] = $file;
    }
}
$choice = \random_int(0, \count($fileList) - 1);
echo "Audit this file:\n\t";
$l = Binary::safeStrlen(\dirname(__DIR__));
echo Binary::safeSubstr($fileList[$choice], $l), "\n";
Ejemplo n.º 21
0
// Are we still installing?
/** @noinspection PhpUsageOfSilenceOperatorInspection */
if (@\is_readable(dirname(__DIR__) . '/tmp/installing.json') || !\file_exists(dirname(__DIR__) . '/config/databases.json')) {
    include dirname(__DIR__) . '/Installer/launch.php';
    exit;
}
/**
 * Load the bare minimum:
 */
require_once \dirname(__DIR__) . '/preload.php';
$start = \microtime(true);
if (empty($_POST)) {
    /**
     * Let's get rid of trailing slashes in URLs without POST data
     */
    $sliceAt = Binary::safeStrlen($_SERVER['REQUEST_URI']) - 1;
    if ($sliceAt > 0 && $_SERVER['REQUEST_URI'][$sliceAt] === '/') {
        \Airship\redirect('/' . \trim($_SERVER['REQUEST_URI'], '/'));
    }
    /**
     * Let's handle static content caching
     */
    if (\extension_loaded('apcu')) {
        $staticCache = (new MemoryCache())->personalize('staticPage:');
        $cspCache = (new MemoryCache())->personalize('contentSecurityPolicy:');
    } else {
        if (!\is_dir(ROOT . '/tmp/cache/static')) {
            require_once ROOT . '/tmp_dirs.php';
        }
        $staticCache = new FileCache(ROOT . '/tmp/cache/static');
        $cspCache = new FileCache(ROOT . '/tmp/cache/csp_static');
Ejemplo n.º 22
0
 /**
  * Actually serve the HTTP request
  */
 public function route()
 {
     $this->loadInjectedRoutes();
     $args = [];
     foreach ($this->cabin['data']['routes'] as $path => $landing) {
         $path = self::makePath($path);
         if (self::testLanding($path, $_SERVER['REQUEST_URI'], $args)) {
             self::$mypath = $path;
             self::$path = Binary::safeSubstr($_SERVER['REQUEST_URI'], Binary::safeStrlen(self::$patternPrefix) + 1);
             try {
                 // Attempt to serve the page:
                 return $this->serve($landing, \array_slice($args, 1));
             } catch (EmulatePageNotFound $ex) {
                 // If this exception is throw, we will attempt to serve
                 // the fallback route (which might end up with a 404 page)
                 return $this->serveFallback();
             }
         }
     }
     return $this->serveFallback();
 }
Ejemplo n.º 23
0
 /**
  * Coerce a string into base64 format.
  *
  * @param string $hash
  * @param string $algo
  * @return string
  * @throws \Exception
  */
 protected function coerceBase64(string $hash, string $algo = 'sha256') : string
 {
     switch ($algo) {
         case 'sha256':
             $limits = ['raw' => 32, 'hex' => 64, 'pad_min' => 40, 'pad_max' => 44];
             break;
         default:
             throw new \Exception('Browsers currently only support sha256 public key pins.');
     }
     $len = Binary::safeStrlen($hash);
     if ($len === $limits['hex']) {
         $hash = Base64::encode(Hex::decode($hash));
     } elseif ($len === $limits['raw']) {
         $hash = Base64::encode($hash);
     } elseif ($len > $limits['pad_min'] && $len < $limits['pad_max']) {
         // Padding was stripped!
         $hash .= \str_repeat('=', $len % 4);
         // Base64UrlSsafe encoded.
         if (\strpos($hash, '_') !== false || \strpos($hash, '-') !== false) {
             $hash = Base64UrlSafe::decode($hash);
         } else {
             $hash = Base64::decode($hash);
         }
         $hash = Base64::encode($hash);
     }
     return $hash;
 }