caldav-Synchronisation der Erinnerungen-App in iOS 13 und MacOS 10.15

Seit dem Erscheinen von iOS 13 und MacOS 10.15 (Catalina) mehren sich im Netz die Berichte, dass Apple das Synchronisieren der App Erinnerungen über den weit verbreiteten caldav-Standard nicht mehr unterstützen würde. Belege dafür findet man jedoch keine. Zur allgemeinen Verwirrung hat auch eine etwas unklare Aussage von Apple geführt, die man so verstehen konnte, dass die Synchronisation der neuen Erinnerungen-App über caldav eingestellt worden sei (mittlerweile hat Apple den entsprechenden Artikel korrigiert und für Klarheit gesorgt).

Was allerdings seltsam ist: bei manchen Usern werden Erinnerungen auch unter den neuesten Apple-Betriebssystemen synchronisiert, bei anderen jedoch nicht. Die Synchronisation von Kalender über caldav funktioniert aber in jedem Fall weiterhin.

Woran liegt das und gibt es eine Lösung?

Es liegt am Zertifikat: Apple hat die Anforderungen für gültige Serverzertifikate in iOS 13 und MacOS 10.15 hochgeschraubt. Die Serverzertifikate müssen folgende Mindestvoraussetzungen erfüllen, um vom Betriebssystem als gültig akzeptiert zu werden:

  • der Schlüssel muss mindestens 2048 Bit groß sein
  • es muss mit einem Hash-Algorithmus aus der SHA-2 Familie signiert sein
  • der DNS-Name muss in der SAN-Erweiterung des Zertifikats stehen (er darf nicht im CommonName stehen!)

Für Zertifikate, deren Gültigkeitsbeginn nach dem 01.07.2019 liegt, gilt außerdem:

Soweit ich weiß, benötigt man eine Konfigurationsdatei, um ein Serverzertifikat mit openssl zu erstellen, das diese neuen Bedingungen erfüllt. Da im Internet hauptsächlich nur Einzeiler kursieren, um ein selbstsigniertes Serverzertifikat zu erstellen, dürften die allermeisten selbstsignierten Serverzertifikate daher diese neuen Bedingungen nicht erfüllen. Möglicherweise scheitern auch offiziell signierte Serverzertifikate daran. LetsEncrypt immerhin erfüllt Apples neue Bedingungen.

Aber warum funktioniert die Kalender-Synchronisation über caldav weiterhin? Ich erkläre mir das so: Apple hat die Erinnerungen-App für iOS 13 und MacOS Catalina von Grund auf neu geschrieben. Sie funktioniert nur mit einem solchermaßen gültigen Zertifikat, während die Kalender-App noch mit älteren Zertifikaten funktioniert, die die neuen Zertifikatsbedingungen nicht erfüllen. Vermutlich ist es nur eine Frage der Zeit, bis Apple auch die Kalender- und Adressbuch-Synchronisation nur noch mit den strengeren Zertifikatsanforderungen ermöglicht.

Zum Erstellen und Signieren eines solchermaßen gültigen Serverzertifikats richtet man sich am Besten eine eigene Certificate Authority ein (im Folgenden CA genannt). So umgeht man das spätestens alle 825 Tage notwendige manuelle Importieren und Vertrauen in iOS/MacOS, da das Root-Zertifikat der CA (im Gegensatz zum Serverzertifikat) beliebig lange gültig sein kann. Vor allem, wenn man das bei Freunden/Bekannten oder Kunden einrichten muss, ist das ein enormer Vorteil!

Klingt kompliziert? Ist es nicht! Zunächst erstellen wir ein Verzeichnis, in dem die CA arbeiten soll, und erstellen dort ein paar für deren Betrieb notwendige Dateien:

mkdir -p /etc/CA && cd /etc/CA
touch index.txt && mkdir certs crl newcerts && touch crlnumber

Dann müssen zwei Konfigurationsdateien erstellt werden. Die erste heißt openssl-ca.cnf für die CA und wird mit folgendem Inhalt gefüllt (die Werte unter [req_distinguished_name] können den eigenen Bedürfnissen angepasst werden):

