Ejemplo n.º 1
0
        public SshAuthenticatedSession Authenticate(
            string username,
            ISshKey key,
            AuthenticationCallback callback)
        {
            this.session.Handle.CheckCurrentThreadOwnsHandle();
            Utilities.ThrowIfNullOrEmpty(username, nameof(username));
            Utilities.ThrowIfNull(key, nameof(key));
            Utilities.ThrowIfNull(callback, nameof(callback));

            Exception interactiveCallbackException = null;

            //
            // NB. The callbacks are sparsely documented in the libssh2 sources
            // and docs. For sample usage, the Guacamole sources can be helpful, cf.
            // https://github.com/stuntbadger/GuacamoleServer/blob/a06ae0743b0609cde0ceccc7ed136b0d71009105/src/common-ssh/ssh.c#L335
            //

            int Sign(
                IntPtr session,
                out IntPtr signaturePtr,
                out IntPtr signatureLength,
                IntPtr dataPtr,
                IntPtr dataLength,
                IntPtr context)
            {
                Debug.Assert(context == IntPtr.Zero);
                Debug.Assert(session == this.session.Handle.DangerousGetHandle());

                //
                // Copy data to managed buffer and create signature.
                //
                var data = new byte[dataLength.ToInt32()];

                Marshal.Copy(dataPtr, data, 0, data.Length);

                var signature = key.SignData(data);

                //
                // Copy data back to a buffer that libssh2 can free using
                // the allocator specified in libssh2_session_init_ex.
                //

                signatureLength = new IntPtr(signature.Length);
                signaturePtr    = SshSession.Alloc(signatureLength, IntPtr.Zero);
                Marshal.Copy(signature, 0, signaturePtr, signature.Length);

                return((int)LIBSSH2_ERROR.NONE);
            }

            void InteractiveCallback(
                IntPtr namePtr,
                int nameLength,
                IntPtr instructionPtr,
                int instructionLength,
                int numPrompts,
                IntPtr promptsPtr,
                IntPtr responsesPtr,
                IntPtr context)
            {
                var name = UnsafeNativeMethods.PtrToString(
                    namePtr,
                    nameLength,
                    Encoding.UTF8);
                var instruction = UnsafeNativeMethods.PtrToString(
                    instructionPtr,
                    nameLength,
                    Encoding.UTF8);
                var prompts = UnsafeNativeMethods.PtrToStructureArray <
                    UnsafeNativeMethods.LIBSSH2_USERAUTH_KBDINT_PROMPT>(
                    promptsPtr,
                    numPrompts);

                //
                // NB. libssh2 allocates the responses structure for us, but frees
                // the embedded text strings using its allocator.
                //
                // NB. libssh2 assumes text to be encoded in UTF-8.
                //
                Debug.Assert(SshSession.Alloc != null);

                var responses = new UnsafeNativeMethods.LIBSSH2_USERAUTH_KBDINT_RESPONSE[prompts.Length];

                for (int i = 0; i < prompts.Length; i++)
                {
                    var promptText = UnsafeNativeMethods.PtrToString(
                        prompts[i].TextPtr,
                        prompts[i].TextLength,
                        Encoding.UTF8);

                    SshTraceSources.Default.TraceVerbose("Keyboard/interactive prompt: {0}", promptText);

                    //
                    // NB. Name and instruction aren't used by OS Login,
                    // so flatten the structure to a single level.
                    //
                    string responseText = null;
                    try
                    {
                        responseText = callback(
                            name,
                            instruction,
                            promptText,
                            prompts[i].Echo != 0);
                    }
                    catch (Exception e)
                    {
                        SshTraceSources.Default.TraceError(
                            "Authentication callback threw exception", e);

                        //
                        // Don't let the exception escape into unmanaged code,
                        // instead return null and let the enclosing method
                        // rethrow the exception once we're back on a managed
                        // callstack.
                        //
                        interactiveCallbackException = e;
                    }

                    responses[i] = new UnsafeNativeMethods.LIBSSH2_USERAUTH_KBDINT_RESPONSE();
                    if (responseText == null)
                    {
                        responses[i].TextLength = 0;
                        responses[i].TextPtr    = IntPtr.Zero;
                    }
                    else
                    {
                        var responseTextBytes = Encoding.UTF8.GetBytes(responseText);
                        responses[i].TextLength = responseTextBytes.Length;
                        responses[i].TextPtr    = SshSession.Alloc(
                            new IntPtr(responseTextBytes.Length),
                            IntPtr.Zero);
                        Marshal.Copy(
                            responseTextBytes,
                            0,
                            responses[i].TextPtr,
                            responseTextBytes.Length);
                    }
                }

                UnsafeNativeMethods.StructureArrayToPtr(
                    responsesPtr,
                    responses);
            }

            using (SshTraceSources.Default.TraceMethod().WithParameters(username))
            {
                //
                // NB. The public key must be passed in OpenSSH format, not PEM.
                // cf. https://tools.ietf.org/html/rfc4253#section-6.6
                //
                var publicKey = key.PublicKey;

                var result = (LIBSSH2_ERROR)UnsafeNativeMethods.libssh2_userauth_publickey(
                    this.session.Handle,
                    username,
                    key.PublicKey,
                    new IntPtr(publicKey.Length),
                    Sign,
                    IntPtr.Zero);

                if (result == LIBSSH2_ERROR.PUBLICKEY_UNVERIFIED)
                {
                    SshTraceSources.Default.TraceVerbose(
                        "Server responded that public key is unverified, " +
                        "trying keyboard/interactive auth for MFA challenges");

                    //
                    // Public key wasn't accepted - this might be because the
                    // key is not authorized, or because we need to respond to
                    // some more (MFA) challenges.
                    //
                    // NB. It's not worth checking GetAuthenticationMethods, it
                    // won't indicate whether additional challenges are expected
                    // or not.
                    //

                    //
                    // Temporarily change the timeout since we must give the
                    // user some time to react.
                    //
                    var originalTimeout = this.session.Timeout;
                    this.session.Timeout = this.session.KeyboardInteractivePromptTimeout;
                    try
                    {
                        //
                        // Retry to account for wrong user input.
                        //
                        for (int retry = 0; retry < KeyboardInteractiveRetries; retry++)
                        {
                            result = (LIBSSH2_ERROR)UnsafeNativeMethods.libssh2_userauth_keyboard_interactive_ex(
                                this.session.Handle,
                                username,
                                username.Length,
                                InteractiveCallback,
                                IntPtr.Zero);

                            if (result == LIBSSH2_ERROR.NONE)
                            {
                                break;
                            }
                            else if (interactiveCallbackException != null)
                            {
                                //
                                // Restore exception thrown in callback.
                                //
                                throw interactiveCallbackException;
                            }
                        }
                    }
                    finally
                    {
                        //
                        // Restore timeout.
                        //
                        this.session.Timeout = originalTimeout;
                    }
                }

                if (result != LIBSSH2_ERROR.NONE)
                {
                    throw this.session.CreateException(result);
                }
                else
                {
                    return(new SshAuthenticatedSession(this.session));
                }
            }
        }
