~fkooman/php-saml-idp

Very simple SAML 2.0 IdP
make sure SLO URL is registered
update fkooman/jwt
simplify code somewhat

refs

main
browse  log 

clone

read-only
https://git.sr.ht/~fkooman/php-saml-idp
read/write
git@git.sr.ht:~fkooman/php-saml-idp

You can also use your local clone with git send-email.

Simple SAML 2.0 IdP and OpenID Connect OP.

DO NOT USE

This is very much a work in progress. Do not use (in production) before a 1.0 release!

#Features

  • SAML as IdP & OpenID Connect as OP
  • PHP >= 7.4
  • As simple as possible
  • User Authentication
    • LDAP
    • Local DB (SQLite)
  • Consent/Approval Page before sending data to SP/RP
  • Two factor (TOTP)
    • admin enroll, no self enroll (yet)
  • Users can update their own password (both DB, LDAP)
  • Full admin user management through the web interface (DB)
  • Full management of SAML SPs and OIDC RPs through the web interface
  • Full group management (for DB)

#SAML

  • Only IdP functionality
  • No xmlseclibs
  • Metadata URL
  • HTTP POST binding for SAML Response
  • HTTP Redirect binding for AuthnRequest
  • Only supports RSA+SHA256 signed assertions/requests
  • No encryption support
  • Attribute Mapping (from LDAP to e.g. OID)
  • Attribute Mapping / Filtering (per SP)
  • Subject Identifier (pairwise-id) support
  • Only "transient" NameID support
  • Logout (SLO)
  • Verifies AuthnRequest and LogoutRequest signatures if SP public key is known

#OpenID Connect

  • Only "authorization code" flow
  • /.well-known/openid-configuration support
  • (optional) PKCE
  • (optional) nonce parameter be sent with the "authorization request"
  • RSA/2048 (RS256) used to sign the "ID Token", does not seem EdDSA is supported by anyone
  • userinfo endpoint support
  • ACR acr_values query parameter support for 2FA
    • "supports" refeds mfa/sfa profiles, i.e. uses the strings, I did not verify what all needs to be guaranteed when using those

#Compatibility

We tested with:

#Installation

Git clone the repository. Use Composer to install the dependencies:

$ git clone https://git.sr.ht/~fkooman/php-saml-idp
$ cd php-saml-idp
$ composer update

#Configuration

#SP

$ cp config/config.php.example config/config.php

Modify config/config.php to configure the IdP.

Generate secrets:

$ php libexec/generate-secrets.php

#Generate X.509 Certificates

$ mkdir config/keys
$ openssl req \
    -nodes \
    -subj "/CN=SAML IdP" \
    -x509 \
    -sha256 \
    -newkey rsa:3072 \
    -keyout "config/keys/saml.key" \
    -out "config/keys/saml.crt" \
    -days 3600

#Run

$ make dev

Then go to http://localhost:8080/.

#Add User

$ php bin/add-user.php
User ID: foo
Setting password for user "foo"
Password: 
Password (repeat):

By default the uid attribute is created for the newly created user set to the "User ID".

Once you add your first user you can make it "Admin" by adding the User ID to adminUserIdList in config/config.php. Afterwards you can manage your account and additional users through the web interface.

#Add Attributes for User

$ php bin/add-attribute.php 
User ID: foo
Attribute Name: eduPersonEntitlement
Attribute Value: https://eduvpn.org/expiry#P1Y

Or use the web interface if you prefer.

#Add TOTP

$ php bin/add-otp.php 
User ID: foo
OTP Secret: ZJDLQWPRG44QNCFTWMDZOXIHJI5LASWR
Provide OTP key: 587196
otpauth://totp/fralen-tuxed-net:foo?secret=ZJDLQWPRG44QNCFTWMDZOXIHJI5LASWR&algorithm=SHA1&digits=6&period=30&issuer=fralen-tuxed-net

Using CLI tooling you can generate an OTP use oathtool like this:

$ oathtool --totp -b ZJDLQWPRG44QNCFTWMDZOXIHJI5LASWR
587196

This is not yet possible through the web interface.

#OpenID Compatibility

To configure mod_auth_openidc use the following:

OIDCProviderMetadataURL https://idp.example.org/.well-known/openid-configuration
OIDCClientID q9ZduFZHNUIFjWG8
OIDCClientSecret fKUsLWQIL7sRVoo5
OIDCRedirectURI https://rp.example.org/app/redirect_uri
OIDCCryptoPassphrase sV4OIiIeO7QZ748beMWaAH1G421jfXET87TGdGJtoHs9jHfo3upxAWRgpZ9bIWH0

# if you want to use e.g. preferred_username as identifier in the RP, you need
# to request the `profile` scope
OIDCScope "openid profile"
OIDCRemoteUserClaim preferred_username@

