Skip to content

Commit 6975aef

Browse files
authored
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
1 parent d7b3f6a commit 6975aef

File tree

2 files changed

+235
-33
lines changed

2 files changed

+235
-33
lines changed

Net/SMTP.php

+180-4
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ class Net_SMTP
162162
*/
163163
protected $gssapi_cname = null;
164164

165+
/**
166+
* SCRAM SHA-Hash algorithm.
167+
*
168+
* @var string
169+
*/
170+
protected $scram_sha_hash_algorithm = null;
171+
165172
/**
166173
* Instantiates a new Net_SMTP object, overriding any defaults
167174
* with parameters that are passed in.
@@ -215,6 +222,11 @@ public function __construct($host = null, $port = null, $localhost = null,
215222
if (@include_once 'Auth/SASL.php') {
216223
$this->setAuthMethod('CRAM-MD5', array($this, 'authCramMD5'));
217224
$this->setAuthMethod('DIGEST-MD5', array($this, 'authDigestMD5'));
225+
$this->setAuthMethod('SCRAM-SHA-1', array($this, 'authScramSHA1'));
226+
$this->setAuthMethod('SCRAM-SHA-224', array($this, 'authScramSHA224'));
227+
$this->setAuthMethod('SCRAM-SHA-256', array($this, 'authScramSHA256'));
228+
$this->setAuthMethod('SCRAM-SHA-384', array($this, 'authScramSHA384'));
229+
$this->setAuthMethod('SCRAM-SHA-512', array($this, 'authScramSHA512'));
218230
}
219231

220232
/* These standard authentication methods are always available. */
@@ -819,9 +831,13 @@ public function setAuthMethod($name, $callback, $prepend = true)
819831
* @return mixed Returns a PEAR_Error with an error message on any
820832
* kind of failure, or true on success.
821833
* @since 1.1.0
834+
* @deprecated 1.11.0
822835
*/
823836
protected function authDigestMD5($uid, $pwd, $authz = '')
824837
{
838+
trigger_error(__CLASS__ . ' (' . $this->host . '): Authentication method DIGEST-MD5' .
839+
' is no longer secure and should be avoided.', E_USER_DEPRECATED);
840+
825841
if (PEAR::isError($error = $this->put('AUTH', 'DIGEST-MD5'))) {
826842
return $error;
827843
}
@@ -834,8 +850,7 @@ protected function authDigestMD5($uid, $pwd, $authz = '')
834850
return $error;
835851
}
836852

