1: <?php
2: /*
3: * SimpleID
4: *
5: * Copyright (C) Kelvin Mo 2012-2025
6: *
7: * This program is free software; you can redistribute it and/or
8: * modify it under the terms of the GNU General Public
9: * License as published by the Free Software Foundation; either
10: * version 2 of the License, or (at your option) any later version.
11: *
12: * This program is distributed in the hope that it will be useful,
13: * but WITHOUT ANY WARRANTY; without even the implied warranty of
14: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15: * General Public License for more details.
16: *
17: * You should have received a copy of the GNU General Public
18: * License along with this program; if not, write to the Free
19: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20: *
21: */
22:
23: namespace SimpleID\Auth;
24:
25: use Psr\Log\LogLevel;
26: use SimpleID\Auth\NonInteractiveAuthEvent;
27: use SimpleID\Store\StoreManager;
28: use SimpleID\Util\Events\BaseDataCollectionEvent;
29:
30: /**
31: * An authentication scheme that provides automatic authentication
32: * via a client certificate.
33: */
34: class CertAuthSchemeModule extends AuthSchemeModule {
35: /**
36: * Attempts to automatically login using the client certificate
37: *
38: * @param NonInteractiveAuthEvent $event the event
39: * @return void
40: */
41: public function onNonInteractiveAuthEvent(NonInteractiveAuthEvent $event) {
42: if (!$this->hasClientCert()) return;
43:
44: $cert = trim($_SERVER['SSL_CLIENT_M_SERIAL']) . ';' . trim($_SERVER['SSL_CLIENT_I_DN']);
45: $this->logger->log(LogLevel::DEBUG, 'Client SSL certificate: ' . $cert);
46:
47: $store = StoreManager::instance();
48: /** @var \SimpleID\Models\User $test_user */
49: $test_user = $store->findUser('cert.certs', $cert);
50: if ($test_user != NULL) {
51: $this->logger->log(LogLevel::DEBUG, 'Client SSL certificate accepted for ' . $test_user['uid']);
52: $event->setUser($test_user, static::class);
53: $event->setAuthLevel(AuthManager::AUTH_LEVEL_NON_INTERACTIVE);
54: } else {
55: $this->logger->log(LogLevel::DEBUG, 'Client SSL certificate presented, but no user with that certificate exists.');
56: }
57: }
58:
59: /**
60: * Determines whether the user agent supplied valid a certificate identifying the
61: * user.
62: *
63: * A valid certificate is supplied if all of the following occurs:
64: *
65: * - the connection is done using HTTPS (i.e. {@link is_https()} is true)
66: * - the web server has been set up to request a certificate from the user agent
67: * - the web server has been set up to pass the certificate details to PHP
68: * - the certificate has not been revoked
69: * - the certificate contains a serial number and a valid issuer
70: *
71: * @return bool true if the user agent has supplied a valid SSL certificate
72: */
73: protected function hasClientCert() {
74: // False if we are not in HTTP
75: if (!$this->isHttps()) return false;
76:
77: // False if certificate is not valid
78: if (!isset($_SERVER['SSL_CLIENT_VERIFY']) || ($_SERVER['SSL_CLIENT_VERIFY'] !== 'SUCCESS')) return false;
79:
80: // False if certificate is expired or has no expiry date
81: if (!isset($_SERVER['SSL_CLIENT_V_REMAIN']) || ($_SERVER['SSL_CLIENT_V_REMAIN'] < 0)) return false;
82: if (!isset($_SERVER['SSL_CLIENT_V_END'])) return false;
83:
84: // False if no serial number
85: if (!isset($_SERVER['SSL_CLIENT_M_SERIAL'])) return false;
86:
87: // False if no issuer
88: if (!isset($_SERVER['SSL_CLIENT_I_DN'])) return false;
89:
90: return true;
91: }
92:
93: /**
94: * @return void
95: */
96: public function onUserSecretDataPaths(BaseDataCollectionEvent $event) {
97: $event->addResult('cert.certs');
98: }
99: }
100: ?>
101: