macos - 在 OS X 上使用 X509Certificate2 时避免使用钥匙串(keychain)

标签 macos cryptography .net-core bouncycastle keychain

我们的应用程序在引导时生成根 CA + 服务器证书以供内部使用。我们不使用钥匙串(keychain)(该应用程序是多平台的),但是发现我们自己受到苹果加密技术(.NET Core 2.x 在内部使用它)的依赖而受到相当大的限制。

我们使用 BouncyCaSTLe 作为我们的加密库。

似乎每次我们生成(或尝试)生成任何类型的证书时,它都会进入用户的登录钥匙串(keychain)。这不是故意的,并且会在没有运行 UI session 的纯守护进程环境中导致问题(因此无法写入钥匙串(keychain))。

该应用程序不会在 Windows 或 Linux 中执行此操作,因此我们非常好奇这是从何而来。理想情况下,我们希望完全停止与钥匙串(keychain)交互。

我们的证书类(class)(完整)可在此处获取:https://paste.ee/p/CiXo3#9TFSTycJqh5E1xTNzt9vtBbT7ZOyB4zk

但是,我也会引用此处调用的相关函数:

public X509Certificate2 CreateCertificateAuthorityCertificate(string subjectName, string[] subjectAlternativeNames, KeyPurposeID[] usages, string password = null)
{
    // It's self-signed, so these are the same.
    var issuerName = subjectName;

    var random = GetSecureRandom();
    var subjectKeyPair = GenerateKeyPair(random, 2048);

    // It's self-signed, so these are the same.
    var issuerKeyPair = subjectKeyPair;

    var serialNumber = GenerateSerialNumber(random);
    var issuerSerialNumber = serialNumber; // Self-signed, so it's the same serial number.

    const bool isCertificateAuthority = true;
    var certificate = GenerateCertificate(random, subjectName, subjectKeyPair, serialNumber,
                                          subjectAlternativeNames, issuerName, issuerKeyPair,
                                          issuerSerialNumber, isCertificateAuthority,
                                          usages);
    return ConvertCertificate(certificate, subjectKeyPair, random, password);
}

public X509Certificate GenerateCertificate(SecureRandom random,
                                           string subjectName,
                                           AsymmetricCipherKeyPair subjectKeyPair,
                                           BigInteger subjectSerialNumber,
                                           string[] subjectAlternativeNames,
                                           string issuerName,
                                           AsymmetricCipherKeyPair issuerKeyPair,
                                           BigInteger issuerSerialNumber,
                                           bool isCertificateAuthority,
                                           KeyPurposeID[] usages)
{
    var certificateGenerator = new X509V3CertificateGenerator();

    certificateGenerator.SetSerialNumber(subjectSerialNumber);

    // Set the signature algorithm. This is used to generate the thumbprint which is then signed
    // with the issuer's private key. We'll use SHA-256, which is (currently) considered fairly strong.
    const string signatureAlgorithm = "SHA256WithRSA";
    certificateGenerator.SetSignatureAlgorithm(signatureAlgorithm);

    var issuerDN = new X509Name(issuerName);
    certificateGenerator.SetIssuerDN(issuerDN);

    // Note: The subject can be omitted if you specify a subject alternative name (SAN).
    var subjectDN = new X509Name(subjectName);
    certificateGenerator.SetSubjectDN(subjectDN);

    // Our certificate needs valid from/to values.
    var notBefore = DateTime.UtcNow.Date;
    var notAfter = notBefore.AddYears(10);

    certificateGenerator.SetNotBefore(notBefore);
    certificateGenerator.SetNotAfter(notAfter);

    // The subject's public key goes in the certificate.
    certificateGenerator.SetPublicKey(subjectKeyPair.Public);

    AddAuthorityKeyIdentifier(certificateGenerator, issuerDN, issuerKeyPair, issuerSerialNumber);
    AddSubjectKeyIdentifier(certificateGenerator, subjectKeyPair);
    AddBasicConstraints(certificateGenerator, isCertificateAuthority);

    if (usages != null && usages.Any())
        AddExtendedKeyUsage(certificateGenerator, usages);

    if (subjectAlternativeNames != null && subjectAlternativeNames.Any())
        AddSubjectAlternativeNames(certificateGenerator, subjectAlternativeNames);

    // The certificate is signed with the issuer's private key.
    var certificate = certificateGenerator.Generate(issuerKeyPair.Private, random);
    return certificate;
}

