dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.19k stars 1.57k forks source link

`SecureSocket.connect` throws `HandshakeException: WRONG_VERSION_NUMBER` #52886

Open rubenferreira97 opened 1 year ago

rubenferreira97 commented 1 year ago

I am using Dart on the server and trying to send an email by connecting and authenticating to a SMTP server using TLS (let's say mail.myserver.com on port 587) with the package mailer. After setting everything up correctly (as far as I know), a HandshakeException is thrown here.

After some debugging and attempting to isolate the problem, I wrote the following script that simply connects to a secure host:

import 'dart:io';

void main() async {
  try {
    // Executes successfully and prints 'Connected!':
    // await SecureSocket.connect('mail.myserver.com', 465, timeout: Duration(seconds: 3));
    // await SecureSocket.connect('smtp.gmail.com', 465, timeout: Duration(seconds: 3));
    // await SecureSocket.connect('google.com', 443, timeout: Duration(seconds: 3));

    // Fails with a `HandshakeException`
    await SecureSocket.connect('smtp.gmail.com', 587, timeout: Duration(seconds: 3));
    // await SecureSocket.connect('mail.myserver.com', 587, timeout: Duration(seconds: 3));
    print('Connected!');
  } catch (e) {
    print('$e');
  }
}

The following error is thrown:

HandshakeException: Handshake error in client (OS Error: 
    WRONG_VERSION_NUMBER(../../third_party/boringssl/src/ssl/tls_record.cc:242))

Changing the port from 587 to 465 prints Connected!.


For testing purposes, I tried the following commands on the same machine where I executed the previous Dart script:

Using the telnet smtp.gmail.com 587 command in cmd (Windows) successfully connects and outputs:

220 smtp.gmail.com ESMTP t25-20020a1c7719000000b003fbfef555d2sm4374952wmi.23 - gsmtp

Using the telnet smtp.gmail.com 465 and telnet google.com 443 commands also connect successfully.

Is this a bug or am I missing something?

busslina commented 3 months ago

Same error here using mailer.

@rubenferreira97 as you closed the issue as completed, can you share the solution you found?

I was thinking that maybe Dart doesn't support the latest TLS protocols. Or maybe Dart delegates it to the OS... but still is weird because I'm testing it on a fresh latest Raspberry OS (Debian 12).

UPDATE I:

It is really strange. I tested with https://www.checktls.com/TestReceiver and my SMTP server seems to work fine: imagen

The error that I get on the SMTP server (Postfix) when executin Dart's mailer is:

postfix/smtpd[12656]: connect from [...]
postfix/smtpd[12656]: improper command pipelining after CONNECT from [...]
postfix/smtpd[12656]: lost connection after CONNECT from [...]
postfix/smtpd[12656]: disconnect from [...]

UPDATE II:

This simple Dart code fails with the same output so I'm pretty sure that Dart is not supporting latest version of TLS, as a simple openssl command works...

import 'dart:io';

void main(List<String> arguments) async {
  final socket = await SecureSocket.connect('example.com', 587);
  await Future.delayed(const Duration(seconds: 10));
  await socket.close();
}
openssl s_client -starttls smtp -connect example.com:587

UPDATE III:

Executing plain openssl without -starttls smtp gives me the same error so this changes my mind:

openssl s_client -connect example.com:587
rubenferreira97 commented 3 months ago

Hey @busslina I will be honest, I forgot how I solved the problem. I am almost sure that it was a problem on my end. I am looking at the old code and this is working for me:

import 'package:mailer/mailer.dart';
import 'package:mailer/smtp_server.dart';

void main() async {
  final address = Address('youremail@email.com', 'YourEmailName');

  final smtpServer = SmtpServer(
    'yourhost',
    port: 587,
    name: address.name,
    username: address.mailAddress,
    password: 'yourpassword',
  );

  final message = Message()
    ..from = address
    ..recipients.addAll(['to@tempmail.com'])
    ..subject = 'Test'
    ..text = 'Test'
    ..html = 'Test';

  await send(message, smtpServer);
}

What's your dart version? I don't know if it's the case but sometimes exceptions are reported by the IDE but they are handled internally (if you use VSCode check if All Exceptions is off).

busslina commented 3 months ago

@rubenferreira97 thanks.

I updated my previous message.

What's your dart version?

3.3.3 (not latest but close)

VSCode.

I'm not executing it with VSCode.

As I posted in my previous message: It's really strange that a simple openssl command opens TLS connection without error and analogous code in Dart gives exactly the same error that Dart's mailer code.

If you solved it, maybe you changed your SMTP server configuration to allow lower TLS versions.

busslina commented 3 months ago

@rubenferreira97

In your code seems that your solution was to disable TLS, as it's disabled by default on mailer.

imagen

So this could be a mailer issue. Gonna report it.

rubenferreira97 commented 3 months ago

@busslina I reopened the issue for further investigation. It seems the default options are all good (allowInsecure is false so it shouldn't allow insecure connections. ignoreBadCertificate is also false). As far as I recall TLS is an improved version of SSL so they are different.

busslina commented 3 months ago

@rubenferreira97 Thanks

You can see here that what matters to select between SecureSocket and Socket is the ssl parameter... so you are sending emails over an insecure connection.

rubenferreira97 commented 3 months ago

You are right! I was debugging and isSecure returns false.

image

close2 commented 3 months ago

TL;DR: Don't use ssl. mailer will automatically upgrade the connection to an encrypted connection using the smtp command starttls. If the server does not support tls mailer will either continue over an insecure connection if the option allowInsecure is given (off by default) or fail otherwise.

There are 2 ways to send mails over secure connections:

In your screenshot we are waiting for the response to get list of capabilities. That's also the reason for the 2 different exceptions. If we don't get a response at all, it is possible, that we should have used an ssl connection and the server is waiting for commands to initiate a secure ssl connection.

The openssl command shows that we should use starttls. If the -starttls smtp option is given, openssl will connect over an unencrypted connection and then send the starttls command, upgrading the connection to an encrypted connection.

rubenferreira97 commented 3 months ago

@busslina ☝️ Now that I remember, this was the conclusion that I got. At the time, I inspected/debugged more careful and checked the logs of my SMTP server. It was indeed upgraded to a secure connection.

busslina commented 3 months ago

Thanks both :)