From 6975aefed9e8cb0e87d1af9eeaddd97327649281 Mon Sep 17 00:00:00 2001 From: Armin Graefe Date: Mon, 23 Oct 2023 06:52:13 +0200 Subject: [PATCH] Add SCRAM-SHA-1, SCRAM-SHA-224, SCRAM-SHA-256, SCRAM-SHA-384 and SCRAM-SHA-512 support (#76) * SCRAM-SHA-1(-PLUS) + SCRAM-SHA-256(-PLUS) + SCRAM-SHA-512(-PLUS) supports #57 * Update README.rst * Sort authentication methods alphabetical and mark CRAM-MD5 and DIGEST-MD5 as DEPRECATED * Mark LOGIN and PLAIN as DEPRECATED * Mark CRAM-MD5, DIGEST-MD5, LOGIN and PLAIN as DEPRECATED in Sourcecode * Trigger deprecation warning for CRAM-MD5, DIGEST-MD5, LOGIN and PLAIN in error-log * Split lines for deprecation warnings * SCRAM-SHA-1(-PLUS) + SCRAM-SHA-256(-PLUS) + SCRAM-SHA-512(-PLUS) supports #57 * Remove deprecation warning for PLAIN authentication method --- Net/SMTP.php | 184 +++++++++++++++++++++++++++++++++++++++++++++++++-- README.rst | 84 +++++++++++++++-------- 2 files changed, 235 insertions(+), 33 deletions(-) diff --git a/Net/SMTP.php b/Net/SMTP.php index 685f95e..c67244c 100644 --- a/Net/SMTP.php +++ b/Net/SMTP.php @@ -162,6 +162,13 @@ class Net_SMTP */ protected $gssapi_cname = null; + /** + * SCRAM SHA-Hash algorithm. + * + * @var string + */ + protected $scram_sha_hash_algorithm = null; + /** * Instantiates a new Net_SMTP object, overriding any defaults * with parameters that are passed in. @@ -215,6 +222,11 @@ public function __construct($host = null, $port = null, $localhost = null, if (@include_once 'Auth/SASL.php') { $this->setAuthMethod('CRAM-MD5', array($this, 'authCramMD5')); $this->setAuthMethod('DIGEST-MD5', array($this, 'authDigestMD5')); + $this->setAuthMethod('SCRAM-SHA-1', array($this, 'authScramSHA1')); + $this->setAuthMethod('SCRAM-SHA-224', array($this, 'authScramSHA224')); + $this->setAuthMethod('SCRAM-SHA-256', array($this, 'authScramSHA256')); + $this->setAuthMethod('SCRAM-SHA-384', array($this, 'authScramSHA384')); + $this->setAuthMethod('SCRAM-SHA-512', array($this, 'authScramSHA512')); } /* These standard authentication methods are always available. */ @@ -819,9 +831,13 @@ public function setAuthMethod($name, $callback, $prepend = true) * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @since 1.1.0 + * @deprecated 1.11.0 */ protected function authDigestMD5($uid, $pwd, $authz = '') { + trigger_error(__CLASS__ . ' (' . $this->host . '): Authentication method DIGEST-MD5' . + ' is no longer secure and should be avoided.', E_USER_DEPRECATED); + if (PEAR::isError($error = $this->put('AUTH', 'DIGEST-MD5'))) { return $error; } @@ -834,8 +850,7 @@ protected function authDigestMD5($uid, $pwd, $authz = '') return $error; } - $auth_sasl = new Auth_SASL; - $digest = $auth_sasl->factory('digest-md5'); + $digest = Auth_SASL::factory('digest-md5'); $challenge = base64_decode($this->arguments[0]); $auth_str = base64_encode( $digest->getResponse($uid, $pwd, $challenge, $this->host, "smtp", $authz) @@ -871,9 +886,13 @@ protected function authDigestMD5($uid, $pwd, $authz = '') * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @since 1.1.0 + * @deprecated 1.11.0 */ protected function authCRAMMD5($uid, $pwd, $authz = '') { + trigger_error(__CLASS__ . ' (' . $this->host . '): Authentication method CRAM-MD5' . + ' is no longer secure and should be avoided.', E_USER_DEPRECATED); + if (PEAR::isError($error = $this->put('AUTH', 'CRAM-MD5'))) { return $error; } @@ -886,9 +905,8 @@ protected function authCRAMMD5($uid, $pwd, $authz = '') return $error; } - $auth_sasl = new Auth_SASL; $challenge = base64_decode($this->arguments[0]); - $cram = $auth_sasl->factory('cram-md5'); + $cram = Auth_SASL::factory('cram-md5'); $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge)); if (PEAR::isError($error = $this->put($auth_str))) { @@ -911,9 +929,13 @@ protected function authCRAMMD5($uid, $pwd, $authz = '') * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @since 1.1.0 + * @deprecated 1.11.0 */ protected function authLogin($uid, $pwd, $authz = '') { + trigger_error(__CLASS__ . ' (' . $this->host . '): Authentication method LOGIN' . + ' is no longer secure and should be avoided.', E_USER_DEPRECATED); + if (PEAR::isError($error = $this->put('AUTH', 'LOGIN'))) { return $error; } @@ -1075,6 +1097,7 @@ protected function authGSSAPI($uid, $pwd, $authz = '') * @param string $uid The userid to authenticate as. * @param string $token The access token to authenticate with. * @param string $authz The optional authorization proxy identifier. + * @param object $conn The current object * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. @@ -1129,6 +1152,159 @@ public function authXOAuth2($uid, $token, $authz, $conn) return true; } + /** + * Authenticates the user using the SCRAM-SHA-1 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.11.0 + */ + protected function authScramSHA1($uid, $pwd, $authz = '') + { + $this->scram_sha_hash_algorithm = 'SCRAM-SHA-1'; + return $this->authScramSHA($uid, $pwd, $authz); + } + + /** + * Authenticates the user using the SCRAM-SHA-224 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.11.0 + */ + protected function authScramSHA224($uid, $pwd, $authz = '') + { + $this->scram_sha_hash_algorithm = 'SCRAM-SHA-224'; + return $this->authScramSHA($uid, $pwd, $authz); + } + + /** + * Authenticates the user using the SCRAM-SHA-256 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.11.0 + */ + protected function authScramSHA256($uid, $pwd, $authz = '') + { + $this->scram_sha_hash_algorithm = 'SCRAM-SHA-256'; + return $this->authScramSHA($uid, $pwd, $authz); + } + + /** + * Authenticates the user using the SCRAM-SHA-384 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.11.0 + */ + protected function authScramSHA384($uid, $pwd, $authz = '') + { + $this->scram_sha_hash_algorithm = 'SCRAM-SHA-384'; + return $this->authScramSHA($uid, $pwd, $authz); + } + + /** + * Authenticates the user using the SCRAM-SHA-512 method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.11.0 + */ + protected function authScramSHA512($uid, $pwd, $authz = '') + { + $this->scram_sha_hash_algorithm = 'SCRAM-SHA-512'; + return $this->authScramSHA($uid, $pwd, $authz); + } + + /** + * Authenticates the user using the SCRAM-SHA method. + * + * @param string $uid The userid to authenticate as. + * @param string $pwd The password to authenticate with. + * @param string $authz The optional authorization proxy identifier. + * + * @return mixed Returns a PEAR_Error with an error message on any + * kind of failure, or true on success. + * @since 1.11.0 + */ + protected function authScramSHA($uid, $pwd, $authz = '') + { + if (PEAR::isError($error = $this->put('AUTH', $this->scram_sha_hash_algorithm))) { + return $error; + } + /* 334: Continue authentication request */ + if (PEAR::isError($error = $this->parseResponse(334))) { + /* 503: Error: already authenticated */ + if ($this->code === 503) { + return true; + } + return $error; + } + + $cram = Auth_SASL::factory($this->scram_sha_hash_algorithm); + $auth_str = base64_encode($cram->getResponse($uid, $pwd)); + + /* Step 1: Send first authentication request */ + if (PEAR::isError($error = $this->put($auth_str))) { + return $error; + } + + /* 334: Continue authentication request with password salt */ + if (PEAR::isError($error = $this->parseResponse(334))) { + return $error; + } + + $challenge = base64_decode($this->arguments[0]); + $auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge)); + + /* Step 2: Send salted authentication request */ + if (PEAR::isError($error = $this->put($auth_str))) { + return $error; + } + + /* 334: Continue authentication request with password salt */ + if (PEAR::isError($error = $this->parseResponse(334))) { + return $error; + } + + /* Verify server signature */ + $verification = $cram->processOutcome(base64_decode($this->arguments[0])); + if ($verification == false) { + return PEAR::raiseError("SCRAM Server verification on step 3 not successful"); + } + + /* Step 3: Send a request to acknowledge verification */ + if (PEAR::isError($error = $this->put("NOOP"))) { + return $error; + } + + /* 235: Authentication successful */ + if (PEAR::isError($error = $this->parseResponse(235))) { + return $error; + } + } + /** * Send the HELO command. * diff --git a/README.rst b/README.rst index 3871fb9..8322a0b 100644 --- a/README.rst +++ b/README.rst @@ -6,8 +6,11 @@ User Documentation -------------------- -:Author: Jon Parise -:Contact: jon@php.net ++--------+-----------+----------------------+ +|Author: |Jon Parise |Armin Graefe | ++--------+-----------+----------------------+ +|Contact:|jon@php.net|schengawegga@gmail.com| ++--------+-----------+----------------------+ .. contents:: Table of Contents .. section-numbering:: @@ -41,9 +44,9 @@ The ``Auth_SASL`` Package ------------------------- The `Auth_SASL`_ package is an optional dependency. If it is available, the -Net_SMTP package will be able to support the DIGEST-MD5_ and CRAM-MD5_ SMTP -authentication methods. Otherwise, only the LOGIN_ and PLAIN_ methods will -be available. +Net_SMTP package will be able to support the DIGEST-MD5_, CRAM-MD5_ and +SCRAM-SHA_ SMTP authentication methods. Otherwise, only the LOGIN_ and +PLAIN_ methods will be available. Error Handling ============== @@ -67,25 +70,25 @@ methods, in order of preference: .. _RFC-2554: https://www.ietf.org/rfc/rfc2554.txt -GSSAPI ------- +CRAM-MD5 (DEPRECATED) +-------- -The GSSAPI authentication method uses Kerberos 5 protocol (RFC-4120_). -Does not use user/password. -Requires Service Principal ``gssapi_principal`` parameter and -has an optional Credentials Cache ``gssapi_cname`` parameter. -Requires DNS and Key Distribution Center (KDC) setup. -It is considered the most secure method of SMTP authentication. +**DEPRECATED** +This authentication method is no longer secure and should be avoided. -**Note:** The GSSAPI authentication method is only supported -if the krb5_ php extension is available. +The CRAM-MD5 authentication method has been superseded by the DIGEST-MD5_ +method in terms of security. It is provided here for compatibility with +older SMTP servers that may not support the newer DIGEST-MD5 algorithm. -.. _RFC-4120: https://tools.ietf.org/html/rfc4120 -.. _krb5: https://pecl.php.net/package/krb5 +**Note:** The CRAM-MD5 authentication method is only supported if the +AUTH_SASL_ package is available. -DIGEST-MD5 +DIGEST-MD5 (DEPRECATED) ---------- +**DEPRECATED** +This authentication method is no longer secure and should be avoided. + The DIGEST-MD5 authentication method uses `RSA Data Security Inc.`_'s MD5 Message Digest algorithm. It is considered a more secure method of SMTP authentication than PLAIN or LOGIN, while still vulnerable to MitM attacks @@ -96,31 +99,54 @@ AUTH_SASL_ package is available. .. _RSA Data Security Inc.: https://www.rsasecurity.com/ -CRAM-MD5 --------- +GSSAPI +------ -The CRAM-MD5 authentication method has been superseded by the DIGEST-MD5_ -method in terms of security. It is provided here for compatibility with -older SMTP servers that may not support the newer DIGEST-MD5 algorithm. +The GSSAPI authentication method uses Kerberos 5 protocol (RFC-4120_). +Does not use user/password. +Requires Service Principal ``gssapi_principal`` parameter and +has an optional Credentials Cache ``gssapi_cname`` parameter. +Requires DNS and Key Distribution Center (KDC) setup. +It is considered the most secure method of SMTP authentication. -**Note:** The CRAM-MD5 authentication method is only supported if the -AUTH_SASL_ package is available. +**Note:** The GSSAPI authentication method is only supported +if the krb5_ php extension is available. -LOGIN +.. _RFC-4120: https://tools.ietf.org/html/rfc4120 +.. _krb5: https://pecl.php.net/package/krb5 + +LOGIN (DEPRECATED) ----- +**DEPRECATED** +This authentication method is no longer secure and should be avoided. + The LOGIN authentication method encrypts the user's password using the Base64_ encoding scheme. Because decrypting a Base64-encoded string is -trivial, LOGIN is not considered a secure authentication method and should -be avoided. +trivial. .. _Base64: https://www.php.net/manual/en/function.base64-encode.php PLAIN ----- +This authentication method is no longer secure and should only be used +local or via an TLS encrypted connection. + The PLAIN authentication method sends the user's password in plain text. -This method of authentication is not secure and should be avoided. + +SCRAM +-------- + +In cryptography, the Salted Challenge Response Authentication Mechanism (SCRAM) +is a family of modern, password-based challenge–response authentication mechanisms +providing authentication to a server. + +Available mechanisms are SCRAM-SHA-1, SCRAM-SHA-224, SCRAM-SHA-256, SCRAM-SHA-384 +and SCRAM-SHA-512. + +**Note:** The SCRAM-SHA authentication method is only supported if the +AUTH_SASL_ package is available. XOAUTH2 -------