I’ve generally found .NET Core code I’ve worked on to behave the same across Windows, Mac, and Linux (when not explicitly using OS-specific features). Recently I ran into a couple platform-specific issues where a .NET Core app behaved differently on Mac than Windows. This post covers the first issue I came across, reading certificates.

An Authentication API read a X.509 certificate with new X509Certificate2(certBytes). While not ideal, this private .PFX certificate was created without a password and that worked on Windows and Linux. On Mac the following exception occurred.

Interop.AppleCrypto.AppleCommonCryptoCryptographicException : MAC verification failed during PKCS12 import (wrong password?)
at Interop.AppleCrypto.X509ImportCertificate(Byte[] bytes, X509ContentType contentType, SafePasswordHandle importPassword, SafeKeychainHandle keychain, Boolean exportable, SafeSecIdentityHandle& identityHandle)
at Internal.Cryptography.Pal.CertificatePal.FromBlob(Byte[] rawData, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(Byte[] data)
at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData)

To work around this I created a script to regenerate the certs with encrypted keys, at least for local dev use. Certs for the deployed API came from elsewhere.

usage() {
  cat << EOF >&2
Usage: ./certs.sh [-p <certPass>]
 -p <certPass>: Password for cert
     ./certs.sh -p $MY_CERT_PWD
  exit 1
while getopts p: o; do
  case $o in
    (p) pass=$OPTARG;;
    (*) usage
shift "$((OPTIND - 1))"
if [ -z "$pass" ]; then
if [ ! -d "$output_dir" ]; then
  mkdir "$output_dir"
# Create private key and public certificate. Password and subject are important:
echo "Generating certs..."
openssl req \
    -newkey rsa:4096 -nodes -sha256 \
    -keyout "$output_dir/auth-private.key" -x509 -days 356 \
    -passin pass:"$pass" \
    -subj "/CN=www.some-company.com/O=SomeCompany/C=US" \
    -out "$output_dir/auth-public.crt"
# Combine the private key and public certificate into a pfx file:
openssl pkcs12 \
    -inkey "$output_dir/auth-private.key" -in "$output_dir/auth-public.crt" \
    -password pass:"$pass" \
    -export -out "$output_dir/auth-private.pfx"
# Generate a PEM file from the auth-public.crt file:
openssl x509 -in "$output_dir/auth-public.crt" -out "$output_dir/auth-public.pem" -outform PEM
# TODO: Copy private key to Auth API, public key to callers.

This of course also required changing the certificate reading code to new X509Certificate2(certBytes, password) along with any associated configuration of reading that (environment variables, user secrets, Azure Key Vault etc.).

More context on this issue can be found dotnet/runtime #23635.