diff --git a/CHANGELOG.md b/CHANGELOG.md index 691acb47d..26981f208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,50 @@ +## 2.35.2 – 2024-02-26 + +### Added +- GnuPG error handling +- Missing strings for localization inside identity popup (Cryptography > S/MIME) + [#1458](https://github.com/the-djmaze/snappymail/issues/1458) + +### Changed +- Update Portuguese by @ner00 + +### Fixed +- Drop support for gnupg PECL extension as it fails with "no passphrase" issues +- Error 352 when getting Filters + [#1455](https://github.com/the-djmaze/snappymail/issues/1455) +- Nextcloud SetPassword(): Argument #1 must be of type SensitiveString, string given + [#1456](https://github.com/the-djmaze/snappymail/issues/1456) + + +## 2.35.1 – 2024-02-26 + +### Added +- Search functionality in Admin -> Config +- Cache S/MIME passphrases when "remember" is checked +- Import S/MIME certificate popup +- pre-verify S/MIME opaque signed messages so we have a body to view +- Sort PGP keys and S/MIME certificates on email address +- Optionally use existing private key to generate S/MIME certificate + +### Changed +- Better handling to detect which PGP or S/MIME sign/encrypt to use +- Improved StorageType handling +- Cleanup and improved Capa handling +- OPEN_PGP should be OPENPGP as it is one word +- Use get_debug_type() instead of gettype() +- Require OpenSSL due to S/MIME +- AbstractProvider::IsActive() is now an abstract method and must be defined in child class +- Make better use of SnappyMail\SensitiveString +- Update Polish translation by @tinola + +### Fixed +- Verify S/MIME signatures got broken allong the way while implementing this +- Generate S/MIME self-signed certificate failed to keep existing private key +- MIME parser RegExp didn't escape boundary which caused issues +- TypeError: b64Encode(...).match(...) is null on saving compose draft +- Fix timestampToString() for future dates + + ## 2.35.0 – 2024-02-20 ### Added diff --git a/README.md b/README.md index b582d07b4..1c68bed22 100644 --- a/README.md +++ b/README.md @@ -140,26 +140,26 @@ RainLoop 1.17 vs SnappyMail |js/* |RainLoop |Snappy | |--------------- |--------: |--------: | -|admin.js |2.170.153 | 81.641 | -|app.js |4.207.787 | 426.852 | +|admin.js |2.170.153 | 82.445 | +|app.js |4.207.787 | 429.565 | |boot.js | 868.735 | 4.142 | -|libs.js | 658.812 | 192.625 | +|libs.js | 658.812 | 192.786 | |sieve.js | 0 | 84.707 | |polyfills.js | 334.608 | 0 | |serviceworker.js | 0 | 285 | -|TOTAL |8.240.095 | 790.252 | +|TOTAL |8.240.095 | 793.930 | |js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli | |--------------- |--------: |--------: |------: |------: |--------: |--------: | -|admin.min.js | 256.831 | 40.142 | 73.606 | 13.445 | 60.877 | 12.047 | -|app.min.js | 515.367 | 193.738 |139.456 | 65.570 |110.485 | 56.281 | +|admin.min.js | 256.831 | 40.573 | 73.606 | 13.585 | 60.877 | 12.188 | +|app.min.js | 515.367 | 195.013 |139.456 | 66.135 |110.485 | 56.623 | |boot.min.js | 84.659 | 2.084 | 26.998 | 1.202 | 23.643 | 1.003 | -|libs.min.js | 584.772 | 92.664 |180.901 | 34.414 |155.182 | 30.850 | +|libs.min.js | 584.772 | 92.746 |180.901 | 34.452 |155.182 | 30.890 | |sieve.min.js | 0 | 41.164 | 0 | 10.365 | 0 | 9.359 | |polyfills.min.js | 32.837 | 0 | 11.406 | 0 | 10.175 | 0 | -|TOTAL user |1.217.635 | 288.486 |358.761 |101.186 |299.485 | 88.134 | -|TOTAL user+sieve |1.217.635 | 329.650 |358.761 |111.551 |299.485 | 97.493 | -|TOTAL admin | 959.099 | 134.890 |292.911 | 49.061 |249.877 | 43.900 | +|TOTAL user |1.217.635 | 289.843 |358.761 |101.789 |299.485 | 88.516 | +|TOTAL user+sieve |1.217.635 | 331.007 |358.761 |112.154 |299.485 | 97.875 | +|TOTAL admin | 959.099 | 135.403 |292.911 | 49.239 |249.877 | 44.081 | For a user it is around 69% smaller and faster than traditional RainLoop. @@ -188,8 +188,8 @@ For a user it is around 69% smaller and faster than traditional RainLoop. |css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli | |------------ |-------: |------: |------: |------: |--------: | -|app.css | 340.331 | 84.435 | 46.946 | 17.612 | 15.091 | -|app.min.css | 274.947 | 67.825 | 39.647 | 15.526 | 13.540 | +|app.css | 340.331 | 84.472 | 46.946 | 17.622 | 15.112 | +|app.min.css | 274.947 | 67.857 | 39.647 | 15.537 | 13.549 | |boot.css | | 1.326 | | 664 | 545 | |boot.min.css | | 1.071 | | 590 | 474 | |admin.css | | 30.576 | | 7.013 | 6.096 | diff --git a/dev/Stores/User/GnuPG.js b/dev/Stores/User/GnuPG.js index f4351cb61..e390162d5 100644 --- a/dev/Stores/User/GnuPG.js +++ b/dev/Stores/User/GnuPG.js @@ -69,10 +69,10 @@ export const GnuPGUserStore = new class { } }; if (isPrivate) { - key.password = async (btnTxt = 'SIGN') => { + key.password = async (btnTxt = 'CRYPTO/SIGN') => { const pass = await Passphrases.ask(key, 'GnuPG key
' + key.id + ' ' + key.emails[0], - 'CRYPTO/'+btnTxt + btnTxt ); pass && pass.remember && Passphrases.set(key, pass.password); return pass?.password; @@ -82,8 +82,8 @@ export const GnuPGUserStore = new class { if (key.armor) { callback && callback(); } else { - let pass = isPrivate ? await key.password('POPUP_VIEW_TITLE') : ''; - if (null != pass) { + let pass = isPrivate ? await key.password('OPENPGP/POPUP_VIEW_TITLE') : ''; + if (null != pass) try { const result = await Remote.post('GnupgExportKey', null, { keyId: key.id, isPrivate: isPrivate, @@ -95,6 +95,8 @@ export const GnuPGUserStore = new class { } else { Passphrases.delete(key); } + } catch (e) { + Passphrases.delete(key); } } return key.armor; @@ -182,15 +184,20 @@ export const GnuPGUserStore = new class { uid: message.uid, partId: pgpInfo.partId, keyId: key.id, - passphrase: await key.password('DECRYPT'), + passphrase: await key.password('CRYPTO/DECRYPT'), data: '' // message.plain() optional } - if (null !== params.passphrase) { - const result = await Remote.post('GnupgDecrypt', null, params); - if (result?.Result?.data) { - return result.Result; + if (null != params.passphrase) { + try { + const response = await Remote.post('GnupgDecrypt', null, params); + if (response?.Result?.data) { + return response.Result; + } + throw response; + } catch (e) { + Passphrases.delete(key); + throw e; } - Passphrases.delete(key); } } } diff --git a/dev/Styles/User/FolderList.less b/dev/Styles/User/FolderList.less index f1e5b243a..6157ec2e1 100644 --- a/dev/Styles/User/FolderList.less +++ b/dev/Styles/User/FolderList.less @@ -65,6 +65,7 @@ overflow: hidden; overflow-y: auto; min-width: 100px; + color: var(--folders-color, #333); } hr { @@ -77,22 +78,18 @@ white-space: nowrap; a { - display: block; - position: relative; - z-index: 1; - line-height: 34px; background-color: transparent; - vertical-align: middle; - color: var(--folders-disabled-color, #666); border-left: 3px solid transparent; - + color: var(--folders-color, #333); + display: block; + line-height: 34px; padding: 0 2em 0 @folderItemPadding; - + position: relative; text-decoration: none; + vertical-align: middle; + z-index: 1; &.selectable { - - color: var(--folders-color, #333); cursor: pointer; &:hover { @@ -115,6 +112,7 @@ } } &:not(.selectable) { + color: var(--folders-disabled-color, #666); cursor: default; font-style: italic; } diff --git a/dev/View/Popup/Identity.js b/dev/View/Popup/Identity.js index 0a15cd6de..97b8de9d9 100644 --- a/dev/View/Popup/Identity.js +++ b/dev/View/Popup/Identity.js @@ -52,10 +52,13 @@ export class IdentityPopupView extends AbstractViewPopup { if (oData.Result.x509) { identity.smimeKey(oData.Result.pkey); identity.smimeCertificate(oData.Result.x509); + } else { + this.submitError(oData.ErrorMessage); } }, { name: identity.name(), email: identity.email(), + privateKey: identity.smimeKey(), passphrase: pass.password }); } diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 551915799..70c113131 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -587,10 +587,13 @@ export class MailMessageView extends AbstractViewRight { } } else { // TODO: translate - alert('Decryption failed, canceled or not possible'); + throw 'Decryption failed, canceled or not possible'; } }) - .catch(e => console.error(e)); + .catch(e => { + console.error(e) + alert(e.message); + }); } pgpVerify(/*self, event*/) { diff --git a/integrations/cloudron/DESCRIPTION.md b/integrations/cloudron/DESCRIPTION.md index dd880b220..668ac8df4 100644 --- a/integrations/cloudron/DESCRIPTION.md +++ b/integrations/cloudron/DESCRIPTION.md @@ -1,4 +1,4 @@ -This app packages SnappyMail 2.35.0. +This app packages SnappyMail 2.35.2. SnappyMail is a simple, modern, lightweight & fast web-based email client. diff --git a/integrations/cloudron/Dockerfile b/integrations/cloudron/Dockerfile index fa0c1dfba..29f540199 100644 --- a/integrations/cloudron/Dockerfile +++ b/integrations/cloudron/Dockerfile @@ -4,7 +4,7 @@ RUN mkdir -p /app/code WORKDIR /app/code # If you change the extraction below, be sure to test on scaleway -VERSION=2.35.0 +VERSION=2.35.2 RUN wget https://github.com/the-djmaze/snappymail/releases/download/v${VERSION}/snappymail-${VERSION}.zip -O /tmp/snappymail.zip && \ unzip /tmp/snappymail.zip -d /app/code && \ rm /tmp/snappymail.zip && \ diff --git a/integrations/nextcloud/snappymail/appinfo/info.xml b/integrations/nextcloud/snappymail/appinfo/info.xml index d0cd260d7..c6e1b1a84 100644 --- a/integrations/nextcloud/snappymail/appinfo/info.xml +++ b/integrations/nextcloud/snappymail/appinfo/info.xml @@ -3,7 +3,7 @@ snappymail SnappyMail SnappyMail Webmail - 2.35.0 + 2.35.2 agpl SnappyMail, RainLoop Team, Nextgen-Networks, Tab Fitts, Nathan Kinkade, Pierre-Alain Bandinelli info('Generate admin password'); $sPassword = \substr(\base64_encode(\random_bytes(16)), 0, 12); - $oConfig->SetPassword($sPassword); + $oConfig->SetPassword(new \SnappyMail\SensitiveString($sPassword)); \RainLoop\Utils::saveFile(APP_PRIVATE_DATA . 'admin_password.txt', $sPassword . "\n"); $bSave = true; } diff --git a/integrations/nextcloud/snappymail/lib/Util/SnappyMailHelper.php b/integrations/nextcloud/snappymail/lib/Util/SnappyMailHelper.php index 1752da30a..721cdf86e 100644 --- a/integrations/nextcloud/snappymail/lib/Util/SnappyMailHelper.php +++ b/integrations/nextcloud/snappymail/lib/Util/SnappyMailHelper.php @@ -91,7 +91,6 @@ class SnappyMailHelper */ if ($doLogin && $aCredentials[1] && $aCredentials[2]) { try { - $oActions->Logger()->AddSecret($aCredentials[2]); $oAccount = $oActions->LoginProcess($aCredentials[1], $aCredentials[2]); if ($oAccount) { // Must be here due to bug #1241 @@ -202,9 +201,10 @@ class SnappyMailHelper return \SnappyMail\Crypt::EncryptUrlSafe($sPassword, $sSalt); } - public static function decodePassword(string $sPassword, string $sSalt)/* : mixed */ + public static function decodePassword(string $sPassword, string $sSalt) : ?\SnappyMail\SensitiveString { static::loadApp(); - return \SnappyMail\Crypt::DecryptUrlSafe($sPassword, $sSalt); + $result = \SnappyMail\Crypt::DecryptUrlSafe($sPassword, $sSalt); + return $result ? new \SnappyMail\SensitiveString($result) : $result; } } diff --git a/integrations/virtualmin/snappymail.pl b/integrations/virtualmin/snappymail.pl index 1fb3ace20..7f06862aa 100644 --- a/integrations/virtualmin/snappymail.pl +++ b/integrations/virtualmin/snappymail.pl @@ -20,7 +20,7 @@ return "SnappyMail Webmail is a browser-based multilingual IMAP client with an a # script_snappymail_versions() sub script_snappymail_versions { -return ( "2.35.0" ); +return ( "2.35.2" ); } sub script_snappymail_version_desc diff --git a/package.json b/package.json index f325ea0d8..9dd8bd8b4 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "title": "SnappyMail", "description": "Simple, modern & fast web-based email client", "private": true, - "version": "2.35.0", + "version": "2.35.2", "homepage": "https://snappymail.eu", "author": { "name": "DJ Maze", diff --git a/plugins/ldap-identities/LdapConfig.php b/plugins/ldap-identities/LdapConfig.php index 8cebd8b2c..c55f88bcb 100644 --- a/plugins/ldap-identities/LdapConfig.php +++ b/plugins/ldap-identities/LdapConfig.php @@ -8,6 +8,7 @@ class LdapConfig public const CONFIG_SERVER = "server"; public const CONFIG_PROTOCOL_VERSION = "server_version"; public const CONFIG_STARTTLS = "starttls"; + public const CONFIG_MAIL_PREFIX = "mail_prefix"; public const CONFIG_BIND_USER = "bind_user"; public const CONFIG_BIND_PASSWORD = "bind_password"; @@ -30,6 +31,7 @@ class LdapConfig public $server; public $protocol; public $starttls; + public $mail_prefix; public $bind_user; public $bind_password; public $user_base; @@ -51,6 +53,7 @@ class LdapConfig $ldap->server = trim($config->Get("plugin", self::CONFIG_SERVER)); $ldap->protocol = (int)trim($config->Get("plugin", self::CONFIG_PROTOCOL_VERSION, 3)); $ldap->starttls = (bool)trim($config->Get("plugin", self::CONFIG_STARTTLS)); + $ldap->mail_prefix = trim($config->Get("plugin", self::CONFIG_MAIL_PREFIX)); $ldap->bind_user = trim($config->Get("plugin", self::CONFIG_BIND_USER)); $ldap->bind_password = trim($config->Get("plugin", self::CONFIG_BIND_PASSWORD)); $ldap->user_base = trim($config->Get("plugin", self::CONFIG_USER_BASE)); diff --git a/plugins/ldap-identities/LdapIdentities.php b/plugins/ldap-identities/LdapIdentities.php index 4f14b1bee..4cc3e69d0 100644 --- a/plugins/ldap-identities/LdapIdentities.php +++ b/plugins/ldap-identities/LdapIdentities.php @@ -69,7 +69,8 @@ class LdapIdentities implements IIdentities $this->config->user_base, $this->config->user_objectclass, $this->config->user_field_name, - $this->config->user_field_mail + $this->config->user_field_mail, + $this->config->mail_prefix ); } catch (LdapException $e) { return []; // exceptions are only thrown from the handleerror function that does logging already @@ -104,7 +105,8 @@ class LdapIdentities implements IIdentities $this->config->group_base, $this->config->group_objectclass, $this->config->group_field_name, - $this->config->group_field_mail + $this->config->group_field_mail, + $this->config->mail_prefix ); } catch (LdapException $e) { return []; // exceptions are only thrown from the handleerror function that does logging already @@ -241,7 +243,7 @@ class LdapIdentities implements IIdentities * @return LdapResult[] * @throws LdapException */ - private function FindLdapResults(string $searchField, string $searchValue, string $searchBase, string $objectClass, string $nameField, string $mailField): array + private function FindLdapResults(string $searchField, string $searchValue, string $searchBase, string $objectClass, string $nameField, string $mailField, string $mailPrefix): array { $this->EnsureBound(); @@ -261,6 +263,8 @@ class LdapIdentities implements IIdentities return []; } + $entries = $this->CleanupMailAddresses($entries, $mailField, $mailPrefix); + $results = []; for ($i = 0; $i < $entries["count"]; $i++) { $entry = $entries[$i]; @@ -276,6 +280,49 @@ class LdapIdentities implements IIdentities return $results; } + // Function CleanupMailAddresses(): If a prefix is given this function removes addresses without / with the wrong prefix and then the prefix itself from all remaining values. + // This is usefull for example for importing Active Directory LDAP entry "proxyAddresses" which can hold different address types with prefixes like "X400:", "smtp:" "sip:" and others. + + /** + @param array $entries + @param string $mailField + @paraam string $mailPrefix + @return array + */ + private function CleanupMailAddresses(array $entries, string $mailField, string $mailPrefix) + { + if (!empty($mailPrefix)) { + for ($i = 0; $i < $entries["count"]; $i++) { + // Remove addresses without the given prefix + $entries[$i]["$mailField"] = array_filter($entries[$i]["$mailField"], + function($prefixMail) + { + // $mailPrefix can't be used here, because it's nailed to the CleanupMailAddresses function and can't be passed to the array_filter function afaik. + // Ideas to avoid this are welcome. + if (stripos($prefixMail, $this->config->mail_prefix) === 0) { + return TRUE; + } + return FALSE; + } + ); + // Set "count" to new value + $newcount = count($entries[$i]["$mailField"]); + if (array_key_exists("count", $entries[$i]["$mailField"])) { + $newcount = $newcount - 1; + } + $entries[$i]["$mailField"]["count"] = $newcount; + + // Remove the prefix + for ($j = 0; $j < $entries[$i]["$mailField"]["count"]; $j++) { + $mailPrefixLen = mb_strlen($mailPrefix); + $entries[$i]["$mailField"][$j] = substr($entries[$i]["$mailField"][$j], $mailPrefixLen); + } + } + } + + return $entries; + } + /** * @param array $entry * @param string $attribute diff --git a/plugins/ldap-identities/index.php b/plugins/ldap-identities/index.php index 19e7a1503..4ac3f2faf 100644 --- a/plugins/ldap-identities/index.php +++ b/plugins/ldap-identities/index.php @@ -8,10 +8,10 @@ class LdapIdentitiesPlugin extends AbstractPlugin { const NAME = 'LDAP Identities', - VERSION = '2.2', + VERSION = '2.3', AUTHOR = 'FWest98', URL = 'https://github.com/FWest98', - RELEASE = '2024-02-22', + RELEASE = '2024-02-27', REQUIRED = '2.20.0', CATEGORY = 'Accounts', DESCRIPTION = 'Adds functionality to import account identities from LDAP.'; @@ -57,13 +57,19 @@ class LdapIdentitiesPlugin extends AbstractPlugin ->SetLabel("LDAP Protocol Version") ->SetType(PluginPropertyType::SELECTION) ->SetDefaultValue([2, 3]), - + Property::NewInstance(LdapConfig::CONFIG_STARTTLS) ->SetLabel("Use StartTLS") ->SetType(PluginPropertyType::BOOL) ->SetDescription("Whether or not to use TLS encrypted connection") ->SetDefaultValue(true), + Property::NewInstance(LdapConfig::CONFIG_MAIL_PREFIX) + ->SetLabel("Email prefix") + ->SetType(PluginPropertyType::STRING) + ->SetDescription("Only addresses with this prefix will be used as identity. The prefix is removed from the identity list.\nThis is useful for example to import identities from Exchange, which stores mail addresses in the ProxyAddresses attribut of Active Directory with \"smtp:\" as prefix. (e.g. \"smtp:john.doe@topsecret.info\")\n-> To use addresses set by Exchange use \"smtp:\" as prefix.") + ->SetDefaultValue(""), + Property::NewInstance(LdapConfig::CONFIG_BIND_USER) ->SetLabel("Bind User DN") ->SetDescription("The user to use for binding to the LDAP server. Should be a DN or RDN. Leave empty for anonymous bind") diff --git a/plugins/nextcloud/index.php b/plugins/nextcloud/index.php index c6a31ae2d..d17aaa0ed 100644 --- a/plugins/nextcloud/index.php +++ b/plugins/nextcloud/index.php @@ -329,7 +329,7 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin ); } /* - if ('storage' === $sName || 'storage-local' === $sName) { + if ($this->Config()->Get('plugin', 'storage', false) && ('storage' === $sName || 'storage-local' === $sName)) { require_once __DIR__ . '/storage.php'; $oDriver = new \NextcloudStorage(APP_PRIVATE_DATA.'storage', $sName === 'storage-local'); } @@ -346,6 +346,11 @@ class NextcloudPlugin extends \RainLoop\Plugins\AbstractPlugin \RainLoop\Plugins\Property::NewInstance('ignoreSystemAddressbook')->SetLabel('Ignore system addressbook') ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetDefaultValue(true), +/* + \RainLoop\Plugins\Property::NewInstance('storage')->SetLabel('Use Nextcloud user ID in config storage path') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) + ->SetDefaultValue(false) +*/ \RainLoop\Plugins\Property::NewInstance('calendar')->SetLabel('Enable "Put ICS in calendar"') ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) ->SetDefaultValue(false) diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/SieveClient.php b/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/SieveClient.php index 65dced595..669273225 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/SieveClient.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Sieve/SieveClient.php @@ -184,7 +184,12 @@ class SieveClient extends \MailSo\Net\NetClient public function Logout() : void { if ($this->bIsLoggined) { - $this->sendRequestWithCheck('LOGOUT'); + try { + $this->sendRequestWithCheck('LOGOUT'); + } catch (\Throwable $e) { + // https://github.com/the-djmaze/snappymail/issues/1455 + $this->writeLogException($e, \LOG_WARNING, false); + } $this->bIsLoggined = false; } } @@ -408,6 +413,7 @@ class SieveClient extends \MailSo\Net\NetClient if (null === $sResponseBuffer) { break; } + // \MailSo\Imap\Enumerations\ResponseStatus $bEnd = \in_array(\substr($sResponseBuffer, 0, 2), array('OK', 'NO')); // convertEndOfLine $sLine = \trim($sResponseBuffer); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php index 5a583f6e9..1fd3d2003 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Accounts.php @@ -81,7 +81,7 @@ trait Accounts $aAccounts = $this->GetAccounts($oMainAccount); $sEmail = \trim($this->GetActionParam('email', '')); - $sPassword = $this->GetActionParam('password', ''); + $oPassword = new \SnappyMail\SensitiveString($this->GetActionParam('password', '')); $sName = \trim($this->GetActionParam('name', '')); $bNew = !empty($this->GetActionParam('new', 1)); @@ -92,8 +92,8 @@ trait Accounts throw new ClientException(Notifications::AccountDoesNotExist); } - if ($bNew || $sPassword) { - $oNewAccount = $this->LoginProcess($sEmail, $sPassword, false); + if ($bNew || \strlen($oPassword)) { + $oNewAccount = $this->LoginProcess($sEmail, $oPassword, false); $aAccounts[$sEmail] = $oNewAccount->asTokenArray($oMainAccount); } else { $aAccounts[$sEmail] = \RainLoop\Model\AdditionalAccount::convertArray($aAccounts[$sEmail]); @@ -261,7 +261,11 @@ trait Accounts if (!$oIdentity->FromJSON($this->GetActionParams(), true)) { throw new ClientException(Notifications::InvalidInputArgument); } - +/* // TODO: verify private key for certificate? + if ($oIdentity->smimeCertificate && $oIdentity->smimeKey) { + new \SnappyMail\SMime\Certificate($oIdentity->smimeCertificate, $oIdentity->smimeKey); + } +*/ $this->IdentitiesProvider()->UpdateIdentity($oAccount, $oIdentity); return $this->TrueResponse(); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php index c7b1bc35d..c917f3bbf 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/AdminDomains.php @@ -60,9 +60,9 @@ trait AdminDomains public function DoAdminDomainMatch() : array { $sEmail = $this->GetActionParam('username'); - $sPassword = '********'; + $oPassword = new \SnappyMail\SensitiveString('********'); $sLogin = ''; - $this->resolveLoginCredentials($sEmail, $sPassword, $sLogin); + $this->resolveLoginCredentials($sEmail, $oPassword, $sLogin); $oDomain = \str_contains($sEmail, '@') ? $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true) : null; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index c5a3937ae..f8988aefb 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -1065,8 +1065,7 @@ trait Messages } } - $sPassphrase = $this->GetActionParam('signPassphrase', ''); - $this->logMask($sPassphrase); + $oPassphrase = new \SnappyMail\SensitiveString($this->GetActionParam('signPassphrase', '')); $sFingerprint = $this->GetActionParam('signFingerprint', ''); if ($sFingerprint) { @@ -1084,7 +1083,7 @@ trait Messages $oMessage->SubParts->Clear(); $oMessage->Attachments()->Clear(); - $GPG->addSignKey($sFingerprint, $sPassphrase); + $GPG->addSignKey($sFingerprint, $oPassphrase); $GPG->setsignmode(GNUPG_SIG_MODE_DETACH); $sSignature = $GPG->signStream($fp); if (!$sSignature) { @@ -1127,7 +1126,7 @@ trait Messages $SMIME = $this->SMIME(); $SMIME->setCertificate($sCertificate); - $SMIME->setPrivateKey($sPrivateKey, $sPassphrase); + $SMIME->setPrivateKey($sPrivateKey, $oPassphrase); $sSignature = $SMIME->sign($tmp, $detached); if (!$sSignature) { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php index 768855c19..57aba42ea 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Pgp.php @@ -24,7 +24,7 @@ trait Pgp $GPG = $this->GnuPG(); if ($GPG) { - $keys = $GPG->keyInfo(''); + $keys = $GPG->allKeysInfo(''); foreach ($keys['public'] as $key) { $key = $GPG->export($key['subkeys'][0]['fingerprint'] ?: $key['subkeys'][0]['keyid']); if ($key) { @@ -47,7 +47,7 @@ trait Pgp /** * @throws \MailSo\RuntimeException */ - public function GnuPG() : ?GnuPG + public function GnuPG() : ?\SnappyMail\PGP\PGPInterface { $oAccount = $this->getMainAccountFromToken(); if (!$oAccount) { @@ -81,7 +81,7 @@ trait Pgp if (\is_link($link) || \symlink($homedir, $link)) { $homedir = $link; } else { - \error_log("symlink('{$homedir}', '{$link}') failed"); + $this->logWrite("symlink('{$homedir}', '{$link}') failed", \LOG_WARNING, 'GnuPG'); } } // Else try ~/.gnupg/ + hash(email address) @@ -97,10 +97,6 @@ trait Pgp $homedir = $tmpdir; } } - - if (104 <= \strlen($homedir . '/S.gpg-agent.extra')) { - throw new \Exception("socket name for '{$homedir}/S.gpg-agent.extra' is too long"); - } } return GnuPG::getInstance($homedir); @@ -113,15 +109,14 @@ trait Pgp return $this->FalseResponse(); } - $sPassphrase = $this->GetActionParam('passphrase', ''); - $this->logMask($sPassphrase); + $oPassphrase = new \SnappyMail\SensitiveString($this->GetActionParam('passphrase', '')); - $GPG->addDecryptKey($this->GetActionParam('keyId', ''), $sPassphrase); + $GPG->addDecryptKey($this->GetActionParam('keyId', ''), $oPassphrase); $sData = $this->GetActionParam('data', ''); $oPart = null; $result = [ - 'data' => '', + 'data' => null, 'signatures' => [] ]; if ($sData) { @@ -131,11 +126,13 @@ trait Pgp $this->initMailClientConnection(); $this->MailClient()->MessageMimeStream( function ($rResource) use ($GPG, &$result, &$oPart) { - if (\is_resource($rResource)) { + if (\is_resource($rResource)) try { $result['data'] = $GPG->decryptStream($rResource); // $oPart = \MailSo\Mime\Part::FromString($result); // $GPG->decryptStream($rResource, $rStreamHandle); // $oPart = \MailSo\Mime\Part::FromStream($rStreamHandle); + } catch (\Throwable $e) { + $result = $e; } }, $this->GetActionParam('folder', ''), @@ -149,23 +146,28 @@ trait Pgp // $result['signatures'] = $oPart->SubParts[0]; } + if ($result instanceof \Throwable) { + throw $result; + } + return $this->DefaultResponse($result); } public function DoGnupgGetKeys() : array { $GPG = $this->GnuPG(); - return $this->DefaultResponse($GPG ? $GPG->keyInfo('') : false); + return $this->DefaultResponse($GPG ? $GPG->allKeysInfo('') : false); } public function DoGnupgExportKey() : array { - $sPassphrase = $this->GetActionParam('passphrase', ''); - $this->logMask($sPassphrase); + $oPassphrase = $this->GetActionParam('isPrivate', '') + ? new \SnappyMail\SensitiveString($this->GetActionParam('passphrase', '')) + : null; $GPG = $this->GnuPG(); return $this->DefaultResponse($GPG ? $GPG->export( $this->GetActionParam('keyId', ''), - $sPassphrase + $oPassphrase ) : false); } @@ -176,11 +178,10 @@ trait Pgp if ($GPG) { $sName = $this->GetActionParam('name', ''); $sEmail = $this->GetActionParam('email', ''); - $sPassphrase = $this->GetActionParam('passphrase', ''); - $this->logMask($sPassphrase); + $oPassphrase = new \SnappyMail\SensitiveString($this->GetActionParam('passphrase', '')); $fingerprint = $GPG->generateKey( $sName ? "{$sName} <{$sEmail}>" : $sEmail, - $sPassphrase + $oPassphrase ); } return $this->DefaultResponse($fingerprint); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php index 446d3fcc4..a61ba47fc 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Response.php @@ -45,9 +45,10 @@ trait Response public function ExceptionResponse(\Throwable $oException) : array { - $iErrorCode = 0; - $sErrorMessage = ''; + $iErrorCode = Notifications::UnknownError; + $sErrorMessage = $oException->getMessage(); $sErrorMessageAdditional = ''; + $iExceptionCode = 0; if ($oException instanceof \RainLoop\Exceptions\ClientException) { $iErrorCode = $oException->getCode(); @@ -56,8 +57,7 @@ trait Response } $sErrorMessageAdditional = $oException->getAdditionalMessage(); } else { - $iErrorCode = Notifications::UnknownError; - $sErrorMessage = $oException->getCode().' - '.$oException->getMessage(); + $iExceptionCode = $oException->getCode(); } $this->logException($oException->getPrevious() ?: $oException); @@ -65,7 +65,8 @@ trait Response return $this->DefaultResponse(false, [ 'ErrorCode' => $iErrorCode, 'ErrorMessage' => $sErrorMessage, - 'ErrorMessageAdditional' => $sErrorMessageAdditional + 'ErrorMessageAdditional' => $sErrorMessageAdditional, + 'ExceptionCode' => $iExceptionCode ]); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php index 96263411b..75e35e5d2 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/SMime.php @@ -53,21 +53,18 @@ trait SMime } /** - * Can be use by Identity + * Can be used by Identity */ public function DoSMimeCreateCertificate() : array { $oAccount = $this->getAccountFromToken(); - $sName = $this->GetActionParam('name', '') ?: $oAccount->Name(); - $sEmail = $this->GetActionParam('email', '') ?: $oAccount->Email(); - $sPassphrase = $this->GetActionParam('passphrase', ''); - $this->logMask($sPassphrase); + $oPassphrase = new \SnappyMail\SensitiveString($this->GetActionParam('passphrase', '')); $cert = new Certificate(); - $cert->distinguishedName['commonName'] = $sName; - $cert->distinguishedName['emailAddress'] = $sEmail; - $result = $cert->createSelfSigned($sPassphrase); + $cert->distinguishedName['commonName'] = $this->GetActionParam('name', '') ?: $oAccount->Name(); + $cert->distinguishedName['emailAddress'] = $this->GetActionParam('email', '') ?: $oAccount->Email(); + $result = $cert->createSelfSigned($oPassphrase, $this->GetActionParam('privateKey', '')); return $this->DefaultResponse($result ?: false); } @@ -78,8 +75,7 @@ trait SMime $sPartId = $this->GetActionParam('partId', ''); $sCertificate = $this->GetActionParam('certificate', ''); $sPrivateKey = $this->GetActionParam('privateKey', ''); - $sPassphrase = $this->GetActionParam('passphrase', ''); - $this->logMask($sPassphrase); + $oPassphrase = new \SnappyMail\SensitiveString($this->GetActionParam('passphrase', '')); $this->initMailClientConnection(); $oImapClient = $this->ImapClient(); @@ -106,7 +102,7 @@ trait SMime $SMIME = $this->SMIME(); $SMIME->setCertificate($sCertificate); - $SMIME->setPrivateKey($sPrivateKey, $sPassphrase); + $SMIME->setPrivateKey($sPrivateKey, $oPassphrase); $result = $SMIME->decrypt($sBody); return $this->DefaultResponse($result ?: false); @@ -142,9 +138,9 @@ trait SMime $sBody = \trim($sBody); $certificates = []; \openssl_pkcs7_read( - "-----BEGIN CERTIFICATE-----\n\n{$sBody}\n-----END CERTIFICATE-----", + "-----BEGIN PKCS7-----\n\n{$sBody}\n-----END PKCS7-----", $certificates - ) || \error_log("OpenSSL openssl_pkcs7_read: " . \openssl_error_string()); + ) || $this->logWrite("openssl_pkcs7_read: " . \openssl_error_string(), \LOG_ERR, 'OpenSSL'); foreach ($certificates as $certificate) { $this->SMIME()->storeCertificate($certificate); } @@ -179,7 +175,7 @@ trait SMime $sBody = \trim($sBody); $certificates = []; \openssl_pkcs7_read( - "-----BEGIN CERTIFICATE-----\n\n{$sBody}\n-----END CERTIFICATE-----", + "-----BEGIN PKCS7-----\n\n{$sBody}\n-----END PKCS7-----", $certificates ); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php index b0bd71965..2f223e696 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php @@ -36,10 +36,10 @@ trait User public function DoLogin() : array { $sEmail = \MailSo\Base\Utils::Trim($this->GetActionParam('Email', '')); - $sPassword = $this->GetActionParam('Password', ''); + $oPassword = new \SnappyMail\SensitiveString($this->GetActionParam('Password', '')); try { - $oAccount = $this->LoginProcess($sEmail, $sPassword); + $oAccount = $this->LoginProcess($sEmail, $oPassword); } catch (\Throwable $oException) { $this->loginErrorDelay(); throw $oException; diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php index fc93974b9..89b72d1a8 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/UserAuth.php @@ -22,10 +22,7 @@ trait UserAuth /** * @throws \RainLoop\Exceptions\ClientException */ - public function resolveLoginCredentials(string &$sEmail, - #[\SensitiveParameter] - string &$sPassword, - string &$sLogin): void + public function resolveLoginCredentials(string &$sEmail, \SnappyMail\SensitiveString $oPassword, string &$sLogin): void { $sEmail = \MailSo\Base\Utils::Trim($sEmail); if ($this->Config()->Get('login', 'login_lowercase', true)) { @@ -101,43 +98,40 @@ trait UserAuth } } + $sPassword = (string) $oPassword; $this->Plugins()->RunHook('login.credentials.step-2', array(&$sEmail, &$sPassword)); + $this->logMask($sPassword); $sLogin = $sEmail; if ($this->Config()->Get('login', 'login_lowercase', true)) { $sLogin = \mb_strtolower($sLogin); } - $this->logMask($sPassword); $this->Plugins()->RunHook('login.credentials', array(&$sEmail, &$sLogin, &$sPassword)); $this->logMask($sPassword); + + $oPassword->setValue($sPassword); } /** * @throws \RainLoop\Exceptions\ClientException */ - public function LoginProcess(string &$sEmail, - #[\SensitiveParameter] - string &$sPassword, - bool $bMainAccount = true - ): Account + public function LoginProcess(string &$sEmail, \SnappyMail\SensitiveString $oPassword, bool $bMainAccount = true): Account { $sInputEmail = $sEmail; - $this->logMask($sPassword); - $sLogin = ''; - $this->resolveLoginCredentials($sEmail, $sPassword, $sLogin); + $this->resolveLoginCredentials($sEmail, $oPassword, $sLogin); - if (!\str_contains($sEmail, '@') || !\strlen($sPassword)) { + if (!\str_contains($sEmail, '@') || !\strlen($oPassword)) { throw new ClientException(Notifications::InvalidInputArgument); } $oAccount = null; try { $oAccount = $bMainAccount - ? MainAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $sPassword, true) - : AdditionalAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $sPassword, true); + ? MainAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $oPassword, true) + : AdditionalAccount::NewInstanceFromCredentials($this, $sEmail, $sLogin, $oPassword, true); if (!$oAccount) { throw new ClientException(Notifications::AuthError); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/ActionsAdmin.php b/snappymail/v/0.0.0/app/libraries/RainLoop/ActionsAdmin.php index eed8be6f1..757169491 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/ActionsAdmin.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/ActionsAdmin.php @@ -111,17 +111,15 @@ class ActionsAdmin extends Actions public function DoAdminLogin() : array { $sLogin = trim($this->GetActionParam('Login', '')); - $sPassword = $this->GetActionParam('Password', ''); - - $this->logMask($sPassword); + $oPassword = new \SnappyMail\SensitiveString($this->GetActionParam('Password', '')); $totp = $this->Config()->Get('security', 'admin_totp', ''); // \explode(':',`getent shadow root`)[1]; - if (!\strlen($sLogin) || !\strlen($sPassword) || + if (!\strlen($sLogin) || !\strlen($oPassword) || !$this->Config()->Get('security', 'allow_admin_panel', true) || $sLogin !== $this->Config()->Get('security', 'admin_login', '') || - !$this->Config()->ValidatePassword($sPassword) + !$this->Config()->ValidatePassword($oPassword) || ($totp && !\SnappyMail\TOTP::Verify($totp, $this->GetActionParam('TOTP', '')))) { $this->LoggerAuthHelper(null, $this->getAdditionalLogParamsByUserLogin($sLogin, true), true); @@ -183,17 +181,13 @@ class ActionsAdmin extends Actions $bResult = false; $oConfig = $this->Config(); - $sPassword = $this->GetActionParam('Password', ''); - $this->logMask($sPassword); + $oPassword = new \SnappyMail\SensitiveString($this->GetActionParam('Password', '')); - $sNewPassword = $this->GetActionParam('newPassword', ''); - if (\strlen($sNewPassword)) { - $this->logMask($sNewPassword); - } + $oNewPassword = new \SnappyMail\SensitiveString($this->GetActionParam('newPassword', '')); $passfile = APP_PRIVATE_DATA.'admin_password.txt'; - if ($oConfig->ValidatePassword($sPassword)) { + if ($oConfig->ValidatePassword($oPassword)) { $sLogin = \trim($this->GetActionParam('Login', '')); if (\strlen($sLogin)) { $oConfig->Set('security', 'admin_login', $sLogin); @@ -201,9 +195,9 @@ class ActionsAdmin extends Actions $oConfig->Set('security', 'admin_totp', $this->GetActionParam('TOTP', '')); - if (\strlen($sNewPassword)) { - $oConfig->SetPassword($sNewPassword); - if (\is_file($passfile) && \trim(\file_get_contents($passfile)) !== $sNewPassword) { + if (\strlen($oNewPassword)) { + $oConfig->SetPassword($oNewPassword); + if (\is_file($passfile) && \trim(\file_get_contents($passfile)) !== (string) $oNewPassword) { \unlink($passfile); } } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php index 3df2f6403..d88f7441c 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -120,25 +120,14 @@ class Application extends \RainLoop\Config\AbstractConfig parent::Set($sSectionKey, $sParamKey, $mParamValue); } - public function SetPassword( - #[\SensitiveParameter] - string $sPassword - ) : void + public function SetPassword(\SnappyMail\SensitiveString $oPassword) : void { - $this->Set('security', 'admin_password', \password_hash($sPassword, PASSWORD_DEFAULT)); + $this->Set('security', 'admin_password', \password_hash($oPassword, PASSWORD_DEFAULT)); } - public function ValidatePassword( - #[\SensitiveParameter] - string $sPassword - ) : bool + public function ValidatePassword(\SnappyMail\SensitiveString $oPassword) : bool { - $sConfigPassword = (string) $this->Get('security', 'admin_password', ''); - if (32 == \strlen($sPassword) && \md5(APP_SALT.$sPassword.APP_SALT) === $sConfigPassword) { - $this->SetPassword($sPassword); - return true; - } - return \strlen($sPassword) && \password_verify($sPassword, $sConfigPassword); + return \strlen($oPassword) && \password_verify($oPassword, $this->Get('security', 'admin_password', '')); } public function Save() : bool diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php index 05564db93..f2b9ad8b2 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -15,7 +15,7 @@ abstract class Account implements \JsonSerializable private string $sLogin = ''; - private ?SensitiveString $sPassword = null; + private ?SensitiveString $oPassword = null; private string $sSmtpLogin = ''; @@ -46,7 +46,7 @@ abstract class Account implements \JsonSerializable public function IncPassword() : string { - return $this->sPassword ? $this->sPassword->getValue() : ''; + return $this->oPassword ? $this->oPassword->getValue() : ''; } public function OutLogin() : string @@ -66,16 +66,13 @@ abstract class Account implements \JsonSerializable $this->sEmail, $this->sLogin, // \json_encode($this->Domain()), -// $this->sPassword +// $this->oPassword ])); } - public function SetPassword( - #[\SensitiveParameter] - string $sPassword - ) : void + public function SetPassword(SensitiveString $oPassword) : void { - $this->sPassword = new SensitiveString($sPassword); + $this->oPassword = $oPassword; } public function SetSmtpPassword( @@ -125,12 +122,11 @@ abstract class Account implements \JsonSerializable public static function NewInstanceFromCredentials(\RainLoop\Actions $oActions, string $sEmail, string $sLogin, - #[\SensitiveParameter] - string $sPassword, + SensitiveString $oPassword, bool $bThrowException = false): ?self { $oAccount = null; - if ($sEmail && $sLogin && $sPassword) { + if ($sEmail && $sLogin && \strlen($oPassword)) { $oDomain = $oActions->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true); if ($oDomain) { if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) { @@ -138,7 +134,7 @@ abstract class Account implements \JsonSerializable $oAccount->sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true); $oAccount->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin); - $oAccount->SetPassword($sPassword); + $oAccount->SetPassword($oPassword); $oAccount->oDomain = $oDomain; $oActions->Plugins()->RunHook('filter.account', array($oAccount)); @@ -194,25 +190,22 @@ abstract class Account implements \JsonSerializable $oActions, $aAccountHash['email'], $aAccountHash['login'], - $aAccountHash['pass'], + new SensitiveString($aAccountHash['pass']), $bThrowExceptionOnFalse ); if ($oAccount) { if (isset($aAccountHash['name'])) { $oAccount->sName = $aAccountHash['name']; } - $oActions->logMask($oAccount->IncPassword()); // init smtp user/password if (isset($aAccountHash['smtp'])) { $oAccount->sSmtpLogin = $aAccountHash['smtp']['user']; $oAccount->SetSmtpPassword($aAccountHash['smtp']['pass']); - $oActions->logMask($oAccount->sSmtpPassword); } // init proxy user/password if (isset($aAccountHash['proxy'])) { $oAccount->sProxyAuthUser = $aAccountHash['proxy']['user']; $oAccount->SetProxyAuthPassword($aAccountHash['proxy']['pass']); - $oActions->logMask($oAccount->sProxyAuthPassword); } } } @@ -240,7 +233,7 @@ abstract class Account implements \JsonSerializable $oImapClient->Connect($oSettings); $oPlugins->RunHook('imap.after-connect', array($this, $oImapClient, $oSettings)); - $oSettings->Password = $this->sPassword; + $oSettings->Password = $this->oPassword; return $this->netClientLogin($oImapClient, $oPlugins); } @@ -264,7 +257,7 @@ abstract class Account implements \JsonSerializable throw new RequireCredentialsException } */ - $oSettings->Password = $this->sSmtpPassword ?: $this->sPassword; + $oSettings->Password = $this->sSmtpPassword ?: $this->oPassword; return $this->netClientLogin($oSmtpClient, $oPlugins); } @@ -279,7 +272,7 @@ abstract class Account implements \JsonSerializable $oSieveClient->Connect($oSettings); $oPlugins->RunHook('sieve.after-connect', array($this, $oSieveClient, $oSettings)); - $oSettings->Password = $this->sPassword; + $oSettings->Password = $this->oPassword; return $this->netClientLogin($oSieveClient, $oPlugins); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php index 34f3d2398..3cbd725ee 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Model/Identity.php @@ -37,6 +37,16 @@ class Identity implements \JsonSerializable $this->sEmail = $sEmail; } + function __get(string $name) + { + if (!\property_exists($this, $name)) { + $name = \substr($name, 1); + } + if (\property_exists($this, $name)) { + return $this->$name; + } + } + function toMime() : \MailSo\Mime\Email { return new \MailSo\Mime\Email($this->sEmail, $this->sName); diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/ServiceActions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/ServiceActions.php index a4986f70f..5e7092b26 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/ServiceActions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/ServiceActions.php @@ -112,24 +112,16 @@ class ServiceActions $this->oActions->logWrite('Action: '.$sMethodName, \LOG_INFO, 'JSON'); - $aPost = $_POST ?? null; - if ($aPost) { - $this->oActions->SetActionParams($aPost, $sMethodName); + if ($_POST) { + $this->oActions->SetActionParams($_POST, $sMethodName); + $aPost = $_POST; foreach ($aPost as $key => $value) { - if (false !== \stripos($key, 'Password')) { + // password & passphrase + if (false !== \stripos($key, 'pass')) { $aPost[$key] = '*******'; +// $this->oActions->logMask($value); } } -/* - switch ($sMethodName) - { - case 'DoLogin': - case 'DoAdminLogin': - case 'DoAccountAdd': - $this->oActions->logMask($this->oActions->GetActionParam('Password', '')); - break; - } -*/ $this->oActions->logWrite(Utils::jsonEncode($aPost), \LOG_INFO, 'POST'); } else if (3 < \count($this->aPaths) && $this->oHttp->IsGet()) { $this->oActions->SetActionParams(array( @@ -561,14 +553,14 @@ class ServiceActions (0 === $aData['Time'] || \time() - 10 < $aData['Time'])) { $sEmail = \trim($aData['Email']); - $sPassword = $aData['Password']; + $oPassword = new \SnappyMail\SensitiveString($aData['Password']); $aAdditionalOptions = (isset($aData['AdditionalOptions']) && \is_array($aData['AdditionalOptions'])) ? $aData['AdditionalOptions'] : []; try { - $oAccount = $this->oActions->LoginProcess($sEmail, $sPassword); + $oAccount = $this->oActions->LoginProcess($sEmail, $oPassword); if ($aAdditionalOptions) { $bNeedToSettings = false; diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/gpg/base.php b/snappymail/v/0.0.0/app/libraries/snappymail/gpg/base.php index 858270ed1..2518677a7 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/gpg/base.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/gpg/base.php @@ -5,6 +5,8 @@ namespace SnappyMail\GPG; +use SnappyMail\SensitiveString; + abstract class Base { const @@ -64,7 +66,13 @@ abstract class Base function __construct(string $homedir) { - $this->options['homedir'] = \rtrim($homedir, '/\\'); + $homedir = \rtrim($homedir, '/\\'); + // BSD 4.4 max length + if (104 <= \strlen($homedir . '/S.gpg-agent.extra')) { + throw new \Exception('Socket name for S.gpg-agent.extra is too long'); + } + $this->options['homedir'] = $homedir; +// \putenv("GNUPGHOME={$homedir}"); } function __destruct() @@ -169,22 +177,13 @@ abstract class Base } /** - * Exports a public key + * Exports a public or private key */ - public function export(string $fingerprint) /*: string|false*/ + public function export(string $fingerprint, ?SensitiveString $passphrase = null) /*: string|false*/ { return false; } - /** - * Exports a private key - */ - public function exportPrivateKey(string $fingerprint) /*: string|false*/ - { - return false; - } - - /** * Imports a key */ @@ -209,12 +208,11 @@ abstract class Base /** * Returns an array with information about all keys that matches the given pattern */ - public function keyInfo(string $pattern, int $private = 0) : array + public function keyInfo(string $pattern, bool $private = false) : array { return []; } - /** * Sets the mode for error_reporting * GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT. @@ -243,7 +241,7 @@ abstract class Base /** * Signs a given file */ - public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ + public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/ { return false; } @@ -284,10 +282,7 @@ abstract class Base /** * Add a key for decryption */ - public function addDecryptKey(string $fingerprint, - #[\SensitiveParameter] - string $passphrase - ) : bool + public function addDecryptKey(string $fingerprint, SensitiveString $passphrase) : bool { $this->decryptKeys[$fingerprint] = $passphrase; // $this->decryptKeys[\substr($fingerprint, -16)] = $passphrase; @@ -306,10 +301,7 @@ abstract class Base /** * Add a key for signing */ - public function addSignKey(string $fingerprint, - #[\SensitiveParameter] - string $passphrase - ) : bool + public function addSignKey(string $fingerprint, SensitiveString $passphrase) : bool { $this->signKeys[$fingerprint] = $passphrase; // $this->signKeys[\substr($fingerprint, -16)] = $passphrase; @@ -356,10 +348,7 @@ abstract class Base ]; } - public function addPassphrase($keyId, - #[\SensitiveParameter] - $passphrase - ) + public function addPassphrase($keyId, SensitiveString $passphrase) { $this->passphrases[$keyId] = $passphrase; return $this; @@ -367,10 +356,11 @@ abstract class Base /** * Toggle the armored output + * When true the output is ASCII */ - public function setArmor(int $armor = 1) : bool + public function setArmor(bool $armor = true) : bool { - $this->armor = !!$armor; + $this->armor = $armor; return true; } diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/gpg/pgp.php b/snappymail/v/0.0.0/app/libraries/snappymail/gpg/pgp.php index af80bac23..c0c675202 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/gpg/pgp.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/gpg/pgp.php @@ -7,7 +7,9 @@ namespace SnappyMail\GPG; -class PGP extends Base +use SnappyMail\SensitiveString; + +class PGP extends Base implements \SnappyMail\PGP\PGPInterface { private $_message, @@ -95,7 +97,7 @@ class PGP extends Base $fclose = $this->setOutput($output); if ($this->decryptKeys) { - $_ENV['PINENTRY_USER_DATA'] = \json_encode($this->decryptKeys); + $_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', $this->decryptKeys)); } $result = $this->exec(['--decrypt','--skip-verify']); @@ -137,6 +139,7 @@ class PGP extends Base if (!$fp || !\is_resource($fp)) { throw new \Exception('Invalid stream resource'); } +// \rewind($fp); return $this->_decrypt($fp, $output); } @@ -215,6 +218,7 @@ class PGP extends Base if (!$fp || !\is_resource($fp)) { throw new \Exception('Invalid stream resource'); } + \rewind($fp); return $this->_encrypt($fp, $output); } @@ -234,14 +238,14 @@ class PGP extends Base return false; } - protected function _exportKey($keyId, $private = false) + protected function _exportKey($keyId, bool $private = false) { - $keys = $this->keyInfo($keyId, $private ? 1 : 0); + $keys = $this->keyInfo($keyId, $private); if (!$keys) { throw new \Exception(($private ? 'Private' : 'Public') . ' key not found: ' . $keyId); } if ($private && $this->passphrases) { - $_ENV['PINENTRY_USER_DATA'] = \json_encode($this->passphrases); + $_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', $this->passphrases)); } $result = $this->exec([ $private ? '--export-secret-keys' : '--export', @@ -252,21 +256,19 @@ class PGP extends Base } /** - * Exports a public key + * Exports a public or private key */ - public function export(string $fingerprint) /*: string|false*/ + public function export(string $fingerprint, ?SensitiveString $passphrase = null) /*: string|false*/ { +// \SnappyMail\Log::debug('GnuPG', "export({$fingerprint}, {$passphrase})"); + if (null !== $passphrase) { + return $this + ->addPassphrase($fingerprint, $passphrase) + ->_exportKey($fingerprint, true); + } return $this->_exportKey($fingerprint); } - /** - * Exports a private key - */ - public function exportPrivateKey(string $fingerprint) /*: string|false*/ - { - return $this->_exportKey($fingerprint, true); - } - /** * Returns the errortext, if a function fails */ @@ -296,8 +298,13 @@ class PGP extends Base * Also saves revocation certificate in {homedir}/openpgp-revocs.d/ * https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html */ - public function generateKey(PGPKeySettings $settings) /*: string|false*/ + public function generateKey(string $uid, SensitiveString $passphrase) /*: string|false*/ { + $settings = new PGPKeySettings; + $settings->name = $uid; + $settings->email = $uid; + $settings->passphrase = $passphrase; + $arguments = [ '--batch', '--yes', @@ -362,7 +369,7 @@ class PGP extends Base $arguments = ['--import']; if ($this->passphrases) { - $_ENV['PINENTRY_USER_DATA'] = \json_encode($this->passphrases); + $_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', $this->passphrases)); } else { $arguments[] = '--batch'; } @@ -421,13 +428,13 @@ class PGP extends Base } } - public function deleteKey(string $keyId, bool $private) + public function deleteKey(string $keyId, bool $private) : bool { - $key = $this->keyInfo($keyId, $private ? 1 : 0); + $key = $this->keyInfo($keyId, $private); if (!$key) { throw new \Exception(($private ? 'Private' : 'Public') . ' key not found: ' . $keyId); } -// if (!$private && $this->keyInfo($keyId, 1)) { +// if (!$private && $this->keyInfo($keyId, true)) { // throw new \Exception('Delete private key first: ' . $keyId); // } @@ -448,7 +455,7 @@ class PGP extends Base /** * Returns an array with information about all keys that matches the given pattern */ - public function keyInfo(string $pattern, int $private = 0) : array + public function keyInfo(string $pattern, bool $private = false) : array { // According to The file 'doc/DETAILS' in the GnuPG distribution, using // double '--with-fingerprint' also prints the fingerprint for subkeys. @@ -572,6 +579,30 @@ class PGP extends Base return $keys; } + /** + * Returns an array with information about all keys that matches the given pattern + */ + public function allKeysInfo(string $pattern) : array + { + $keys = [ + 'public' => [], + 'private' => [] + ]; + // Public + foreach (($this->keyinfo($pattern) ?: []) as $key) { + $key['can_verify'] = $key['can_sign']; + unset($key['can_sign']); + $keys['public'][] = $key; + } + // Private, read https://github.com/php-gnupg/php-gnupg/issues/5 + foreach (($this->keyinfo($pattern, 1) ?: []) as $key) { + $key['can_decrypt'] = $key['can_encrypt']; + unset($key['can_encrypt']); + $keys['private'][] = $key; + } + return $keys; + } + /** * Sets the mode for error_reporting * GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT. @@ -629,7 +660,7 @@ class PGP extends Base foreach ($this->signKeys as $fingerprint => $pass) { $arguments[] = '--local-user ' . \escapeshellarg($fingerprint); } - $_ENV['PINENTRY_USER_DATA'] = \json_encode($this->signKeys); + $_ENV['PINENTRY_USER_DATA'] = \json_encode(\array_map('strval', $this->signKeys)); } $result = $this->exec($arguments); @@ -666,15 +697,16 @@ class PGP extends Base /** * Signs a given file */ - public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ + public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/ { if (!$fp || !\is_resource($fp)) { throw new \Exception('Invalid stream resource'); } + \rewind($fp); return $this->_sign($fp, $output); } - protected function _verify($input, string $signature) + protected function _verify($input, string $signature) /*: array|false*/ { $arguments = ['--verify']; if ($signature) { @@ -768,6 +800,7 @@ class PGP extends Base if (!$fp || !\is_resource($fp)) { throw new \Exception('Invalid stream resource'); } +// \rewind($fp); return $this->_verify($fp, $signature); } diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/gpg/smime.php b/snappymail/v/0.0.0/app/libraries/snappymail/gpg/smime.php index b958e2d6b..3e8d76f5e 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/gpg/smime.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/gpg/smime.php @@ -11,6 +11,8 @@ namespace SnappyMail\GPG; +use SnappyMail\SensitiveString; + class SMIME extends Base { private @@ -235,9 +237,9 @@ class SMIME extends Base return false; } - protected function _exportKey($keyId, $private = false) + protected function _exportKey($keyId, bool $private = false) { - $keys = $this->keyInfo($keyId, $private ? 1 : 0); + $keys = $this->keyInfo($keyId, $private); if (!$keys) { throw new \Exception(($private ? 'Private' : 'Public') . ' key not found: ' . $keyId); } @@ -253,21 +255,18 @@ class SMIME extends Base } /** - * Exports a public key + * Exports a public or private key */ - public function export(string $fingerprint) /*: string|false*/ + public function export(string $fingerprint, ?SensitiveString $passphrase = null) /*: string|false*/ { + if ($passphrase) { + return $this + ->addPassphrase($fingerprint, $passphrase) + ->_exportKey($fingerprint, true); + } return $this->_exportKey($fingerprint); } - /** - * Exports a private key - */ - public function exportPrivateKey(string $fingerprint) /*: string|false*/ - { - return $this->_exportKey($fingerprint, true); - } - protected function _importKey($input) /*: array|false*/ { $arguments = ['--import']; @@ -334,7 +333,7 @@ class SMIME extends Base public function deleteKey(string $keyId, bool $private) { - $key = $this->keyInfo($keyId, $private ? 1 : 0); + $key = $this->keyInfo($keyId, $private); if (!$key) { throw new \Exception(($private ? 'Private' : 'Public') . ' key not found: ' . $keyId); } @@ -359,7 +358,7 @@ class SMIME extends Base /** * Returns an array with information about all keys that matches the given pattern */ - public function keyInfo(string $pattern, int $private = 0) : array + public function keyInfo(string $pattern, bool $private = false) : array { // According to The file 'doc/DETAILS' in the GnuPG distribution, using // double '--with-fingerprint' also prints the fingerprint for subkeys. @@ -540,7 +539,7 @@ class SMIME extends Base /** * Signs a given file */ - public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ + public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/ { if (!$fp || !\is_resource($fp)) { throw new \Exception('Invalid stream resource'); @@ -548,7 +547,7 @@ class SMIME extends Base return $this->_sign($fp, $output); } - protected function _verify($input, string $signature) + protected function _verify($input, string $signature) /*: array|false*/ { $arguments = ['--verify']; if ($signature) { diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php index 8b4a7e684..0bdf9468e 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/request.php @@ -7,10 +7,11 @@ abstract class Request const /** * Authentication + * These are bitwise options */ AUTH_BASIC = 1, AUTH_DIGEST = 2, - AUTH_BEARER = 3; + AUTH_BEARER = 4; public $timeout = 5, // timeout in seconds. diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/http/response.php b/snappymail/v/0.0.0/app/libraries/snappymail/http/response.php index 3593aa41d..126af9564 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/http/response.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/http/response.php @@ -42,6 +42,10 @@ class Response if (\function_exists('gzinflate') && isset($this->headers['content-encoding']) && (false !== \stripos($this->headers['content-encoding'], 'gzip'))) { $this->body = \gzinflate(\substr($body, 10, -4)); + if (false === $this->body) { + $err = \error_get_last() ?: ['message' => 'gzinflate failed']; + throw new \RuntimeException("{$err['message']} for {$request_uri}"); + } } else { $this->body = $body; } diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php index c744d0ac5..72494fd68 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/gnupg.php @@ -8,423 +8,26 @@ defined('GNUPG_SIG_MODE_CLEAR') || define('GNUPG_SIG_MODE_CLEAR', 2); use SnappyMail\GPG\PGP as GPG; -class GnuPG +abstract class GnuPG { - private - $homedir, - // Instance of gnupg pecl extension https://www.php.net/gnupg - $GnuPG, - // Instance of \SnappyMail\GPG\PGP - $GPG; - - function __construct(string $homedir) - { - $homedir = \rtrim($homedir, '/\\'); - // BSD 4.4 max length - if (104 <= \strlen($homedir . '/S.gpg-agent.extra')) { - throw new \Exception('socket name for S.gpg-agent.extra is too long'); - } - $this->homedir = $homedir; -// \putenv("GNUPGHOME={$homedir}"); - - if (\class_exists('gnupg') && \version_compare(\phpversion('gnupg'), '1.5', '>=')) { - $this->GnuPG = new \gnupg([ - // It is the file name of the executable program implementing this protocol which is usually path of the gpg executable. -// 'file_name' => '/usr/bin/gpg', - // It is the directory name of the configuration directory. It also overrides GNUPGHOME environment variable that is used for the same purpose. - 'home_dir' => $homedir - ]); - // Output is ASCII - $this->GnuPG->setarmor(1); - } else { - $this->getGPG(); - } - -/* - $conf = "{$homedir}/gpg-agent.conf"; - if (!\file_exists($conf)) { - \file_put_contents($conf, 'default-cache-ttl 1'); - } - $conf = "{$homedir}/gpg.conf"; - if (!\file_exists($conf)) { - \file_put_contents($conf, "batch\nno-comments"); - } -*/ - } - public static function isSupported() : bool { - return \class_exists('gnupg') || GPG::isSupported(); + return GPG::isSupported(); } private static $instance; - public static function getInstance(string $homedir) : ?self + public static function getInstance(string $homedir) : ?PGPInterface { if (!static::$instance) { - static::$instance = new self($homedir); + if (GPG::isSupported()) { + static::$instance = new GPG($homedir); + } +/* + else if (PECL::isSupported()) { + static::$instance = new PECL($homedir); + } +*/ } return static::$instance; } - - public function handler() - { - return $this->GnuPG ?: $this->GPG; - } - - public function getGPG(bool $throw = true) : ?GPG - { - if (!$this->GPG) { - if (GPG::isSupported()) { - $this->GPG = new GPG($this->homedir); - } else if ($throw) { - throw new \Exception('GnuPG not supported'); - } - } - return $this->GPG; - } - - /** - * Add a key for decryption - */ - public function addDecryptKey(string $fingerprint, - #[\SensitiveParameter] - string $passphrase - ) : bool - { - return $this->handler()->adddecryptkey($fingerprint, $passphrase); - } - - /** - * Add a key for encryption - */ - public function addEncryptKey(string $fingerprint) : bool - { - return $this->handler()->addencryptkey($fingerprint); - } - - /** - * Add a key for signing - */ - public function addSignKey(string $fingerprint, - #[\SensitiveParameter] - ?string $passphrase - ) : bool - { - return $this->handler()->addsignkey($fingerprint, $passphrase); - } - - /** - * Removes all keys which were set for decryption before - */ - public function clearDecryptKeys() : bool - { - return $this->handler()->cleardecryptkeys(); - } - - /** - * Removes all keys which were set for encryption before - */ - public function clearEncryptKeys() : bool - { - return $this->handler()->clearencryptkeys(); - } - - /** - * Removes all keys which were set for signing before - */ - public function clearSignKeys() : bool - { - return $this->handler()->clearsignkeys(); - } - - /** - * Decrypts a given text - */ - public function decrypt(string $text) /*: string|false */ - { - return $this->GnuPG - ? $this->GnuPG->decrypt($text) - : $this->GPG->decrypt($text); - } - - /** - * Decrypts a given file - */ - public function decryptFile(string $filename) /*: string|false */ - { - return $this->GnuPG - ? $this->GnuPG->decrypt(\file_get_contents($filename)) - : $this->GPG->decryptFile($filename); - } - - /** - * Decrypts a given resource - */ - public function decryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false */ - { - if (!$fp || !\is_resource($fp)) { - throw new \Exception('Invalid stream resource'); - } - return $this->GnuPG - ? $this->GnuPG->decrypt(\stream_get_contents($fp)) - : $this->GPG->decryptStream($fp, $output); - } - - /** - * Decrypts and verifies a given text - */ - public function decryptVerify(string $text, string &$plaintext) /*: array|false*/ - { - return $this->GnuPG - ? $this->GnuPG->decryptverify($text, $plaintext) - : $this->GPG->decryptverify($text, $plaintext); - } - - /** - * Decrypts and verifies a given file - */ - public function decryptVerifyFile(string $filename, string &$plaintext) /*: array|false*/ - { - return $this->GnuPG - ? $this->GnuPG->decryptverify(\file_get_contents($filename), $plaintext) - : $this->GPG->decryptverifyFile($filename, $plaintext); - } - - public function deleteKey(string $keyId, bool $private) : bool - { - return $this->getGPG()->deleteKey($keyId, $private); - } - - /** - * Encrypts a given text - */ - public function encrypt(string $plaintext) /*: string|false*/ - { - return $this->GnuPG - ? $this->GnuPG->encrypt($plaintext) - : $this->GPG->encrypt($plaintext); - } - - /** - * Encrypts a given text - */ - public function encryptFile(string $filename) /*: string|false*/ - { - return $this->GnuPG - ? $this->GnuPG->encrypt(\file_get_contents($filename)) - : $this->GPG->encryptFile($filename); - } - - public function encryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false*/ - { - \rewind($fp); - return $this->GnuPG - ? $this->GnuPG->encrypt(\stream_get_contents($fp)) - : $this->GPG->encryptStream($fp); - } - - /** - * Exports a key - */ - public function export(string $fingerprint, - #[\SensitiveParameter] - string $passphrase = '' - ) /*: string|false*/ - { - if ($passphrase) { - return $this->getGPG() - ->addPassphrase($fingerprint, $passphrase) - ->exportPrivateKey($fingerprint); - } - return $this->GnuPG - ? $this->GnuPG->export($fingerprint) - : $this->GPG->export($fingerprint); - } - - /** - * Returns the engine info - */ - public function getEngineInfo() : array - { - return $this->handler()->getengineinfo(); - } - - /** - * Returns the errortext, if a function fails - */ - public function getError() /*: string|false*/ - { - return $this->handler()->geterror(); - } - - /** - * Returns the error info - */ - public function getErrorInfo() : array - { - return $this->handler()->geterrorinfo(); - } - - /** - * Returns the currently active protocol for all operations - */ - public function getProtocol() : int - { - return $this->handler()->getprotocol(); - } - - /** - * Generates a key - */ - public function generateKey(string $uid, - #[\SensitiveParameter] - string $passphrase - ) /*: string|false*/ - { - $GPG = $this->getGPG(false); - return $GPG ? $GPG->generateKey($uid, $passphrase) : false; - } - - /** - * Imports a key - */ - public function import(string $keydata) /*: array|false*/ - { - return $this->handler()->import($keydata); - } - - /** - * Imports a key - */ - public function importFile(string $filename) /*: array|false*/ - { - return $this->GnuPG - ? $this->GnuPG->import(\file_get_contents($filename)) - : $this->GPG->importFile($filename); - } - - /** - * Returns an array with information about all keys that matches the given pattern - */ - public function keyInfo(string $pattern) : array - { - $keys = [ - 'public' => [], - 'private' => [] - ]; - // Public - foreach (($this->handler()->keyinfo($pattern) ?: []) as $key) { - $key['can_verify'] = $key['can_sign']; - unset($key['can_sign']); - $keys['public'][] = $key; - } - // Private, read https://github.com/php-gnupg/php-gnupg/issues/5 - foreach (($this->handler()->keyinfo($pattern, 1) ?: []) as $key) { - $key['can_decrypt'] = $key['can_encrypt']; - unset($key['can_encrypt']); - $keys['private'][] = $key; - } - return $keys; - } - - /** - * Toggle armored output - * When true the output is ASCII - */ - public function setArmor(bool $armor = true) : bool - { - return $this->handler()->setarmor($armor ? 1 : 0); - } - - /** - * Sets the mode for error_reporting - * GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT. - * By default GNUPG_ERROR_SILENT is used. - */ - public function setErrorMode(int $errormode) : void - { - $this->handler()->seterrormode($errormode); - } - - /** - * Sets the mode for signing - * GNUPG_SIG_MODE_NORMAL, GNUPG_SIG_MODE_DETACH and GNUPG_SIG_MODE_CLEAR. - * By default GNUPG_SIG_MODE_CLEAR - */ - public function setSignMode(int $signmode) : bool - { - return $this->handler()->setsignmode($signmode); - } - - /** - * Signs a given text - */ - public function sign(string $plaintext) /*: string|false*/ - { - return $this->GnuPG - ? $this->GnuPG->sign($plaintext) - : $this->GPG->sign($plaintext); - } - - /** - * Signs a given file - */ - public function signFile(string $filename) /*: string|false*/ - { - return $this->GnuPG - ? $this->GnuPG->sign(\file_get_contents($filename)) - : $this->GPG->signFile($filename); - } - - /** - * Signs a given file - */ - public function signStream($fp, /*string|resource*/ $output = null) /*: array|false*/ - { - \rewind($fp); - return $this->GnuPG - ? $this->GnuPG->sign(\stream_get_contents($fp)) - : $this->GPG->signStream($fp); - } - - /** - * Verifies a signed text - */ - public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/ - { - $result = $this->GnuPG - ? $this->GnuPG->verify($signed_text, $signature ?: false, $plaintext) - : $this->GPG->verify($signed_text, $signature, $plaintext); - if (!$result) { - if ($this->GnuPG) { - \SnappyMail\Log::notice('GnuPG', 'gnupg_verify() failed: ' . $this->GnuPG->geterror()); - \SnappyMail\Log::info('GnuPG', \print_r($this->GnuPG->geterrorinfo(),1)); - } else { - \SnappyMail\Log::notice('GPG', 'GPG->verify() failed'); - } - } - return $result; - } - - /** - * Verifies a signed file - */ - public function verifyFile(string $filename, string $signature, string &$plaintext = null) /*: array|false*/ - { - return $this->GnuPG - ? $this->GnuPG->verify(\file_get_contents($filename), $signature, $plaintext) - : $this->GPG->verifyFile($filename, $signature, $plaintext); - } - - /** - * Verifies a given resource - */ - public function verifyStream(/*resource*/ $fp, string $signature, string &$plaintext = null) /*: string|false */ - { - if (!$fp || !\is_resource($fp)) { - throw new \Exception('Invalid stream resource'); - } - return $this->getGPG()->verifyStream($fp, $signature, $plaintext); - } - } diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/pecl.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/pecl.php new file mode 100644 index 000000000..a8582b578 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/pecl.php @@ -0,0 +1,397 @@ +homedir = $homedir; +// \putenv("GNUPGHOME={$homedir}"); + + $this->GnuPG = new \gnupg([ + // It is the file name of the executable program implementing this protocol which is usually path of the gpg executable. +// 'file_name' => '/usr/bin/gpg', + // It is the directory name of the configuration directory. It also overrides GNUPGHOME environment variable that is used for the same purpose. + 'home_dir' => $homedir + ]); + // Output is ASCII + $this->GnuPG->setarmor(1); +/* + $conf = "{$homedir}/gpg-agent.conf"; + if (!\file_exists($conf)) { + \file_put_contents($conf, 'default-cache-ttl 1'); + } + $conf = "{$homedir}/gpg.conf"; + if (!\file_exists($conf)) { + \file_put_contents($conf, "batch\nno-comments"); + } +*/ + } + + function __destruct() + { + $this->GnuPG->cleardecryptkeys(); + $this->GnuPG->clearsignkeys(); + } + + public static function isSupported() : bool + { + return \class_exists('gnupg') && \version_compare(\phpversion('gnupg'), '1.5', '>='); + } + + public function gnupgError() + { + $error = $this->GnuPG->geterrorinfo(); + if ($error) { + throw new \Exception("{$error['gpgme_source']} {$error['generic_message']}. {$error['gpgme_message']}", $error['gpgme_code']); + } + } + + public function getGPG(bool $throw = true) : ?GPG + { + if (!$this->GPG) { + if (GPG::isSupported()) { + $this->GPG = new GPG($this->homedir); + } else if ($throw) { + throw new \Exception('GPG not supported'); + } + } + return $this->GPG; + } + + /** + * Add a key for decryption + */ + public function addDecryptKey(string $fingerprint, SensitiveString $passphrase) : bool + { +// \SnappyMail\Log::debug('GnuPG', "addDecryptKey({$fingerprint}, {$passphrase})"); + return $this->GnuPG->adddecryptkey($fingerprint, \strval($passphrase)) || $this->gnupgError(); + } + + /** + * Add a key for encryption + */ + public function addEncryptKey(string $fingerprint) : bool + { + return $this->GnuPG->addencryptkey($fingerprint) || $this->gnupgError(); + } + + /** + * Add a key for signing + */ + public function addSignKey(string $fingerprint, SensitiveString $passphrase) : bool + { +// \SnappyMail\Log::debug('GnuPG', "addSignKey({$fingerprint}, {$passphrase})"); + return $this->GnuPG->addsignkey($fingerprint, \strval($passphrase)) || $this->gnupgError(); + } + + /** + * Removes all keys which were set for decryption before + */ + public function clearDecryptKeys() : bool + { + return $this->GnuPG->cleardecryptkeys(); + } + + /** + * Removes all keys which were set for encryption before + */ + public function clearEncryptKeys() : bool + { + return $this->GnuPG->clearencryptkeys(); + } + + /** + * Removes all keys which were set for signing before + */ + public function clearSignKeys() : bool + { + return $this->GnuPG->clearsignkeys(); + } + + /** + * Decrypts a given text + */ + public function decrypt(string $text) /*: string|false */ + { + $result = $this->GnuPG->decrypt($text); + (false === $result) && $this->gnupgError(); + return $result; + } + + /** + * Decrypts a given file + */ + public function decryptFile(string $filename) /*: string|false */ + { + $result = $this->GnuPG->decrypt(\file_get_contents($filename)); + (false === $result) && $this->gnupgError(); + return $result; + } + + /** + * Decrypts a given resource + */ + public function decryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false */ + { + if (!$fp || !\is_resource($fp)) { + throw new \Exception('Invalid stream resource'); + } +// \rewind($fp); + $result = $this->GnuPG->decrypt(\stream_get_contents($fp)); + (false === $result) && $this->gnupgError(); + return $result; + } + + /** + * Decrypts and verifies a given text + */ + public function decryptVerify(string $text, string &$plaintext) /*: array|false*/ + { + $result = $this->GnuPG->decryptverify($text, $plaintext); + (false === $result) && $this->gnupgError(); + return $result; + } + + /** + * Decrypts and verifies a given file + */ + public function decryptVerifyFile(string $filename, string &$plaintext) /*: array|false*/ + { + $result = $this->GnuPG->decryptverify(\file_get_contents($filename), $plaintext); + (false === $result) && $this->gnupgError(); + return $result; + } + + public function deleteKey(string $keyId, bool $private) : bool + { + return $this->getGPG()->deleteKey($keyId, $private); + } + + /** + * Encrypts a given text + */ + public function encrypt(string $plaintext) /*: string|false*/ + { + return $this->GnuPG->encrypt($plaintext) ?: $this->gnupgError(); + } + + /** + * Encrypts a given text + */ + public function encryptFile(string $filename) /*: string|false*/ + { + return $this->GnuPG->encrypt(\file_get_contents($filename)) ?: $this->gnupgError(); + } + + public function encryptStream(/*resource*/ $fp, /*string|resource*/ $output = null) /*: string|false*/ + { + \rewind($fp); + return $this->GnuPG->encrypt(\stream_get_contents($fp)) ?: $this->gnupgError(); + } + + /** + * Exports a key + */ + public function export(string $fingerprint, ?SensitiveString $passphrase = null) /*: string|false*/ + { + if (null !== $passphrase) { + return $this->getGPG()->export($fingerprint, $passphrase); + } + return $this->GnuPG->export($fingerprint) ?: $this->gnupgError(); + } + + /** + * Returns the engine info + */ + public function getEngineInfo() : array + { + return $this->GnuPG->getengineinfo(); + } + + /** + * Returns the errortext, if a function fails + */ + public function getError() /*: string|false*/ + { + return $this->GnuPG->geterror(); + } + + /** + * Returns the error info + */ + public function getErrorInfo() : array + { + return $this->GnuPG->geterrorinfo(); + } + + /** + * Returns the currently active protocol for all operations + */ + public function getProtocol() : int + { + return $this->GnuPG->getprotocol(); + } + + /** + * Generates a key + */ + public function generateKey(string $uid, SensitiveString $passphrase) /*: string|false*/ + { + $GPG = $this->getGPG(false); + return $GPG ? $GPG->generateKey($uid, $passphrase) : false; + } + + /** + * Imports a key + */ + public function import(string $keydata) /*: array|false*/ + { + return $this->GnuPG->import($keydata); + } + + /** + * Imports a key + */ + public function importFile(string $filename) /*: array|false*/ + { + return $this->GnuPG->import(\file_get_contents($filename)) ?: $this->gnupgError(); + } + + public function keyInfo(string $pattern, bool $private = false) : array + { + return $this->GnuPG->keyinfo($pattern, $private ? 1 : 0); + } + + /** + * Returns an array with information about all keys that matches the given pattern + */ + public function allKeysInfo(string $pattern) : array + { + $keys = [ + 'public' => [], + 'private' => [] + ]; + // Public + foreach (($this->GnuPG->keyinfo($pattern) ?: []) as $key) { + $key['can_verify'] = $key['can_sign']; + unset($key['can_sign']); + $keys['public'][] = $key; + } + // Private, read https://github.com/php-gnupg/php-gnupg/issues/5 + foreach (($this->GnuPG->keyinfo($pattern, 1) ?: []) as $key) { + $key['can_decrypt'] = $key['can_encrypt']; + unset($key['can_encrypt']); + $keys['private'][] = $key; + } + return $keys; + } + + /** + * Toggle armored output + * When true the output is ASCII + */ + public function setArmor(bool $armor = true) : bool + { + return $this->GnuPG->setarmor($armor ? 1 : 0); + } + + /** + * Sets the mode for error_reporting + * GNUPG_ERROR_WARNING, GNUPG_ERROR_EXCEPTION and GNUPG_ERROR_SILENT. + * By default GNUPG_ERROR_SILENT is used. + */ + public function setErrorMode(int $errormode) : void + { + $this->GnuPG->seterrormode($errormode); + } + + /** + * Sets the mode for signing + * GNUPG_SIG_MODE_NORMAL, GNUPG_SIG_MODE_DETACH and GNUPG_SIG_MODE_CLEAR. + * By default GNUPG_SIG_MODE_CLEAR + */ + public function setSignMode(int $signmode) : bool + { + return $this->GnuPG->setsignmode($signmode); + } + + /** + * Signs a given text + */ + public function sign(string $plaintext) /*: string|false*/ + { + return $this->GnuPG->sign($plaintext) ?: $this->gnupgError(); + } + + /** + * Signs a given file + */ + public function signFile(string $filename) /*: string|false*/ + { + return $this->GnuPG->sign(\file_get_contents($filename)) ?: $this->gnupgError(); + } + + /** + * Signs a given file + */ + public function signStream($fp, /*string|resource*/ $output = null) /*: string|false*/ + { + \rewind($fp); + return $this->GnuPG->sign(\stream_get_contents($fp)) ?: $this->gnupgError(); + } + + /** + * Verifies a signed text + */ + public function verify(string $signed_text, string $signature, string &$plaintext = null) /*: array|false*/ + { + $result = $this->GnuPG->verify($signed_text, $signature ?: false, $plaintext) ?: $this->gnupgError(); + if (!$result) { + \SnappyMail\Log::notice('GnuPG', 'gnupg_verify() failed: ' . $this->GnuPG->geterror()); + \SnappyMail\Log::info('GnuPG', \print_r($this->GnuPG->geterrorinfo(),1)); + } + return $result; + } + + /** + * Verifies a signed file + */ + public function verifyFile(string $filename, string $signature, string &$plaintext = null) /*: array|false*/ + { + return $this->GnuPG->verify(\file_get_contents($filename), $signature, $plaintext) ?: $this->gnupgError(); + } + + /** + * Verifies a given resource + */ + public function verifyStream(/*resource*/ $fp, string $signature, string &$plaintext = null) /*: array|false */ + { + if (!$fp || !\is_resource($fp)) { + throw new \Exception('Invalid stream resource'); + } +// \rewind($fp); + return $this->GnuPG->verify(\stream_get_contents($fp), $signature, $plaintext) ?: $this->gnupgError(); + } + +} diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/pgp/pgpinterface.php b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/pgpinterface.php new file mode 100644 index 000000000..50fc1350e --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/snappymail/pgp/pgpinterface.php @@ -0,0 +1,43 @@ +logMask($value); if (\is_callable('sodium_crypto_secretbox')) { $this->nonce = \random_bytes(\SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); if (!static::$key) { @@ -60,6 +61,12 @@ class SensitiveString /* extends SensitiveParameterValue | SensitiveParameter */ return $this->getValue(); } + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + throw new \Exception("JSON serialization of 'SnappyMail\\SensitiveString' is not allowed"); + } + public function __debugInfo(): array { return []; diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/smime/certificate.php b/snappymail/v/0.0.0/app/libraries/snappymail/smime/certificate.php index 801cebfaa..2a12ba905 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/smime/certificate.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/smime/certificate.php @@ -112,7 +112,7 @@ class Certificate return \openssl_get_cipher_methods($aliases); } - public function createSelfSigned(string $passphrase = '') : array + public function createSelfSigned(\SnappyMail\SensitiveString $passphrase, ?string $privateKey = null) : array { $options = array( 'config' => __DIR__ . '/openssl.cnf', @@ -134,7 +134,13 @@ class Certificate } $pkey = null; // openssl_pkey_new($options); - $csr = \openssl_csr_new($dn, $pkey, $options); + if ($privateKey) { + $pkey = \openssl_pkey_get_private($privateKey, $passphrase); + if (!$pkey) { + throw new \RuntimeException('OpenSSL pkey: ' . \openssl_error_string()); + } + } + $csr = \openssl_csr_new($dn, $pkey, $options); if ($csr) { $this->x509 = \openssl_csr_sign( $csr, @@ -145,13 +151,11 @@ class Certificate ); if ($this->x509/* && $this->canSign() && $this->canEncrypt()*/) { $this->pkey = $pkey; - $privatekey = ''; + $privateKey || \openssl_pkey_export($pkey, $privateKey, $passphrase); $certificate = ''; - $csrStr = ''; - \openssl_pkey_export($pkey, $privatekey, $passphrase); \openssl_x509_export($this->x509, $certificate); return array( - 'pkey' => $privatekey, + 'pkey' => $privateKey, 'x509' => $certificate, // 'pkcs12' => $this->asPKCS12($pkey, $passphrase/*, array $args = array()*/) // 'canSign' => $this->canSign(), @@ -167,7 +171,11 @@ class Certificate } // returns binary data - public function asPKCS12(string $pass = '', array $args = array()) : string + public function asPKCS12( + #[\SensitiveParameter] + string $pass = '', + array $args = array() + ) : string { $out = ''; \openssl_pkcs12_export($this->x509, $out, $this->pkey, $pass, $args); diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php b/snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php index 776aed98e..235ba7cfb 100644 --- a/snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php +++ b/snappymail/v/0.0.0/app/libraries/snappymail/smime/openssl.php @@ -34,15 +34,18 @@ class OpenSSL { $data = \openssl_x509_parse(\openssl_x509_read($certificate)); if (!$data) { - \error_log("OpenSSL parse: " . \openssl_error_string()); + \SnappyMail\Log::error('OpenSSL', "parse: " . \openssl_error_string()); return false; } $key = \str_replace(':', '', $data['extensions']['subjectKeyIdentifier'] ?? $data['hash']); $filename = "{$this->homedir}/{$key}.crt"; - if (!\file_exists($filename)) { + if (\file_exists($filename)) { + \SnappyMail\Log::debug('OpenSSL', "certificate {$key} already imported"); + } else { \file_put_contents("{$this->homedir}/{$key}.crt", $certificate); // \unlink("{$this->homedir}/certificates.json"); $this->certificates(true); + \SnappyMail\Log::debug('OpenSSL', "certificate {$key} imported"); } return true; } @@ -50,7 +53,7 @@ class OpenSSL public function certificates(bool $force = false) : array { $cacheFile = "{$this->homedir}/certificates.json"; - $result = \file_exists($cacheFile) + $result = (!$force && \file_exists($cacheFile)) ? \json_decode(\file_get_contents($cacheFile), true) : null; if (!\is_array($result)) { @@ -98,7 +101,7 @@ class OpenSSL } $result[] = $short; } else { - \error_log("OpenSSL parse({$file}): " . \openssl_error_string()); + \SnappyMail\Log::error('OpenSSL', "parse({$file}): " . \openssl_error_string()); } } \file_put_contents($cacheFile, \json_encode($result)); @@ -129,8 +132,7 @@ class OpenSSL } public function setPrivateKey(/*OpenSSLAsymmetricKey|string*/$privateKey, - #[\SensitiveParameter] - ?string $passphrase = null + ?\SnappyMail\SensitiveString $passphrase = null ) : void { $this->privateKey = \openssl_pkey_get_private($privateKey, $passphrase); @@ -142,6 +144,19 @@ class OpenSSL } } +/* + public function asn1parse(/*string|Temporary* / $input) : ?string + { + if (\is_string($input)) { + $tmp = new Temporary('smimein-'); + if (!$tmp->putContents($input)) { + return null; + } + $input = $tmp; + } + `openssl asn1parse -in $input->filename()` + } +*/ public function decrypt(/*string|Temporary*/ $input) : ?string { if (\is_string($input)) { diff --git a/snappymail/v/0.0.0/app/localization/ar-SA/user.json b/snappymail/v/0.0.0/app/localization/ar-SA/user.json index a5b5c6c44..681ec190d 100644 --- a/snappymail/v/0.0.0/app/localization/ar-SA/user.json +++ b/snappymail/v/0.0.0/app/localization/ar-SA/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "توقيع جيد من %USER%", "ERROR": "%TYPE% error: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "OpenPGP استيراد مفتاح", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME signed message", "ENCRYPTED_MESSAGE": "S\/MIME encrypted message", diff --git a/snappymail/v/0.0.0/app/localization/bg-BG/user.json b/snappymail/v/0.0.0/app/localization/bg-BG/user.json index 553dc3d84..0bd9a57e2 100644 --- a/snappymail/v/0.0.0/app/localization/bg-BG/user.json +++ b/snappymail/v/0.0.0/app/localization/bg-BG/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Валиден подпис за %USER%", "ERROR": "%TYPE% грешка: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Внасяне на OpenPGP ключ", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Подписано с S\/MIME", "ENCRYPTED_MESSAGE": "Шифровано с S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/cs-CZ/user.json b/snappymail/v/0.0.0/app/localization/cs-CZ/user.json index 349c021d6..67b9c5efa 100644 --- a/snappymail/v/0.0.0/app/localization/cs-CZ/user.json +++ b/snappymail/v/0.0.0/app/localization/cs-CZ/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Ověřený podpis od %USER%", "ERROR": "%TYPE% chyba: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Import OpenPGP klíče", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Zpráva podepsaná S\/MIME", "ENCRYPTED_MESSAGE": "Zpráva šifrovaná S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/da-DK/user.json b/snappymail/v/0.0.0/app/localization/da-DK/user.json index 7f59e18c7..a81a91d3b 100644 --- a/snappymail/v/0.0.0/app/localization/da-DK/user.json +++ b/snappymail/v/0.0.0/app/localization/da-DK/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Accepteret signatur for %USER\"", "ERROR": "%TYPE% fejl: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importer OpenPGP nøgle", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME signeret meddelelse", "ENCRYPTED_MESSAGE": "S\/MIME krypteret meddelelse", diff --git a/snappymail/v/0.0.0/app/localization/de-DE/user.json b/snappymail/v/0.0.0/app/localization/de-DE/user.json index a9b9675fc..5e07f0630 100644 --- a/snappymail/v/0.0.0/app/localization/de-DE/user.json +++ b/snappymail/v/0.0.0/app/localization/de-DE/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Gültige Unterschrift von %USER%", "ERROR": "%TYPE%-Fehler: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "OpenPGP-Schlüssel importieren", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME-signierte Nachricht", "ENCRYPTED_MESSAGE": "S\/MIME-verschlüsselte Nachricht", diff --git a/snappymail/v/0.0.0/app/localization/el-GR/user.json b/snappymail/v/0.0.0/app/localization/el-GR/user.json index a356d916c..8e71fbdda 100644 --- a/snappymail/v/0.0.0/app/localization/el-GR/user.json +++ b/snappymail/v/0.0.0/app/localization/el-GR/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Good signature from %USER%", "ERROR": "%TYPE% error: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Εισαγωγή κλειδιού OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Μήνυμα υπογεγραμμένο με S\/MIME", "ENCRYPTED_MESSAGE": "Μήνυμα κωδικοποιημένο με S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/en-GB/user.json b/snappymail/v/0.0.0/app/localization/en-GB/user.json index a45ee06a5..599754e0c 100644 --- a/snappymail/v/0.0.0/app/localization/en-GB/user.json +++ b/snappymail/v/0.0.0/app/localization/en-GB/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Good signature from %USER%", "ERROR": "%TYPE% error: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Import OpenPGP key", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME signed message", "ENCRYPTED_MESSAGE": "S\/MIME encrypted message", diff --git a/snappymail/v/0.0.0/app/localization/en/user.json b/snappymail/v/0.0.0/app/localization/en/user.json index c4013b6cd..c42199b18 100644 --- a/snappymail/v/0.0.0/app/localization/en/user.json +++ b/snappymail/v/0.0.0/app/localization/en/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Good signature from %USER%", "ERROR": "%TYPE% error: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Import OpenPGP key", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S/MIME Certificates", "SIGNED_MESSAGE": "S/MIME signed message", "ENCRYPTED_MESSAGE": "S/MIME encrypted message", diff --git a/snappymail/v/0.0.0/app/localization/es-ES/user.json b/snappymail/v/0.0.0/app/localization/es-ES/user.json index bbf3c8512..15ae227ad 100644 --- a/snappymail/v/0.0.0/app/localization/es-ES/user.json +++ b/snappymail/v/0.0.0/app/localization/es-ES/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Buena firma de %USER%", "ERROR": "Error %TYPE%: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importar clave OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Mensaje firmado mediante S\/MIME", "ENCRYPTED_MESSAGE": "Mensaje cifrado mediante S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/et-EE/user.json b/snappymail/v/0.0.0/app/localization/et-EE/user.json index d62282c68..3629b8826 100644 --- a/snappymail/v/0.0.0/app/localization/et-EE/user.json +++ b/snappymail/v/0.0.0/app/localization/et-EE/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Korralik signatuur kasutajalt %USER%", "ERROR": "%TYPE% viga: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Impordi OpenPGP võti", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME abil signeeritud kiri", "ENCRYPTED_MESSAGE": "S\/MIME abil krüpteeritud kiri", diff --git a/snappymail/v/0.0.0/app/localization/eu/user.json b/snappymail/v/0.0.0/app/localization/eu/user.json index a86fcfbc5..2a560f4b7 100644 --- a/snappymail/v/0.0.0/app/localization/eu/user.json +++ b/snappymail/v/0.0.0/app/localization/eu/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "%USER%-en sinadura egokia", "ERROR": "%TYPE% errorea: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Inportatu OpenPGP gakoa", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME sinatutako mezua", "ENCRYPTED_MESSAGE": "S\/MIME zifratutako mezua", diff --git a/snappymail/v/0.0.0/app/localization/fa-IR/user.json b/snappymail/v/0.0.0/app/localization/fa-IR/user.json index 39ae02b55..90eae77c2 100644 --- a/snappymail/v/0.0.0/app/localization/fa-IR/user.json +++ b/snappymail/v/0.0.0/app/localization/fa-IR/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "امضای صحیح از %USER%", "ERROR": "خطای %TYPE%: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "واردکردن کلید OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "پیام توسط S\/MIME امضاء شد", "ENCRYPTED_MESSAGE": "پیام توسط S\/MIME رمزنگاری شد", diff --git a/snappymail/v/0.0.0/app/localization/fi-FI/user.json b/snappymail/v/0.0.0/app/localization/fi-FI/user.json index 3218d34d4..61de7d2f1 100644 --- a/snappymail/v/0.0.0/app/localization/fi-FI/user.json +++ b/snappymail/v/0.0.0/app/localization/fi-FI/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Oikea allekirjoitus %USER% lle", "ERROR": "%TYPE% virhe: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Tuo OpenPGP avain", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME allekirjoitettu viesti", "ENCRYPTED_MESSAGE": "S\/MIME salattu visti", diff --git a/snappymail/v/0.0.0/app/localization/fr-FR/user.json b/snappymail/v/0.0.0/app/localization/fr-FR/user.json index 192e71452..a7fb60907 100644 --- a/snappymail/v/0.0.0/app/localization/fr-FR/user.json +++ b/snappymail/v/0.0.0/app/localization/fr-FR/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Signature valide pour %USER%", "ERROR": "Erreur %TYPE%: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importer la clef OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Message signé par S\/MIME", "ENCRYPTED_MESSAGE": "Message chiffré par S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/hu-HU/user.json b/snappymail/v/0.0.0/app/localization/hu-HU/user.json index 05e81b2ba..e52ba69b0 100644 --- a/snappymail/v/0.0.0/app/localization/hu-HU/user.json +++ b/snappymail/v/0.0.0/app/localization/hu-HU/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Jó aláírás tőle: %USER%", "ERROR": "%TYPE% hiba: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "OpenPGP kulcs importálás", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME aláírt üzenet", "ENCRYPTED_MESSAGE": "S\/MIME kódolt üzenet", diff --git a/snappymail/v/0.0.0/app/localization/id-ID/user.json b/snappymail/v/0.0.0/app/localization/id-ID/user.json index 3a53d77c5..a80534b95 100644 --- a/snappymail/v/0.0.0/app/localization/id-ID/user.json +++ b/snappymail/v/0.0.0/app/localization/id-ID/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Tandatangan sah dari user %USER%", "ERROR": "Kesalahan %TYPE%: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Impor kunci OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Pesan bertanda-tangan S\/MIME", "ENCRYPTED_MESSAGE": "Pesan terenkripsi S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/is-IS/user.json b/snappymail/v/0.0.0/app/localization/is-IS/user.json index dab352ccc..51d0207a3 100644 --- a/snappymail/v/0.0.0/app/localization/is-IS/user.json +++ b/snappymail/v/0.0.0/app/localization/is-IS/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Gild undirritun frá %USER%", "ERROR": "%TYPE% villa: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Flytja inn OpenPGP-lykil", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Skeyti undirritað með S\/MIME", "ENCRYPTED_MESSAGE": "Skeyti dulritað með S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/it-IT/user.json b/snappymail/v/0.0.0/app/localization/it-IT/user.json index 85454dbf7..d8fb8b1dd 100644 --- a/snappymail/v/0.0.0/app/localization/it-IT/user.json +++ b/snappymail/v/0.0.0/app/localization/it-IT/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Firma valida da %USER%", "ERROR": "Errore di %TYPE%: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importa chiave OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Messaggio firmato con S\/MIME", "ENCRYPTED_MESSAGE": "Messaggio cifrato con S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/ja-JP/user.json b/snappymail/v/0.0.0/app/localization/ja-JP/user.json index d9d3ca459..77b27be1e 100644 --- a/snappymail/v/0.0.0/app/localization/ja-JP/user.json +++ b/snappymail/v/0.0.0/app/localization/ja-JP/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "%USER%からの正しい署名", "ERROR": "%TYPE% エラー: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "OpenPGPキーをインポート", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME署名済みメッセージ", "ENCRYPTED_MESSAGE": "S\/MIME暗号化メッセージ", diff --git a/snappymail/v/0.0.0/app/localization/ko-KR/user.json b/snappymail/v/0.0.0/app/localization/ko-KR/user.json index 21ec495a4..1c74e40a2 100644 --- a/snappymail/v/0.0.0/app/localization/ko-KR/user.json +++ b/snappymail/v/0.0.0/app/localization/ko-KR/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "%USER%(으)로부터 수신한 양호한 서명", "ERROR": "%TYPE% 오류: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "OpenPGP 키 가져오기", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME로 서명된 메세지입니다.", "ENCRYPTED_MESSAGE": "S\/MIME로 암호화된 메세지입니다.", diff --git a/snappymail/v/0.0.0/app/localization/lt-LT/user.json b/snappymail/v/0.0.0/app/localization/lt-LT/user.json index 131f0ff17..91f6a5883 100644 --- a/snappymail/v/0.0.0/app/localization/lt-LT/user.json +++ b/snappymail/v/0.0.0/app/localization/lt-LT/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Tinkamas parašas nuo %USER%", "ERROR": "%TYPE% klaida: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importuoti OpenPGP raktą", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME pasirašytas pranešimas", "ENCRYPTED_MESSAGE": "S\/MIME šifruotas pranešimas", diff --git a/snappymail/v/0.0.0/app/localization/lv-LV/user.json b/snappymail/v/0.0.0/app/localization/lv-LV/user.json index 457f236dd..9b06be54d 100644 --- a/snappymail/v/0.0.0/app/localization/lv-LV/user.json +++ b/snappymail/v/0.0.0/app/localization/lv-LV/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Good signature from %USER%", "ERROR": "%TYPE% error: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Import OpenPGP key", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME signed message", "ENCRYPTED_MESSAGE": "S\/MIME encrypted message", diff --git a/snappymail/v/0.0.0/app/localization/nb-NO/user.json b/snappymail/v/0.0.0/app/localization/nb-NO/user.json index 7c0edcc20..c3bfd3c08 100644 --- a/snappymail/v/0.0.0/app/localization/nb-NO/user.json +++ b/snappymail/v/0.0.0/app/localization/nb-NO/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Gyldig signatur fra %USER%", "ERROR": "%TYPE%-feil: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importer OpenPGP-nøkkel", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME-signert melding", "ENCRYPTED_MESSAGE": "S\/MIME-kryptert melding", diff --git a/snappymail/v/0.0.0/app/localization/nl-NL/user.json b/snappymail/v/0.0.0/app/localization/nl-NL/user.json index fb4f0bf2f..c7b374905 100644 --- a/snappymail/v/0.0.0/app/localization/nl-NL/user.json +++ b/snappymail/v/0.0.0/app/localization/nl-NL/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Juiste handtekening van %USER%", "ERROR": "%TYPE% foutmelding: %ERROR%", "CREATE_SELF_SIGNED": "Zelfondertekend certificaat aanmaken", - "VALID_UNTIL": "Geldig tot" + "VALID_UNTIL": "Geldig tot", + "PRIVATE_KEY": "Privésleutel" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importeer OpenPGP sleutel", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Importeer S\/MIME certificaat", + "CERTIFICATE": "Certificaat", "CERTIFICATES": "S\/MIME Certificaten", "SIGNED_MESSAGE": "S\/MIME ondertekend bericht", "ENCRYPTED_MESSAGE": "S\/MIME versleuteld bericht", diff --git a/snappymail/v/0.0.0/app/localization/pl-PL/user.json b/snappymail/v/0.0.0/app/localization/pl-PL/user.json index 1f1930b27..64d30cb08 100644 --- a/snappymail/v/0.0.0/app/localization/pl-PL/user.json +++ b/snappymail/v/0.0.0/app/localization/pl-PL/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Poprawnie podpisana przez %USER%", "ERROR": "Błąd %TYPE%: %ERROR%", "CREATE_SELF_SIGNED": "Utwórz samodzielnie podpisany certyfikat", - "VALID_UNTIL": "Ważny do" + "VALID_UNTIL": "Ważny do", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importowanie klucza OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Importuj certyfikat S\/MIME", + "CERTIFICATE": "Certificate", "CERTIFICATES": "Certyfikaty S\/MIME", "SIGNED_MESSAGE": "Wiadomość podpisana S\/MIME", "ENCRYPTED_MESSAGE": "Wiadomość zaszyfrowana S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/pt-BR/user.json b/snappymail/v/0.0.0/app/localization/pt-BR/user.json index b043e1de0..917bb3189 100644 --- a/snappymail/v/0.0.0/app/localization/pt-BR/user.json +++ b/snappymail/v/0.0.0/app/localization/pt-BR/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Boa assinatura de %USER%", "ERROR": "Erro %TYPE%: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importar chave OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Mensagem assinada com S\/MIME", "ENCRYPTED_MESSAGE": "Mensagem criptografada com S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/pt-PT/user.json b/snappymail/v/0.0.0/app/localization/pt-PT/user.json index 11b2854d2..cdd061104 100644 --- a/snappymail/v/0.0.0/app/localization/pt-PT/user.json +++ b/snappymail/v/0.0.0/app/localization/pt-PT/user.json @@ -154,8 +154,8 @@ "HAS_VIRUS_WARNING": "AVISO: vírus detetado", "TAGS": "Etiquetas", "NEW_TAG": "Nova etiqueta", - "ENABLE_TRACKING_LINKS": "Enable tracking links", - "TRACKING_ENABLED": "Links are now tracked when clicked!" + "ENABLE_TRACKING_LINKS": "Ativar ligações de rastreamento", + "TRACKING_ENABLED": "As ligações irão ser rastreadas quando clicadas!" }, "MESSAGE_TAGS": { "$important": "Importante", @@ -255,7 +255,7 @@ "POPUPS_IDENTITY": { "TITLE_ADD_IDENTITY": "Adicionar identidade?", "TITLE_UPDATE_IDENTITY": "Atualizar identidade?", - "LABEL": "Label", + "LABEL": "Rótulo", "BUTTON_ADD_IDENTITY": "Adicionar", "BUTTON_UPDATE_IDENTITY": "Atualizar", "SIGNATURE": "Assinatura", @@ -281,8 +281,9 @@ "VERIFY": "Validar", "GOOD_SIGNATURE": "Boa assinatura de %USER%", "ERROR": "Erro %TYPE%: %ERROR%", - "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "CREATE_SELF_SIGNED": "Criar auto-assinado", + "VALID_UNTIL": "Válido até", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importar chave OpenPGP", @@ -295,18 +296,19 @@ "SIGNED_MESSAGE": "Mensagem assinada com OpenPGP", "ENCRYPTED_MESSAGE": "Mensagem encriptada com OpenPGP", "STORE_IN_GNUPG": "Guardar no servidor em GnuPG", - "STORE_PUBLIC_KEY_IN_GNUPG": "Store public key on server in GnuPG", - "STORE_PRIVATE_KEY_IN_GNUPG": "Store private key on server in GnuPG", + "STORE_PUBLIC_KEY_IN_GNUPG": "Guardar a chave pública no servidor em GnuPG", + "STORE_PRIVATE_KEY_IN_GNUPG": "Guardar a chave privada no servidor em GnuPG", "BACKUP_ON_SERVER": "Guardar (encriptado) no servidor", - "BACKUP_PUBLIC_KEY_ON_SERVER": "Backup public key on server", - "BACKUP_PRIVATE_KEY_ON_SERVER": "Backup private key on server" + "BACKUP_PUBLIC_KEY_ON_SERVER": "Fazer cópia de segurança da chave pública no servidor", + "BACKUP_PRIVATE_KEY_ON_SERVER": "Fazer cópia de segurança da chave privada no servidor" }, "SMIME": { - "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", - "CERTIFICATES": "S\/MIME Certificates", + "POPUP_IMPORT_TITLE": "Importar certificado S\/MIME", + "CERTIFICATE": "Certificate", + "CERTIFICATES": "Certificados S\/MIME", "SIGNED_MESSAGE": "Mensagem assinada com S\/MIME", "ENCRYPTED_MESSAGE": "Mensagem encriptada com S\/MIME", - "PRIVATE_KEY_OF": "S\/MIME private key of %EMAIL%" + "PRIVATE_KEY_OF": "Chave privada S\/MIME de %EMAIL%" }, "POPUPS_FILTER": { "TITLE_CREATE_FILTER": "Criar um filtro?", @@ -405,7 +407,7 @@ "IMAGEUPLOAD": "Escolher imagem", "UNDO": "Desfazer", "REDO": "Refazer", - "REMOVESTYLE": "Remove style" + "REMOVESTYLE": "Remover estilo" }, "SETTINGS_LABELS": { "GENERAL": "Geral", @@ -530,7 +532,7 @@ "SETTINGS_OPENPGP": { "BUTTON_IMPORT_KEY": "Importar chave", "BUTTON_GENERATE_KEY_PAIR": "Gerar chaves", - "IMPORT_FROM_SERVER": "Import from server", + "IMPORT_FROM_SERVER": "Importar de servidor", "TITLE_PRIVATE": "Privada", "TITLE_PUBLIC": "Pública", "GET_MAILVELOPE": "Obter extensão Mailvelope para o navegador." diff --git a/snappymail/v/0.0.0/app/localization/pt/user.json b/snappymail/v/0.0.0/app/localization/pt/user.json index 11b2854d2..cdd061104 100644 --- a/snappymail/v/0.0.0/app/localization/pt/user.json +++ b/snappymail/v/0.0.0/app/localization/pt/user.json @@ -154,8 +154,8 @@ "HAS_VIRUS_WARNING": "AVISO: vírus detetado", "TAGS": "Etiquetas", "NEW_TAG": "Nova etiqueta", - "ENABLE_TRACKING_LINKS": "Enable tracking links", - "TRACKING_ENABLED": "Links are now tracked when clicked!" + "ENABLE_TRACKING_LINKS": "Ativar ligações de rastreamento", + "TRACKING_ENABLED": "As ligações irão ser rastreadas quando clicadas!" }, "MESSAGE_TAGS": { "$important": "Importante", @@ -255,7 +255,7 @@ "POPUPS_IDENTITY": { "TITLE_ADD_IDENTITY": "Adicionar identidade?", "TITLE_UPDATE_IDENTITY": "Atualizar identidade?", - "LABEL": "Label", + "LABEL": "Rótulo", "BUTTON_ADD_IDENTITY": "Adicionar", "BUTTON_UPDATE_IDENTITY": "Atualizar", "SIGNATURE": "Assinatura", @@ -281,8 +281,9 @@ "VERIFY": "Validar", "GOOD_SIGNATURE": "Boa assinatura de %USER%", "ERROR": "Erro %TYPE%: %ERROR%", - "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "CREATE_SELF_SIGNED": "Criar auto-assinado", + "VALID_UNTIL": "Válido até", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importar chave OpenPGP", @@ -295,18 +296,19 @@ "SIGNED_MESSAGE": "Mensagem assinada com OpenPGP", "ENCRYPTED_MESSAGE": "Mensagem encriptada com OpenPGP", "STORE_IN_GNUPG": "Guardar no servidor em GnuPG", - "STORE_PUBLIC_KEY_IN_GNUPG": "Store public key on server in GnuPG", - "STORE_PRIVATE_KEY_IN_GNUPG": "Store private key on server in GnuPG", + "STORE_PUBLIC_KEY_IN_GNUPG": "Guardar a chave pública no servidor em GnuPG", + "STORE_PRIVATE_KEY_IN_GNUPG": "Guardar a chave privada no servidor em GnuPG", "BACKUP_ON_SERVER": "Guardar (encriptado) no servidor", - "BACKUP_PUBLIC_KEY_ON_SERVER": "Backup public key on server", - "BACKUP_PRIVATE_KEY_ON_SERVER": "Backup private key on server" + "BACKUP_PUBLIC_KEY_ON_SERVER": "Fazer cópia de segurança da chave pública no servidor", + "BACKUP_PRIVATE_KEY_ON_SERVER": "Fazer cópia de segurança da chave privada no servidor" }, "SMIME": { - "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", - "CERTIFICATES": "S\/MIME Certificates", + "POPUP_IMPORT_TITLE": "Importar certificado S\/MIME", + "CERTIFICATE": "Certificate", + "CERTIFICATES": "Certificados S\/MIME", "SIGNED_MESSAGE": "Mensagem assinada com S\/MIME", "ENCRYPTED_MESSAGE": "Mensagem encriptada com S\/MIME", - "PRIVATE_KEY_OF": "S\/MIME private key of %EMAIL%" + "PRIVATE_KEY_OF": "Chave privada S\/MIME de %EMAIL%" }, "POPUPS_FILTER": { "TITLE_CREATE_FILTER": "Criar um filtro?", @@ -405,7 +407,7 @@ "IMAGEUPLOAD": "Escolher imagem", "UNDO": "Desfazer", "REDO": "Refazer", - "REMOVESTYLE": "Remove style" + "REMOVESTYLE": "Remover estilo" }, "SETTINGS_LABELS": { "GENERAL": "Geral", @@ -530,7 +532,7 @@ "SETTINGS_OPENPGP": { "BUTTON_IMPORT_KEY": "Importar chave", "BUTTON_GENERATE_KEY_PAIR": "Gerar chaves", - "IMPORT_FROM_SERVER": "Import from server", + "IMPORT_FROM_SERVER": "Importar de servidor", "TITLE_PRIVATE": "Privada", "TITLE_PUBLIC": "Pública", "GET_MAILVELOPE": "Obter extensão Mailvelope para o navegador." diff --git a/snappymail/v/0.0.0/app/localization/ro-RO/user.json b/snappymail/v/0.0.0/app/localization/ro-RO/user.json index 3b804924a..f6bf1d448 100644 --- a/snappymail/v/0.0.0/app/localization/ro-RO/user.json +++ b/snappymail/v/0.0.0/app/localization/ro-RO/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Good signature from %USER%", "ERROR": "%TYPE% error: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Import OpenPGP key", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME signed message", "ENCRYPTED_MESSAGE": "S\/MIME encrypted message", diff --git a/snappymail/v/0.0.0/app/localization/ru-RU/user.json b/snappymail/v/0.0.0/app/localization/ru-RU/user.json index c306d7226..a9b841bc7 100644 --- a/snappymail/v/0.0.0/app/localization/ru-RU/user.json +++ b/snappymail/v/0.0.0/app/localization/ru-RU/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Подтвержденная сигнатура для %USER%", "ERROR": "%TYPE% ошибка: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Импорт OpenPGP ключа", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME подписанное сообщение", "ENCRYPTED_MESSAGE": "S\/MIME шифрованное сообщение", diff --git a/snappymail/v/0.0.0/app/localization/sk-SK/user.json b/snappymail/v/0.0.0/app/localization/sk-SK/user.json index 14edc1aab..9f9eab751 100644 --- a/snappymail/v/0.0.0/app/localization/sk-SK/user.json +++ b/snappymail/v/0.0.0/app/localization/sk-SK/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Good signature from %USER%", "ERROR": "%TYPE% error: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Importovať kľúč OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Správa podpísaná s S\/MIME", "ENCRYPTED_MESSAGE": "Správa šifrovaná s S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/sl-SI/user.json b/snappymail/v/0.0.0/app/localization/sl-SI/user.json index a491aeb58..a62f793f0 100644 --- a/snappymail/v/0.0.0/app/localization/sl-SI/user.json +++ b/snappymail/v/0.0.0/app/localization/sl-SI/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Dober podpis od %USER%", "ERROR": "%TYPE% napaka: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Uvoz OpenPGP ključa", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Sporočilo, podpisano z S\/MIME", "ENCRYPTED_MESSAGE": "Sporočilo, šifrirano z S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/sv-SE/user.json b/snappymail/v/0.0.0/app/localization/sv-SE/user.json index 52941c9ed..4041f143c 100644 --- a/snappymail/v/0.0.0/app/localization/sv-SE/user.json +++ b/snappymail/v/0.0.0/app/localization/sv-SE/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Godkänd signatur för %USER%", "ERROR": "%TYPE%-fel: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Import av OpenPGP-nyckel", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME-signerat meddelande", "ENCRYPTED_MESSAGE": "S\/MIME-krypterat meddelande", diff --git a/snappymail/v/0.0.0/app/localization/tr-TR/user.json b/snappymail/v/0.0.0/app/localization/tr-TR/user.json index 93e7c1e88..6d0e83dd6 100644 --- a/snappymail/v/0.0.0/app/localization/tr-TR/user.json +++ b/snappymail/v/0.0.0/app/localization/tr-TR/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Geçerli imza %USER%", "ERROR": "%TYPE% hatası: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "OpenPGP key'i içe aktar", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME imzalı mesaj", "ENCRYPTED_MESSAGE": "S\/MIME şifreli mesaj", diff --git a/snappymail/v/0.0.0/app/localization/uk-UA/user.json b/snappymail/v/0.0.0/app/localization/uk-UA/user.json index a7bd28f5b..b4742ae0d 100644 --- a/snappymail/v/0.0.0/app/localization/uk-UA/user.json +++ b/snappymail/v/0.0.0/app/localization/uk-UA/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Підтверджена сигнатура для %USER%", "ERROR": "%TYPE% помилка: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Імпорт OpenPGP ключа", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "S\/MIME підписане повідомлення", "ENCRYPTED_MESSAGE": "S\/MIME шифроване повідомлення", diff --git a/snappymail/v/0.0.0/app/localization/vi-VN/user.json b/snappymail/v/0.0.0/app/localization/vi-VN/user.json index dd617e949..358270e2d 100644 --- a/snappymail/v/0.0.0/app/localization/vi-VN/user.json +++ b/snappymail/v/0.0.0/app/localization/vi-VN/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "Chữ ký ổn từ %USER%", "ERROR": "Lỗi %TYPE%: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "Nhập mật mã OpenPGP", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "Thư đã được ký xác nhận bằng mã S\/MIME", "ENCRYPTED_MESSAGE": "Thư đã được mã hóa bởi S\/MIME", diff --git a/snappymail/v/0.0.0/app/localization/zh-CN/user.json b/snappymail/v/0.0.0/app/localization/zh-CN/user.json index 325c7e3b5..60edd171f 100644 --- a/snappymail/v/0.0.0/app/localization/zh-CN/user.json +++ b/snappymail/v/0.0.0/app/localization/zh-CN/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "来自 %USER% 的可用签名", "ERROR": "%TYPE% 错误: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "导入 OpenPGP 密钥", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "由 S\/MIME 签名", "ENCRYPTED_MESSAGE": "由 S\/MIME 加密", diff --git a/snappymail/v/0.0.0/app/localization/zh-TW/user.json b/snappymail/v/0.0.0/app/localization/zh-TW/user.json index 54a15fdee..907397303 100644 --- a/snappymail/v/0.0.0/app/localization/zh-TW/user.json +++ b/snappymail/v/0.0.0/app/localization/zh-TW/user.json @@ -282,7 +282,8 @@ "GOOD_SIGNATURE": "來自「%USER%」的良好簽署", "ERROR": "%TYPE% 錯誤: %ERROR%", "CREATE_SELF_SIGNED": "Create self-signed", - "VALID_UNTIL": "Valid until" + "VALID_UNTIL": "Valid until", + "PRIVATE_KEY": "Private key" }, "OPENPGP": { "POPUP_IMPORT_TITLE": "匯入 OpenPGP 金鑰", @@ -303,6 +304,7 @@ }, "SMIME": { "POPUP_IMPORT_TITLE": "Import S\/MIME certificate", + "CERTIFICATE": "Certificate", "CERTIFICATES": "S\/MIME Certificates", "SIGNED_MESSAGE": "已透過 S\/MIME 簽署郵件", "ENCRYPTED_MESSAGE": "已透過 S\/MIME 加密郵件", diff --git a/snappymail/v/0.0.0/app/templates/Views/User/PopupsIdentity.html b/snappymail/v/0.0.0/app/templates/Views/User/PopupsIdentity.html index f49013933..57c451116 100644 --- a/snappymail/v/0.0.0/app/templates/Views/User/PopupsIdentity.html +++ b/snappymail/v/0.0.0/app/templates/Views/User/PopupsIdentity.html @@ -89,14 +89,14 @@
S/MIME
- +
- +
-
+