Samba AD Smart Card Login for macOS clients

From SambaWiki

Introduction

This page covers a basic configuration for PIV smart Card login with a macOS client domain member of a Samba domain. The intended use is in a lab environment for experimentation, i.e. it works but is not necessarily best practices, which surely would be a lot longer as the three main areas that it covers (Domain controllers, Public Key Infrastructure and PIV smart cards) are vast in their own right, without even getting into the interrelations between them.

These instructions attempt to be PIV smart card hardware-agnostic, so user key pairs are created with the Certificate Authority (CA). Instructions on how to transfer the key pairs and certificate to the smart card will vary depending on the smart card's manufacturer. Generally speaking, it involves creating a key pair and certificate for the smart card's 9a slot (authentication).

This page is largely based on the Samba AD Smart Card Login page.

Prerequisites

These instructions have only been tested using the configuration below.

macOS

Smart card logon was added in macOS Sierra 10.12 and Directory logon in macOS High Sierra 10.13.

PIV smart card

Any macOS compatible PIV smart card should work.

Samba AD DC

The Samba AD DC was set up following the instructions on the Setting up Samba as an Active Directory Domain Controller page using Debian.

CRL Distribution Point

To make the .crl file available to clients, a CRL distribution point will be configured. These instructions will use lighttpd on FreeBSD as a web server, although different methods (FTP, LDAP) may also work.

If intending to follow the Configure CRL Distribution Point section's instructions, a fresh FreeBSD installation is required. Details on the configuration for the FreeBSD installation are given in the corresponding section.

Tested configuration

  • Samba AD DC: version 4.13.13 on Debian 11 bullseye
  • macOS client: macOS Monterey 12.6.6 (21G646)
  • PIV smart card: Yubikey 5 NFC (Firmware 5.1.2)
  • CRL distribution point: lighttpd-1.4.71 on FreeBSD 12.4-RELEASE

Instructions

Configure OpenSSL

In depth file permissions are not covered in these instructions. Generally speaking, the principal of least privilege should be applied. Private keys should be kept private. For example, on a system with a root user, private keys could have read only permissions just for root. As a side note, ideally, CA private keys would be stored with specialised air-gapped hardware.

Open a shell on your Samba AD DC and proceed with the instructions. All commands are for Debian, run as the root user.

Get Samba AD DC GUID in hex

Get your Samba AD DC GUID in hex:

If not done already, install ldb-tools:

# apt-get install ldb-tools

Adapt the following command for your domain to get your DC's GUID in hex and note it for later. Example for dc1.samdom.example.com:

