From 349ba54ae166fbf9552ba7b7694de71bd41a589b Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Tue, 25 Nov 2008 17:04:07 -0700 Subject: [PATCH] Rework storage of passphrases. Need to keep symmetric passphrases in session or else it is impossible to download the embedded parts. --- imp/compose.php | 8 +- imp/lib/Compose.php | 16 ++-- imp/lib/Crypt/pgp.php | 163 ++++++++++++++++++++------------------ imp/lib/Mime/Viewer/pgp.php | 72 +++++++++++------ imp/pgp.php | 29 ++++--- imp/templates/pgp/passphrase.html | 11 ++- 6 files changed, 164 insertions(+), 135 deletions(-) diff --git a/imp/compose.php b/imp/compose.php index f806bc835..c71bdf885 100644 --- a/imp/compose.php +++ b/imp/compose.php @@ -724,7 +724,11 @@ if ($get_sig && isset($msg) && !empty($sig)) { if ($pgp_passphrase_dialog || $pgp_symmetric_passphrase_dialog) { $imp_pgp = &Horde_Crypt::singleton(array('imp', 'pgp')); Horde::addScriptFile('popup.js', 'imp', true); - $notification->push($imp_pgp->getJSOpenWinCode($pgp_symmetric_passphrase_dialog ? 'open_symmetric_passphrase_dialog' : 'open_passphrase_dialog', "opener.focus();opener.uniqSubmit('send_message');"), 'javascript'); + if ($pgp_passphrase_dialog) { + $notification->push($imp_pgp->getJSOpenWinCode('open_passphrase_dialog', "opener.focus();opener.uniqSubmit('send_message');"), 'javascript'); + } else { + $notification->push($imp_pgp->getJSOpenWinCode('open_symmetric_passphrase_dialog', "opener.focus();opener.uniqSubmit('send_message');", array('symmetricid' => 'imp_compose_' . $composeCacheID)), 'javascript'); + } } elseif ($smime_passphrase_dialog) { $imp_smime = &Horde_Crypt::singleton(array('imp', 'smime')); Horde::addScriptFile('popup.js', 'imp', true); @@ -1243,7 +1247,7 @@ if ($redirect) { ); if ($type != 'application/octet-stream') { - $preview_url = Util::addParameter(Horde::applicationUrl('view.php'), array('actionID' => 'compose_attach_preview', 'id' => $atc_num, 'composeCache' => $imp_compose->getCacheId())); + $preview_url = Util::addParameter(Horde::applicationUrl('view.php'), array('actionID' => 'compose_attach_preview', 'id' => $atc_num, 'composeCache' => $composeCacheID)); $entry['name'] = Horde::link($preview_url, _("Preview") . ' ' . $entry['name'], 'link', 'compose_preview_window') . $entry['name'] . ''; } diff --git a/imp/lib/Compose.php b/imp/lib/Compose.php index 1bb8a8d61..96a49d3b8 100644 --- a/imp/lib/Compose.php +++ b/imp/lib/Compose.php @@ -106,7 +106,7 @@ class IMP_Compose } if (is_null($cacheid) || empty($instance[$cacheid])) { - $cacheid = uniqid(mt_rand()); + $cacheid = is_null($cacheid) ? uniqid(mt_rand()) : $cacheid; $instance[$cacheid] = new IMP_Compose($cacheid); } @@ -1025,7 +1025,7 @@ class IMP_Compose case IMP::PGP_SIGNENC: case IMP::PGP_SYM_SIGNENC: /* Check to see if we have the user's passphrase yet. */ - $passphrase = $imp_pgp->getPassphrase(); + $passphrase = $imp_pgp->getPassphrase('personal'); if (empty($passphrase)) { return PEAR::raiseError(_("PGP: Need passphrase for personal private key."), 'horde.message', null, null, 'pgp_passphrase_dialog'); } @@ -1035,7 +1035,7 @@ class IMP_Compose case IMP::PGP_SYM_SIGNENC: /* Check to see if we have the user's symmetric passphrase * yet. */ - $symmetric_passphrase = $imp_pgp->getSymmetricPassphrase(); + $symmetric_passphrase = $imp_pgp->getPassphrase('symmetric', 'imp_compose_' . $this->_cacheid); if (empty($symmetric_passphrase)) { return PEAR::raiseError(_("PGP: Need passphrase to encrypt your message with."), 'horde.message', null, null, 'pgp_symmetric_passphrase_dialog'); } @@ -1053,10 +1053,7 @@ class IMP_Compose $to_list = empty($options['from']) ? $to : array_keys(array_flip(array_merge($to, array($options['from'])))); - $base = $imp_pgp->IMPencryptMIMEPart($base, $to_list, $encrypt == IMP::PGP_SYM_ENCRYPT); - if ($encrypt == IMP::PGP_SYM_ENCRYPT) { - $imp_pgp->unsetSymmetricPassphrase(); - } + $base = $imp_pgp->IMPencryptMIMEPart($base, $to_list, ($encrypt == IMP::PGP_SYM_ENCRYPT) ? $symmetric_passphrase : null); break; case IMP::PGP_SIGNENC: @@ -1064,10 +1061,7 @@ class IMP_Compose $to_list = empty($options['from']) ? $to : array_keys(array_flip(array_merge($to, array($options['from'])))); - $base = $imp_pgp->IMPsignAndEncryptMIMEPart($base, $to_list, $encrypt == IMP::PGP_SYM_SIGNENC); - if ($encrypt == IMP::PGP_SYM_SIGNENC) { - $imp_pgp->unsetSymmetricPassphrase(); - } + $base = $imp_pgp->IMPsignAndEncryptMIMEPart($base, $to_list, ($encrypt == IMP::PGP_SYM_SIGNENC) ? $symmetric_passphrase : null); break; } diff --git a/imp/lib/Crypt/pgp.php b/imp/lib/Crypt/pgp.php index 2ca26005d..30afb6b01 100644 --- a/imp/lib/Crypt/pgp.php +++ b/imp/lib/Crypt/pgp.php @@ -1,6 +1,6 @@ setValue('pgp_public_key', ''); $GLOBALS['prefs']->setValue('pgp_private_key', ''); - $this->unsetPassphrase(); + $this->unsetPassphrase('personal'); } /** @@ -339,102 +339,95 @@ class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp /** * Decrypt a message with user's public/private keypair or a passphrase. * - * @param string $text The text to decrypt. - * @param boolean $symmetric_hint Whether the text has been encrypted - * symmetrically. If null, we try to find - * out. - * @param boolean $passphrase Whether a passphrase has to be used. + * @param string $text The text to decrypt. + * @param string $type Either 'literal', 'personal', or + * 'symmetric'. + * @param boolean $passphrase If $type is 'personal' or 'symmetrical', + * the passphrase to use. * * @return string The decrypted message. Returns PEAR_Error object on * error. */ - public function decryptMessage($text, $symmetric_hint = null, - $passphrase = true) + public function decryptMessage($text, $type, $passphrase = null) { - if (is_null($symmetric_hint)) { - $symmetric_hint = $this->encryptedSymmetrically($text); - } - /* decrypt() returns a PEAR_Error object on error. */ - if (!$passphrase) { + switch ($type) { + case 'literal': return $this->decrypt($text, array('type' => 'message', 'no_passphrase' => true)); - } elseif ($symmetric_hint) { - return $this->decrypt($text, array('type' => 'message', 'passphrase' => $this->getSymmetricPassphrase())); - } else { - return $this->decrypt($text, array('type' => 'message', 'pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $this->getPassphrase())); - } - } - /** - * Gets the user's passphrase from the session cache. - * - * @return string The passphrase, if set. - */ - public function getPassphrase() - { - if (isset($_SESSION['imp']['pgp_passphrase'])) { - return Secret::read(IMP::getAuthKey(), $_SESSION['imp']['pgp_passphrase']); + case 'symmetric': + return $this->decrypt($text, array('type' => 'message', 'passphrase' => $passphrase)); + + case 'personal': + return $this->decrypt($text, array('type' => 'message', 'pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $passphrase)); } } /** - * Gets the user's passphrase for symmetric encryption from the session - * cache. + * Gets a passphrase from the session cache. + * + * @param integer $type The type of passphrase. Either 'personal' or + * 'symmetric'. + * @param string $id If $type is 'symmetric', the ID of the stored + * passphrase. * - * @return string The passphrase, if set. + * @return mixed The passphrase, if set, or null. */ - public function getSymmetricPassphrase() + public function getPassphrase($type, $id) { - if (isset($_SESSION['imp']['pgp_sym_passphrase'])) { - return Secret::read(IMP::getAuthKey(), $_SESSION['imp']['pgp_sym_passphrase']); + if ($type == 'personal') { + $id = 'personal'; } + + return isset($_SESSION['imp']['cache']['pgp'][$type][$id]) + ? Secret::read(IMP::getAuthKey(), $_SESSION['imp']['cache']['pgp'][$type][$id]) + : null; } /** * Store's the user's passphrase in the session cache. * + * @param integer $type The type of passphrase. Either 'personal' or + * 'symmetric'. * @param string $passphrase The user's passphrase. + * @param string $id If $type is 'symmetric', the ID of the + * stored passphrase. * * @return boolean Returns true if correct passphrase, false if incorrect. */ - public function storePassphrase($passphrase) + public function storePassphrase($type, $passphrase, $id) { - if ($this->verifyPassphrase($this->getPersonalPublicKey(), $this->getPersonalPrivateKey(), $passphrase) === false) { - return false; + if ($type == 'personal') { + if ($this->verifyPassphrase($this->getPersonalPublicKey(), $this->getPersonalPrivateKey(), $passphrase) === false) { + return false; + } + $id = 'personal'; } - $_SESSION['imp']['pgp_passphrase'] = Secret::write(IMP::getAuthKey(), $passphrase); - return true; - } - - /** - * Store's the user's passphrase for symmetric encryption in the session - * cache. - * - * @param string $passphrase The user's passphrase. - * - * @return boolean True - */ - public function storeSymmetricPassphrase($passphrase) - { - $_SESSION['imp']['pgp_sym_passphrase'] = Secret::write(IMP::getAuthKey(), $passphrase); + $_SESSION['imp']['cache']['pgp'][$type][$id] = Secret::write(IMP::getAuthKey(), $passphrase); return true; } /** * Clear the passphrase from the session cache. */ - public function unsetPassphrase() + public function unsetPassphrase($type) { - unset($_SESSION['imp']['pgp_passphrase']); + unset($_SESSION['imp']['cache']['pgp'][$type]); } /** - * Clear the passphrase for symmetric encryption from the session cache. + * Generates a cache ID for symmetric message data. + * + * @param string $mailbox The mailbox of the message. + * @param integer $uid The UID of the message. + * @param string $id The MIME ID of the message. + * + * @return string A unique symmetric cache ID. */ - public function unsetSymmetricPassphrase() + public function getSymmetricID($mailbox, $uid, $id) { - unset($_SESSION['imp']['pgp_sym_passphrase']); + return implode('|', array($mailbox, $uid, $id)); } /** @@ -458,12 +451,13 @@ class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp * @param mixed $reload If true, reload base window on close. If text, * run this JS on close. If false, don't do * anything on close. - * @param array $params Additional parameters needed for the reload + * @param array $params Additional parameters needed for the popup * page. * * @return string The javascript link. */ - public function getJSOpenWinCode($actionid, $reload = true, $params = array()) + public function getJSOpenWinCode($actionid, $reload = true, + $params = array()) { $params['actionID'] = $actionid; if (!empty($reload)) { @@ -486,24 +480,31 @@ class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp */ protected function _signParameters() { - return array('pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $this->getPassphrase()); + return array( + 'pubkey' => $this->getPersonalPublicKey(), + 'privkey' => $this->getPersonalPrivateKey(), + 'passphrase' => $this->getPassphrase('personal') + ); } /** * Provide the list of parameters needed for encrypting a message. * - * @param array $addresses The e-mail address of the keys to use for - * encryption. - * @param boolean $symmetric Whether to encrypt symmetrically. + * @param array $addresses The e-mail address of the keys to use for + * encryption. + * @param string $symmetric If true, the symmetric password to use for + * encrypting. If null, uses the personal key. * * @return array The list of parameters needed by encrypt(). * Returns PEAR_Error on error. */ - protected function _encryptParameters($addresses, $symmetric = false) + protected function _encryptParameters($addresses, $symmetric) { - if ($symmetric) { - return array('symmetric' => true, - 'passphrase' => $this->getSymmetricPassphrase()); + if (!is_null($symmetric)) { + return array( + 'symmetric' => true, + 'passphrase' => $symmetric + ); } $addr_list = array(); @@ -540,15 +541,17 @@ class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp * Encrypt a Horde_Mime_Part using PGP using IMP default parameters. * * @param Horde_Mime_Part $mime_part The object to encrypt. - * @param array $addresses The e-mail address of the keys to use for - * encryption. - * @param boolean $symmetric Whether to encrypt symmetrically. + * @param array $addresses The e-mail address of the keys to + * use for encryption. + * @param string $symmetric If true, the symmetric password to + * use for encrypting. If null, uses + * the personal key. * * @return Horde_Mime_Part See Horde_Crypt_pgp::encryptMIMEPart(). Returns * PEAR_Error object on error. */ public function IMPencryptMIMEPart($mime_part, $addresses, - $symmetric = false) + $symmetric = null) { $params = $this->_encryptParameters($addresses, $symmetric); if (is_a($params, 'PEAR_Error')) { @@ -561,15 +564,17 @@ class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp * Sign and Encrypt a Horde_Mime_Part using PGP using IMP default parameters. * * @param Horde_Mime_Part $mime_part The object to sign and encrypt. - * @param array $addresses The e-mail address of the keys to use for - * encryption. - * @param boolean $symmetric Whether to encrypt symmetrically. + * @param array $addresses The e-mail address of the keys to + * use for encryption. + * @param string $symmetric If true, the symmetric password to + * use for encrypting. If null, uses + * the personal key. * * @return Horde_Mime_Part See Horde_Crypt_pgp::signAndencryptMIMEPart(). - * Returns PEAR_Error object on error. + * Returns PEAR_Error object on error. */ public function IMPsignAndEncryptMIMEPart($mime_part, $addresses, - $symmetric = false) + $symmetric = null) { $encrypt_params = $this->_encryptParameters($addresses, $symmetric); if (is_a($encrypt_params, 'PEAR_Error')) { @@ -579,8 +584,8 @@ class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp } /** - * Generate a Horde_Mime_Part object, in accordance with RFC 2015/3156, that - * contains the user's public key. + * Generate a Horde_Mime_Part object, in accordance with RFC 2015/3156, + * that contains the user's public key. * * @return Horde_Mime_Part See Horde_Crypt_pgp::publicKeyMIMEPart(). */ diff --git a/imp/lib/Mime/Viewer/pgp.php b/imp/lib/Mime/Viewer/pgp.php index c832a7958..2002f6a93 100644 --- a/imp/lib/Mime/Viewer/pgp.php +++ b/imp/lib/Mime/Viewer/pgp.php @@ -160,50 +160,74 @@ class IMP_Horde_Mime_Viewer_pgp extends Horde_Mime_Viewer_Driver $encrypted_part = $this->_params['contents']->getMIMEPart($data_id); $encrypted_data = $encrypted_part->getContents(); + $symmetric_pass = $personal_pass = null; + /* Check if this a symmetrically encrypted message. */ $symmetric = $this->_imppgp->encryptedSymmetrically($encrypted_data); - if ($symmetric && !$this->_imppgp->getSymmetricPassphrase()) { - if ($_SESSION['imp']['view'] == 'imp') { - // TODO: Fix to work with DIMP - /* Ask for the correct passphrase if this is encrypted - * symmetrically. */ - $status[] = Horde::link('#', _("The message below has been encrypted with PGP. You must enter the passphrase that was used to encrypt this message."), null, null, $this->_imppgp->getJSOpenWinCode('open_symmetric_passphrase_dialog') . ' return false;') . _("You must enter the passphrase that was used to encrypt this message.") . ''; + if ($symmetric) { + $symmetric_id = $this->_imppgp->getSymmetricID($this->_params['contents']->getMailbox(), $this->_params['contents']->getIndex(), $this->_mimepart->getMimeId()); + $symmetric_pass = $this->_imppgp->getPassphrase('symmetric', $symmetric_id); + + if (is_null($symmetric_pass)) { + if ($_SESSION['imp']['view'] == 'imp') { + // TODO: Fix to work with DIMP + /* Ask for the correct passphrase if this is encrypted + * symmetrically. */ + $status[] = Horde::link('#', _("The message below has been encrypted with PGP. You must enter the passphrase that was used to encrypt this message."), null, null, $this->_imppgp->getJSOpenWinCode('open_symmetric_passphrase_dialog', true, array('symmetricid' => $symmetric_id)) . ' return false;') . _("You must enter the passphrase that was used to encrypt this message.") . ''; + } + return null; } - return null; } /* Check if this is a literal compressed message. */ $info = $this->_imppgp->pgpPacketInformation($encrypted_data); $literal = !empty($info['literal']); - if (!$literal && !$symmetric) { - if (!$this->_imppgp->getPersonalPrivateKey()) { - /* Output if there is no personal private key to decrypt - * with. */ - $status[] = _("The message below has been encrypted with PGP, however, no personal private key exists so the message cannot be decrypted."); - return null; - } elseif (!$this->_imppgp->getPassphrase() && - ($_SESSION['imp']['view'] == 'imp')) { - // TODO: Fix to work with DIMP - /* Ask for the private key's passphrase if this is encrypted - * asymmetrically. */ - $status[] = Horde::link('#', _("The message below has been encrypted with PGP. You must enter the passphrase for your PGP private key before it can be decrypted."), null, null, $this->_imppgp->getJSOpenWinCode('open_passphrase_dialog') . ' return false;') . _("You must enter the passphrase for your PGP private key to view this message.") . ''; - return null; + if ($literal) { + $status[] = _("The message below has been compressed with PGP."); + } else { + $status[] = _("The message below has been encrypted with PGP."); + if (!$symmetric) { + if (!$this->_imppgp->getPersonalPrivateKey()) { + /* Output if there is no personal private key to decrypt + * with. */ + $status[] = _("The message below has been encrypted with PGP, however, no personal private key exists so the message cannot be decrypted."); + return null; + } else { + $personal_pass = $this->_imppgp->getPassphrase('personal'); + + if (is_null($personal_pass)) { + if ($_SESSION['imp']['view'] == 'imp') { + // TODO: Fix to work with DIMP + /* Ask for the private key's passphrase if this is + * encrypted asymmetrically. */ + $status[] = Horde::link('#', _("The message below has been encrypted with PGP. You must enter the passphrase for your PGP private key before it can be decrypted."), null, null, $this->_imppgp->getJSOpenWinCode('open_passphrase_dialog') . ' return false;') . _("You must enter the passphrase for your PGP private key to view this message.") . ''; + } + return null; + } + } } } - $status[] = $literal ? _("The message below has been compressed with PGP.") : _("The message below has been encrypted with PGP."); + if (!is_null($symmetric_pass)) { + $decrypted_data = $this->_imppgp->decryptMessage($encrypted_data, 'symmetric', $symmetric_pass); + } elseif (!is_null($personal_pass)) { + $decrypted_data = $this->_imppgp->decryptMessage($encrypted_data, 'personal', $personal_pass); + } else { + $decrypted_data = $this->_imppgp->decryptMessage($encrypted_data, 'literal'); + } - $decrypted_data = $this->_imppgp->decryptMessage($encrypted_data, $symmetric, !$literal); if (is_a($decrypted_data, 'PEAR_Error')) { $status[] = _("The message below does not appear to be a valid PGP encrypted message. Error: ") . $decrypted_data->getMessage(); return null; } unset(self::$_inlinecache[$base_id][$data_id]); - $this->_imppgp->unsetSymmetricPassphrase(); - return array($data_id => Horde_Mime_Part::parseMessage($decrypted_data->message)); + $msg = Horde_Mime_Part::parseMessage($decrypted_data->message); + $msg->buildMimeIds($data_id); + + return array($data_id => $msg); } /** diff --git a/imp/pgp.php b/imp/pgp.php index 28c4ae680..b11a0934b 100644 --- a/imp/pgp.php +++ b/imp/pgp.php @@ -14,7 +14,7 @@ function _printKeyInfo($key = '') _textWindowOutput('PGP Key Information', empty($key_info) ? _("Invalid key") : $key_info); } -function _outputPassphraseDialog($secure_check, $symmetric = false) +function _outputPassphraseDialog($secure_check, $symmetricid = null) { if (is_a($secure_check, 'PEAR_Error')) { $GLOBALS['notification']->push($secure_check, 'horde.warning'); @@ -30,8 +30,8 @@ function _outputPassphraseDialog($secure_check, $symmetric = false) $t = new IMP_Template(); $t->setOption('gettext', true); - $t->set('symmetric', $symmetric); - $t->set('submit_url', Util::addParameter(Horde::applicationUrl('pgp.php'), 'actionID', $symmetric ? 'process_symmetric_passphrase_dialog' : 'process_passphrase_dialog')); + $t->set('symmetricid', $symmetricid); + $t->set('submit_url', Util::addParameter(Horde::applicationUrl('pgp.php'), 'actionID', $symmetricid ? 'process_symmetric_passphrase_dialog' : 'process_passphrase_dialog')); $t->set('reload', htmlspecialchars(Util::getFormData('reload'))); $t->set('action', Util::getFormData('passphrase_action')); $t->set('locked_img', Horde::img('locked.png', _("PGP"), null, $GLOBALS['registry']->getImageDir('horde'))); @@ -273,7 +273,7 @@ case 'save_attachment_public_key': exit; case 'open_passphrase_dialog': - if ($imp_pgp->getPassphrase()) { + if ($imp_pgp->getPassphrase('personal')) { Util::closeWindowJS(); } else { _outputPassphraseDialog($secure_check); @@ -281,25 +281,24 @@ case 'open_passphrase_dialog': exit; case 'open_symmetric_passphrase_dialog': - if ($imp_pgp->getSymmetricPassphrase()) { + $symmetricid = Util::getFormData('symmetricid'); + if ($imp_pgp->getPassphrase('symmetric', $symmetricid)) { Util::closeWindowJS(); } else { - _outputPassphraseDialog($secure_check, true); + _outputPassphraseDialog($secure_check, $symmetricid); } exit; case 'process_passphrase_dialog': case 'process_symmetric_passphrase_dialog': $symmetric = $actionID == 'process_symmetric_passphrase_dialog'; + $symmetricid = Util::getFormData('symmetricid'); $passphrase = Util::getFormData('passphrase'); if (is_a($secure_check, 'PEAR_Error')) { - _outputPassphraseDialog($secure_check, $symmetric); + _outputPassphraseDialog($secure_check, $symmetricid); } elseif ($passphrase) { - if ($symmetric) { - $success = $imp_pgp->storeSymmetricPassphrase($passphrase); - } else { - $success = $imp_pgp->storePassphrase($passphrase); - } + $success = $imp_pgp->storePassphrase($symmetric ? 'symmetric' : 'personal', $passphrase, $symmetricid); + if ($success) { if (Util::getFormData('passphrase_action')) { $oid = Util::getFormData('passphrase_action'); @@ -313,16 +312,16 @@ case 'process_symmetric_passphrase_dialog': } } else { $notification->push("Invalid passphrase entered.", 'horde.error'); - _outputPassphraseDialog($secure_check, $symmetric); + _outputPassphraseDialog($secure_check, $symmetricid); } } else { $notification->push("No passphrase entered.", 'horde.error'); - _outputPassphraseDialog($secure_check, $symmetric); + _outputPassphraseDialog($secure_check, $symmetricid); } exit; case 'unset_passphrase': - $imp_pgp->unsetPassphrase(); + $imp_pgp->unsetPassphrase('personal'); $notification->push(_("Passphrase successfully unloaded."), 'horde.success'); break; diff --git a/imp/templates/pgp/passphrase.html b/imp/templates/pgp/passphrase.html index 16233cbe5..153afb573 100644 --- a/imp/templates/pgp/passphrase.html +++ b/imp/templates/pgp/passphrase.html @@ -5,14 +5,17 @@ + + +
- - Please enter the passphrase for this message. - + + Please enter the passphrase for this message. You will only need to enter this passphrase once per session. + Please enter the passphrase for your PGP private key. You will only need to enter this passphrase once per session. - +
-- 2.11.0