PHP Classes

File: src/Commands/Key.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   Airship barge   src/Commands/Key.php   Download  
File: src/Commands/Key.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: 7,863 bytes
 

Contents

Class file image Download
<?php
declare(strict_types=1);
namespace
Airship\Barge\Commands;

use \
Airship\Barge as Base;
use \
ParagonIE\Halite\Asymmetric\Crypto as Asymmetric;
use \
ParagonIE\Halite\KeyFactory;

class
Key extends Keygen
{
    public
$essential = false;
    public
$name = 'Key Management';
    public
$description = 'Manage signing keys';
    public
$display = 3;

   
/**
     * Execute the key command
     *
     * @param array $args - CLI arguments
     * @echo
     * @return null
     */
   
public function fire(array $args = [])
    {
       
$argc = \count($args);
        if (
$argc === 0) {
           
parent::fire();
            return;
        }
       
$argPass = \array_slice($args, 1);
        switch (
$args[0]) {
            case
'generate':
               
parent::fire($argPass);
                break;
            case
'revoke':
               
$this->handleKeyRevoke($argPass);
                break;
        }
    }

   
/**
     * We are revoking a key.
     *
     * @param array $args
     * @throws \Exception
     * @return mixed
     */
   
protected function handleKeyRevoke(array $args)
    {
        if (
count($this->config['suppliers']) === 1) {
           
$supplier = \count($args) > 0
               
? $args[0]
                : \
array_keys($this->config['suppliers'])[0];
        } else {
           
$supplier = \count($args) > 0
               
? $args[0]
                :
$this->prompt("Please enter the name of the supplier: ");
        }

        if (!\
array_key_exists($supplier, $this->config['suppliers'])) {
            echo
'Please authenticate before attempting to revoke keys.', "\n";
            echo
'Run this command: ', $this->c['yellow'], 'barge login', $this->c[''], "\n";
            exit(
255);
        }

       
$masterKeys = [];
       
$keyList = [];
        foreach (
$this->config['suppliers'][$supplier]['signing_keys'] as $key) {
            if (
$key['type'] === 'master') {
                if (!empty(
$key['salt'])) {
                   
$masterKeys[] = $key;
                } else {
                   
$keyList[] = $key;
                }
            } else {
               
$keyList[] = $key;
            }
        }
        if (empty(
$masterKeys)) {
            echo
'No usable master keys found. Make sure the salt is loaded locally.', "\n";
            exit(
255);
        }
        if (empty(
$keyList)) {
           
// If and only if you have nothing more to revoke, allow revoking the master key:
           
$keyList = $masterKeys;
        }
        if (\
count($masterKeys) === 1) {
           
$masterKey = $masterKeys[0];
        } else {
           
$masterKey = $this->selectKeyFromList('Select your master key: ', $masterKeys);

           
// Add other master keys to the list
           
foreach ($masterKeys as $key) {
                if (
$key['public_key'] !== $masterKey['public_key']) {
                   
$keyList[] = $key;
                }
            }
        }

        if (\
count($keyList) === 1) {
           
$revokingKey = $keyList[0];
        } else {
           
$revokingKey = $this->selectKeyFromList('Select which key to revoke: ', $keyList);
        }

       
$confirm_revoke = null;
        while (
$confirm_revoke === null) {
           
$choice = $this->prompt('Are you sure you wish to revoke this key? (y/N): ');
            switch (
$choice) {
                case
'YES':
                case
'yes':
                case
'Y':
                case
'y':
                   
$confirm_revoke = true;
                    break;
                case
'N':
                case
'NO':
                case
'n':
                case
'no':
                case
'': // Just pressing enter means "don't store it"!
                   
$confirm_revoke = false;
                    break;
                default:
                    echo
"\n", $this->c['yellow'], 'Invalid response. Please enter yes or no.', $this->c[''], "\n";
            }
        }

       
// This is what get signed by our master key:
       
$message = [
           
'action' =>
               
'REVOKE',
           
'date_revoked' =>
                \
date('Y-m-d\TH:i:s'),
           
'public_key' =>
               
$revokingKey['public_key'],
           
'supplier' =>
               
$supplier
       
];
       
$messageToSign = \json_encode($message);

       
$iter = false;
        do {
            if (
$iter) {
                echo
'Incorrect password.', "\n";
            }
           
$password = $this->silentPrompt('Enter the password for your master key: ');
            if (empty(
$password)) {
               
// Okay, let's cancel.
               
throw new \Exception('Aborted.');
            }
           
$masterKeyPair = KeyFactory::deriveSignatureKeyPair(
               
$password,
                \
Sodium\hex2bin($masterKey['salt']),
               
false,
               
KeyFactory::SENSITIVE
           
);
            \
Sodium\memzero($password);

           
$masterPublicKeyString = \Sodium\bin2hex(
               
$masterKeyPair
                   
->getPublicKey()
                    ->
getRawKeyMaterial()
            );
           
$iter = true;
        } while (!\
hash_equals($masterKey['public_key'], $masterPublicKeyString));

       
$signature = Asymmetric::sign(
           
$messageToSign,
           
$masterKeyPair->getSecretKey()
        );

       
$response = $this->sendRevocation(
           
$supplier,
           
$message,
           
$signature,
           
$masterPublicKeyString
       
);

        if (
$response['status'] === 'OK') {
            foreach (
$this->config['suppliers'][$supplier]['signing_keys'] as $i => $key) {
                if (
$key['public_key'] === $message['public_key']) {
                    unset(
$this->config['suppliers'][$supplier]['signing_keys'][$i]);
                }
            }
        }
        return
$response;
    }

   
/**
     * Send the upstream revocation notice
     *
     * @param string $supplier
     * @param array $data
     * @param string $masterSignature
     * @param string $masterPublicKey
     * @return array
     * @throws \Exception
     */
   
protected function sendRevocation(
       
string $supplier,
        array
$data = [],
       
string $masterSignature,
       
string $masterPublicKey
   
): array {
        list (
$skyport, $publicKey) = $this->getSkyport();

       
$postData = [
           
'token' => $this->getToken($supplier),
           
'message' => $data,
           
'master' => [
               
// Only used for "which key?", don't trust this input
               
'public_key' => $masterPublicKey,
               
// Should validate date_generated and public key
               
'signature' => $masterSignature
           
]
        ];

       
// The user must opt in for this to be invoked:
       
if ($data['store_in_cloud']) {
           
$postData['stored_salt'] = $data['salt'];
        }
        return
Base\HTTP::postSignedJSON(
           
$skyport . 'key/revoke',
           
$publicKey,
           
$postData
       
);
    }

   
/**
     * Select a key from a list
     *
     * @param string $prompt
     * @param array $keys
     * @return array
     * @throws \Exception
     */
   
protected function selectKeyFromList(
       
string $prompt = 'Select one: ',
        array
$keys = []
    ): array {
       
$countKeys = \count($keys);
        while (
true) {
            for (
$i = 1; $i <= $countKeys; ++$i) {
                echo
"\t" . \str_pad($i, 4, ' ', STR_PAD_LEFT) . "\t{$keys[$i]['public_key']}\n";
            }
           
$idx = $this->prompt($prompt);
            if (empty(
$idx)) {
                throw new \
Exception('Aborted.');
            }
           
$idx += 0;
            if (
$idx > 0 && $idx <= $countKeys) {
                return
$keys[$idx - 1];
            }
        }
        throw new \
Exception('Aborted.');
    }
}