File: src/GPGMailer.php

Recommend this page to a friend!
  Classes of Scott Arciszewski  >  PHP GPG Email Encrypt  >  src/GPGMailer.php  >  Download  
File: src/GPGMailer.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PHP GPG Email Encrypt
Encrypt, decrypt and sign email messages with GPG
Author: By
Last change: Update to Laminas
Date: 1 month ago
Size: 11,102 bytes
 

Contents

Class file image Download
<?php
declare(strict_types=1);
namespace ParagonIE\GPGMailer;

use Laminas\Mail\{
    Message,
    Transport\TransportInterface
};

/**
 * Class GPGMailer
 * @package ParagonIE\GPGMailer
 */
class GPGMailer
{
    /**
     * @var TransportInterface
     */
    protected $mailer;

    /**
     * @var array<string, string|int|float|bool|null|array>
     */
    protected $options;

    /**
     * @var string
     */
    protected $serverKeyFingerprint = '';

    /**
     * GPGMailer constructor.
     *
     * @param TransportInterface $transport
     * @param array<string, string|int|float|bool|null|array> $options
     *        For Crypt_GPG
     * @param string $serverKey
     *
     * @throws GPGMailerException
     */
    public function __construct(
        TransportInterface $transport,
        array $options = [],
        string $serverKey = ''
    ) {
        $this->mailer = $transport;
        $this->options = $options;
        if (!empty($serverKey)) {
            $this->serverKeyFingerprint = $this->import($serverKey);
        }
    }

    /**
     * Get the public key corresponding to a fingerprint.
     *
     * @param string $fingerprint
     * @return string
     * @throws \Crypt_GPG_Exception
     * @throws \Crypt_GPG_FileException
     * @throws GPGMailerException
     * @throws \PEAR_Exception
     */
    public function export(string $fingerprint): string
    {
        $gnupg = new \Crypt_GPG($this->options);
        try {
            $gnupg->addEncryptKey($fingerprint);
            return $gnupg->exportPublicKey($fingerprint, true);
        } catch (\PEAR_Exception $ex) {
            throw new GPGMailerException(
                'Could not export fingerprint "' . $fingerprint . '": ' .
                    $ex->getMessage(),
                (int) $ex->getCode(),
                $ex
            );
        }
    }

    /**
     * Encrypt the body of an email.
     *
     * @param Message $message
     * @return Message
     * @throws \Crypt_GPG_FileException
     * @throws GPGMailerException
     * @throws \PEAR_Exception
     */
    public function decrypt(Message $message): Message
    {
        $gnupg = new \Crypt_GPG($this->options);

        try {
            $gnupg->addDecryptKey($this->serverKeyFingerprint);
            // Replace the message with its encrypted counterpart
            $decrypted = $gnupg->decrypt($message->getBodyText());
            return (clone $message)->setBody($decrypted);
        } catch (\PEAR_Exception $ex) {
            throw new GPGMailerException(
                'Could not decrypt message: ' . $ex->getMessage(),
                (int) $ex->getCode(),
                $ex
            );
        }
    }

    /**
     * Encrypt the body of an email.
     *
     * @param Message $message
     * @param string $fingerprint
     * @return Message
     * @throws \Crypt_GPG_Exception
     * @throws \Crypt_GPG_FileException
     * @throws GPGMailerException
     * @throws \PEAR_Exception
     */
    public function encrypt(Message $message, string $fingerprint): Message
    {
        $gnupg = new \Crypt_GPG($this->options);
        try {
            $gnupg->addEncryptKey($fingerprint);

            // Replace the message with its encrypted counterpart
            $encrypted = $gnupg->encrypt($message->getBodyText(), true);

            return (clone $message)->setBody($encrypted);
        } catch (\PEAR_Exception $ex) {
            throw new GPGMailerException(
                'Could not encrypt message: ' . $ex->getMessage(),
                (int) $ex->getCode(),
                $ex
            );
        }
    }

    /**
     * Encrypt and sign the body of an email.
     *
     * @param Message $message
     * @param string $fingerprint
     * @return Message
     * @throws GPGMailerException
     * @throws \Crypt_GPG_Exception
     * @throws \Crypt_GPG_FileException
     * @throws \PEAR_Exception
     */
    public function encryptAndSign(Message $message, string $fingerprint): Message
    {
        if (!$this->serverKeyFingerprint) {
            throw new GPGMailerException('No signing key provided');
        }
        $gnupg = new \Crypt_GPG($this->options);
        $gnupg->addEncryptKey($fingerprint);
        $gnupg->addSignKey($this->serverKeyFingerprint);

        try {
            // Replace the message with its encrypted counterpart
            $encrypted = $gnupg->encryptAndSign($message->getBodyText(), true);

            return (clone $message)->setBody($encrypted);
        } catch (\PEAR_Exception $ex) {
            throw new GPGMailerException(
                'Could not encrypt and sign message: ' . $ex->getMessage(),
                (int) $ex->getCode(),
                $ex
            );
        }
    }

    /**
     * Return the stored Transport.
     *
     * @return TransportInterface
     */
    public function getTransport(): TransportInterface
    {
        return $this->getTransport();
    }