#OIDCPKCEMethod S256
# if you want to request 2FA from the OP
#OIDCPathAuthRequestParams "acr_values=https://refeds.org/profile/mfa"

<Location /vpn-user-portal>
    AuthType openid-connect
    <RequireAll> 
        Require valid-user
        #Require claim acr:https://refeds.org/profile/mfa
    </RequireAll>
</Location>

NOTE: update the values as required, make sure to generate your own OIDCCryptoPassphrase, e.g. using pwgen -s 64 -n 1.

#Client Registration

Use php-saml-idp-add-client to configure a new RP. You'll need to provide a redirect_uri and a "Display Name". The client_id and client_secret are generated.

$ php bin/add-client.php
Display Name: My Test RP
Redirect URI: https://rp.example.org/callback
Client ID    : 3c3dbb8972aad7dfbd358bf2757d0848
Client Secret: 82fa0e9e0d5a9a28a60ad19e8edbbdf4
Display Name : My Test RP
Redirect URI : https://rp.example.org/callback

You can also use the web interface to register/update/delete RPs if you make yourself an "admin" by adding your user ID to adminUserIdList in the configuration file, e.g.:

'adminUserIdList' => ['foo'],

#Webfinger

If you want to be able to use Webfinger, you can host this file on the domain you want to use, e.g. on https://tuxed.net/.well-known/webfinger. It can be a static file. The Content-Type MUST be application/jrd+json. Make sure you point href to the OIDC IdP URL.

{
    "links": [
        {
            "href": "https://idp.tuxed.net",
            "rel": "http://openid.net/specs/connect/1.0/issuer"
        }
    ]
}

For Apache you'd use something like this to set the Content-Type for a static file:

<Location "/.well-known/webfinger">
	Header set Content-Type "application/jrd+json"
</Location>

#RPM Packages

Tested on Fedora 38.

Repository Key:

# rpm --import https://repo.tuxed.net/php-saml-idp/v1-dev/rpm/fkooman+repo@tuxed.net.asc

Repository Config:

$ cat << 'EOF' | sudo tee /etc/yum.repos.d/php-saml-idp_v1-dev.repo
[php-saml-idp_v1-dev]
name=Repository for php-saml-idp
baseurl=https://repo.tuxed.net/php-saml-idp/v1-dev/rpm/fedora-$releasever-$basearch
gpgcheck=1
enabled=1
EOF
$ sudo dnf -y install php-fpm httpd mod_ssl php-saml-idp
$ sudo systemctl enable --now php-fpm
$ sudo systemctl enable --now httpd

Generate keys:

$ sudo /usr/libexec/php-saml-idp/generate-secrets

Fix permissions (as root):

TODO: fix permissions on directory with sticky bit

# chmod 0644 /etc/php-saml-idp/keys/*

Generate SAML keys:

$ sudo mkdir /etc/php-saml-idp/keys
$ sudo openssl req \
    -nodes \
    -subj "/CN=SAML IdP" \
    -x509 \
    -sha256 \
    -newkey rsa:3072 \
    -keyout "/etc/php-saml-idp/keys/saml.key" \
    -out "/etc/php-saml-idp/keys/saml.crt" \
    -days 3600
...

Fix SELinux to allow LDAP connections:

$ setsebool -P httpd_can_connect_ldap=on

You should now be able to visit http://host/php-saml-idp/.

Install certbot to configure HTTPS:

$ sudo dnf -y install certbot
$ sudo systemctl stop httpd
$ sudo certbot certonly

Modify /etc/httpd/conf.d/ssl.conf to point to the chain and key.

$ sudo systemctl start httpd

TODO: document auto renew TLS certs using certbot.

#TODO

#Common

  • implement internationalization
  • better error messages / CSS, e.g. wrong username/password
  • implement admin password (re)set for users
  • implement self account delete (when using local user DB)
  • make it possible to remove users from groups from the user's page instead of only from the group page

#SAML

  • allow option to import all SPs from a federation metadata file
    • also keep this list up to date automatically
  • include SP metadata import script (or do something through web)
  • implement encrypted assertions (implement "Key Transport"?)
  • audit log of authentications, as the IdP has no further state, there is no user control possible
  • fix 2FA for SAML
  • make it possible to configure per SP skip consent/user approval (requires JS)
  • allow creating/deleting mappings through web-ui (almost done, except per-SP)

#OpenID

  • can't yet skip consent/user approval when 2FA is required, the prompt will never be shown
  • audit log of authentications
  • when deleting RPs make sure all user authorizations are deleted as well (database CASCADE?)
  • when revoking an RP authorization, make sure to revoke all of them, not just that particular authorization, e.g. when a user uses multiple browsers and thus has multiple authorizations
  • implement "Revoke all" button for users to revoke all RPs in one-shot
  • fix revoke authorizations, button is broken
  • expose isMemberOf as groups?
  • also allow creating a mapping from LDAP/DB attribute (+particular value) to another claim with either the same or different value