####################################################################
# openssl-ca.cnf
# openssl Konfigurationsdatei für eine Certificate Authority (CA)
# um ein Root-Zertifikat zu erstellen und CSRs zu signieren

HOME                    = .
RANDFILE                = $ENV::HOME/.rnd


####################################################################
# CA definition
[ ca ]
default_ca      = CA_default            # die Default-CA-Sektion


####################################################################
# Werte für die CA (siehe oben)
[ CA_default ]
dir             = .                     # Hier wird alles gespeichert
certs           = $dir/certs            # Hier werden die Zertifikate gespeichert
crl_dir         = $dir/crl              # Hier werden die CRLs gespeichert
database        = $dir/index.txt        # Datenbank Index Datei
new_certs_dir   = $dir/newcerts         # Default Speicherort für neue Zertifikate
certificate     = $dir/cacert.pem       # Das Root-Zertifikat der CA
serial          = $dir/serial           # Die Seriennummer
crlnumber       = $dir/crlnumber        # Die CRL-Nummer (auskommentieren, um V1 CRLs zu erzeugen)
crl             = $dir/crl.pem          # Das aktuelle CRL
private_key     = $dir/cakey.pem        # Der geheime Schlüssel der CA

# Auf 'yes' ändern, um dasselbe Subject für verschiedene Zertifikate zu verbieten:
unique_subject  = no

# Option, um die Extension vom CSR zum signierten Zertifikat zu kopieren:
copy_extensions = copy

# OpenSSL anweisen, nicht das traditionelle (und kaputte) Format zu benutzen:
name_opt        = ca_default            # Optionen für den Subject Namen
cert_opt        = ca_default            # Optionen für die Zertifikatsfelder

x509_extensions = usr_cert              # Erweiterungen, die dem Zertifikat hinzugefügt werden

default_days    = 825                   # Gültigkeitsdauer des Zertifikats (iOS 13 + MacOS 10.15: max 825)
default_crl_days= 30                    # Dauer bis zum nächsten CRL
default_md      = default               # benutze Standard MD für den öffentlichen Schlüssel (derzeit SHA256)
preserve        = no                    # übergebene DN-Reihenfolge beibehalten?

# Es folgen zwei verschiedene Policies, wie der Request erstellt sein sollte.
policy          = policy_match


####################################################################
# Die CA-Policy, die beim Signieren von CSRs benutzt wird:
[ policy_match ]
countryName             = match
stateOrProvinceName     = optional
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional


