private byte[] ExportPfx(string password) { using (SafeX509StackHandle publicCerts = Interop.Crypto.NewX509Stack()) { X509Certificate2 privateCert = null; // Walk the collection backwards, because we're pushing onto a stack. // This will cause the read order later to be the same as it was now. for (int i = _certs.Length - 1; i >= 0; --i) { X509Certificate2 cert = _certs[i]; if (cert.HasPrivateKey) { if (privateCert != null) { // OpenSSL's PKCS12 accelerator (PKCS12_create) only supports one // private key. The data structure supports more than one, but // being able to use that functionality requires a lot more code for // a low-usage scenario. throw new PlatformNotSupportedException(SR.NotSupported_Export_MultiplePrivateCerts); } privateCert = cert; } else { using (SafeX509Handle certHandle = Interop.Crypto.X509Duplicate(cert.Handle)) { if (!Interop.Crypto.PushX509StackField(publicCerts, certHandle)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } // The handle ownership has been transferred into the STACK_OF(X509). certHandle.SetHandleAsInvalid(); } } } SafeX509Handle privateCertHandle; SafeEvpPKeyHandle privateCertKeyHandle; if (privateCert != null) { OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)privateCert.Pal; privateCertHandle = pal.SafeHandle; privateCertKeyHandle = pal.PrivateKeyHandle ?? InvalidPKeyHandle; } else { privateCertHandle = SafeX509Handle.InvalidHandle; privateCertKeyHandle = InvalidPKeyHandle; } using (SafePkcs12Handle pkcs12 = Interop.Crypto.Pkcs12Create( password, privateCertKeyHandle, privateCertHandle, publicCerts)) { if (pkcs12.IsInvalid) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } return(Interop.Crypto.OpenSslEncode( Interop.Crypto.GetPkcs12DerSize, Interop.Crypto.EncodePkcs12, pkcs12)); } } }
private byte[] ExportPfx(SafePasswordHandle password) { using (SafeX509StackHandle publicCerts = Interop.Crypto.NewX509Stack()) { SafeX509Handle privateCertHandle = SafeX509Handle.InvalidHandle; SafeEvpPKeyHandle privateCertKeyHandle = InvalidPKeyHandle; if (_singleCertPal != null) { var certPal = (OpenSslX509CertificateReader)_singleCertPal; if (_singleCertPal.HasPrivateKey) { privateCertHandle = certPal.SafeHandle; privateCertKeyHandle = certPal.PrivateKeyHandle; } else { PushHandle(certPal.Handle, publicCerts); } GC.KeepAlive(certPal); // ensure reader's safe handle isn't finalized while raw handle is in use } else { X509Certificate2 privateCert = null; // Walk the collection backwards, because we're pushing onto a stack. // This will cause the read order later to be the same as it was now. for (int i = _certs.Count - 1; i >= 0; --i) { X509Certificate2 cert = _certs[i]; if (cert.HasPrivateKey) { if (privateCert != null) { // OpenSSL's PKCS12 accelerator (PKCS12_create) only supports one // private key. The data structure supports more than one, but // being able to use that functionality requires a lot more code for // a low-usage scenario. throw new PlatformNotSupportedException(SR.NotSupported_Export_MultiplePrivateCerts); } privateCert = cert; var certPal = (OpenSslX509CertificateReader)cert.Pal; privateCertHandle = certPal.SafeHandle; privateCertKeyHandle = certPal.PrivateKeyHandle; } else { PushHandle(cert.Handle, publicCerts); } } } using (SafePkcs12Handle pkcs12 = Interop.Crypto.Pkcs12Create( password, privateCertKeyHandle, privateCertHandle, publicCerts)) { if (pkcs12.IsInvalid) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } byte[] result = Interop.Crypto.OpenSslEncode( Interop.Crypto.GetPkcs12DerSize, Interop.Crypto.EncodePkcs12, pkcs12); // ensure cert handles aren't finalized while the raw handles are in use GC.KeepAlive(_certs); return(result); } } }
internal Interop.Crypto.X509VerifyStatusCode FindChainViaAia( ref List <X509Certificate2> downloadedCerts) { IntPtr lastCert = IntPtr.Zero; SafeX509StoreCtxHandle storeCtx = _storeCtx; Interop.Crypto.X509VerifyStatusCode statusCode = Interop.Crypto.X509VerifyStatusCode.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; while (!IsCompleteChain(statusCode)) { using (SafeX509Handle currentCert = Interop.Crypto.X509StoreCtxGetCurrentCert(storeCtx)) { IntPtr currentHandle = currentCert.DangerousGetHandle(); // No progress was made, give up. if (currentHandle == lastCert) { break; } lastCert = currentHandle; ArraySegment <byte> authorityInformationAccess = OpenSslX509CertificateReader.FindFirstExtension( currentCert, Oids.AuthorityInformationAccess); if (authorityInformationAccess.Count == 0) { break; } X509Certificate2 downloaded = DownloadCertificate( authorityInformationAccess, ref _remainingDownloadTime); // The AIA record is contained in a public structure, so no need to clear it. CryptoPool.Return(authorityInformationAccess.Array, clearSize: 0); if (downloaded == null) { break; } if (downloadedCerts == null) { downloadedCerts = new List <X509Certificate2>(); } AddToStackAndUpRef(downloaded.Handle, _untrustedLookup); downloadedCerts.Add(downloaded); Interop.Crypto.X509StoreCtxRebuildChain(storeCtx); statusCode = Interop.Crypto.X509StoreCtxGetError(storeCtx); } } if (statusCode == Interop.Crypto.X509VerifyStatusCode.X509_V_OK && downloadedCerts != null) { using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(_storeCtx)) { int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack); Span <IntPtr> tempChain = stackalloc IntPtr[DefaultChainCapacity]; byte[] tempChainRent = null; if (chainSize <= tempChain.Length) { tempChain = tempChain.Slice(0, chainSize); } else { int targetSize = checked (chainSize * IntPtr.Size); tempChainRent = CryptoPool.Rent(targetSize); tempChain = MemoryMarshal.Cast <byte, IntPtr>(tempChainRent.AsSpan(0, targetSize)); } for (int i = 0; i < chainSize; i++) { tempChain[i] = Interop.Crypto.GetX509StackField(chainStack, i); } // In the average case we never made it here. // // Given that we made it here, in the average remaining case // we are doing a one item for which will match in the second position // of an (on-average) 3 item collection. // // The only case where this loop really matters is if downloading the // certificate made an alternate chain better, which may have resulted in // an extra download and made the first one not be involved any longer. In // that case, it's a 2 item for loop matching against a three item set. // // So N*M is well contained. for (int i = downloadedCerts.Count - 1; i >= 0; i--) { X509Certificate2 downloadedCert = downloadedCerts[i]; if (!tempChain.Contains(downloadedCert.Handle)) { downloadedCert.Dispose(); downloadedCerts.RemoveAt(i); } } if (downloadedCerts.Count == 0) { downloadedCerts = null; } if (tempChainRent != null) { CryptoPool.Return(tempChainRent); } } } return(statusCode); }
public static IChainPal BuildChain( X509Certificate2 leaf, HashSet <X509Certificate2> candidates, HashSet <X509Certificate2> systemTrusted, OidCollection applicationPolicy, OidCollection certificatePolicy, X509RevocationMode revocationMode, X509RevocationFlag revocationFlag, DateTime verificationTime, ref TimeSpan remainingDownloadTime) { X509ChainElement[] elements; List <X509ChainStatus> overallStatus = new List <X509ChainStatus>(); WorkingChain workingChain = new WorkingChain(); Interop.Crypto.X509StoreVerifyCallback workingCallback = workingChain.VerifyCallback; // An X509_STORE is more comparable to Cryptography.X509Certificate2Collection than to // Cryptography.X509Store. So read this with OpenSSL eyes, not CAPI/CNG eyes. // // (If you need to think of it as an X509Store, it's a volatile memory store) using (SafeX509StoreHandle store = Interop.Crypto.X509StoreCreate()) using (SafeX509StoreCtxHandle storeCtx = Interop.Crypto.X509StoreCtxCreate()) using (SafeX509StackHandle extraCerts = Interop.Crypto.NewX509Stack()) { Interop.Crypto.CheckValidOpenSslHandle(store); Interop.Crypto.CheckValidOpenSslHandle(storeCtx); Interop.Crypto.CheckValidOpenSslHandle(extraCerts); bool lookupCrl = revocationMode != X509RevocationMode.NoCheck; foreach (X509Certificate2 cert in candidates) { OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)cert.Pal; using (SafeX509Handle handle = Interop.Crypto.X509UpRef(pal.SafeHandle)) { if (!Interop.Crypto.PushX509StackField(extraCerts, handle)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } // Ownership was transferred to the cert stack. handle.SetHandleAsInvalid(); } if (lookupCrl) { CrlCache.AddCrlForCertificate( cert, store, revocationMode, verificationTime, ref remainingDownloadTime); // If we only wanted the end-entity certificate CRL then don't look up // any more of them. lookupCrl = revocationFlag != X509RevocationFlag.EndCertificateOnly; } } if (revocationMode != X509RevocationMode.NoCheck) { if (!Interop.Crypto.X509StoreSetRevocationFlag(store, revocationFlag)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } } foreach (X509Certificate2 trustedCert in systemTrusted) { OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)trustedCert.Pal; if (!Interop.Crypto.X509StoreAddCert(store, pal.SafeHandle)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } } SafeX509Handle leafHandle = ((OpenSslX509CertificateReader)leaf.Pal).SafeHandle; if (!Interop.Crypto.X509StoreCtxInit(storeCtx, store, leafHandle, extraCerts)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } Interop.Crypto.X509StoreCtxSetVerifyCallback(storeCtx, workingCallback); Interop.Crypto.SetX509ChainVerifyTime(storeCtx, verificationTime); int verify = Interop.Crypto.X509VerifyCert(storeCtx); if (verify < 0) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } // Because our callback tells OpenSSL that every problem is ignorable, it should tell us that the // chain is just fine (unless it returned a negative code for an exception) Debug.Assert(verify == 1, "verify == 1"); using (SafeX509StackHandle chainStack = Interop.Crypto.X509StoreCtxGetChain(storeCtx)) { int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack); elements = new X509ChainElement[chainSize]; int maybeRootDepth = chainSize - 1; // The leaf cert is 0, up to (maybe) the root at chainSize - 1 for (int i = 0; i < chainSize; i++) { List <X509ChainStatus> status = new List <X509ChainStatus>(); List <Interop.Crypto.X509VerifyStatusCode> elementErrors = i < workingChain.Errors.Count ? workingChain.Errors[i] : null; if (elementErrors != null) { AddElementStatus(elementErrors, status, overallStatus); } IntPtr elementCertPtr = Interop.Crypto.GetX509StackField(chainStack, i); if (elementCertPtr == IntPtr.Zero) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } // Duplicate the certificate handle X509Certificate2 elementCert = new X509Certificate2(elementCertPtr); elements[i] = new X509ChainElement(elementCert, status.ToArray(), ""); } } } GC.KeepAlive(workingCallback); if ((certificatePolicy != null && certificatePolicy.Count > 0) || (applicationPolicy != null && applicationPolicy.Count > 0)) { List <X509Certificate2> certsToRead = new List <X509Certificate2>(); foreach (X509ChainElement element in elements) { certsToRead.Add(element.Certificate); } CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead); bool failsPolicyChecks = false; if (certificatePolicy != null) { if (!policyChain.MatchesCertificatePolicies(certificatePolicy)) { failsPolicyChecks = true; } } if (applicationPolicy != null) { if (!policyChain.MatchesApplicationPolicies(applicationPolicy)) { failsPolicyChecks = true; } } if (failsPolicyChecks) { X509ChainElement leafElement = elements[0]; X509ChainStatus chainStatus = new X509ChainStatus { Status = X509ChainStatusFlags.NotValidForUsage, StatusInformation = SR.Chain_NoPolicyMatch, }; var elementStatus = new List <X509ChainStatus>(leafElement.ChainElementStatus.Length + 1); elementStatus.AddRange(leafElement.ChainElementStatus); AddUniqueStatus(elementStatus, ref chainStatus); AddUniqueStatus(overallStatus, ref chainStatus); elements[0] = new X509ChainElement( leafElement.Certificate, elementStatus.ToArray(), leafElement.Information); } } return(new OpenSslX509ChainProcessor { ChainStatus = overallStatus.ToArray(), ChainElements = elements, }); }
internal static partial int GetX509StackFieldCount(SafeX509StackHandle stack);
internal static extern SafePkcs12Handle Pkcs12Create( SafePasswordHandle pass, SafeEvpPKeyHandle pkey, SafeX509Handle cert, SafeX509StackHandle ca);
internal static extern bool PushX509StackField(SafeX509StackHandle stack, SafeX509Handle x509);
internal static SafeX509StoreHandle X509ChainNew(SafeX509StackHandle systemTrust, SafeX509StackHandle userTrust) { SafeX509StoreHandle store = CryptoNative_X509ChainNew(systemTrust, userTrust); if (store.IsInvalid) { Exception e = CreateOpenSslCryptographicException(); store.Dispose(); throw e; } return(store); }
internal static extern int GetX509StackFieldCount(SafeX509StackHandle stack);
internal static extern IntPtr GetX509StackField(SafeX509StackHandle stack, int loc);
private static partial int CryptoNative_X509StackAddMultiple(SafeX509StackHandle dest, SafeX509StackHandle src);
private static partial int CryptoNative_X509StackAddDirectoryStore(SafeX509StackHandle stack, string storePath);
internal static extern bool Pkcs12Parse( SafePkcs12Handle p12, SafePasswordHandle pass, out SafeEvpPKeyHandle pkey, out SafeX509Handle cert, out SafeX509StackHandle ca);
internal static partial bool PushX509StackField(SafeX509StackHandle stack, SafeX509Handle x509);
private static Tuple <SafeX509StackHandle, SafeX509StackHandle> LoadMachineStores( DirectoryInfo rootStorePath, FileInfo rootStoreFile) { Debug.Assert( Monitor.IsEntered(s_recheckStopwatch), "LoadMachineStores assumes a lock(s_recheckStopwatch)"); SafeX509StackHandle rootStore = Interop.Crypto.NewX509Stack(); Interop.Crypto.CheckValidOpenSslHandle(rootStore); SafeX509StackHandle intermedStore = Interop.Crypto.NewX509Stack(); Interop.Crypto.CheckValidOpenSslHandle(intermedStore); DateTime newFileTime = default; DateTime newDirTime = default; var uniqueRootCerts = new HashSet <X509Certificate2>(); var uniqueIntermediateCerts = new HashSet <X509Certificate2>(); if (rootStoreFile != null && rootStoreFile.Exists) { newFileTime = rootStoreFile.LastWriteTimeUtc; ProcessFile(rootStoreFile); } if (rootStorePath != null && rootStorePath.Exists) { newDirTime = rootStorePath.LastWriteTimeUtc; foreach (FileInfo file in rootStorePath.EnumerateFiles()) { ProcessFile(file); } } void ProcessFile(FileInfo file) { using (SafeBioHandle fileBio = Interop.Crypto.BioNewFile(file.FullName, "rb")) { // The handle may be invalid, for example when we don't have read permission for the file. if (fileBio.IsInvalid) { Interop.Crypto.ErrClearError(); return; } // Some distros ship with two variants of the same certificate. // One is the regular format ('BEGIN CERTIFICATE') and the other // contains additional AUX-data ('BEGIN TRUSTED CERTIFICATE'). // The additional data contains the appropriate usage (e.g. emailProtection, serverAuth, ...). // Because corefx doesn't validate for a specific usage, derived certificates are rejected. // For now, we skip the certificates with AUX data and use the regular certificates. ICertificatePal pal; while (OpenSslX509CertificateReader.TryReadX509PemNoAux(fileBio, out pal) || OpenSslX509CertificateReader.TryReadX509Der(fileBio, out pal)) { X509Certificate2 cert = new X509Certificate2(pal); // The HashSets are just used for uniqueness filters, they do not survive this method. if (StringComparer.Ordinal.Equals(cert.Subject, cert.Issuer)) { if (uniqueRootCerts.Add(cert)) { using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(pal.Handle)) { if (!Interop.Crypto.PushX509StackField(rootStore, tmp)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } // The ownership has been transferred to the stack tmp.SetHandleAsInvalid(); } continue; } } else { if (uniqueIntermediateCerts.Add(cert)) { using (SafeX509Handle tmp = Interop.Crypto.X509UpRef(pal.Handle)) { if (!Interop.Crypto.PushX509StackField(intermedStore, tmp)) { throw Interop.Crypto.CreateOpenSslCryptographicException(); } // The ownership has been transferred to the stack tmp.SetHandleAsInvalid(); } continue; } } // There's a good chance we'll encounter duplicates on systems that have both one-cert-per-file // and one-big-file trusted certificate stores. Anything that wasn't unique will end up here. cert.Dispose(); } } } foreach (X509Certificate2 cert in uniqueRootCerts) { cert.Dispose(); } foreach (X509Certificate2 cert in uniqueIntermediateCerts) { cert.Dispose(); } Tuple <SafeX509StackHandle, SafeX509StackHandle> newCollections = Tuple.Create(rootStore, intermedStore); Debug.Assert( Monitor.IsEntered(s_recheckStopwatch), "LoadMachineStores assumes a lock(s_recheckStopwatch)"); // The existing collections are not Disposed here, intentionally. // They could be in the gap between when they are returned from this method and not yet used // in a P/Invoke, which would result in exceptions being thrown. // In order to maintain "finalization-free" the GetNativeCollections method would need to // DangerousAddRef, and the callers would need to DangerousRelease, adding more interlocked operations // on every call. Volatile.Write(ref s_nativeCollections, newCollections); s_directoryCertsLastWrite = newDirTime; s_fileCertsLastWrite = newFileTime; s_recheckStopwatch.Restart(); return(newCollections); }
private static partial SafeX509StoreHandle CryptoNative_X509ChainNew(SafeX509StackHandle systemTrust, SafeX509StackHandle userTrust);
internal static extern bool PKCS12_parse(SafePkcs12Handle p12, string pass, out SafeEvpPKeyHandle pkey, out SafeX509Handle cert, out SafeX509StackHandle ca);
internal static partial bool X509StoreCtxInit( SafeX509StoreCtxHandle ctx, SafeX509StoreHandle store, SafeX509Handle x509, SafeX509StackHandle extraCerts);
internal static partial SafePkcs7Handle Pkcs7CreateCertificateCollection(SafeX509StackHandle certs);
internal static partial IntPtr GetX509StackField(SafeX509StackHandle stack, int loc);
public static IChainPal BuildChain( X509Certificate2 leaf, List <X509Certificate2> candidates, List <X509Certificate2> downloaded, OidCollection applicationPolicy, OidCollection certificatePolicy, DateTime verificationTime) { X509ChainElement[] elements; List <X509ChainStatus> overallStatus = new List <X509ChainStatus>(); // An X509_STORE is more comparable to Cryptography.X509Certificate2Collection than to // Cryptography.X509Store. So read this with OpenSSL eyes, not CAPI/CNG eyes. // // (If you need to think of it as an X509Store, it's a volatile memory store) using (SafeX509StoreHandle store = Interop.libcrypto.X509_STORE_new()) using (SafeX509StoreCtxHandle storeCtx = Interop.libcrypto.X509_STORE_CTX_new()) { Interop.libcrypto.CheckValidOpenSslHandle(store); Interop.libcrypto.CheckValidOpenSslHandle(storeCtx); foreach (X509Certificate2 cert in candidates) { OpenSslX509CertificateReader pal = (OpenSslX509CertificateReader)cert.Pal; if (!Interop.libcrypto.X509_STORE_add_cert(store, pal.SafeHandle)) { throw Interop.libcrypto.CreateOpenSslCryptographicException(); } } // When CRL checking support is added, it should be done before the call to // X509_STORE_CTX_init (aka here) by calling X509_STORE_set_flags(store, flags); SafeX509Handle leafHandle = ((OpenSslX509CertificateReader)leaf.Pal).SafeHandle; if (!Interop.libcrypto.X509_STORE_CTX_init(storeCtx, store, leafHandle, IntPtr.Zero)) { throw Interop.libcrypto.CreateOpenSslCryptographicException(); } Interop.Crypto.SetX509ChainVerifyTime(storeCtx, verificationTime); int verify = Interop.libcrypto.X509_verify_cert(storeCtx); if (verify < 0) { throw Interop.libcrypto.CreateOpenSslCryptographicException(); } using (SafeX509StackHandle chainStack = Interop.libcrypto.X509_STORE_CTX_get1_chain(storeCtx)) { int chainSize = Interop.Crypto.GetX509StackFieldCount(chainStack); int errorDepth = -1; Interop.libcrypto.X509VerifyStatusCode errorCode = 0; if (verify == 0) { errorCode = Interop.libcrypto.X509_STORE_CTX_get_error(storeCtx); errorDepth = Interop.libcrypto.X509_STORE_CTX_get_error_depth(storeCtx); } elements = new X509ChainElement[chainSize]; int maybeRootDepth = chainSize - 1; // The leaf cert is 0, up to (maybe) the root at chainSize - 1 for (int i = 0; i < chainSize; i++) { List <X509ChainStatus> status = new List <X509ChainStatus>(); if (i == errorDepth) { AddElementStatus(errorCode, status, overallStatus); } IntPtr elementCertPtr = Interop.Crypto.GetX509StackField(chainStack, i); if (elementCertPtr == IntPtr.Zero) { throw Interop.libcrypto.CreateOpenSslCryptographicException(); } // Duplicate the certificate handle X509Certificate2 elementCert = new X509Certificate2(elementCertPtr); // If the last cert is self signed then it's the root cert, do any extra checks. if (i == maybeRootDepth && IsSelfSigned(elementCert)) { // If the root certificate was downloaded, it's untrusted. if (downloaded.Contains(elementCert)) { AddElementStatus( Interop.libcrypto.X509VerifyStatusCode.X509_V_ERR_CERT_UNTRUSTED, status, overallStatus); } } elements[i] = new X509ChainElement(elementCert, status.ToArray(), ""); } } } if ((certificatePolicy != null && certificatePolicy.Count > 0) || (applicationPolicy != null && applicationPolicy.Count > 0)) { List <X509Certificate2> certsToRead = new List <X509Certificate2>(); foreach (X509ChainElement element in elements) { certsToRead.Add(element.Certificate); } CertificatePolicyChain policyChain = new CertificatePolicyChain(certsToRead); bool failsPolicyChecks = false; if (certificatePolicy != null) { if (!policyChain.MatchesCertificatePolicies(certificatePolicy)) { failsPolicyChecks = true; } } if (applicationPolicy != null) { if (!policyChain.MatchesApplicationPolicies(applicationPolicy)) { failsPolicyChecks = true; } } if (failsPolicyChecks) { X509ChainElement leafElement = elements[0]; X509ChainStatus chainStatus = new X509ChainStatus { Status = X509ChainStatusFlags.InvalidPolicyConstraints, StatusInformation = SR.Chain_NoPolicyMatch, }; var elementStatus = new List <X509ChainStatus>(leafElement.ChainElementStatus.Length + 1); elementStatus.AddRange(leafElement.ChainElementStatus); AddUniqueStatus(elementStatus, ref chainStatus); AddUniqueStatus(overallStatus, ref chainStatus); elements[0] = new X509ChainElement( leafElement.Certificate, elementStatus.ToArray(), leafElement.Information); } } return(new OpenSslX509ChainProcessor { ChainStatus = overallStatus.ToArray(), ChainElements = elements, }); }