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.

#!/bin/sh

pass=""

usage() {
  cat << EOF >&2
Usage: ./certs.sh [-p <certPass>]

 -p <certPass>: Password for cert

 Examples:
     ./certs.sh -p $MY_CERT_PWD
EOF
  exit 1
}

while getopts p: o; do
  case $o in
    (p) pass=$OPTARG;;
    (*) usage
  esac
done

shift "$((OPTIND - 1))"

if [ -z "$pass" ]; then
    usage
fi

output_dir="./output"

if [ ! -d "$output_dir" ]; then
  mkdir "$output_dir"
fi

# 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.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.