GUID=$(/usr/bin/ldbsearch -H /var/lib/samba/private/sam.ldb --basedn="OU=Domain Controllers,DC=samdom,DC=example,DC=com" "CN=DC1" objectGUID | grep "objectGUID:" | sed 's/objectGUID: //g' | sed 's/-//g'); HEX="${GUID:6:2}"; HEX="${HEX}${GUID:4:2}"; HEX="${HEX}${GUID:2:2}"; HEX="${HEX}${GUID:0:2}"; HEX="${HEX}${GUID:10:2}"; HEX="${HEX}${GUID:8:2}"; HEX="${HEX}${GUID:14:2}"; HEX="${HEX}${GUID:12:2}"; len=${#HEX}; HEX="${HEX}${GUID:16:${len}}"; printf '%s\n' "${HEX}"

Create OpenSSL Root CA directory structure

Create the directory structure for the CA:

# mkdir /root/CA /root/CA/certs /root/CA/crl /root/CA/private /root/CA/newcerts

Create the serial file:

# echo 00 > /root/CA/serial

Create the crlnumber file:

# echo 00 > /root/CA/crlnumber

Create the index.txt file:

# touch /root/CA/index.txt

Configure openssl.cnf

Adapt the following openssl.cnf to suit your needs. Use your DC's FQDN for the set_dns option. Use the Samba AD DC GUID in hex previously obtained, for the set_dc_guid option.

Later in the instructions, a CRL Distribution Point will be configured. This consists of copying the .crl file to a web server to make it accessible to clients. The .crl file's URL used here, for the set_crp_default option, needs to be the same as the one used when configuring the CRL Distribution Point.

######################################################################################### EDIT BELOW THIS LINE
set_countryName_default            = My Country (2 letter code)
set_stateOrProvinceName_default    = My State or Province
set_localityName_default           = My Locality
set_organizationName_default       = My Organisation
set_organizationalUnitName_default = My Department
set_crp_default                    = http://crl.samdom.example.com/samdom.crl
set_dns                            = dc1.samdom.example.com
set_dc_guid                        = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
######################################################################################### DO NOT EDIT BELOW THIS LINE


CRLDISTPT                          = $set_crp_default                                   # Variable used for CRL URL
oid_section                        = new_oids                                           # Extra OBJECT IDENTIFIER info

[ new_oids ]                                                                            # New OIDs
msADGUID                           = 1.3.6.1.4.1.311.25.1                               # Identifies AD GUID

[ ca ]                                                                                  # Default CA section
default_ca                         = CA_default                                         # Default CA name

[ CA_default ]                                                                          # Default settings for CA
dir                                = /root/CA                                           # CA directory
certs                              = $dir/certs                                         # Certificates directory
crl_dir                            = $dir/crl                                           # CRL directory
new_certs_dir                      = $dir/newcerts                                      # New certificates directory
database                           = $dir/index.txt                                     # Certificate index file
serial                             = $dir/serial                                        # Serial number file
RANDFILE                           = $dir/private/.rand                                 # Random number file
private_key                        = $dir/private/ca.key.pem                            # Root CA private key
certificate                        = $dir/certs/ca.cert.pem                             # Root CA certificate
crl                                = $dir/crl/ca.crl.pem                                # Root CA CRL
crlnumber                          = $dir/crlnumber                                     # Root CA CRL number
crl_extensions                     = crl_ext                                            # CRL extensions
unique_subject                     = yes                                                # Prevent creation of multiple certificates with same subject
name_opt                           = ca_default                                         # Subject Name options
cert_opt                           = ca_default                                         # Certificate field options
copy_extensions                    = copy                                               # Extension copying use with caution
default_days                       = 730                                                # How long to certify for
default_crl_days                   = 30                                                 # How long before next CRL
default_md                         = sha256                                             # Use public key default MD
preserve                           = no                                                 # Keep passed DN ordering
policy                             = policy_match                                       # Certificate policy

[ policy_match ]                                                                        # For CA policy
countryName                        = match
stateOrProvinceName                = match
organizationName                   = match
organizationalUnitName             = optional
commonName                         = supplied
emailAddress                       = optional

[ policy_anything ]                                                                     # For anything policy
countryName                        = match
stateOrProvinceName                = match
localityName                       = match
organizationName                   = match
organizationalUnitName             = match
commonName                         = supplied
emailAddress                       = supplied

[ req ]                                                                                 # Request settings
default_bits                       = 2048                                               # Default key size
default_keyfile                    = priv.key.pem
distinguished_name                 = req_distinguished_name                             # Default DN template
attributes                         = req_attributes
x509_extensions                    = v3_ca                                              # Extensions added to self signed cert
string_mask                        = utf8only                                           # UTF-8 encoding

[ req_distinguished_name ]                                                              # Template for DN in CSR
countryName                        = Country Name (2 letter code)
countryName_default                = $set_countryName_default
countryName_min                    = 2
countryName_max                    = 2
stateOrProvinceName                = State or Province Name (full name)
stateOrProvinceName_default        = $set_stateOrProvinceName_default
localityName                       = Locality Name (eg, city)
localityName_default               = $set_localityName_default
organizationName                   = Organization Name (eg, company)
organizationName_default           = $set_organizationName_default
organizationalUnitName             = Organizational Unit Name (eg, section)
organizationalUnitName_default     = $set_organizationalUnitName_default
commonName                         = Common Name (eg, YOUR name)
commonName_max                     = 64
emailAddress                       = Email Address
emailAddress_max                   = 64

[ req_attributes ]
challengePassword                  = A challenge password
challengePassword_min              = 4
challengePassword_max              = 20
unstructuredName                   = An optional company name

[ v3_req ]                                                                              # Extensions added to certificate requests
basicConstraints                   = CA:FALSE
keyUsage                           = nonRepudiation, digitalSignature, keyEncipherment

[ v3_ca ]                                                                               # Root CA certificate extensions
subjectKeyIdentifier               = hash
authorityKeyIdentifier             = keyid:always,issuer
basicConstraints                   = CA:true
keyUsage                           = cRLSign, keyCertSign
crlDistributionPoints              = URI:$CRLDISTPT
nsCertType                         = sslCA, emailCA
issuerAltName                      = issuer:copy

[ crl_ext ]                                                                             # CRL extensions
issuerAltName                      = issuer:copy
authorityKeyIdentifier             = keyid:always

[ usr_cert_mskdc ]                                                                      # Domain Controller Certificate
basicConstraints                   = CA:FALSE
crlDistributionPoints              = URI:$CRLDISTPT
nsCertType                         = server
keyUsage                           = nonRepudiation, digitalSignature, keyEncipherment
nsComment                          = "Domain Controller Certificate"                    # Displayed in Netscape's comment listbox
subjectKeyIdentifier               = hash
authorityKeyIdentifier             = keyid,issuer
subjectAltName                     = @dc_subjalt
issuerAltName                      = issuer:copy
nsCaRevocationUrl                  = $CRLDISTPT
extendedKeyUsage                   = clientAuth,serverAuth,pkInitKDC                    # serverAuth cert identifies ssl server, pkInitKDC cert identifies Kerberos DC

[dc_subjalt]                                                                            # Further Domain Controller Certificate
DNS                                = $set_dns
otherName                          = msADGUID;FORMAT:HEX,OCTETSTRING:$set_dc_guid

[ usr_cert_scarduser ]                                                                  # User Certificates
basicConstraints                   = CA:FALSE
crlDistributionPoints              = URI:$CRLDISTPT
nsCertType                         = client, email
keyUsage                           = nonRepudiation, digitalSignature, keyEncipherment
nsComment                          = "Smart Card Login Certificate"                     # Displayed in Netscape's comment listbox
subjectKeyIdentifier               = hash
authorityKeyIdentifier             = keyid,issuer
issuerAltName                      = issuer:copy
nsCaRevocationUrl                  = $CRLDISTPT
extendedKeyUsage                   = clientAuth,msSmartcardLogin                        # Extended Key requirements for client certs
#subjectAltName                    = email:copy,otherName:msUPN;UTF8:UserPrincipalName  # Can be used for CSR from smart card

Create the openssl.cnf file:

# touch /root/CA/openssl.cnf

Add your modified openssl.cnf to the newly created file.

Side note: CSR from smart card

This section is a side note that can be skipped, it concerns generating a CSR with a smart card.

Although outside the scope of these instructions, if generating the user key pair with a smart card, the last line of the openssl.cnf can be modified for a Certificate Signing Request (CSR) from a smart card.

Go through these instructions and modify where required. Transfer the CSR to the CA. Uncomment the last line of the openssl.cnf and add the user's UPN for the user requesting the certificate. For example, subjectAltName = email:copy,otherName:msUPN;UTF8:sambauser@samdom.example.com.

Modify the command below to suit your needs, when prompted use the CA root key's password:

# openssl ca -config /root/CA/openssl.cnf -subj "/emailAddress=sambauser@samdom.example.com/CN=samba user/OU=My Departement/O=My Departement/L=My Locality/ST=My State or Province/C=My Country 2 letter code" -extensions usr_cert_scarduser -in /root/CA/crl/sambauser.csr -out /root/CA/certs/sambauser.crt

Comment out the previously uncommented line and transfer the certificate to the smart card.

Generate key pair and certificate for root CA

Generate a key pair and certificate for the root CA:

 # openssl req -new -x509 -days 3650 -sha256 -extensions v3_ca -addext 'subjectAltName = email:copy' -keyout /root/CA/private/ca.key.pem -out /root/CA/certs/ca.cert.pem -config /root/CA/openssl.cnf

Go through the interactive session. Example:

…
Common Name (eg, YOUR name) []:samdom.example.com Certificate Authority
Email Address []:ca@samdom.example.com
…

Set read only permissions for root on the private key:

# chmod 400 /root/CA/private/ca.key.pem

Generate key pair and certificate for Domain Controller

Generate a key pair and certificate for the Domain Controller:

# openssl req -new -addext 'subjectAltName = email:copy' -newkey rsa:2048 -keyout /root/CA/private/dc.key.pem -out /root/CA/certs/dc.csr.pem -config /root/CA/openssl.cnf

Go through the interactive session, using the Domain Controller's DNS name as the Common Name. The Email can be left blank. Example:

…
Common Name (eg, YOUR name) []:dc1.samdom.example.com
…

Set read only permissions for root on the private key:

# chmod 400 /root/CA/private/dc.key.pem

Issue the certificate, when prompted, use the CA root key's password:

# openssl ca -config /root/CA/openssl.cnf -extensions usr_cert_mskdc -in /root/CA/certs/dc.csr.pem -out /root/CA/certs/dc.cert.pem

Create user that will login with smart card

Create the user that will log in using the smart card:

# samba-tool user add sambauser

Get the UserPrincipalName for the user

Get the UserPrincipalName for the user that will log in using a Smart Cart. Example for a user named sambauser:

# samba-tool user show sambauser | grep userPrincipalName

Here the UserPrincipalName is sambauser@samdom.example.com.

Generate key pair and certificate for user

Create a certificate request and a private key. Example for a user with the UserPrincipalName sambauser@samdom.example.com:

# openssl req -new -addext 'subjectAltName = otherName:msUPN;UTF8:sambauser@samdom.example.com,email:copy' -newkey rsa:2048 -keyout /root/CA/private/sambauser.key.pem -out /root/CA/certs/sambauser.csr.pem -config /root/CA/openssl.cnf

Go through the interactive session, using the UserPrincipalName for the Email Address. Example:

…
Email Address []:sambauser@samdom.example.com
…

Set read only permissions for root on the private key:

# chmod 400 /root/CA/private/sambauser.key.pem

Sign the certificate request:

# openssl ca -config /root/CA/openssl.cnf -extensions usr_cert_scarduser -in /root/CA/certs/sambauser.csr.pem -out /root/CA/certs/sambauser.cert.pem

Generate a CRL

Generate a CRL:

# openssl ca -config /root/CA/openssl.cnf -gencrl -out /root/CA/crl/samdom.crl

Generate a DH parameter file

Create DH parameter file:

# openssl dhparam -outform PEM -out /root/CA/dcdhparams.pem 2048

Configure Samba AD DC

Stop Samba:

# systemctl stop samba-ad-dc smbd

Archive Samba generated Certificates and CA Files, just in case:

# tar -czvf /root/samba.tls.tar.gz /var/lib/samba/private/tls

Delete Samba generated Certificates and CA Files:

# rm /var/lib/samba/private/tls/ca.pem /var/lib/samba/private/tls/cert.pem /var/lib/samba/private/tls/key.pem

Copy files to Samba Provision Directory:

# cp -p /root/CA/certs/dc.cert.pem /root/CA/certs/ca.cert.pem /root/CA/crl/samdom.crl /root/CA/dcdhparams.pem /root/CA/private/dc.key.pem /var/lib/samba/private/tls/

Decrypt the Domain Controller Private Key:

# openssl rsa -in /var/lib/samba/private/tls/dc.key.pem -inform PEM -out /var/lib/samba/private/tls/dc.priv.key.pem -outform PEM

Add the following to the global section of your /etc/samba/smb.conf:

tls enabled = yes
tls certfile = /var/lib/samba/private/tls/dc.cert.pem
tls keyfile = /var/lib/samba/private/tls/dc.priv.key.pem
tls cafile = /var/lib/samba/private/tls/ca.cert.pem
tls crlfile = /var/lib/samba/private/tls/samdom.crl
tls dhparams file = /var/lib/samba/private/tls/dcdhparams.pem

Start Samba:

# systemctl start samba-ad-dc

Check that were no errors loading certificates or associated files:

# systemctl status samba-ad-dc

Stop Samba:

# systemctl stop samba-ad-dc

Adapt your /var/lib/samba/private/krb5.conf to suit your needs. Example for DC1.SAMDOM.EXAMPLE.COM:

[libdefaults]
        default_realm = SAMDOM.EXAMPLE.COM
        dns_lookup_realm = false
        dns_lookup_kdc = true
        pkinit_anchors = FILE:/var/lib/samba/private/tls/ca.cert.pem

[appdefaults]
        pkinit_anchors = FILE:/var/lib/samba/private/tls/ca.cert.pem

[realms]
        SAMDOM.EXAMPLE.COM = {
                pkinit_require_eku = true
        }

[kdc]
        enable-pkinit = yes
        pkinit_identity = FILE:/var/lib/samba/private/tls/dc.cert.pem,/var/lib/samba/private/tls/dc.priv.key.pem
        pkinit_anchors = FILE:/var/lib/samba/private/tls/ca.cert.pem
        pkinit_principal_in_certificate = yes
        pkinit_win2k = no
        pkinit_win2k_require_binding = yes

[domain_realm]
        DC1 = SAMDOM.EXAMPLE.COM

Note: the dash in enable-pkinit is not a typo.

Archive current /etc/krb5.conf just in case:

# mv /etc/krb5.conf /etc/krb5.conf.old

Copy Samba's krb5.conf file to your operating system's Kerberos configuration:

# cp /var/lib/samba/private/krb5.conf /etc/krb5.conf

Start Samba:

# systemctl start samba-ad-dc

Check status:

# systemctl status samba-ad-dc

Configure CRL Distribution Point

Here, the previously generated .crl file in the Generate a CRL section will be copied to a web server. The URL for the .crl file should be the same as the one used in the openssl.cnf set_crp_default option configured in the Configure openssl.cnf section.

The following instructions use lighttpd on FreeBSD as a web server, but any web server could probably be used or other types of server such as FTP or LDAP may also work.

The following is for a fresh FreeBSD installation with the following configuration that needs to be adapted to suit your needs:

  • Hostname: crl
  • No optional system components installed
  • IPv4 Static Configuration:
    • IP Address: 192.168.1.6
    • Subnet Mask: 255.255.255.0
    • Default Router: 192.168.1.1
  • DNS Configuration:
    • Search: samdom.example.com
    • IPv4 DNS: 192.168.1.2 (Samba AD DC's IPv4)

Open a shell on your FreeBSD and proceed with the instructions. All commands are for FreeBSD 12.4-RELEASE-amd64, run as the root user.

Install git:

# pkg install git
# git clone https://git.FreeBSD.org/ports.git /usr/ports

Install lib32:

# pkg install wget
# wget https://download.freebsd.org/ftp/releases/amd64/12.4-RELEASE/lib32.txz
# tar -C / -xpf lib32.txz
# freebsd-update fetch
# freebsd-update install

Install lighttpd:

# cd /usr/ports/www/lighttpd

Run the following command, default options are acceptable:

# make config-recursive

Keep running make config-recursive until all dependent ports options have been defined, and ports options screens no longer appear, as options may create new dependencies.

# make install clean
# mkdir /usr/local/www/data

Copy the .crl file generated in the Generate a CRL section to the /usr/local/www/data directory on the web server.

Make the .crl file world readable, example for samdom.crl:

# chmod 444 /usr/local/www/data/samdom.crl

Add the following line to /etc/rc.conf:

lighttpd_enable="YES"

Adapt the following line to suit your needs and add it /usr/local/etc/lighttpd/lighttpd.conf:

server.bind = "192.168.1.6"

Start lighttpd:

# /usr/local/etc/rc.d/lighttpd start

Open a shell on your Samba AD DC and proceed with the instructions. All commands are for Debian, run as the root user.

Create a A DNS record for the web server by adapting the following command to suit your needs, example for dc1.samdom.example.com with 192.168.1.6 as the web server's IP address and crl as its name:

# samba-tool dns add dc1.samdom.example.com samdom.example.com crl A 192.168.1.6 -U administrator

Check that the A DNS record is working:

# host -t A crl.samdom.example.com

Optional, requires having created a reverse zone beforehand, as indicated on the Setting up Samba as an Active Directory Domain Controller page. Create a PTR DNS record for the web server by adapting the following command to suit your needs, example for dc1.samdom.example.com with 192.168.1.6 as the web server's IP address and crl.samdom.example.com as its FQDN:

# samba-tool dns add dc1.samdom.example.com 1.168.192.in-addr.arpa 6 PTR crl.samdom.example.com -U administrator

Check that the PTR DNS record is working:

# host 192.168.1.6

With a web browser on a client with the Samba AD DC as its DNS server, check that the .crl file downloads, for example, go to crl.samdom.example.com/samdom.crl.

macOS client configuration

Modify SmartcardLogin.plist

Add the following to /private/etc/SmartcardLogin.plist and make it word readable:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
     <key>AttributeMapping</key>
     <dict>
          <key>fields</key>
          <array>
                <string>NT Principal Name</string>
          </array>
          <key>formatString</key>
          <string>$1</string>
          <key>dsAttributeString</key>
          <string>dsAttrTypeNative:userPrincipalName</string>
     </dict>
</dict>
</plist>

Join macOS client to domain

The macOS client needs to be joined to the domain. For instructions, see the Joining a macOS Client to a Domain page.

Turn on mobile accounts

The mobile account feature allows using a network account when the client is disconnected from the network. As these instructions don't cover the configuration for setting up user's home folders on a file server, this feature allows us to login without setting up user's home folders on a network share.

  • Open System Preferences, then click on Users & Groups.
macOS security settings may require clicking on the padlock in the bottom left of the window and entering a local administrator account's credentials, before being able to make changes.
  • On the left pane, click on Login Options, then click on the Edit… button.
  • Click on the Open Directory Utility… button.
  • Click on the padlock in the bottom left of the Directory Utility window and enter a local administrator account's credentials, then click on the Modify Configuration button.
  • On the Services tab, select the Active Directory service and click the pen icon on the bottom left of the window. If required, enter a local administrator account's credentials, then click on the Modify Configuration button.
  • In the window that opens, expand the Show Options section by clicking the arrow to its left. Check the Create mobile account at login checkbox then click OK. If required, enter a local administrator account's credentials, then click on the Modify Configuration button.
  • Click on the padlock in the bottom left of the Directory Utility window to lock the preferences, then quit Directory Utility.
  • If your security settings initially required you to click on the padlock for System Preferences, click on it again to lock the preferences.
  • Close System Preferences.

Import certificates to smart card

The user's key pair and certificate need to be transferred to the smart card's 9a slot (authentication). See your smart card's manufacturer documentation on how to do this.

For the Yubikey 5 NFC used to test these instructions, the following was done:

Open a shell on your Samba AD DC and proceed with the instructions. The command is for Debian, run as the root user.

# openssl pkcs12 -in /root/CA/certs/sambauser.cert.pem -inkey /root/CA/private/sambauser.key.pem -export -out sambauser.p12

Transfer the sambauser.p12 file to a computer with YubiKey Manager (ykman) CLI installed and the smart card connected.

Get the smart card's serial number. This isn't necessary, but it makes sure that the correct YubiKey is being addressed:

# ykman list --serials

In the following commands the serial number 0000000 is used and needs to be substituted. The -P switch defines the smart card's PIN, here the default 123456 is used.

# ykman --device 0000000 piv keys import -P 123456 9a /path/to/sambauser.p12
# ykman --device 0000000 piv certificates import -P 123456 -v 9a /path/to/sambauser.p12

Login with smart card

Occasionally, there can be some delay before macOS is ready to use network accounts, in which case, at the login screen, a red light appears in the top-right corner of the screen, and will go away when macOS is ready. Additionally, sometimes the Smart Card needs to be removed and reconnected to be recognised.

At the login screen, connect the smart card to the macOS client.

Your user should be recognised, allowing for the entry of the smart card's PIN to log in to the domain.

Enter your smart card's PIN, a dialog box will appear asking to create a mobile account, click the Create Now button.

Done!

Side note: Further configuration options

Several options exist both for smart card login and for domain login on macOS clients, such as domain password policies or automatically enabling the screen saver on smart card removal.

For more information, see the following Apple guides:

  • Directory Utility User Guide
  • Apple Platform Deployment: Smart card integration
  • Apple Platform Deployment: Directory Service MDM payload settings