Ejemplo n.º 2
0
        public SshAuthenticatedSession Authenticate(
            string username,
            ISshKey key)
        {
            this.session.Handle.CheckCurrentThreadOwnsHandle();
            Utilities.ThrowIfNullOrEmpty(username, nameof(username));
            Utilities.ThrowIfNull(key, nameof(key));

            //
            // NB. The callbacks very sparsely documented in the libssh2 sources
            // and docs. For sample usage, the Guacamole sources can be helpful, cf.
            // https://github.com/stuntbadger/GuacamoleServer/blob/a06ae0743b0609cde0ceccc7ed136b0d71009105/src/common-ssh/ssh.c#L335
            //

            int Sign(
                IntPtr session,
                out IntPtr signaturePtr,
                out IntPtr signatureLength,
                IntPtr dataPtr,
                IntPtr dataLength,
                IntPtr context)
            {
                Debug.Assert(context == IntPtr.Zero);
                Debug.Assert(session == this.session.Handle.DangerousGetHandle());

                //
                // Copy data to managed buffer and create signature.
                //
                var data = new byte[dataLength.ToInt32()];

                Marshal.Copy(dataPtr, data, 0, data.Length);

                var signature = key.SignData(data);

                //
                // Copy data back to a buffer that libssh2 can free using
                // the allocator specified in libssh2_session_init_ex.
                //

                signatureLength = new IntPtr(signature.Length);
                signaturePtr    = SshSession.Alloc(signatureLength, IntPtr.Zero);
                Marshal.Copy(signature, 0, signaturePtr, signature.Length);

                return((int)LIBSSH2_ERROR.NONE);
            }

            using (SshTraceSources.Default.TraceMethod().WithParameters(username))
            {
                //
                // NB. The public key must be passed in OpenSSH format, not PEM.
                // cf. https://tools.ietf.org/html/rfc4253#section-6.6
                //
                var publicKey = key.PublicKey;

                var result = (LIBSSH2_ERROR)UnsafeNativeMethods.libssh2_userauth_publickey(
                    this.session.Handle,
                    username,
                    key.PublicKey,
                    new IntPtr(publicKey.Length),
                    Sign,
                    IntPtr.Zero);

                if (result != LIBSSH2_ERROR.NONE)
                {
                    throw this.session.CreateException(result);
                }
                else
                {
                    return(new SshAuthenticatedSession(this.session));
                }
            }
        }