Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SCRAM-SHA-1, SCRAM-SHA-224, SCRAM-SHA-256, SCRAM-SHA-384 and SCRAM-SHA-512 support #76

Merged
merged 11 commits into from
Oct 23, 2023
Merged
186 changes: 181 additions & 5 deletions Net/SMTP.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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. */
Expand Down Expand Up @@ -426,7 +438,7 @@ public function command($command, $valid)
*/
public function getResponse()
{
return array($this->code, join("\n", $this->arguments));
return array($this->code, implode("\n", $this->arguments));
}

/**
Expand Down Expand Up @@ -765,9 +777,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;
}
Expand All @@ -780,8 +796,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)
Expand Down Expand Up @@ -817,9 +832,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;
}
Expand All @@ -832,9 +851,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))) {
Expand All @@ -857,9 +875,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;
}
Expand Down Expand Up @@ -1021,6 +1043,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.
Expand Down Expand Up @@ -1075,6 +1098,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.
*
Expand Down
84 changes: 55 additions & 29 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
User Documentation
--------------------

:Author: Jon Parise
:Contact: [email protected]
+--------+-----------+----------------------+
|Author: |Jon Parise |Armin Graefe |
+--------+-----------+----------------------+
|Contact:|[email protected]|[email protected]|
+--------+-----------+----------------------+

.. contents:: Table of Contents
.. section-numbering::
Expand Down Expand Up @@ -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
==============
Expand All @@ -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
Expand All @@ -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
-------
Expand Down
Loading