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.3k stars 1.59k forks source link

TlsException in Http/2 based Apple push notification service #26954

Closed jerrywell closed 8 years ago

jerrywell commented 8 years ago

Hi, our server is using apple push notification service (APNs) with dart Http/2 package. Before sending a http2 create connection, i set the encoded certificate into SecurityContext. This step sometime fail with the following exception. do you have any idea about it. thank you very much.

dart version: 1.17.1 http 0.11.3+9 http2 0.1.1+1

Cause: TlsException: Failure in useCertificateChainBytes (OS Error: errno = 0)
#0      _SecurityContext.useCertificateChainBytes (dart:io-patch/secure_socket_patch.dart:163)
#1      APNProvider.APNProvider (package:server/src/push/apple_push.dart:185:14)
#2      _applePush.<_applePush_async_body> (package:server/src/push/apple_push.dart:147:24)
#3      Future.Future.microtask.<anonymous closure> (dart:async/future.dart:144)
#4      _microtaskLoop (dart:async/schedule_microtask.dart:41)
#5      _startMicrotaskLoop (dart:async/schedule_microtask.dart:50)
#6      _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:96)
#7      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:149)
zoechi commented 8 years ago

Can you please post some code that demonstrates what you're doing?

jerrywell commented 8 years ago

Thanks for reply, @zoechi :) Here are the part of code.

Our server create the long running connection to APN server as a client. And send notification payload to apple for generating push notification on user's iOS device.

import 'dart:io';
import 'package:http2/transport.dart';

const host = '';
const port = '';
const deviceToken = '';
const BUNDLE_ID = '';

var _payload = '';
var _utf8Certificate = UTF8.encode('');
var _utf8Key = UTF8.encode('');

// create context for establishing socket later
SecurityContext _context = SecurityContext.defaultContext;
_context.useCertificateChainBytes(_utf8Certificate); // PlsException sometime happened here
_context.usePrivateKeyBytes(_utf8Key);

// establish socket and create connection
SecureSocket _secureSocket = await SecureSocket.connect(host, port, context: _context);
ClientTransportConnection _transport = new ClientTransportConnection.viaSocket(_secureSocket);

// send request
var headers = [
  new Header.ascii(':method', 'POST'),
  new Header.ascii(':path', '/3/device/$deviceToken'),
  new Header.ascii(':scheme', 'https'),
  new Header.ascii('apns-topic', BUNDLE_ID)
];
_transport.makeRequest(headers).sendData(UTF8.encode(JSON.encode(payload)), endStream: true);

// get data and close connection
kevmoo commented 8 years ago

@mkustermann @sgjesse Issue w/ core SDK or pkg/http2?

mkustermann commented 8 years ago

This could be an issue with the BoringSSL implementation (and seems to have nothing to do with package:http2).

@jerrywell

If you say "TlsException sometimes happened here", does that mean that you sometimes get an exception and sometimes not using the same client certificate (i.e. you pass the same _utf8Certificate and _utf8Key) or does it depend on which client certificate you use?

Does it happen immediately or does it happen after making many connections?

What operating system do you use?

It would be great if you could provide us with an example client certificate which causes this issue.

@whesse Could you look at this?

jerrywell commented 8 years ago

First, Thanks all guys' help :)

For the question:

If you say "TlsException sometimes happened here", does that mean that you sometimes get an exception and sometimes not using the same client certificate (i.e. you pass the same _utf8Certificate and _utf8Key) or does it depend on which client certificate you use?

We use the same certificate and key.