    /**
     * Import a public key, return the fingerprint
     *
     * @param string $gpgKey An ASCII armored public key
     * @return string The GPG fingerprint for this key
     * @throws GPGMailerException
     */
    public function import(string $gpgKey): string
    {
        try {
            $gnupg = new \Crypt_GPG($this->options);
            /** @var array<string, string> $data */
            $data = $gnupg->importKey($gpgKey);

            return $data['fingerprint'];
        } catch (\PEAR_Exception $ex) {
            throw new GPGMailerException(
                'Could not import public key: ' . $ex->getMessage(),
                (int) $ex->getCode(),
                $ex
            );
        }
    }

    /**
     * Encrypt then email a message
     *
     * @param Message $message    The message data
     * @param string $fingerprint Which public key fingerprint to use
     * @return void
     *
     * @throws \Crypt_GPG_Exception
     * @throws \Crypt_GPG_FileException
     * @throws GPGMailerException
     * @throws \PEAR_Exception
     */
    public function send(Message $message, string $fingerprint)
    {
        if ($this->serverKeyFingerprint) {
            $this->mailer->send(
                // Encrypted, signed
                $this->encryptAndSign($message, $fingerprint)
            );
        } else {
            $this->mailer->send(
                // Encrypted, unsigned
                $this->encrypt($message, $fingerprint)
            );
        }
    }

    /**
     * Email a message without encrypting it.
     *
     * @param Message $message The message data
     * @param bool $force      Send even if we don't have a private key?
     * @return void
     *
     * @throws GPGMailerException
     * @throws \Crypt_GPG_FileException
     * @throws \PEAR_Exception
     */
    public function sendUnencrypted(Message $message, bool $force = false)
    {
        if (!$this->serverKeyFingerprint) {
            if ($force) {
                // Unencrypted, unsigned
                $message->setBody($message->getBodyText());
            }
            return;
        }
        $this->mailer->send(
            // Unencrypted, signed:
            $this->sign($message)
        );
    }

    /**
     * @param string $key
     * @return string|int|float|bool|null|array
     *
     * @throws GPGMailerException
     */
    public function getOption(string $key)
    {
        if (!\array_key_exists($key, $this->options)) {
            throw new GPGMailerException('Key ' . $key . ' not defined');
        }
        return $this->options[$key];
    }

    /**
     * @return array<string, string|int|float|bool|null|array>
     */
    public function getOptions(): array
    {
        return $this->options;
    }

    /**
     * Override an option at runtime
     *
     * @param string $key
     * @param string|int|float|bool|null|array $value
     *
     * @return self
     * @throws GPGMailerException
     */
    public function setOption(string $key, $value): self
    {
        $options = $this->options;
        $options[$key] = $value;
        // Try to set this, so it will throw an exception
        try {
            (new \Crypt_GPG($options));
        } catch (\PEAR_Exception $ex) {
            throw new GPGMailerException(
                'Could not set option "' . $key . '": ' . $ex->getMessage(),
                (int) $ex->getCode(),
                $ex
            );
        }
        $this->options = $options;
        return $this;
    }

    /**
     * Sets the private key for signing.
     *
     * @param string $serverKey
     * @return self
     * @throws GPGMailerException
     */
    public function setPrivateKey(string $serverKey): self
    {
        $this->serverKeyFingerprint = $this->import($serverKey);
        return $this;
    }

    /**
     * @param TransportInterface $transport
     *
     * @return self
     */
    public function setTransport(TransportInterface $transport): self
    {
        $this->mailer = $transport;
        return $this;
    }

    /**
     * Sign a message (but don't encrypt)
     *
     * @param Message $message
     * @return Message
     *
     * @throws \Crypt_GPG_FileException
     * @throws GPGMailerException
     * @throws \PEAR_Exception
     */
    public function sign(Message $message): Message
    {
        if (!$this->serverKeyFingerprint) {
            throw new GPGMailerException('No signing key provided');
        }
        $gnupg = new \Crypt_GPG($this->options);
        $gnupg->addSignKey($this->serverKeyFingerprint);

        try {
            return (clone $message)->setBody(
                $gnupg->sign(
                    $message->getBodyText(),
                    \Crypt_GPG::SIGN_MODE_CLEAR,
                    true
                )
            );
        } catch (\PEAR_Exception $ex) {
            throw new GPGMailerException(
                'Could not sign message: ' . $ex->getMessage(),
                (int) $ex->getCode(),
                $ex
            );
        }
    }

    /**
     * Verify a message
     *
     * @param Message $message
     * @param string $fingerprint
     * @return bool
     *
     * @throws \Crypt_GPG_FileException
     * @throws GPGMailerException
     * @throws \PEAR_Exception
     */
    public function verify(Message $message, string $fingerprint): bool
    {
        $gnupg = new \Crypt_GPG($this->options);
        $gnupg->addSignKey($fingerprint);

        /**
         * @var \Crypt_GPG_Signature[] $verified
         */
        try {
            $verified = $gnupg->verify($message->getBodyText());
            /**
             * @var \Crypt_GPG_Signature $sig
             */
            foreach ($verified as $sig) {
                if ($sig->isValid()) {
                    return true;
                }
            }
            return false;
        } catch (\PEAR_Exception $ex) {
            throw new GPGMailerException(
                'An error occurred trying to verify this message: ' .
                    $ex->getMessage(),
                (int) $ex->getCode(),
                $ex
            );
        }
    }
}

For more information send a message to info at phpclasses dot org.