/*
 * SecurityOps.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.Collections;
using System.Net.Security;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Security.Policy;
using System.Security.Principal;

namespace Eagle._Components.Private
{
    [Guid("aa944fc9-6286-443b-bb3c-61fa70cc56a7")]
    internal static class SecurityOps
    {
        #region Private Security Data
        [Guid("99639a18-01b9-4dc0-b3de-05aa9609709a")]
        private static class PublicKeyToken
        {
            internal static readonly byte[] Default = {
                0x29, 0xc6, 0x29, 0x76, 0x30, 0xbe, 0x05, 0xeb
            };
        }

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

        [Guid("a08f8b96-f714-4bf1-8efa-e410001bf8c5")]
        private static class PublicKey
        {
            //
            // NOTE: All builds (whether public or private) of the Eagle
            //       runtime library WILL trust and use this key for
            //       checking for and/or installing updates to itself.
            //
            //       This key MAY also be used to sign release builds.
            //
            internal static readonly byte[] Default = {
                0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01,
                0x00, 0xd1, 0x21, 0x9f, 0xbe, 0x43, 0x9d, 0xb6,
                0xc7, 0x65, 0xe6, 0xb4, 0x54, 0x35, 0xd1, 0x70,
                0xec, 0x62, 0x6c, 0x20, 0xc2, 0xaa, 0x42, 0x30,
                0xcd, 0x1e, 0x0e, 0x6f, 0x98, 0xf9, 0x08, 0x05,
                0x41, 0x2a, 0x29, 0x13, 0xc0, 0xb4, 0x9b, 0x47,
                0x42, 0xe3, 0x6e, 0xfc, 0x6d, 0x6d, 0x6e, 0xbb,
                0x56, 0x23, 0xda, 0xaa, 0x39, 0xeb, 0x83, 0x36,
                0x95, 0xd6, 0x66, 0x81, 0xed, 0x0c, 0x3a, 0xd0,
                0x53, 0x62, 0x09, 0xb6, 0x1c, 0x8b, 0xbd, 0x74,
                0x07, 0xfc, 0x2d, 0x88, 0x8f, 0x0f, 0x46, 0x62,
                0x19, 0xa3, 0x69, 0x7b, 0x52, 0x57, 0xbe, 0x32,
                0x64, 0xae, 0xd2, 0xef, 0xb9, 0x32, 0x85, 0x31,
                0x46, 0x2e, 0xb5, 0x77, 0x18, 0x62, 0x69, 0xf0,
                0xd2, 0xd0, 0xe0, 0x7a, 0x1a, 0x08, 0xc1, 0x9d,
                0x07, 0xe4, 0x70, 0x08, 0xf3, 0xdd, 0xa5, 0xa5,
                0x38, 0x70, 0xfc, 0xce, 0xaa, 0xc1, 0x80, 0x41,
                0x1a, 0x64, 0xe9, 0xa0, 0x1d, 0x1c, 0x55, 0x30,
                0xea, 0x18, 0xee, 0x44, 0x0f, 0xdc, 0x5a, 0xcd,
                0x00, 0xc5, 0xc3, 0x9b, 0x76, 0x95, 0x87, 0x32,
                0x0d, 0x7d, 0x00, 0x72, 0x53, 0x64, 0x38, 0xde,
                0xc2, 0x01, 0x58, 0x02, 0xe3, 0x50, 0x3e, 0xf1,
                0xb3, 0x1b, 0x89, 0x01, 0x05, 0x40, 0x50, 0xe4,
                0x5d, 0xef, 0x14, 0xb4, 0xf3, 0x89, 0x52, 0x09,
                0x46, 0x74, 0xb3, 0x49, 0x33, 0x37, 0x41, 0x50,
                0xef, 0x18, 0x77, 0x0f, 0x15, 0x93, 0x78, 0xdd,
                0xda, 0x9a, 0xbb, 0x73, 0xfe, 0xa9, 0xa8, 0xe1,
                0x31, 0xd8, 0xf8, 0xbe, 0x27, 0xf1, 0xcd, 0x7b,
                0x1d, 0xb7, 0xd1, 0x9a, 0xfb, 0xdc, 0xc1, 0xa2,
                0x5f, 0x68, 0x9a, 0x54, 0x28, 0x19, 0x55, 0x36,
                0xd3, 0xdd, 0x1b, 0xb9, 0x10, 0x8e, 0x8e, 0x88,
                0xaa, 0xe5, 0x55, 0x4b, 0x42, 0xf9, 0x14, 0x8c,
                0xbd, 0x43, 0xd4, 0x32, 0x8a, 0xe6, 0x70, 0x0a,
                0xe7, 0x02, 0x03, 0x01, 0x00, 0x01
            };
        }
        #endregion

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

        #region Administrator Support Methods
        public static bool IsAdministrator()
        {
            try
            {
                //
                // BUGBUG: This may not work correctly on Mono.
                //
                WindowsIdentity identity = WindowsIdentity.GetCurrent();

                if (identity == null)
                    return false;

                WindowsPrincipal principal = new WindowsPrincipal(identity);

                return principal.IsInRole(WindowsBuiltInRole.Administrator);
            }
            catch (Exception)
            {
                // do nothing.
            }

            return false;
        }
        #endregion

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

        #region Signature Checking Methods
        public static bool IsAuthenticodeSigned(
            Assembly assembly,
            string subject,
            bool verify,
            ref X509Certificate2 certificate2,
            ref string error
            )
        {
#if !DEBUG
            if (assembly != null)
            {
                return IsAuthenticodeSigned(
                    assembly.Location, subject, verify,
                    ref certificate2, ref error);
            }

            return false;
#else
            //
            // NOTE: In-development version, fake it.  We can do this
            //       because DEBUG builds are never officially released.
            //
            return true;
#endif
        }

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

        public static bool IsAuthenticodeSigned(
            string fileName,
            string subject,
            bool verify,
            ref X509Certificate2 certificate2,
            ref string error
            )
        {
            try
            {
                X509Certificate certificate =
                    X509Certificate.CreateFromSignedFile(fileName);

                if (certificate != null)
                {
                    certificate2 = new X509Certificate2(certificate);

                    if (certificate2 != null)
                    {
                        if ((subject == null) ||
                            String.Equals(certificate2.Subject,
                                subject, StringComparison.Ordinal) ||
                            String.Equals(certificate2.GetNameInfo(
                                X509NameType.SimpleName, false),
                                subject, StringComparison.Ordinal))
                        {
                            if (verify)
                                return certificate2.Verify();
                            else
                                return true;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                error = e.ToString();
            }

            return false;
        }

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

        public static bool IsDefaultPublicKeyToken(
            byte[] publicKeyToken
            )
        {
            if (publicKeyToken == null)
                return true;

            byte[] defaultPublicKeyToken = PublicKeyToken.Default;

            if (defaultPublicKeyToken == null)
                return false;

            if (GenericOps<byte>.Equals(publicKeyToken, defaultPublicKeyToken))
                return true;

            return false;
        }

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

        public static bool IsStrongNameSigned(
            Assembly assembly,
            ref byte[] publicKeyToken,
            ref string error
            )
        {
            if (assembly == null)
            {
                error = "assembly is invalid";
                return false;
            }

            AssemblyName assemblyName = assembly.GetName();

            if (assemblyName == null)
            {
                error = "assembly has invalid name";
                return false;
            }

            byte[] assemblyPublicKey = assemblyName.GetPublicKey();

            if (assemblyPublicKey == null)
            {
                error = "assembly has invalid public key";
                return false;
            }

            if (assembly.Evidence == null)
            {
                error = "assembly has invalid evidence";
                return false;
            }

            IEnumerator enumerator =
                assembly.Evidence.GetHostEnumerator();

            if (enumerator == null)
            {
                error = "assembly has invalid evidence enumerator";
                return false;
            }

            while (enumerator.MoveNext())
            {
                StrongName strongName =
                    enumerator.Current as StrongName;

                if (strongName == null)
                    continue;

                if (GenericOps<byte>.Equals(
                        ParseOps.HexString(
                            strongName.PublicKey.ToString()),
                        assemblyPublicKey))
                {
                    publicKeyToken =
                        assemblyName.GetPublicKeyToken();

                    if (publicKeyToken == null)
                    {
                        error = "assembly has invalid public key token";
                        return false;
                    }

                    return true;
                }
            }

            error = "assembly is not signed";
            return false;
        }
        #endregion

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

        #region Software Update Trust Methods
        public static bool RemoteCertificateValidationCallback(
            object sender,
            X509Certificate certificate,
            X509Chain chain,
            SslPolicyErrors sslPolicyErrors
            )
        {
            return (sslPolicyErrors == SslPolicyErrors.None) ||
                MatchCertificate(certificate, PublicKey.Default);
        }

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

        private static bool MatchCertificate(
            X509Certificate certificate,
            byte[] publicKey
            )
        {
            //
            // NOTE: Make sure the certificate public key matches what
            //       we expect it to be for our own software updates.
            //
            if (certificate != null)
            {
                byte[] certificatePublicKey = certificate.GetPublicKey();

                return GenericOps<byte>.Equals(certificatePublicKey,
                    publicKey);
            }

            return false;
        }
        #endregion
    }
}