public X509Certificate2 ConvertCertificate(X509Certificate certificate,
                                                   AsymmetricCipherKeyPair subjectKeyPair,
                                                   SecureRandom random, string password)
{
    // Now to convert the Bouncy Castle certificate to a .NET certificate.
    // See http://web.archive.org/web/20100504192226/http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx
    // ...but, basically, we create a PKCS12 store (a .PFX file) in memory, and add the public and private key to that.
    var store = new Pkcs12Store();

    // What Bouncy Castle calls "alias" is the same as what Windows terms the "friendly name".
    string friendlyName = certificate.SubjectDN.ToString();

    // Add the certificate.
    var certificateEntry = new X509CertificateEntry(certificate);
    store.SetCertificateEntry(friendlyName, certificateEntry);

    // Add the private key.
    store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });

    // Convert it to an X509Certificate2 object by saving/loading it from a MemoryStream.
    // It needs a password. Since we'll remove this later, it doesn't particularly matter what we use.
    var stream = new MemoryStream();
    store.Save(stream, password.ToCharArray(), random);

    var convertedCertificate =
        new X509Certificate2(stream.ToArray(),
                             password,
                             X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
    return convertedCertificate;
}

在 MacOS 上,记录了各种钥匙串(keychain)异常(exception)情况(下面引用了其中之一):

Unhandled Exception: Interop+AppleCrypto+AppleCommonCryptoCryptographicException: User interaction is not allowed.
   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[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
   at Spectero.daemon.Libraries.Core.Crypto.CryptoService.ConvertCertificate(X509Certificate certificate, AsymmetricCipherKeyPair subjectKeyPair, SecureRandom random, String password) in /opt/spectero/daemon/deploy/daemon/Libraries/Core/Crypto/CryptoService.cs:line 398
   at Spectero.daemon.Migrations.Initialize.Up() in /opt/spectero/daemon/deploy/daemon/Migrations/Initialize.cs:line 116
   at Spectero.daemon.Startup.Configure(IOptionsSnapshot`1 configMonitor, IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IMigration migration, IAutoStarter autoStarter, IServiceProvider serviceProvider) in /opt/spectero/daemon/deploy/daemon/Startup.cs:line 193
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.Configure(IApplicationBuilder app)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at Spectero.daemon.Program.Main(String[] args) in /opt/spectero/daemon/deploy/daemon/Program.cs:line 12

最佳答案

删除 PersistKeySet 标志。

在 Windows 上,该标志表明导入的私钥文件永远不应该被删除,因此您会慢慢地填充正在运行的 key 目录(并且在某些时候,您将拥有如此多的私钥文件,导致性能变得非常糟糕)。

在 Linux 上,该标志不执行任何操作。

在 macOS 上,该标志会导致证书直接导入到默认钥匙串(keychain),因为将证书和 key 相互关联的唯一方法是通过 SecIdentityRef,并且只有 KeyChain 可以创建它们。通常在 macOS 上,PFX 会加载到临时钥匙串(keychain)中,但如果它是在没有可导出的情况下加载的,那么现在就无法移动它,从而使得“加载持久并将其添加到 X509Store”的 Windows 代码不起作用。

关于macos - 在 OS X 上使用 X509Certificate2 时避免使用钥匙串(keychain),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50340712/

相关文章:

macos - 在主目录中创建文件夹

python - 需要运行 Python 3.5

macos - 为什么需要在重新分配 NSView 后删除/添加它才能显示

cryptography - Android中 "AES"密码的含义?

c# - 未找到匹配命令 'dotnet-aspnet-codegenerator' 的可执行文件“

java - 使用 Eclipse 从 java 读取环境变量

.net - 可移植类库是否支持 System.Security.Cryptography 命名空间

security - 关于 p2p 社交网络的注意事项

.net-core - Azure Devops + Coverlet + SonarQube 显示 0%

c# - 我可以根据 .NET Core 中的运行时标识符定义常量吗?