Overriding TLS chain validation for self-signed certificates

basmbasm USMember ✭✭

I've been working on implementing self-signed certificate validation code in Xamarin.iOS as laid out in the Apple technical note "Overriding TLS Chain Validation Correctly". When I debug the code I can connect to my server with the self-signed certificate, but my C# version of the callback shown in "Listing 5" of the technical note is not fired. I don't know if this is due to a misconfiguration or something else. Any help or hints are appreciated.

My connection setup code is as follows.

public void Connect(string host, ushort port)
{
    // Create socket
    CFReadStream cfRead;
    CFWriteStream cfWrite;
    CFStream.CreatePairWithSocketToHost(host, port, out cfRead, out cfWrite);

    // Bind streams to NSInputStream/NSOutputStream
    NSInputStream inStream = Runtime.GetNSObject<NSInputStream>(cfRead.Handle);
    NSOutputStream outStream = Runtime.GetNSObject<NSOutputStream>(cfWrite.Handle);

    // Set SSL protocol
    inStream.SocketSecurityLevel = NSStreamSocketSecurityLevel.NegotiatedSsl;
    outStream.SocketSecurityLevel = NSStreamSocketSecurityLevel.NegotiatedSsl;

    // Set stream to not validate the certificate, we will do it in a callback
    // If callback doesn't fire, then any certificate will be accepted (not good)
    NSDictionary sslSettings = NSDictionary.FromObjectAndKey(NSNumber.FromBoolean(false), new NSString("kCFStreamSSLValidatesCertificateChain"));
    if (!CFReadStreamSetProperty(cfRead, new NSString("kCFStreamPropertySSLSettings"), sslSettings)) {
        throw new InvalidOperationException("Failed to set input stream properties to perform custom validation");
    }
    if (!CFWriteStreamSetProperty(cfWrite, new NSString("kCFStreamPropertySSLSettings"), sslSettings)) {
        throw new InvalidOperationException("Failed to set output stream properties to perform custom validation");
    }

    // Set callback for events, including for certificate validation, do not appear to be called
    inStream.Delegate = new CustomStreamDelegate();
    outStream.Delegate = new CustomStreamDelegate();

    // Set run loop (thread) for stream, just use current and default mode
    inStream.Schedule(NSRunLoop.Current, NSRunLoopMode.Default);
    outStream.Schedule(NSRunLoop.Current, NSRunLoopMode.Default);

    // Open the streams
    inStream.Open();
    outStream.Open();
}

The properties "kCFStreamSSLValidatesCertificateChain" to override the certificate chain validation doesn't appear to be exposed in Xamarin. So code is modified from Xamarin bug 31167 to set the properties. I'm fairly sure this works as the connection accepts any SSL certificate as expected of disabling the chain validation.

[DllImport(Constants.CoreFoundationLibrary, EntryPoint = "CFReadStreamSetProperty")]
[return: MarshalAs(UnmanagedType.I1)]
private static extern /* Boolean */ bool CFReadStreamSetPropertyExtern( /* CFReadStreamRef */
    IntPtr stream, /* CFStreamRef */ IntPtr propertyName, /* CFTypeRef */ IntPtr propertyValue);

private static bool CFReadStreamSetProperty(CFReadStream stream, NSString name, INativeObject value)
{
    return CFReadStreamSetPropertyExtern(stream.Handle, name.Handle, value == null ? IntPtr.Zero : value.Handle);
}

[DllImport(Constants.CoreFoundationLibrary, EntryPoint = "CFWriteStreamSetProperty")]
[return: MarshalAs(UnmanagedType.I1)]
private static extern /* Boolean */ bool CFWriteStreamSetPropertyExtern( /* CFWriteStreamRef */
    IntPtr stream, /* CFStreamRef */ IntPtr propertyName, /* CFTypeRef */ IntPtr propertyValue);

private static bool CFWriteStreamSetProperty(CFWriteStream stream, NSString name, INativeObject value)
{
    return CFWriteStreamSetPropertyExtern(stream.Handle, name.Handle, value == null ? IntPtr.Zero : value.Handle);
}

Finally the callback delegate in the custom NSStreamDelegate is as follows. I'm sure it's not called as breakpoints are not hit, any logging in the function gives no results, and all certificates are trusted so the custom validation doesn't occur.

// Delegate callback that is not being called    
public override void HandleEvent(NSStream theStream, NSStreamEvent streamEvent)
{
    // Only validate certificate when known to be connected
    if (streamEvent != NSStreamEvent.HasBytesAvailable && streamEvent != NSStreamEvent.HasSpaceAvailable) {
        return;
    }

    // Get trust object from stream
    SecTrust trust = Runtime.GetINativeObject<SecTrust>(theStream[new NSString("kCFStreamPropertySSLPeerTrust")].Handle, false);

    // Only add the certificate if it hasn't already been added
    NSNumber alreadyAdded = (NSNumber) theStream[new NSString("kAnchorAlreadyAdded")];
    if (!alreadyAdded.BoolValue) {
        // Add the custom certificate
        X509CertificateCollection collection = new X509CertificateCollection(new[] {v_Certificate});
        trust.SetAnchorCertificates(collection);

        // Allow (false) or disallow (true) all other already trusted certificates
        trust.SetAnchorCertificatesOnly(true);

        // Set that the certificate has been added
        theStream[new NSString("kAnchorAlreadyAdded")] = NSNumber.FromBoolean(true);
    }

    // Evaluate the trust policy, a result of Proceed or Unspecified indicates a trusted certificate
    SecTrustResult res = trust.Evaluate();
    if (res != SecTrustResult.Proceed && res != SecTrustResult.Unspecified) {
        // Not trusted, close the connection
        Disconnect();
    }
}

I know self signed certificates are not recommended and have many risks but it's a legacy system so my hands are tied.

Answers

  • basmbasm USMember ✭✭

    I was able to answer my own question with some more work. I've updated it on the corresponding Stack Overflow question (sorry can't post links yet so the question id is "48084903").

Sign In or Register to comment.