PHP Classes

File: src/Commands/Sign.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   Airship barge   src/Commands/Sign.php   Download  
File: src/Commands/Sign.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Airship barge
Build extensions for the Airship CMS
Author: By
Last change:
Date: 5 years ago
Size: 9,797 bytes
 

Contents

Class file image Download
<?php
namespace Airship\Barge\Commands;

use \
Airship\Barge as Base;
use
ParagonIE\ConstantTime\Base64UrlSafe;
use \
ParagonIE\Halite\{
   
File,
   
KeyFactory,
   
Asymmetric\SignatureSecretKey
};

/**
 * Class Sign
 *
 * This command allows you to sign a cabin, gadget, or motif
 * with one of your signing keys.
 *
 * @package Airship\Barge\Commands
 */
class Sign extends Base\Command
{
    protected
$signWithMasterKeys = false;
    public
$essential = true;
    public
$name = 'Sign';
    public
$description = 'Digitally sign the current Cabin/Gadget/Motif.';
    public
$display = 3;
   
   
/**
     * Execute the sign command
     *
     * @param array $args - CLI arguments
     * @echo
     * @return null
     */
   
public function fire(array $args = [])
    {
       
$path = \count($args) > 0
           
? $args[0]
            : \
getcwd();

        if (\
count($args) > 1) {
           
// Not enabled by default.
           
if (\in_array('--sign-with-master', \array_slice($args, 1))) {
               
$this->signWithMasterKeys = true;
            }
        }

       
// Cabins:
       
if (\is_readable($path.'/cabin.json')) {
           
$manifest = \json_decode(
                \
file_get_contents($path.'/cabin.json'),
               
true
           
);
            return
$this->signCabin($manifest, $path);
        }

       
// Gadgets:
       
if (\is_readable($path.'/gadget.json')) {
           
$manifest = \json_decode(
                \
file_get_contents($path.'/gadget.json'),
               
true
           
);
            return
$this->signGadget($manifest, $path);
        }

       
// Motifs:
       
if (\is_readable($path.'/src/motif.json')) {
           
$manifest = \json_decode(
                \
file_get_contents($path.'/src/motif.json'),
               
true
           
);
            return
$this->signMotif($manifest, $path);
        }

        echo
'Could not find manifest file.', "\n";
        exit(
255);
    }

   
/**
     * Common signing process. User selects key, provides password.
     *
     * @param array $manifest
     * @return SignatureSecretKey
     * @throws \Exception
     */
   
protected function signPreamble(array $manifest): SignatureSecretKey
   
{
       
$HTAB = \str_repeat(' ', \intdiv(self::TAB_SIZE, 2));

       
$supplier_name = $manifest['supplier'];

       
// Sanity checks:
       
if (!\array_key_exists('suppliers', $this->config)) {
            echo
'You are not authenticated for any suppliers.', "\n";
            exit(
255);
        }
        if (!\
array_key_exists($supplier_name, $this->config['suppliers'])) {
            echo
'Check the supplier in the JSON file (', $supplier_name, ') for correctness.',
               
'Otherwise, you might need to log in.', "\n";
            exit(
255);
        }

       
$supplier = $this->config['suppliers'][$supplier_name];
       
$numKeys = 0;

        if (
$this->signWithMasterKeys) {
           
$good_keys = [];
           
// This should really not be used:
           
$numKeys = \count($supplier['signing_keys']);
            foreach (
$supplier['signing_keys'] as $k) {
                if (!empty(
$k['salt'])) {
                   
$good_keys[] = $k;
                    ++
$numKeys;
                }
            }
        } else {
           
// This should be used instead:
           
$good_keys = [];
            foreach (
$supplier['signing_keys'] as $k) {
                if (
$k['type'] === 'signing' && !empty($k['salt'])) {
                   
$good_keys[] = $k;
                    ++
$numKeys;
                }
            }
        }
        if (
$numKeys > 1) {
            echo
'You have more than one signing key available.', "\n";

           
$n = 1;
           
$size = (int) \floor(
                \
log($numKeys, 10)
            );
           
$key_associations = $HTAB."ID\t Public Key " . \str_repeat(' ', 33) . "\t Type\n";
            foreach (
$supplier['signing_keys'] as $sign_key) {
                if (!
$this->signWithMasterKeys && $sign_key['type'] === 'master') {
                    continue;
                }
               
$_n = \str_pad($n, $size, ' ', STR_PAD_LEFT);

               
// Short format:
               
$pk = Base64UrlSafe::encode(
                    \
Sodium\hex2bin($sign_key['public_key'])
                );
               
$key_associations .= $HTAB . $_n . $HTAB . $pk . $HTAB . $sign_key['type'] . "\n";
                ++
$n;
            }
           
// Let's ascertain the user's key selection
           
do {
                echo
$key_associations;
               
$choice = (int) $this->prompt('Enter the ID for the key you wish to use: ');
                if (
$choice < 1 || $choice > $numKeys) {
                   
$choice = null;
                }
            } while (empty(
$choice));
           
$supplierKey = $good_keys[$choice - 1];
            echo
"\n";
        } else {
           
$supplierKey = $good_keys[0];
        }

       
// The above !empty($k['salt']) check should have rendered this check redundant:
       
if (empty($supplierKey['salt'])) {
            echo
'Salt not found for this key. It is not possible to reproduce it.', "\n";
            exit(
255);
        }

       
// Short format:
       
$pk = Base64UrlSafe::encode(
            \
Sodium\hex2bin($supplierKey['public_key'])
        );

       
// Color coded: Master keys are red, since they take longer.
        // We don't support signing packages with a master key, but
        // this decision could be undone in the future.
       
$c = $supplierKey['type'] === 'master'
           
? $this->c['red']
            :
$this->c['yellow'];

        echo
'Selected ', $supplierKey['type'], ' key: ', $c, $pk, $this->c[''], "\n";

       
$password = $this->silentPrompt('Enter Password for Signing Key:');

       
// Derive and split the SignatureKeyPair from your password and salt
       
$salt = \Sodium\hex2bin($supplierKey['salt']);
        switch (
$supplierKey['type']) {
            case
'signing':
               
$type = KeyFactory::MODERATE;
                echo
'Verifying (this may take a second or two)...';
                break;
            case
'master':
               
$type = KeyFactory::SENSITIVE;
                echo
'Verifying (this may take a few seconds)...';
                break;
            default:
               
$type = KeyFactory::INTERACTIVE;
                echo
'Verifying...';
        }
       
$keyPair = KeyFactory::deriveSignatureKeyPair(
           
$password,
           
$salt,
           
false,
           
$type
       
);
       
$sign_secret = $keyPair->getSecretKey();
       
$sign_public = $keyPair->getPublicKey();
        echo
' Done.', "\n";

       
// We don't need this anymore.
       
\Sodium\memzero($password);

       
// Check that the public key we derived from the password matches the one on file
       
$pubKey = \Sodium\bin2hex($sign_public->getRawKeyMaterial());
        if (!\
hash_equals($supplierKey['public_key'], $pubKey)) {
           
// Zero the memory ASAP
           
unset($sign_secret);
            unset(
$sign_public);
            echo
'Invalid password for selected key', "\n";
            exit(
255);
        }
       
// Zero the memory ASAP
       
unset($sign_public);

        return
$sign_secret;
    }

   
/**
     * Sign a cabin
     *
     * @param array $manifest
     * @param string $path
     */
   
protected function signCabin(array $manifest, string $path)
    {
       
$pharName = $manifest['supplier'].'.'.$manifest['name'].'.phar';
       
$sign_secret = $this->signPreamble($manifest);

       
// This is the actual signing part.
       
$signature = File::sign(
           
$path.'/dist/'.$pharName,
           
$sign_secret
       
);
       
// We no longer need this, so unset it. Halite will zero the buffer for us.
       
unset($sign_secret);

       
$res = \file_put_contents(
           
$path.'/dist/'.$pharName.'.ed25519.sig',
           
$signature
       
);
        if (
$res !== false) {
            echo
'Signed: ', $path, '/dist/', $pharName, '.ed25519.sig', "\n";
            exit(
0);
        }
    }
   
   
/**
     * Sign a gadget
     *
     * @param array $manifest
     * @param string $path
     */
   
protected function signGadget(array $manifest, string $path)
    {
       
$pharName = $manifest['supplier'].'.'.$manifest['name'].'.phar';
       
$sign_secret = $this->signPreamble($manifest);

       
// This is the actual signing part.
       
$signature = File::sign(
           
$path.'/dist/'.$pharName,
           
$sign_secret
       
);
       
// We no longer need this, so unset it. Halite will zero the buffer for us.
       
unset($sign_secret);
       
       
$res = \file_put_contents(
           
$path.'/dist/'.$pharName.'.ed25519.sig',
           
$signature
       
);
        if (
$res !== false) {
            echo
'Signed: ', $path, '/dist/', $pharName, '.ed25519.sig', "\n";
            exit(
0);
        }
    }

   
/**
     * Sign a motif
     *
     * @param array $manifest
     * @param string $path
     */
   
protected function signMotif(array $manifest, string $path)
    {
       
$zipName = $manifest['supplier'].'.'.$manifest['name'].'.zip';
       
$sign_secret = $this->signPreamble($manifest);

       
// This is the actual signing part.
       
$signature = File::sign(
           
$path.'/dist/'.$zipName,
           
$sign_secret
       
);
       
// We no longer need this, so unset it. Halite will zero the buffer for us.
       
unset($sign_secret);

       
$res = \file_put_contents(
           
$path.'/dist/'.$zipName.'.ed25519.sig',
           
$signature
       
);
        if (
$res !== false) {
            echo
'Signed: ', $path, '/dist/', $zipName, '.ed25519.sig', "\n";
            exit(
0);
        }
    }
}