|
| 1 | +# SASL Mechanisms |
| 2 | + |
| 3 | +This library provides the following SASL mechanisms. All mechanisms are registered by default; use the `supported` option to restrict which are available. |
| 4 | + |
| 5 | +```php |
| 6 | +use FreeDSx\Sasl\Sasl; |
| 7 | + |
| 8 | +$sasl = new Sasl(); |
| 9 | + |
| 10 | +// Restrict to specific mechanisms |
| 11 | +$sasl = new Sasl(['supported' => ['SCRAM-SHA-256', 'PLAIN']]); |
| 12 | + |
| 13 | +// Get a specific mechanism |
| 14 | +$mechanism = $sasl->get('SCRAM-SHA-256'); |
| 15 | + |
| 16 | +// Select the best mechanism from a server-advertised list |
| 17 | +$mechanism = $sasl->select(['SCRAM-SHA-256', 'PLAIN', 'DIGEST-MD5']); |
| 18 | +``` |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## ANONYMOUS |
| 23 | + |
| 24 | +No authentication. Sends optional trace information to the server. |
| 25 | + |
| 26 | +**Security:** No integrity, no privacy, no authentication. |
| 27 | + |
| 28 | +**Client options:** |
| 29 | + |
| 30 | +| Option | Default | Description | |
| 31 | +|------------|---------|------------------------------------------| |
| 32 | +| `username` | `null` | Optional trace string sent to the server | |
| 33 | + |
| 34 | +```php |
| 35 | +$challenge = $mechanism->challenge(); |
| 36 | + |
| 37 | +$response = $challenge->challenge( |
| 38 | + null, |
| 39 | + ['username' => 'guest@example.com'] |
| 40 | +); |
| 41 | + |
| 42 | +// $response->isComplete() === true after one round |
| 43 | +``` |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +## PLAIN |
| 48 | + |
| 49 | +Sends credentials as plaintext. Use only over TLS. |
| 50 | + |
| 51 | +**Security:** Authenticates but transmits password in plaintext. |
| 52 | + |
| 53 | +**Client options:** |
| 54 | + |
| 55 | +| Option | Default | Description | |
| 56 | +|------------|--------------|-------------------------| |
| 57 | +| `username` | *(required)* | Authentication identity | |
| 58 | +| `password` | *(required)* | User password | |
| 59 | + |
| 60 | +**Server options:** |
| 61 | + |
| 62 | +| Option | Default | Description | |
| 63 | +|------------|--------------|----------------------------------------------------------------------| |
| 64 | +| `validate` | *(required)* | `callable(string $authzid, string $authcid, string $password): bool` | |
| 65 | + |
| 66 | +```php |
| 67 | +// Client |
| 68 | +$response = $challenge->challenge(null, [ |
| 69 | + 'username' => 'user', |
| 70 | + 'password' => 'secret', |
| 71 | +]); |
| 72 | + |
| 73 | +// Server |
| 74 | +$response = $challenge->challenge($received, [ |
| 75 | + 'validate' => fn($authzid, $authcid, $password) => $password === getPassword($authcid), |
| 76 | +]); |
| 77 | +``` |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +## CRAM-MD5 |
| 82 | + |
| 83 | +Server sends a challenge; a client responds with an HMAC-MD5 digest. Two-round exchange. |
| 84 | + |
| 85 | +**Security:** Authenticates without transmitting the password in plaintext. |
| 86 | + |
| 87 | +**Client options:** |
| 88 | + |
| 89 | +| Option | Default | Description | |
| 90 | +|------------|--------------|---------------| |
| 91 | +| `username` | *(required)* | Username | |
| 92 | +| `password` | *(required)* | User password | |
| 93 | + |
| 94 | +**Server options:** |
| 95 | + |
| 96 | +| Option | Default | Description | |
| 97 | +|-------------|----------------------|-----------------------------------------------------------------------------------| |
| 98 | +| `challenge` | random 32-byte nonce | Override the server challenge string | |
| 99 | +| `password` | *(required)* | `callable(string $username, string $challenge): string` — returns expected digest | |
| 100 | + |
| 101 | +```php |
| 102 | +// Client (round 1: receive challenge, round 2: send response) |
| 103 | +$response = $challenge->challenge($serverChallenge, [ |
| 104 | + 'username' => 'user', |
| 105 | + 'password' => 'secret', |
| 106 | +]); |
| 107 | +``` |
| 108 | + |
| 109 | +--- |
| 110 | + |
| 111 | +## DIGEST-MD5 |
| 112 | + |
| 113 | +Multi-round MD5-based authentication with optional integrity and privacy security layers. Implements RFC 2831. |
| 114 | + |
| 115 | +**Security:** Authenticates without plaintext password. Optionally provides integrity (`auth-int`) or privacy (`auth-conf`) security layers. |
| 116 | + |
| 117 | +**Client options:** |
| 118 | + |
| 119 | +| Option | Default | Description | |
| 120 | +|-----------------|--------------|---------------------------------------------------| |
| 121 | +| `username` | *(required)* | Username | |
| 122 | +| `password` | *(required)* | User password | |
| 123 | +| `host` | `null` | Hostname for the digest-uri | |
| 124 | +| `realm` | `null` | Authentication realm | |
| 125 | +| `use_integrity` | `false` | Enable integrity layer (`qop=auth-int`) | |
| 126 | +| `use_privacy` | `false` | Enable privacy/encryption layer (`qop=auth-conf`) | |
| 127 | +| `service` | `'ldap'` | SASL service name | |
| 128 | +| `cnonce` | random | Client nonce | |
| 129 | +| `cipher` | auto | Cipher for privacy (e.g. `'rc4'`, `'3des'`) | |
| 130 | + |
| 131 | +**Server options:** |
| 132 | + |
| 133 | +| Option | Default | Description | |
| 134 | +|-----------------|--------------|------------------------------------------| |
| 135 | +| `validate` | *(required)* | Callable to validate the client response | |
| 136 | +| `use_integrity` | `false` | Offer integrity layer | |
| 137 | +| `use_privacy` | `false` | Offer privacy layer | |
| 138 | +| `service` | `'ldap'` | SASL service name | |
| 139 | +| `nonce` | random | Override the server nonce | |
| 140 | + |
| 141 | +```php |
| 142 | +$response = $challenge->challenge($serverMessage, [ |
| 143 | + 'username' => 'user', |
| 144 | + 'password' => 'secret', |
| 145 | + 'host' => 'ldap.example.com', |
| 146 | + 'service' => 'ldap', |
| 147 | +]); |
| 148 | +``` |
| 149 | + |
| 150 | +--- |
| 151 | + |
| 152 | +## SCRAM-SHA-* (RFC 5802 / RFC 7677) |
| 153 | + |
| 154 | +Modern salted challenge-response mechanisms using PBKDF2 key derivation and HMAC signatures. Mutual authentication: the client also verifies the server. `-PLUS` variants add TLS channel binding. |
| 155 | + |
| 156 | +**Available mechanisms:** |
| 157 | + |
| 158 | +| Mechanism | Hash Algorithm | Channel Binding | |
| 159 | +|-----------------------|----------------|-----------------| |
| 160 | +| `SCRAM-SHA-1` | SHA-1 | No | |
| 161 | +| `SCRAM-SHA-1-PLUS` | SHA-1 | Yes | |
| 162 | +| `SCRAM-SHA-224` | SHA-224 | No | |
| 163 | +| `SCRAM-SHA-224-PLUS` | SHA-224 | Yes | |
| 164 | +| `SCRAM-SHA-256` | SHA-256 | No | |
| 165 | +| `SCRAM-SHA-256-PLUS` | SHA-256 | Yes | |
| 166 | +| `SCRAM-SHA-384` | SHA-384 | No | |
| 167 | +| `SCRAM-SHA-384-PLUS` | SHA-384 | Yes | |
| 168 | +| `SCRAM-SHA-512` | SHA-512 | No | |
| 169 | +| `SCRAM-SHA-512-PLUS` | SHA-512 | Yes | |
| 170 | +| `SCRAM-SHA3-512` | SHA3-512 | No | |
| 171 | +| `SCRAM-SHA3-512-PLUS` | SHA3-512 | Yes | |
| 172 | + |
| 173 | +**Security:** Authenticates without transmitting the password. Resistant to replay and server-impersonation attacks. Use `-PLUS` variants with TLS channel binding for the strongest guarantee. |
| 174 | + |
| 175 | +### Client options |
| 176 | + |
| 177 | +**Round 1 (client-first):** |
| 178 | + |
| 179 | +| Option | Default | Description | |
| 180 | +|--------------|-----------------|-------------------------------------------------| |
| 181 | +| `username` | *(required)* | Username (`=` and `,` are encoded per RFC 5802) | |
| 182 | +| `cnonce` | 24 random bytes | Client nonce | |
| 183 | +| `cbind_type` | `'tls-unique'` | Channel binding type (for `-PLUS` variants) | |
| 184 | + |
| 185 | +**Round 2 (client-final):** |
| 186 | + |
| 187 | +| Option | Default | Description | |
| 188 | +|--------------|--------------|-------------------------------------------------| |
| 189 | +| `password` | *(required)* | User password | |
| 190 | +| `cbind_data` | `''` | Raw channel binding data (for `-PLUS` variants) | |
| 191 | + |
| 192 | +### Server options |
| 193 | + |
| 194 | +**Round 1 (server-first):** |
| 195 | + |
| 196 | +| Option | Default | Description | |
| 197 | +|--------------|-----------------|--------------------------------------| |
| 198 | +| `nonce` | 24 random bytes | Server portion of the combined nonce | |
| 199 | +| `salt` | 16 random bytes | PBKDF2 salt (raw binary) | |
| 200 | +| `iterations` | `4096` | PBKDF2 iteration count | |
| 201 | + |
| 202 | +**Round 2 (server-final):** |
| 203 | + |
| 204 | +| Option | Default | Description | |
| 205 | +|------------|--------------|------------------------------------------| |
| 206 | +| `password` | *(required)* | User password to verify the client proof | |
| 207 | + |
| 208 | +### Example (client) |
| 209 | + |
| 210 | +```php |
| 211 | +$mechanism = $sasl->get('SCRAM-SHA-256'); |
| 212 | +$challenge = $mechanism->challenge(); |
| 213 | + |
| 214 | +// Round 1: send client-first message |
| 215 | +$response = $challenge->challenge(null, ['username' => 'user']); |
| 216 | +$clientFirst = $response->get('response'); |
| 217 | + |
| 218 | +// Round 2: receive server-first, send client-final with password |
| 219 | +$response = $challenge->challenge($serverFirst, ['password' => 'secret']); |
| 220 | +$clientFinal = $response->get('response'); |
| 221 | + |
| 222 | +// Round 3: receive server-final, verify server signature |
| 223 | +$response = $challenge->challenge($serverFinal, []); |
| 224 | +// $response->isComplete() === true |
| 225 | +``` |
| 226 | + |
| 227 | +--- |
| 228 | + |
| 229 | +## Security Strength Summary |
| 230 | + |
| 231 | +| Mechanism | Authentication | Integrity Layer | Privacy Layer | Plaintext Password | |
| 232 | +|-------------|:--------------:|:---------------:|:-------------:|:------------------:| |
| 233 | +| ANONYMOUS | No | No | No | No | |
| 234 | +| PLAIN | Yes | No | No | **Yes** | |
| 235 | +| CRAM-MD5 | Yes | No | No | No | |
| 236 | +| DIGEST-MD5 | Yes | Optional | Optional | No | |
| 237 | +| SCRAM-SHA-* | Yes (mutual) | No | No | No | |
| 238 | + |
| 239 | +For new integrations, prefer `SCRAM-SHA-256` or higher over `PLAIN`, `CRAM-MD5`, and `DIGEST-MD5`. |
| 240 | + |
| 241 | +Always use SCRAM or PLAIN over a TLS-protected connection. |
0 commit comments