/*
 * UpdateOps.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Eagle._Attributes;
using Eagle._Components.Public;

namespace Eagle._Components.Private
{
    [ObjectId("711b1e60-8516-4f41-ba61-89c48f904d0a")]
    internal static class UpdateOps
    {
        #region Private Static Data
        private static readonly object syncRoot = new object();

        ///////////////////////////////////////////////////////////////////////

        #region Trusted Public Key Data
        //
        // NOTE: This public key is the one specifically reserved for updates
        //       to the core library.  It is logically constant and should not
        //       be changed.
        //
        private static readonly byte[] PublicKey1 =
            _Constants.PublicKey.SoftwareUpdate1;

        ///////////////////////////////////////////////////////////////////////

        //
        // NOTE: This public key is RESERVED for use by third-party plugins
        //       and applications; however, it is not public because it is
        //       not intended to be used lightly.
        //
        private static byte[] PublicKey2 = null;
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region ICertificatePolicy Support Data
#if !MONO
        private static bool useLegacyCertificatePolicy = false;
#endif

        ///////////////////////////////////////////////////////////////////////

        private static ICertificatePolicy savedCertificatePolicy;
        private static bool haveSavedCertificatePolicy;

        ///////////////////////////////////////////////////////////////////////

        private static readonly ICertificatePolicy certificatePolicy =
            new CertificatePolicy();
        #endregion
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Public Trust Support Methods
        public static bool IsTrusted()
        {
#if MONO
            return IsLegacyCertificatePolicyActive();
#else
            if (!ShouldUseLegacyCertificatePolicy())
                return IsServerCertificateValidationCallbackActive();
            else
                return IsLegacyCertificatePolicyActive();
#endif
        }

        ///////////////////////////////////////////////////////////////////////

        public static ReturnCode SetTrusted(
            bool trusted,
            ref Result error
            )
        {
            try
            {
#if !MONO
                if (!ShouldUseLegacyCertificatePolicy())
                {
                    //
                    // NOTE: When using the .NET Framework, use the newer
                    //       certification validation callback interface.
                    //
                    if (trusted)
                    {
                        ServicePointManager.ServerCertificateValidationCallback +=
                            RemoteCertificateValidationCallback;
                    }
                    else
                    {
                        ServicePointManager.ServerCertificateValidationCallback -=
                            RemoteCertificateValidationCallback;
                    }

                    TraceOps.DebugTrace(String.Format(
                        "{0}: SetTrusted: {1} " +
                        "RemoteCertificateValidationCallback{2}",
                        GlobalState.GetCurrentThreadId(), trusted ?
                        "added" : "removed", Environment.NewLine),
                        typeof(UpdateOps).Name);
                }
                else
#endif
                {
                    lock (syncRoot) /* TRANSACTIONAL */
                    {
                        //
                        // NOTE: When using Mono, fallback to the "obsolete"
                        //       CertificatePolicy property.
                        //
                        if (trusted)
                        {
                            //
                            // NOTE: Save the current certificate policy for
                            //       possible later restoration.
                            //
                            savedCertificatePolicy =
                                ServicePointManager.CertificatePolicy;

                            haveSavedCertificatePolicy = true;

                            //
                            // NOTE: Set the certificate policy to the one we
                            //       use for software updates.
                            //
                            ServicePointManager.CertificatePolicy =
                                certificatePolicy;
                        }
                        else
                        {
                            //
                            // NOTE: Restore the previously saved certificate
                            //       policy, if any.
                            //
                            if (haveSavedCertificatePolicy)
                            {
                                //
                                // NOTE: Restore the saved ICertificatePolicy.
                                //
                                ServicePointManager.CertificatePolicy =
                                    savedCertificatePolicy;

                                //
                                // NOTE: Clear the saved ICertificatePolicy.
                                //
                                savedCertificatePolicy = null;
                                haveSavedCertificatePolicy = false;
                            }
                        }
                    }

                    TraceOps.DebugTrace(String.Format(
                        "{0}: SetTrusted: {1} CertificatePolicy{2}",
                        GlobalState.GetCurrentThreadId(), trusted ?
                        "overridden" : "restored", Environment.NewLine),
                        typeof(UpdateOps).Name);
                }

                return ReturnCode.Ok;
            }
            catch (Exception e)
            {
                error = e;
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: SetTrusted: trusted = {1}, error = {2}{3}",
                GlobalState.GetCurrentThreadId(), trusted,
                FormatOps.WrapOrNull(error), Environment.NewLine),
                typeof(UpdateOps).Name);

            return ReturnCode.Error;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region RemoteCertificateValidationCallback Support Methods