I was trying more to reproduce this case. I found the timing of exception happened is not "sometimes" (sorry, :( wrong information... ). The right timing is first time of connection initialization since the dart app get started. Following is a main.dart file, which can reproduce the issue.

I removed the security related keywords. It won't send a acceptable payload to apple. But you can still run this file to reproduce the PlsException.

BTW. The exception WONT happened in (10.11.5 - 15F34) Mac's dart VM. The reproduced environment is in Linux (Ubuntu 14.04.4 LTS).

// main.dart
import 'dart:convert';
import 'dart:io';
import "dart:async";

import 'package:http2/transport.dart';

final List<int> _UTF8_PLS_CERTIFICATE = UTF8.encode('''
-----BEGIN CERTIFICATE-----
MIIFfDCCBGSgAwIBAgIIJnBuyqb15ccwDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNV
BAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3Js
ZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3
aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkw
HhcNMTYwNjI3MDU1ODUwWhcNMTcwNjI3MDU1ODUwWjB8MRwwGgYKCZImiZPyLGQB
AQwMaW8ucXVpcmUuYXBwMTowOAYDVQQDDDFBcHBsZSBEZXZlbG9wbWVudCBJT1Mg
UHVzaCBTZXJ2aWNlczogaW8ucXVpcmUuYXBwMRMwEQYDVQQLDApKSjQ5S1JMSEEz
MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN6c
GBK86Ao+DJ8ftUf4YNS8dt2VINHEFVjUEEnSCAcGvpSGmkPTDadhmHiowjpDwJ3e
ahyMjjMINoXoIGluJwM0TjwZdtBOJpmBLmgLAfoBxyGlxRvsZpVe3cqkzAQLN/Sx
9PA5HDtUZx+iwOw/u/XYaikGE3b/OiUNh7EwqsfjtJW6/q1vFZeJ5W/UsDSiX7rb
7mOheyXPbz/7+xshI+jsNwLk+/ZTqzxSzzhGTLFYfJPrYUJnpcZQL8cXQo1R+7o3
6OdOFnBjPx+Xr08rmJzech+6iDiiW12StOHoTx3GS1FxWkQg6yrkb75DbJVfQ3ew
RvgfYhbUgScgv6eIDSUCAwEAAaOCAeUwggHhMB0GA1UdDgQWBBSqwb2Nq10gyP7a
vS+roaAoSDf7wTAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFIgnFwmpthhgi+zruvZH
WcVSVKO3MIIBDwYDVR0gBIIBBjCCAQIwgf8GCSqGSIb3Y2QFATCB8TCBwwYIKwYB
BQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBw
YXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBz
dGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRl
IHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjAp
BggrBgEFBQcCARYdaHR0cDovL3d3dy5hcHBsZS5jb20vYXBwbGVjYS8wTQYDVR0f
BEYwRDBCoECgPoY8aHR0cDovL2RldmVsb3Blci5hcHBsZS5jb20vY2VydGlmaWNh
dGlvbmF1dGhvcml0eS93d2RyY2EuY3JsMAsGA1UdDwQEAwIHgDATBgNVHSUEDDAK
BggrBgEFBQcDAjAQBgoqhkiG92NkBgMBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEA
chxwBh1vtnFWrL6zB8clOhoa0U0ppjY6WdxwDwqc+weG6/XC6BNq+HCQvHkl6dDQ
/8+pTmU1Fbfd0NC3gkIAk0w/CM3yMv5fpD+YLmxeSR6Q5nVYMDe6TwY5mZpKBX2f
Din6/YLU3qWHT6A5DwfJjvbI50NM4Y4qYWiHXYUnR3UMXuiTlcaUYASZMjA6u85s
W/Se/pNolB+/+eyN+a8FEyvLL+Kh6DtTuCrhjHvAf83fzDBqJroKIxtv1+NXAD6/
C752OccOLF9GYCBllet0/15mvsDXVJz57FSCNq+P2xgPZhlaTfRW69W1RjOS5mwD
XScKmh/1TTMcIvBr2BqDRw==
-----END CERTIFICATE-----
''');

final List<int> _UTF8_PLS_KEY = UTF8.encode('''...''');

const BUNDLE_ID = '';
const DEVICE_TOKEN = '';

Future main(List<String> arguments) async {

  for (var i = 0; i < 2; i++) { // loop 2 times to show first is failed and second is succeed.
    try {
      // create context for establishing socket later
      final _context = SecurityContext.defaultContext;
      _context.useCertificateChainBytes(_UTF8_PLS_CERTIFICATE); // TlsException will happened here
      _context.usePrivateKeyBytes(_UTF8_PLS_KEY);

      // establish socket and create connection
      final uri = Uri.parse('https://api.development.push.apple.com');
      final _secureSocket = await SecureSocket.connect(uri.host, uri.port, context: _context);
      final _transport = new ClientTransportConnection.viaSocket(_secureSocket);

      final headers = [
        new Header.ascii(':method', 'POST'),
        new Header.ascii(':path', '/3/device/$DEVICE_TOKEN'),
        new Header.ascii(':scheme', 'https'),
        new Header.ascii('apns-topic', BUNDLE_ID)
      ];

      final stream = _transport.makeRequest(headers);

      // send data
      stream.sendData(UTF8.encode(JSON.encode({
        'aps' : {
          'alert' : 'test',
          'badge' : 10,
          'sound' : 'default'
        },
        'url': ''
      })), endStream: true);
    } catch(e) {
      // show stack trace for the first iteration
      print(e);
    }
  }
}
mkustermann commented 8 years ago

@zanderso Bill mentioned you did a lot of BoringSSL work and might want to take a look at this?

whesse commented 8 years ago

Note: we can also provide versions of the SDK with a new BoringSSL version, to see if the problem disappears. We plan to move to this new version within days, and it will be available on dev channel releases. If we can't debug the problem easily, we should check if it will disappear.

zanderso commented 8 years ago

We were failing to clear the error bit after reading the default trusted certs. I'll have a fix ready soon. Also, I've edited your post to remove the private key.

zanderso commented 8 years ago

https://codereview.chromium.org/2211453002/

kevmoo commented 8 years ago

Thanks for filing the issue, @jerrywell – let us know once you've verified your issue is resolved.

jerrywell commented 8 years ago

Thanks @kevmoo and Dart team, our issue is fixed in version 1.19.0 :)