Coding with Titans

so breaking things happens constantly, but never on purpose

WebRequest, HTTPS a Mono.NET i SendFailure

Od jakiegoś czasu staram się rozwijać własną bibliotekę implementującą kliencką część protokołu Bayeux (używającego poniżej formatu JSON). Jest tam jeszcze oczywiście dużo innych rzeczy, ale te są na razie najważniejsze.

Zabawa idzie mi całkiem dobrze i z wyników jestem bardziej niż zadowolony. Jednak z racji tego, że za cel powziąłem sobie współpracę z każdą dostępna platformą .NET, natrafiłem coś zupełnie nieoczekiwanego. O ile przemilczę fakt, że każdy z .NET frameworków (tj. ten z komputera stacjonarnego desktop, Compact Framework, Mono oraz Silverlight dla Windows Phone 7), udostępnia inne API, które nie zawsze są ze sobą zgodne (bo te albo pokazują inne właściwości, albo wręcz brakuje synchronicznych wywołań). To o tyle nie sposób przejść obojętnie obok pewnego problemu, który zmarnował mi trochę czasu ostatnio. Ten sam kod do wymiany danych z użyciem HttpWebRequest, który działa poprawnie na Microsoft .NET Framework nie zadziała na Mono .NET na Linuxie.

Otóż w scenariuszu, w którym łączymy się przy użyciu HTTPS ze znanym z góry serwerem, wszystko pięknie działa na stacjonarnym Windows 7 oraz na Windows Phone 7. Jednak przy testach na Mono 2.8, przy próbie pisania do strumienia sieciowego otrzymujemy wyjątek ze statusem “SendFailure”. Prócz enigmatycznego komunikatu, nie mamy żadnego konkretnego punktu zaczepienia.

The authentication or decryption has failed.

Albo też nagle i niespodziewanie okazuje się, że coś, ale już nie nasz kod, zużywa 100% mocy procesora, 1GB RAMu oraz z wirtualnej maszyny Mono nie ma co zbierać.

Odpowiedzialnym za ten stan rzeczy jest sposób obsługi certyfikatów SSL. Microsoft .NET Framework akceptuje je wszystkie bez wyjątków i pozwala na komunikację. Mono natomiast jest bardziej restrykcyjne i wymaga, aby przed rozpoczęciem wysyłania, zaakceptować certyfikat serwera ręcznie. Jest to tym ciekawsze, że musi być zrobione nawet w sytuacji, gdy mamy zaufany certyfikat, podpisany przez zaufanego dostawcę.

Wystarczy zatem umieścić w swoim kodzie poniższy fragment, aby wszystko zaczęło działać zgodnie z oczekiwaniami:

ServicePointManager.ServerCertificateValidationCallback += CertificateValidationCallback;

public bool CertificateValidationCallback (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
    // tutaj należy wstawić logikę sprawdzania certyfikatu!
    return true;
}