private void CreateStreamAndStartWatcher()
        {
            Debug.Assert(_eventStream.IsInvalid);
            Debug.Assert(_watcherRunLoop == IntPtr.Zero);
            Debug.Assert(_callback == null);

            // Make sure we only do this if there is a valid directory
            if (String.IsNullOrEmpty(_directory) == false)
            {
                _fullDirectory = System.IO.Path.GetFullPath(_directory);

                // Get the path to watch and verify we created the CFStringRef
                SafeCreateHandle path = Interop.CoreFoundation.CFStringCreateWithCString(_fullDirectory);
                if (path.IsInvalid)
                {
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Take the CFStringRef and put it into an array to pass to the EventStream
                SafeCreateHandle arrPaths = Interop.CoreFoundation.CFArrayCreate(new CFStringRef[1] {
                    path.DangerousGetHandle()
                }, 1);
                if (arrPaths.IsInvalid)
                {
                    path.Dispose();
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Create the callback for the EventStream
                _callback = new Interop.EventStream.FSEventStreamCallback(FileSystemEventCallback);

                // Make sure the OS file buffer(s) are fully flushed so we don't get events from cached I/O
                Interop.libc.sync();

                // Create the event stream for the path and tell the stream to watch for file system events.
                _eventStream = Interop.EventStream.FSEventStreamCreate(
                    _callback,
                    arrPaths,
                    Interop.EventStream.kFSEventStreamEventIdSinceNow,
                    0.0f,
                    EventStreamFlags);
                if (_eventStream.IsInvalid)
                {
                    arrPaths.Dispose();
                    path.Dispose();
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Create and start our watcher thread then wait for the thread to initialize and start
                // the RunLoop. We wait for that to prevent this function from returning before the RunLoop
                // has a chance to start so that any callers won't race with the background thread's initialization
                // and calling Stop, which would attempt to stop a RunLoop that hasn't started yet.
                _watcherThread = new Thread(new ThreadStart(WatchForFileSystemEventsThreadStart));
                _watcherThread.Start();
                _runLoopStartedEvent.WaitOne();
            }
        }
示例#2
0
            internal void Start()
            {
                // Get the path to watch and verify we created the CFStringRef
                SafeCreateHandle path = Interop.CoreFoundation.CFStringCreateWithCString(_fullDirectory);

                if (path.IsInvalid)
                {
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Take the CFStringRef and put it into an array to pass to the EventStream
                SafeCreateHandle arrPaths = Interop.CoreFoundation.CFArrayCreate(new CFStringRef[1] {
                    path.DangerousGetHandle()
                }, 1);

                if (arrPaths.IsInvalid)
                {
                    path.Dispose();
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Create the callback for the EventStream if it wasn't previously created for this instance.
                if (_callback == null)
                {
                    _callback = new Interop.EventStream.FSEventStreamCallback(FileSystemEventCallback);
                }

                // Make sure the OS file buffer(s) are fully flushed so we don't get events from cached I/O
                Interop.Sys.Sync();

                // Create the event stream for the path and tell the stream to watch for file system events.
                _eventStream = Interop.EventStream.FSEventStreamCreate(
                    _callback,
                    arrPaths,
                    Interop.EventStream.kFSEventStreamEventIdSinceNow,
                    0.0f,
                    EventStreamFlags);
                if (_eventStream.IsInvalid)
                {
                    arrPaths.Dispose();
                    path.Dispose();
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Create and start our watcher thread then wait for the thread to initialize and start
                // the RunLoop. We wait for that to prevent this function from returning before the RunLoop
                // has a chance to start so that any callers won't race with the background thread's initialization
                // and calling Stop, which would attempt to stop a RunLoop that hasn't started yet.
                var runLoopStarted = new ManualResetEventSlim();

                new Thread(WatchForFileSystemEventsThreadStart)
                {
                    IsBackground = true
                }.Start(runLoopStarted);
                runLoopStarted.Wait();
            }
        private static void RunLoopThreadStart()
        {
            Debug.Assert(s_runLoop == IntPtr.Zero);

            s_runLoop = Interop.RunLoop.CFRunLoopGetCurrent();
            Interop.RunLoop.CFRunLoopAddSource(
                s_runLoop,
                s_runLoopSource.DangerousGetHandle(),
                Interop.RunLoop.kCFRunLoopDefaultMode.DangerousGetHandle());

            s_runLoopStartedEvent.Set();
            Interop.RunLoop.CFRunLoopRun();

            Interop.RunLoop.CFRunLoopRemoveSource(
                s_runLoop,
                s_runLoopSource.DangerousGetHandle(),
                Interop.RunLoop.kCFRunLoopDefaultMode.DangerousGetHandle());

            s_runLoop = IntPtr.Zero;

            s_runLoopSource.Dispose();
            s_runLoopSource = null;

            s_dynamicStoreRef.Dispose();
            s_dynamicStoreRef = null;

            s_runLoopEndedEvent.Set();
        }
        internal static byte[] X509ExportPfx(IntPtr[] certHandles, SafePasswordHandle exportPassword)
        {
            SafeCreateHandle cfPassphrase    = s_emptyExportString;
            bool             releasePassword = false;

            try
            {
                if (!exportPassword.IsInvalid)
                {
                    exportPassword.DangerousAddRef(ref releasePassword);
                    IntPtr passwordHandle = exportPassword.DangerousGetHandle();

                    if (passwordHandle != IntPtr.Zero)
                    {
                        cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle);
                    }
                }

                return(X509Export(X509ContentType.Pkcs12, cfPassphrase, certHandles));
            }
            finally
            {
                if (releasePassword)
                {
                    exportPassword.DangerousRelease();
                }

                if (cfPassphrase != s_emptyExportString)
                {
                    cfPassphrase.Dispose();
                }
            }
        }
示例#5
0
        internal static SafeSecCertificateHandle X509ImportCertificate(
            ReadOnlySpan <byte> bytes,
            X509ContentType contentType,
            SafePasswordHandle importPassword,
            out SafeSecIdentityHandle identityHandle)
        {
            SafeCreateHandle?cfPassphrase    = null;
            bool             releasePassword = false;

            try
            {
                if (!importPassword.IsInvalid)
                {
                    importPassword.DangerousAddRef(ref releasePassword);
                    cfPassphrase = CoreFoundation.CFStringCreateFromSpan(importPassword.DangerousGetSpan());
                }

                return(X509ImportCertificate(
                           bytes,
                           contentType,
                           cfPassphrase,
                           out identityHandle));
            }
            finally
            {
                if (releasePassword)
                {
                    importPassword.DangerousRelease();
                }

                cfPassphrase?.Dispose();
            }
        }
示例#6
0
        internal static unsafe void SslCtxSetAlpnProtos(SafeSslHandle ctx, List <SslApplicationProtocol> protocols)
        {
            SafeCreateHandle cfProtocolsRefs = null;

            SafeCreateHandle[] cfProtocolsArrayRef = null;
            try
            {
                if (protocols.Count == 1 && protocols[0] == SslApplicationProtocol.Http2)
                {
                    cfProtocolsRefs = s_cfAlpnHttp211Protocols;
                }
                else if (protocols.Count == 1 && protocols[0] == SslApplicationProtocol.Http11)
                {
                    cfProtocolsRefs = s_cfAlpnHttp11Protocols;
                }
                else if (protocols.Count == 2 && protocols[0] == SslApplicationProtocol.Http2 && protocols[1] == SslApplicationProtocol.Http11)
                {
                    cfProtocolsRefs = s_cfAlpnHttp211Protocols;
                }
                else
                {
                    // we did not match common case. This is more expensive path allocating Core Foundation objects.
                    cfProtocolsArrayRef = new SafeCreateHandle[protocols.Count];
                    IntPtr[] protocolsPtr = new System.IntPtr[protocols.Count];

                    for (int i = 0; i < protocols.Count; i++)
                    {
                        cfProtocolsArrayRef[i] = CoreFoundation.CFStringCreateWithCString(protocols[i].ToString());
                        protocolsPtr[i]        = cfProtocolsArrayRef[i].DangerousGetHandle();
                    }

                    cfProtocolsRefs = CoreFoundation.CFArrayCreate(protocolsPtr, (UIntPtr)protocols.Count);
                }

                int osStatus;
                int result = SSLSetALPNProtocols(ctx, cfProtocolsRefs, out osStatus);
                if (result != 1)
                {
                    throw CreateExceptionForOSStatus(osStatus);
                }
            }
            finally
            {
                if (cfProtocolsArrayRef != null)
                {
                    for (int i = 0; i < cfProtocolsArrayRef.Length; i++)
                    {
                        cfProtocolsArrayRef[i]?.Dispose();
                    }

                    cfProtocolsRefs?.Dispose();
                }
            }
        }
示例#7
0
        internal static SafeCFArrayHandle X509ImportCollection(
            ReadOnlySpan <byte> bytes,
            X509ContentType contentType,
            SafePasswordHandle importPassword)
        {
            SafeCreateHandle  cfPassphrase    = s_emptyExportString;
            bool              releasePassword = false;
            SafeCFArrayHandle collectionHandle;
            int osStatus;

            try
            {
                if (!importPassword.IsInvalid)
                {
                    importPassword.DangerousAddRef(ref releasePassword);
                    IntPtr passwordHandle = importPassword.DangerousGetHandle();

                    if (passwordHandle != IntPtr.Zero)
                    {
                        cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle);
                    }
                }

                osStatus = AppleCryptoNative_X509ImportCollection(
                    ref MemoryMarshal.GetReference(bytes),
                    bytes.Length,
                    contentType,
                    cfPassphrase,
                    out collectionHandle);

                if (osStatus == 0)
                {
                    return(collectionHandle);
                }
            }
            finally
            {
                if (releasePassword)
                {
                    importPassword.DangerousRelease();
                }

                if (cfPassphrase != s_emptyExportString)
                {
                    cfPassphrase.Dispose();
                }
            }

            collectionHandle.Dispose();
            throw CreateExceptionForOSStatus(osStatus);
        }
        internal static byte[] SecKeyExport(
            SafeSecKeyRefHandle key,
            bool exportPrivate,
            string password)
        {
            SafeCreateHandle exportPassword = exportPrivate
                ? CoreFoundation.CFStringCreateWithCString(password)
                : s_nullExportString;

            int ret;
            SafeCFDataHandle cfData;
            int osStatus;

            try
            {
                ret = AppleCryptoNative_SecKeyExport(
                    key,
                    exportPrivate ? 1 : 0,
                    exportPassword,
                    out cfData,
                    out osStatus);
            }
            finally
            {
                if (exportPassword != s_nullExportString)
                {
                    exportPassword.Dispose();
                }
            }

            byte[] exportedData;

            using (cfData)
            {
                if (ret == 0)
                {
                    throw CreateExceptionForOSStatus(osStatus);
                }

                if (ret != 1)
                {
                    Debug.Fail($"AppleCryptoNative_SecKeyExport returned {ret}");
                    throw new CryptographicException();
                }

                exportedData = CoreFoundation.CFGetData(cfData);
            }

            return(exportedData);
        }
        internal static SafeCFDataHandle SecKeyExportData(
            SafeSecKeyRefHandle?key,
            bool exportPrivate,
            ReadOnlySpan <char> password)
        {
            SafeCreateHandle exportPassword = exportPrivate
                ? CoreFoundation.CFStringCreateFromSpan(password)
                : s_nullExportString;

            int ret;
            SafeCFDataHandle cfData;
            int osStatus;

            try
            {
                ret = AppleCryptoNative_SecKeyExport(
                    key,
                    exportPrivate ? 1 : 0,
                    exportPassword,
                    out cfData,
                    out osStatus);
            }
            finally
            {
                if (exportPassword != s_nullExportString)
                {
                    exportPassword.Dispose();
                }
            }

            if (ret == 1)
            {
                return(cfData);
            }

            cfData.Dispose();

            if (ret == 0)
            {
                throw CreateExceptionForOSStatus(osStatus);
            }

            Debug.Fail($"AppleCryptoNative_SecKeyExport returned {ret}");
            throw new CryptographicException();
        }
        private static unsafe void CreateAndStartRunLoop()
        {
            Debug.Assert(s_dynamicStoreRef == null);

            var storeContext = new Interop.SystemConfiguration.SCDynamicStoreContext();

            using (SafeCreateHandle storeName = Interop.CoreFoundation.CFStringCreateWithCString("NetworkAddressChange.OSX"))
            {
                s_dynamicStoreRef = Interop.SystemConfiguration.SCDynamicStoreCreate(
                    storeName.DangerousGetHandle(),
                    s_storeCallback,
                    &storeContext);
            }

            // Notification key string parts. We want to match notification keys
            // for any kind of IP address change, addition, or removal.
            using (SafeCreateHandle dynamicStoreDomainStateString = Interop.CoreFoundation.CFStringCreateWithCString("State:"))
                using (SafeCreateHandle compAnyRegexString = Interop.CoreFoundation.CFStringCreateWithCString("[^/]+"))
                    using (SafeCreateHandle entNetIpv4String = Interop.CoreFoundation.CFStringCreateWithCString("IPv4"))
                        using (SafeCreateHandle entNetIpv6String = Interop.CoreFoundation.CFStringCreateWithCString("IPv6"))
                        {
                            if (dynamicStoreDomainStateString.IsInvalid || compAnyRegexString.IsInvalid ||
                                entNetIpv4String.IsInvalid || entNetIpv6String.IsInvalid)
                            {
                                s_dynamicStoreRef.Dispose();
                                s_dynamicStoreRef = null;
                                throw new NetworkInformationException(SR.net_PInvokeError);
                            }

                            using (SafeCreateHandle ipv4Pattern = Interop.SystemConfiguration.SCDynamicStoreKeyCreateNetworkServiceEntity(
                                       dynamicStoreDomainStateString.DangerousGetHandle(),
                                       compAnyRegexString.DangerousGetHandle(),
                                       entNetIpv4String.DangerousGetHandle()))
                                using (SafeCreateHandle ipv6Pattern = Interop.SystemConfiguration.SCDynamicStoreKeyCreateNetworkServiceEntity(
                                           dynamicStoreDomainStateString.DangerousGetHandle(),
                                           compAnyRegexString.DangerousGetHandle(),
                                           entNetIpv6String.DangerousGetHandle()))
                                    using (SafeCreateHandle patterns = Interop.CoreFoundation.CFArrayCreate(
                                               new CFStringRef[2]
                                    {
                                        ipv4Pattern.DangerousGetHandle(),
                                        ipv6Pattern.DangerousGetHandle()
                                    }, (UIntPtr)2))
                                    {
                                        // Try to register our pattern strings with the dynamic store instance.
                                        if (patterns.IsInvalid || !Interop.SystemConfiguration.SCDynamicStoreSetNotificationKeys(
                                                s_dynamicStoreRef.DangerousGetHandle(),
                                                IntPtr.Zero,
                                                patterns.DangerousGetHandle()))
                                        {
                                            s_dynamicStoreRef.Dispose();
                                            s_dynamicStoreRef = null;
                                            throw new NetworkInformationException(SR.net_PInvokeError);
                                        }

                                        // Create a "RunLoopSource" that can be added to our listener thread's RunLoop.
                                        s_runLoopSource = Interop.SystemConfiguration.SCDynamicStoreCreateRunLoopSource(
                                            s_dynamicStoreRef.DangerousGetHandle(),
                                            IntPtr.Zero);
                                    }
                        }
            s_runLoopThread = new Thread(RunLoopThreadStart);
            s_runLoopThread.Start();
            s_runLoopStartedEvent.WaitOne(); // Wait for the new thread to finish initialization.
        }
            internal unsafe void Start()
            {
                // Make sure _fullPath doesn't contain a link or alias
                // since the OS will give back the actual, non link'd or alias'd paths
                _fullDirectory = Interop.Sys.RealPath(_fullDirectory);
                if (_fullDirectory == null)
                {
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                Debug.Assert(string.IsNullOrEmpty(_fullDirectory) == false, "Watch directory is null or empty");

                // Normalize the _fullDirectory path to have a trailing slash
                if (_fullDirectory[_fullDirectory.Length - 1] != '/')
                {
                    _fullDirectory += "/";
                }

                // Get the path to watch and verify we created the CFStringRef
                SafeCreateHandle path = Interop.CoreFoundation.CFStringCreateWithCString(_fullDirectory);

                if (path.IsInvalid)
                {
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Take the CFStringRef and put it into an array to pass to the EventStream
                SafeCreateHandle arrPaths = Interop.CoreFoundation.CFArrayCreate(new CFStringRef[1] {
                    path.DangerousGetHandle()
                }, (UIntPtr)1);

                if (arrPaths.IsInvalid)
                {
                    path.Dispose();
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Create the callback for the EventStream if it wasn't previously created for this instance.
                if (_callback == null)
                {
                    _callback = new Interop.EventStream.FSEventStreamCallback(FileSystemEventCallback);
                }

                _context = ExecutionContext.Capture();

                // Make sure the OS file buffer(s) are fully flushed so we don't get events from cached I/O
                Interop.Sys.Sync();

                // Create the event stream for the path and tell the stream to watch for file system events.
                _eventStream = Interop.EventStream.FSEventStreamCreate(
                    _callback,
                    arrPaths,
                    Interop.EventStream.kFSEventStreamEventIdSinceNow,
                    0.0f,
                    EventStreamFlags);
                if (_eventStream.IsInvalid)
                {
                    arrPaths.Dispose();
                    path.Dispose();
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                StaticWatcherRunLoopManager.ScheduleEventStream(_eventStream);

                bool started = Interop.EventStream.FSEventStreamStart(_eventStream);

                if (!started)
                {
                    // Try to get the Watcher to raise the error event; if we can't do that, just silently exit since the watcher is gone anyway
                    FileSystemWatcher watcher;
                    if (_weakWatcher.TryGetTarget(out watcher))
                    {
                        // An error occurred while trying to start the run loop so fail out
                        watcher.OnError(new ErrorEventArgs(new IOException(SR.EventStream_FailedToStart, Marshal.GetLastWin32Error())));
                    }
                }
            }
示例#12
0
        internal void OpenTrustHandle(
            ICertificatePal leafCert,
            X509Certificate2Collection?extraStore,
            X509RevocationMode revocationMode,
            X509Certificate2Collection customTrustStore,
            X509ChainTrustMode trustMode)
        {
            _revocationMode = revocationMode;
            SafeCreateHandle policiesArray = PreparePoliciesArray(revocationMode != X509RevocationMode.NoCheck);
            SafeCreateHandle certsArray    = PrepareCertsArray(leafCert, extraStore, customTrustStore, trustMode);

            int osStatus;

            SafeX509ChainHandle chain;
            int ret = Interop.AppleCrypto.AppleCryptoNative_X509ChainCreate(
                certsArray,
                policiesArray,
                out chain,
                out osStatus);

            if (ret == 1)
            {
                if (trustMode == X509ChainTrustMode.CustomRootTrust)
                {
                    SafeCreateHandle customCertsArray = s_emptyArray;
                    if (customTrustStore != null && customTrustStore.Count > 0)
                    {
                        customCertsArray = PrepareCustomCertsArray(customTrustStore);
                    }

                    try
                    {
                        int error = Interop.AppleCrypto.X509ChainSetTrustAnchorCertificates(chain, customCertsArray);
                        if (error != 0)
                        {
                            throw Interop.AppleCrypto.CreateExceptionForOSStatus(error);
                        }
                    }
                    finally
                    {
                        if (customCertsArray != s_emptyArray)
                        {
                            customCertsArray.Dispose();
                        }
                    }
                }

                _chainHandle = chain;
                return;
            }

            chain.Dispose();

            if (ret == 0)
            {
                throw Interop.AppleCrypto.CreateExceptionForOSStatus(osStatus);
            }

            Debug.Fail($"AppleCryptoNative_X509ChainCreate returned unexpected return value {ret}");
            throw new CryptographicException();
        }
        internal static SafeCFArrayHandle X509ImportCollection(
            byte[] bytes,
            X509ContentType contentType,
            SafePasswordHandle importPassword,
            SafeKeychainHandle keychain,
            bool exportable)
        {
            SafeCreateHandle cfPassphrase    = s_nullExportString;
            bool             releasePassword = false;

            int ret;
            SafeCFArrayHandle collectionHandle;
            int osStatus;

            try
            {
                if (!importPassword.IsInvalid)
                {
                    importPassword.DangerousAddRef(ref releasePassword);
                    IntPtr passwordHandle = importPassword.DangerousGetHandle();

                    if (passwordHandle != IntPtr.Zero)
                    {
                        cfPassphrase = CoreFoundation.CFStringCreateWithCString(passwordHandle);
                    }
                }

                ret = AppleCryptoNative_X509ImportCollection(
                    bytes,
                    bytes.Length,
                    contentType,
                    cfPassphrase,
                    keychain,
                    exportable ? 1 : 0,
                    out collectionHandle,
                    out osStatus);

                if (ret == 1)
                {
                    return(collectionHandle);
                }
            }
            finally
            {
                if (releasePassword)
                {
                    importPassword.DangerousRelease();
                }

                if (cfPassphrase != s_nullExportString)
                {
                    cfPassphrase.Dispose();
                }
            }

            collectionHandle.Dispose();

            const int SeeOSStatus         = 0;
            const int ImportReturnedEmpty = -2;
            const int ImportReturnedNull  = -3;

            switch (ret)
            {
            case SeeOSStatus:
                throw CreateExceptionForOSStatus(osStatus);

            case ImportReturnedNull:
            case ImportReturnedEmpty:
                throw new CryptographicException();

            default:
                Debug.Fail($"Unexpected return value {ret}");
                throw new CryptographicException();
            }
        }
        internal static DerSequenceReader SecKeyExport(
            SafeSecKeyRefHandle key,
            bool exportPrivate)
        {
            // Apple requires all private keys to be exported encrypted, but since we're trying to export
            // as parsed structures we will need to decrypt it for the user.
            const string ExportPassword = "******";

            SafeCreateHandle exportPassword = exportPrivate
                ? CoreFoundation.CFStringCreateWithCString(ExportPassword)
                : s_nullExportString;

            int ret;
            SafeCFDataHandle cfData;
            int osStatus;

            try
            {
                ret = AppleCryptoNative_SecKeyExport(
                    key,
                    exportPrivate ? 1 : 0,
                    exportPassword,
                    out cfData,
                    out osStatus);
            }
            finally
            {
                if (exportPassword != s_nullExportString)
                {
                    exportPassword.Dispose();
                }
            }

            byte[] exportedData;

            using (cfData)
            {
                if (ret == 0)
                {
                    throw CreateExceptionForOSStatus(osStatus);
                }

                if (ret != 1)
                {
                    Debug.Fail($"AppleCryptoNative_SecKeyExport returned {ret}");
                    throw new CryptographicException();
                }

                exportedData = CoreFoundation.CFGetData(cfData);
            }

            DerSequenceReader reader = new DerSequenceReader(exportedData);

            if (!exportPrivate)
            {
                return(reader);
            }

            byte tag = reader.PeekTag();

            // PKCS#8 defines two structures, PrivateKeyInfo, which starts with an integer,
            // and EncryptedPrivateKey, which starts with an encryption algorithm (DER sequence).
            if (tag == (byte)DerSequenceReader.DerTag.Integer)
            {
                return(reader);
            }

            const byte ConstructedSequence =
                DerSequenceReader.ConstructedFlag | (byte)DerSequenceReader.DerTag.Sequence;

            if (tag == ConstructedSequence)
            {
                return(ReadEncryptedPkcs8Blob(ExportPassword, reader));
            }

            Debug.Fail($"Data was neither PrivateKey or EncryptedPrivateKey: {tag:X2}");
            throw new CryptographicException();
        }
            internal unsafe void Start()
            {
                // Make sure _fullPath doesn't contain a link or alias
                // since the OS will give back the actual, non link'd or alias'd paths
                _fullDirectory = Interop.Sys.RealPath(_fullDirectory);
                if (_fullDirectory == null)
                {
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                Debug.Assert(string.IsNullOrEmpty(_fullDirectory) == false, "Watch directory is null or empty");

                // Normalize the _fullDirectory path to have a trailing slash
                if (_fullDirectory[_fullDirectory.Length - 1] != '/')
                {
                    _fullDirectory += "/";
                }

                // Get the path to watch and verify we created the CFStringRef
                SafeCreateHandle path = Interop.CoreFoundation.CFStringCreateWithCString(_fullDirectory);

                if (path.IsInvalid)
                {
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Take the CFStringRef and put it into an array to pass to the EventStream
                SafeCreateHandle arrPaths = Interop.CoreFoundation.CFArrayCreate(new CFStringRef[1] {
                    path.DangerousGetHandle()
                }, (UIntPtr)1);

                if (arrPaths.IsInvalid)
                {
                    path.Dispose();
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Create the callback for the EventStream if it wasn't previously created for this instance.
                if (_callback == null)
                {
                    _callback = new Interop.EventStream.FSEventStreamCallback(FileSystemEventCallback);
                }

                // Make sure the OS file buffer(s) are fully flushed so we don't get events from cached I/O
                Interop.Sys.Sync();

                // Create the event stream for the path and tell the stream to watch for file system events.
                _eventStream = Interop.EventStream.FSEventStreamCreate(
                    _callback,
                    arrPaths,
                    Interop.EventStream.kFSEventStreamEventIdSinceNow,
                    0.0f,
                    EventStreamFlags);
                if (_eventStream.IsInvalid)
                {
                    arrPaths.Dispose();
                    path.Dispose();
                    throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), _fullDirectory, true);
                }

                // Create and start our watcher thread then wait for the thread to initialize and start
                // the RunLoop. We wait for that to prevent this function from returning before the RunLoop
                // has a chance to start so that any callers won't race with the background thread's initialization
                // and calling Stop, which would attempt to stop a RunLoop that hasn't started yet.
                var runLoopStarted = new ManualResetEventSlim();

                new Thread(WatchForFileSystemEventsThreadStart)
                {
                    IsBackground = true
                }.Start(runLoopStarted);
                runLoopStarted.Wait();
            }