#if !MONO
        private static bool IsServerCertificateValidationCallbackActive()
        {
            return (ServicePointManager.ServerCertificateValidationCallback ==
                RemoteCertificateValidationCallback);
        }

        ///////////////////////////////////////////////////////////////////////

        private static bool RemoteCertificateValidationCallback(
            object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors sslPolicyErrors
            )
        {
            if (sslPolicyErrors == SslPolicyErrors.None)
                return true;

            lock (syncRoot) /* TRANSACTIONAL */
            {
                //
                // NOTE: If this ServerCertificateValidationCallback is being
                //       called when it should not be active, then it's not
                //       supposed to be "always trusted" right now; therefore,
                //       just return false.
                //
                if (!IsServerCertificateValidationCallbackActive())
                    return false;

                return IsTrustedCertificate(certificate);
            }
        }
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region ICertificatePolicy Support Class & Methods
        #region ICertificatePolicy Support Class
        [ObjectId("4062e197-ed96-4db3-87e8-f463e5fb818b")]
        private sealed class CertificatePolicy : ICertificatePolicy
        {
            #region ICertificatePolicy Members
            public bool CheckValidationResult(
                ServicePoint srvPoint,
                X509Certificate certificate,
                WebRequest request,
                int certificateProblem
                )
            {
                if (certificateProblem == 0)
                    return true;

                lock (syncRoot) /* TRANSACTIONAL */
                {
                    //
                    // NOTE: If this ICertificatePolicy is being called when
                    //       it should not be active, then it's not supposed
                    //       to be "always trusted" right now; therefore,
                    //       just return false.
                    //
                    if (!IsLegacyCertificatePolicyActive())
                        return false;

                    return IsTrustedCertificate(certificate);
                }
            }
            #endregion
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region ICertificatePolicy Support Methods
        private static bool IsLegacyCertificatePolicyActive()
        {
            return Object.ReferenceEquals(
                ServicePointManager.CertificatePolicy, certificatePolicy);
        }

        ///////////////////////////////////////////////////////////////////////

#if !MONO
        private static bool ShouldUseLegacyCertificatePolicy()
        {
            lock (syncRoot)
            {
                return useLegacyCertificatePolicy ||
                    CommonOps.Runtime.IsMono();
            }
        }
#endif
        #endregion
        #endregion

        ///////////////////////////////////////////////////////////////////////

        #region Private Trusted Certificate Support Methods
        private static bool IsTrustedCertificate(
            X509Certificate certificate
            )
        {
            bool result = false;
            string name = null;

            //
            // NOTE: Make sure the certificate public key matches what
            //       we expect it to be for our own software updates.
            //
            if (certificate != null)
            {
                //
                // NOTE: Grab the public key of the certificate.
                //
                byte[] certificatePublicKey = certificate.GetPublicKey();

                if ((certificatePublicKey != null) &&
                    (certificatePublicKey.Length > 0))
                {
                    lock (syncRoot) /* TRANSACTIONAL */
                    {
                        //
                        // NOTE: Compare the public key of the certificate to
                        //       one that we trust for our software updates.
                        //
                        if ((PublicKey1 != null) && (PublicKey1.Length > 0) &&
                            ArrayOps.Equals(certificatePublicKey, PublicKey1))
                        {
                            name = "PublicKey1";
                            result = true;
                        }

                        //
                        // NOTE: Compare the public key of the certificate to
                        //       the auxiliary one that we trust for use by
                        //       third-party applications and plugins.
                        //
                        if ((PublicKey2 != null) && (PublicKey2.Length > 0) &&
                            ArrayOps.Equals(certificatePublicKey, PublicKey2))
                        {
                            name = "PublicKey2";
                            result = true;
                        }
                    }
                }
            }

            //
            // NOTE: Report this trust result to any trace listeners.
            //
            TraceOps.DebugTrace(String.Format(
                "{0}: IsTrustedCertificate: certificate = {1}, name = {2}, " +
                "result = {3}{4}", GlobalState.GetCurrentThreadId(),
                FormatOps.Certificate(certificate, false, true),
                FormatOps.WrapOrNull(name), result, Environment.NewLine),
                typeof(UpdateOps).Name);

            return result;
        }
        #endregion
    }
}
