1
votes

I am trying to call an external webservice in visual studio but I am getting the error.

System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.SecurityPermission, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' failed. System.Security.SecurityException: at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet) at System.Security.CodeAccessPermission.Demand() at System.Net.ServicePointManager.set_ServerCertificateValidationCallback(RemoteCertificateValidationCallback value)

The following is the program to invoke the webservice.

public partial class UserDefinedFunctions
{
    [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read)]
    [return: SqlFacet(MaxSize = -1)]
    public static SqlString NYP_RestGet(SqlString uri)
    {
        String document;
        System.Net.ServicePointManager.ServerCertificateValidationCallback +=
        delegate(object sender, System.Security.Cryptography.X509Certificates.X509Certificate certificate,
                                System.Security.Cryptography.X509Certificates.X509Chain chain,
                                System.Net.Security.SslPolicyErrors sslPolicyErrors)
        {
            return true; // **** Always accept
        };

        // Set up the request, including authentication
        WebRequest req = WebRequest.Create(Convert.ToString(uri));
        ((HttpWebRequest)req).UserAgent = "CLR web client on SQL Server";
        req.ContentType = "application/xml";
        ((HttpWebRequest)req).Accept = "application/xml";

        WebResponse resp = req.GetResponse();
        Stream dataStream = resp.GetResponseStream();
        StreamReader rdr = new StreamReader(dataStream);
        document = (String)rdr.ReadToEnd();


        rdr.Close();
        dataStream.Close();
        resp.Close();
         return (document);
    }
};
1

1 Answers

0
votes

For network-related requests by themselves you would need to set your Assembly to PERMISSION_SET = EXTERNAL_ACCESS. However, using System.Net.ServicePointManager.ServerCertificateValidationCallback unfortunately requires PERMISSION_SET = UNSAFE. If you do not expressly need to override the handling of SSL Certificates, then you should get rid of that delegate since it would be better for the Assembly to be set to EXTERNAL_ACCESS.

Unfortunately, Visual Studio / SSDT (SQL Server Data Tools) does not make it very easy to take the appropriate steps needed to allow your Assembly to be set to either EXTERNAL_ACCESS or UNSAFE. They do, however, make it easy enough to set the TRUSTWORTHY option to ON, which is mostly a bad idea.

Please do not set TRUSTWORTHY ON unless absolutely necessary! And it should only be "necessary" when loading an Assembly that you did not build and cannot re-sign. And that mostly happens when loading .NET Framework libraries that aren't "supported" and hence not already in SQL Server's CLR host. Outside of those circumstances, you should not be setting the database to TRUSTWORTHY ON as it opens up a security hole.

Instead, it is much better to do the following:

USE [master];

CREATE ASYMMETRIC KEY [SomeKey]
  AUTHORIZATION [dbo]
  FROM EXECUTABLE FILE = 'C:\path\to\Some.dll';

CREATE LOGIN [SomeLogin]
  FROM ASYMMETRIC KEY [SomeKey];

GRANT EXTERNAL ACCESS ASSEMBLY TO [SomeLogin]; -- or "UNSAFE" instead of "EXTERNAL ACCESS"

The above only needs to be done once per Instance, per key. So if you use the same snk / pfx file for all of your assemblies, then the steps shown above only need to be done once per SQL Server Instance; the number of Assemblies and databases containing those Assemblies does not matter.

This approach allows you to keep better security on the database (by keeping TRUSTWORTHY set to OFF) and allows for more granular control of which assemblies are even allowed to be set to EXTERNAL_ACCESS and/or UNSAFE (since you can separate by using different keys for signing and Logins based on those different keys).

For a more detailed walk-through of the security options, please see the following article that I wrote on SQL Server Central: Stairway to SQLCLR Level 4: Security (EXTERNAL and UNSAFE Assemblies) (free registration is required).


Other notes:

  1. For more notes regarding calling Web Services and web pages in general via SQLCLR, please see my answer here: Call web service from SQL CLR?
  2. There is no need to use Convert.ToString(uri). All of the Sql* types have a .Value property that returns the appropriate native type. So replace that with uri.Value.
  3. You do not need DataAccess = DataAccessKind.Read in the SqlFunction attribute since you are not doing any data access. Setting DataAccess = DataAccessKind.Read is a slight performance hit, so since you are not making use of it, just remove it.
  4. dataStream and rdr (being a Stream and StreamReader respectively) are "disposable" objects, so you really need to be calling .Dispose() on them, which you can probably do instead of the .Close().
  5. You do not have any error handling, which means that if there is an error prior to the Dispose method being called on both of those objects, your process could hold open the external network handle, and that might not get released until the App Domain recycles which might not be for a long time. You need to either use the using construct or properly structure a try / catch / finally. This is rather dangerous code to be running within SQL Server without doing one of these two things.
  6. For more details on the various nuances of working with SQL Server's CLR host, please see my article: Stairway to SQLCLR Level 5: Development (Using .NET within SQL Server) (free registration is required).