837-
$auth_sasl = new Auth_SASL;
838-
$digest = $auth_sasl->factory('digest-md5');
853+
$digest = Auth_SASL::factory('digest-md5');
839854
$challenge = base64_decode($this->arguments[0]);
840855
$auth_str = base64_encode(
841856
$digest->getResponse($uid, $pwd, $challenge, $this->host, "smtp", $authz)
@@ -871,9 +886,13 @@ protected function authDigestMD5($uid, $pwd, $authz = '')
871886
* @return mixed Returns a PEAR_Error with an error message on any
872887
* kind of failure, or true on success.
873888
* @since 1.1.0
889+
* @deprecated 1.11.0
874890
*/
875891
protected function authCRAMMD5($uid, $pwd, $authz = '')
876892
{
893+
trigger_error(__CLASS__ . ' (' . $this->host . '): Authentication method CRAM-MD5' .
894+
' is no longer secure and should be avoided.', E_USER_DEPRECATED);
895+
877896
if (PEAR::isError($error = $this->put('AUTH', 'CRAM-MD5'))) {
878897
return $error;
879898
}
@@ -886,9 +905,8 @@ protected function authCRAMMD5($uid, $pwd, $authz = '')
886905
return $error;
887906
}
888907

889-
$auth_sasl = new Auth_SASL;
890908
$challenge = base64_decode($this->arguments[0]);
891-
$cram = $auth_sasl->factory('cram-md5');
909+
$cram = Auth_SASL::factory('cram-md5');
892910
$auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
893911

894912
if (PEAR::isError($error = $this->put($auth_str))) {
@@ -911,9 +929,13 @@ protected function authCRAMMD5($uid, $pwd, $authz = '')
911929
* @return mixed Returns a PEAR_Error with an error message on any
912930
* kind of failure, or true on success.
913931
* @since 1.1.0
932+
* @deprecated 1.11.0
914933
*/
915934
protected function authLogin($uid, $pwd, $authz = '')
916935
{
936+
trigger_error(__CLASS__ . ' (' . $this->host . '): Authentication method LOGIN' .
937+
' is no longer secure and should be avoided.', E_USER_DEPRECATED);
938+
917939
if (PEAR::isError($error = $this->put('AUTH', 'LOGIN'))) {
918940
return $error;
919941
}
@@ -1075,6 +1097,7 @@ protected function authGSSAPI($uid, $pwd, $authz = '')
10751097
* @param string $uid The userid to authenticate as.
10761098
* @param string $token The access token to authenticate with.
10771099
* @param string $authz The optional authorization proxy identifier.
1100+
* @param object $conn The current object
10781101
*
10791102
* @return mixed Returns a PEAR_Error with an error message on any
10801103
* kind of failure, or true on success.
@@ -1129,6 +1152,159 @@ public function authXOAuth2($uid, $token, $authz, $conn)
11291152
return true;
11301153
}
11311154

1155+
/**
1156+
* Authenticates the user using the SCRAM-SHA-1 method.
1157+
*
1158+
* @param string $uid The userid to authenticate as.
1159+
* @param string $pwd The password to authenticate with.
1160+
* @param string $authz The optional authorization proxy identifier.
1161+
*
1162+
* @return mixed Returns a PEAR_Error with an error message on any
1163+
* kind of failure, or true on success.
1164+
* @since 1.11.0
1165+
*/
1166+
protected function authScramSHA1($uid, $pwd, $authz = '')
1167+
{
1168+
$this->scram_sha_hash_algorithm = 'SCRAM-SHA-1';
1169+
return $this->authScramSHA($uid, $pwd, $authz);
1170+
}
1171+
1172+
/**
1173+
* Authenticates the user using the SCRAM-SHA-224 method.
1174+
*
1175+
* @param string $uid The userid to authenticate as.
1176+
* @param string $pwd The password to authenticate with.
1177+
* @param string $authz The optional authorization proxy identifier.
1178+
*
1179+
* @return mixed Returns a PEAR_Error with an error message on any
1180+
* kind of failure, or true on success.
1181+
* @since 1.11.0
1182+
*/
1183+
protected function authScramSHA224($uid, $pwd, $authz = '')
1184+
{
1185+
$this->scram_sha_hash_algorithm = 'SCRAM-SHA-224';
1186+
return $this->authScramSHA($uid, $pwd, $authz);
1187+
}
1188+
1189+
/**
1190+
* Authenticates the user using the SCRAM-SHA-256 method.
1191+
*
1192+
* @param string $uid The userid to authenticate as.
1193+
* @param string $pwd The password to authenticate with.
1194+
* @param string $authz The optional authorization proxy identifier.
1195+
*
1196+
* @return mixed Returns a PEAR_Error with an error message on any
1197+
* kind of failure, or true on success.
1198+
* @since 1.11.0
1199+
*/
1200+
protected function authScramSHA256($uid, $pwd, $authz = '')
1201+
{
1202+
$this->scram_sha_hash_algorithm = 'SCRAM-SHA-256';
1203+
return $this->authScramSHA($uid, $pwd, $authz);
1204+
}
1205+
1206+
/**
1207+
* Authenticates the user using the SCRAM-SHA-384 method.
1208+
*
1209+
* @param string $uid The userid to authenticate as.
1210+
* @param string $pwd The password to authenticate with.
1211+
* @param string $authz The optional authorization proxy identifier.
1212+
*
1213+
* @return mixed Returns a PEAR_Error with an error message on any
1214+
* kind of failure, or true on success.
1215+
* @since 1.11.0
1216+
*/
1217+
protected function authScramSHA384($uid, $pwd, $authz = '')
1218+
{
1219+
$this->scram_sha_hash_algorithm = 'SCRAM-SHA-384';
1220+
return $this->authScramSHA($uid, $pwd, $authz);
1221+
}
1222+
1223+
/**
1224+
* Authenticates the user using the SCRAM-SHA-512 method.
1225+
*
1226+
* @param string $uid The userid to authenticate as.
1227+
* @param string $pwd The password to authenticate with.
1228+
* @param string $authz The optional authorization proxy identifier.
1229+
*
1230+
* @return mixed Returns a PEAR_Error with an error message on any
1231+
* kind of failure, or true on success.
1232+
* @since 1.11.0
1233+
*/
1234+
protected function authScramSHA512($uid, $pwd, $authz = '')
1235+
{
1236+
$this->scram_sha_hash_algorithm = 'SCRAM-SHA-512';
1237+
return $this->authScramSHA($uid, $pwd, $authz);
1238+
}
1239+
1240+
/**
1241+
* Authenticates the user using the SCRAM-SHA method.
1242+
*
1243+
* @param string $uid The userid to authenticate as.
1244+
* @param string $pwd The password to authenticate with.
1245+
* @param string $authz The optional authorization proxy identifier.
1246+
*
1247+
* @return mixed Returns a PEAR_Error with an error message on any
1248+
* kind of failure, or true on success.
1249+
* @since 1.11.0
1250+
*/
1251+
protected function authScramSHA($uid, $pwd, $authz = '')
1252+
{
1253+
if (PEAR::isError($error = $this->put('AUTH', $this->scram_sha_hash_algorithm))) {
1254+
return $error;
1255+
}
1256+
/* 334: Continue authentication request */
1257+
if (PEAR::isError($error = $this->parseResponse(334))) {
1258+
/* 503: Error: already authenticated */
1259+
if ($this->code === 503) {
1260+
return true;
1261+
}
1262+
return $error;
1263+
}
1264+
1265+
$cram = Auth_SASL::factory($this->scram_sha_hash_algorithm);
1266+
$auth_str = base64_encode($cram->getResponse($uid, $pwd));
1267+
1268+
/* Step 1: Send first authentication request */
1269+
if (PEAR::isError($error = $this->put($auth_str))) {
1270+
return $error;
1271+
}
1272+
1273+
/* 334: Continue authentication request with password salt */
1274+
if (PEAR::isError($error = $this->parseResponse(334))) {
1275+
return $error;
1276+
}
1277+
1278+
$challenge = base64_decode($this->arguments[0]);
1279+
$auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
1280+
1281+
/* Step 2: Send salted authentication request */
1282+
if (PEAR::isError($error = $this->put($auth_str))) {
1283+
return $error;
1284+
}
1285+
1286+
/* 334: Continue authentication request with password salt */
1287+
if (PEAR::isError($error = $this->parseResponse(334))) {
1288+
return $error;
1289+
}
1290+
1291+
/* Verify server signature */
1292+
$verification = $cram->processOutcome(base64_decode($this->arguments[0]));
1293+
if ($verification == false) {
1294+
return PEAR::raiseError("SCRAM Server verification on step 3 not successful");
1295+
}
1296+
1297+
/* Step 3: Send a request to acknowledge verification */
1298+
if (PEAR::isError($error = $this->put("NOOP"))) {
1299+
return $error;
1300+
}
1301+
1302+
/* 235: Authentication successful */
1303+
if (PEAR::isError($error = $this->parseResponse(235))) {
1304+
return $error;
1305+
}
1306+
}
1307+
11321308
/**
11331309
* Send the HELO command.
11341310
*

README.rst

+55-29
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
User Documentation
77
--------------------
88

9-
:Author: Jon Parise
10-
:Contact: jon@php.net
9+
+--------+-----------+----------------------+
10+
|Author: |Jon Parise |Armin Graefe |
11+
+--------+-----------+----------------------+
12+
|Contact:|jon@php.net|schengawegga@gmail.com|
13+
+--------+-----------+----------------------+
1114

1215
.. contents:: Table of Contents
1316
.. section-numbering::
@@ -41,9 +44,9 @@ The ``Auth_SASL`` Package
4144
-------------------------
4245

4346
The `Auth_SASL`_ package is an optional dependency. If it is available, the
44-
Net_SMTP package will be able to support the DIGEST-MD5_ and CRAM-MD5_ SMTP
45-
authentication methods. Otherwise, only the LOGIN_ and PLAIN_ methods will
46-
be available.
47+
Net_SMTP package will be able to support the DIGEST-MD5_, CRAM-MD5_ and
48+
SCRAM-SHA_ SMTP authentication methods. Otherwise, only the LOGIN_ and
49+
PLAIN_ methods will be available.
4750

4851
Error Handling
4952
==============
@@ -67,25 +70,25 @@ methods, in order of preference:
6770

6871
.. _RFC-2554: https://www.ietf.org/rfc/rfc2554.txt
6972

70-
GSSAPI
71-
------
73+
CRAM-MD5 (DEPRECATED)
74+
--------
7275

73-
The GSSAPI authentication method uses Kerberos 5 protocol (RFC-4120_).
74-
Does not use user/password.
75-
Requires Service Principal ``gssapi_principal`` parameter and
76-
has an optional Credentials Cache ``gssapi_cname`` parameter.
77-
Requires DNS and Key Distribution Center (KDC) setup.
78-
It is considered the most secure method of SMTP authentication.
76+
**DEPRECATED**
77+
This authentication method is no longer secure and should be avoided.
7978

80-
**Note:** The GSSAPI authentication method is only supported
81-
if the krb5_ php extension is available.
79+
The CRAM-MD5 authentication method has been superseded by the DIGEST-MD5_
80+
method in terms of security. It is provided here for compatibility with
81+
older SMTP servers that may not support the newer DIGEST-MD5 algorithm.
8282

83-
.. _RFC-4120: https://tools.ietf.org/html/rfc4120
84-
.. _krb5: https://pecl.php.net/package/krb5
83+
**Note:** The CRAM-MD5 authentication method is only supported if the
84+
AUTH_SASL_ package is available.
8585

86-
DIGEST-MD5
86+
DIGEST-MD5 (DEPRECATED)
8787
----------
8888

89+
**DEPRECATED**
90+
This authentication method is no longer secure and should be avoided.
91+
8992
The DIGEST-MD5 authentication method uses `RSA Data Security Inc.`_'s MD5
9093
Message Digest algorithm. It is considered a more secure method of SMTP
9194
authentication than PLAIN or LOGIN, while still vulnerable to MitM attacks
@@ -96,31 +99,54 @@ AUTH_SASL_ package is available.
9699

97100
.. _RSA Data Security Inc.: https://www.rsasecurity.com/
98101

99-
CRAM-MD5
100-
--------
102+
GSSAPI
103+
------
101104

102-
The CRAM-MD5 authentication method has been superseded by the DIGEST-MD5_
103-
method in terms of security. It is provided here for compatibility with
104-
older SMTP servers that may not support the newer DIGEST-MD5 algorithm.
105+
The GSSAPI authentication method uses Kerberos 5 protocol (RFC-4120_).
106+
Does not use user/password.
107+
Requires Service Principal ``gssapi_principal`` parameter and
108+
has an optional Credentials Cache ``gssapi_cname`` parameter.
109+
Requires DNS and Key Distribution Center (KDC) setup.
110+
It is considered the most secure method of SMTP authentication.
105111

106-
**Note:** The CRAM-MD5 authentication method is only supported if the
107-
AUTH_SASL_ package is available.
112+
**Note:** The GSSAPI authentication method is only supported
113+
if the krb5_ php extension is available.
108114

109-
LOGIN
115+
.. _RFC-4120: https://tools.ietf.org/html/rfc4120
116+
.. _krb5: https://pecl.php.net/package/krb5
117+
118+
LOGIN (DEPRECATED)
110119
-----
111120

121+
**DEPRECATED**
122+
This authentication method is no longer secure and should be avoided.
123+
112124
The LOGIN authentication method encrypts the user's password using the
113125
Base64_ encoding scheme. Because decrypting a Base64-encoded string is
114-
trivial, LOGIN is not considered a secure authentication method and should
115-
be avoided.
126+
trivial.
116127

117128
.. _Base64: https://www.php.net/manual/en/function.base64-encode.php
118129

119130
PLAIN
120131
-----
121132

133+
This authentication method is no longer secure and should only be used
134+
local or via an TLS encrypted connection.
135+
122136
The PLAIN authentication method sends the user's password in plain text.
123-
This method of authentication is not secure and should be avoided.
137+
138+
SCRAM
139+
--------
140+
141+
In cryptography, the Salted Challenge Response Authentication Mechanism (SCRAM)
142+
is a family of modern, password-based challenge–response authentication mechanisms
143+
providing authentication to a server.
144+
145+
Available mechanisms are SCRAM-SHA-1, SCRAM-SHA-224, SCRAM-SHA-256, SCRAM-SHA-384
146+
and SCRAM-SHA-512.
147+
148+
**Note:** The SCRAM-SHA authentication method is only supported if the
149+
AUTH_SASL_ package is available.
124150

125151
XOAUTH2
126152
-------

0 commit comments

Comments
 (0)