####################################################################
# Die 'anything' Policy für alles andere
[ policy_anything ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional


####################################################################
# Wie CSRs erzeugt werden sollen:
[ req ]
default_bits            = 4096
default_keyfile         = cakey.pem
distinguished_name      = req_distinguished_name
attributes              = req_attributes
x509_extensions         = v3_ca  # Erweiterungen, die einem selbstsignierten Zertifikat hinzugefügt werden sollen
req_extensions          = v3_req # Erweiterungen, die einem CSR hinzugefügt werden sollen
string_mask             = utf8only
prompt                  = no     # Werte von [req_distinguished_name] ohne Nachfrage übernehmen


####################################################################
# Der 'distinguished name', der dem Zertifikat hinzugefügt wird (siehe [req])
# diese Werte können den eigenen Bedürfnissen angepasst werden
# Länderkürzel sollte allerdings gültig sein!
[ req_distinguished_name ]
countryName                     = DE    # Länderkürzel
0.organizationName              = Privat
commonName                      = Privat CA 1.0


####################################################################
# Extra Attribute für CSRs. Kann leer sein, muss aber existieren
[ req_attributes ]


####################################################################
# Diese Erweiterungen werden hinzugefügt, wenn die CA einen CSR signiert:
[ usr_cert ]
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectKeyIdentifier = hash


####################################################################
# Erweiterungen, die einer Zertifikatsanforderung hinzugefügt werden:
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment


####################################################################
# Benutzte Erweiterungen beim Selbstsignieren eines Zertifikats
[ v3_ca ]
authorityKeyIdentifier = keyid,issuer
basicConstraints = critical,CA:true
keyUsage = cRLSign, keyCertSign
subjectKeyIdentifier = hash

Die zweite Konfigurationsdatei openssl-server.cnf wird für das Erstellen eines Certificate Signing Request (CSR) für das Serverzertifikat benötigt und bekommt folgenden Inhalt (die Werte unter [server_distinguished_name] können den eigenen Bedürfnissen angepasst werden – die Werte unter [alternate_names] MÜSSEN(!) den eigenen Bedürfnissen angepasst werden):

####################################################################
# openssl-server.cnf
# openssl Konfigurationsdatei, um einen CSR für ein gültiges Serverzertifikat zu erstellen

HOME            = .
RANDFILE        = $ENV::HOME/.rnd

####################################################################
[ req ]
default_bits       = 4096
default_keyfile    = serverkey.pem
distinguished_name = server_distinguished_name
req_extensions     = server_req_extensions
string_mask        = utf8only
prompt             = no

####################################################################
# der Name des Serverzertifikats
# diese Werte können den eigenen Bedürfnissen angepasst werden
# das Länderkürzel sollte allerdings gültig sein!
[ server_distinguished_name ]
countryName        = DE            # Länderkürzel
organizationName   = Privat
commonName         = Privat Serverzertifikat v1.0

####################################################################
[ server_req_extensions ]
subjectKeyIdentifier = hash
basicConstraints     = CA:FALSE
keyUsage             = digitalSignature, keyEncipherment
extendedKeyUsage     = TLS Web Server Authentication, TLS Web Client Authentication
subjectAltName       = @alternate_names

####################################################################
# die folgenden Domainnamen werden dem Zertifikat hinzugefügt
# und müssen den eigenen Wüsnchen anngepasst werden
# Wildcards sind erlaubt (*.webroot.de)
# Ebenso sind IP-Adressen möglich (allerdings problematisch, da IP-Adressen keine SNI sind!)
[ alternate_names ]
DNS.1  = privat.fritz.box
DNS.2  = domain.example.com
DNS.3  = *.webroot.de
DNS.4  = 127.0.0.1
DNS.5  = 192.168.178.100
IP.1   = 127.0.0.1
IP.2   = 192.168.178.100

Sind beide Konfigurationsdateien unter den genannten Namen angelegt und die DNS-Namen in openssl-server.cnf angepasst kann es losgehen.

Zunächst wird das Root-Zertifikat cacert.pem der CA mit einer Gültigkeitsdauer von 20 Jahren (-days 7300) erstellt:

openssl req -x509 -config openssl-ca.cnf -sha256 -nodes -out cacert.pem -outform PEM -days 7300

Als nächstes wird der private Schlüssel serverkey.pem und der Certificate Signing Request servercert.csr des Serverzertifikats erstellt:

openssl req -config openssl-server.cnf -newkey rsa:4096 -sha256 -nodes -out servercert.csr -outform PEM

Diesen CSR lassen wir nun von unserer CA signieren und erzeugen damit das signierte Serverzertifikat servercert.pem:

openssl ca -create_serial -config openssl-ca.cnf -policy policy_anything -extensions usr_cert -out servercert.pem -infiles servercert.csr

Der private Schlüssel serverkey.pem und das Zertifikat servercert.pem im Arbeitsverzeichnis müssen nun der Webserverkonfiguration hinzugefügt werden. Unter nginx erledigen das die folgenden beiden Zeilen im entsprechenden server-Block:

ssl_certificate /etc/CA/servercert.pem;
ssl_certificate_key /etc/CA/serverkey.pem;

Wie das bei anderen Webservern geht, findet sich in der Dokumentation des eingesetzten Webservers.

Das Root-Zertifikat der CA muss nun unter iOS/MacOS installiert werden. Entweder schickt man sich das Zertifikat per Mail oder man legt es in einem Samba-Share ab und greift über die Dateien-App darauf zu (Danke @barish). Die Frage, ob das Zertifikat installiert werden soll, mit ja beantworten und die weiteren Schritte befolgen.

Als letzter Schritt muss das Betriebssystem angewiesen werden, dem Zertifikat zu vertrauen. Unter iOS findet man die entsprechende Einstellung unter Systemeinstellungen -> Allgemein -> Info -> Zertifikatsvertrauenseinstellungen. Unter MacOS ist die Schlüsselbundverwaltung dafür zuständig.

Ist das erledigt, vertrauen iOS und MacOS fortan allen von der CA signierten Serverzertifikaten, solange das Root-Zertifikat der CA gültig ist (das sind 20 Jahre).

Spätestens alle 825 Tage muss das Serverzertifikat erneuert werden. Mit den folgenden beiden Befehlen wird ein neuer CSR erstellt und anschließend das Zertifikat von unserer CA signiert. Die Option -batch sorgt dafür, dass keine Fragen gestellt werden – so lässt sich das Ganze gut automatisieren z.B. über einen cronjob:

openssl req -config openssl-server.cnf -newkey rsa:4096 -sha256 -nodes -out servercert.csr -outform PEM
openssl ca -batch -create_serial -config openssl-ca.cnf -policy policy_anything -extensions usr_cert -out servercert.pem -infiles servercert.csr

iOS und MacOS vertrauen wie gesagt dem neu erstellten Serverzertifikat ohne weitere Handlungsnotwendigkeit, solange das Root-Zertifikat der CA gültig ist. Sehr komfortabel!

Sind alle Schritte befolgt worden, steht das Zertifikat einer erfolgreichen caldav-Synchronisation von Erinnerungen auch unter den neuesten Apple-Betriebssystemen nicht mehr im Weg.

Changelog:

  • [08.12.2019] komplette Überarbeitung mit Erstellen einer eigenen CA, um nicht alle 825 Tage ein neues Zertifikat importieren zu müssen
  • [05.11.2019] kleine Umformulierungen zur besseren Verständlichkeit

9 Kommentare on "caldav-Synchronisation der Erinnerungen-App in iOS 13 und MacOS 10.15"


  1. Für die Übertragung auf iOS eignet sich auch sehr gut die neue Dateien-App, sofern man auf seinem Server auch Samba laufen hat.

    Antworten

  2. Danke! Die Anleitung hat mir sehr geholfen.
    Ich habe zunächst auch die (anscheinend) weggefallene CalDav-Unterstützung als Grund für den fehlenden Sync von iOS13 mit meinem Server vermutet. Dank der guten Anleitung klappt es nun wieder.

    Antworten

  3. Eine Frage bzgl. der Zeile der Domain, für die das Zertifikat gültig sein soll: Ich möchte auf Nextcloud zugreifen über 192.168.x.x, geht dies auch?
    VG

    Antworten

    1. Das sollte funktionieren.
      Es ist auch möglich mehrere Domains durch Komma getrennt in der Zeile anzugeben – z.B. so (Achtung: die Zeile kommt 2x vor und sollte 2x gleich lauten!):

      subjectAltName = DNS:www.example.com, DNS:192.168.110.123

      Viel Erfolg 🙂

      Antworten

    1. Hm. Ich habe es gerade auch ausprobiert und es ging auch mit einer IP im subjectAltName
      Eine IP-Adresse ist allerdings keine SNI, daher könnte der Server ein anderes Zertifikat ausliefern als gewünscht.
      Liefert der Webserver bei Zugriff auf die IP das richtige Zertifikat aus (überprüfen mit curl -v IP.IP.IP.IP)?

      Antworten

  4. Danke für die Erklärung. Damit rückte das Aufsetzen einer eigenen CA wieder etwas hoch in der Prio-Liste 😉
    Vorläufig hat sich auch definitiv was getan, seit ich das neue Cert eingespielt habe.
    Leider nur bedingt positiv, da jetzt ständig meine Tasks und manuellen Listen nach ein paar Sekunden verschwinden. Ich nutze den WebDAV/CalDAV auf einem Synology NAS.
    Kalender Sync und Kontakte (CardDAV) funktioniert noch einwandfrei.
    Jemand was ähnliches beobachtet?

    Antworten

    1. Hallo Christian,
      mit webdav/caldav auf Synology habe ich leider keine Erfahrungen und kann daher leider nichts dazu sagen. Ich nutze Nextcloud für caldav/carddav und da funktioniert die Synchronisation von Kalendern/Tasks/Adressen problemlos.
      Wegen der CA: ich habe vor, den Artikel demnächst dahingehend zu aktualisieren.
      Gruß, Bernhard.

      Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.