Remote host closed connection during handshake with Nest API

£可爱£侵袭症+ 提交于 2020-01-06 15:33:12

问题


Trying out the Nest API, I got the OAuth flow working without problems, made the first API call (to https://developer-api.nest.com/devices.json), got the 307 redirect as expected, but then my call to the redirect location fails with Remote host closed connection during handshake. I went to the Nest developer event in San Francisco last night, and Lev Stesin told me to post a full log here and mention his name.

Code (Apex, running on Force.com):

public with sharing virtual class NestController {
    public class OAuthResponse {
        public String access_token;
        public String token_type;
        public Integer expires_in;    
        public String refresh_token;
        public String error;
    }

    public static OAuthResponse parse(String json) {
        return (OAuthResponse) System.JSON.deserialize(json, OAuthResponse.class);
    }

    public String accessToken {get; set;}
    public String output {get; set;}

    private String getAll(String accessToken) {
        String url = 'https://developer-api.nest.com/devices.json?auth='+accessToken+'&print=pretty';

        HttpRequest req = new HttpRequest();
        req.setEndpoint(url);
        req.setMethod('GET');
        req.setTimeout(60*1000);

        Http h = new Http();
        String resp;
        HttpResponse res = h.send(req);
        resp = res.getBody();

        if (res.getStatusCode() == 307) {
            url = res.getHeader('Location');
            System.debug('Redirect to: '+url);

            req = new HttpRequest();
            req.setEndpoint(url);
            req.setMethod('GET');
            req.setTimeout(60*1000);

            h = new Http();
            res = h.send(req);
            resp = res.getBody();
        }

        System.debug('Get returns: '+resp);

        return resp;
    }

    public virtual PageReference login() {
        String clientId = '989360fb-9a1f-4d13-929e-0b40111c725a';
        String clientSecret = 'SECRET';
        String sessionId = null;
        String state = 'wow';

        // Get a URL for the page without any query params    
        String url = ApexPages.currentPage().getUrl().split('\\?')[0];

        System.debug('url is '+url);

        // note: connect url in fb application connect setting should be: https://c.na3.visual.force.com/apex/
        // you need the trailing slash even though it bitches about it
        String rediruri = 'https://'+ApexPages.currentPage().getHeaders().get('Host')+url;

        System.debug('rediruri is:'+rediruri);

        String authuri = 'https://home.nest.com/login/oauth2'+
            '?client_id='+clientId+
            '&state='+state;

        // No session
        PageReference pageRef;

        if (ApexPages.currentPage().getParameters().containsKey('error')) {
            // Initial step of OAuth - redirect to OAuth service
            System.debug('Error:' + ApexPages.currentPage().getParameters().get('error'));

            return null;
        }

        if (! ApexPages.currentPage().getParameters().containsKey('code')) {
            // Initial step of OAuth - redirect to OAuth service
            System.debug('Nest OAuth Step 1');

            return new PageReference(authuri);
        }

        // Second step of OAuth - get token from OAuth service
        String code = ApexPages.currentPage().getParameters().get('code');

        System.debug('Nest OAuth Step 2 - code:'+code);

        String tokenuri = 'https://api.home.nest.com/oauth2/access_token';
        String body = 'code='+code+
            '&client_id='+clientId+
            '&client_secret='+clientSecret+
            '&grant_type=authorization_code';
        System.debug('body is:'+body);

        HttpRequest req = new HttpRequest();
        req.setEndpoint(tokenuri);
        req.setMethod('POST');
        req.setTimeout(60*1000);
        req.setBody(body);

        Http h = new Http();
        String resp;
        if (code.equals('TEST')) {
            resp = 'access_token=TEST&expires=3600';
        } else {
            HttpResponse res = h.send(req);
            resp = res.getBody();
        }

        System.debug('FINAL RESP IS:'+resp);

        OAuthResponse oauth = parse(resp);

        if (oauth.error != null) {
            // Error getting token - probably reusing code - start again
            return new PageReference(authuri);            
        }

        accessToken = oauth.access_token;

        output = getAll(accessToken);

        return null;
    }    
}

Initial OAuth Redirect:

https://home.nest.com/login/oauth2?client_id=989360fb-9a1f-4d13-929e-0b40111c725a&state=wow

User authorizes app to access thermostats, Nest redirects back to my app:

https://c.na9.visual.force.com/apex/Nest?state=wow&code=6F3GV6WQ35NGLYB2

I successfully exchange the code for an access token:

POST to https://api.home.nest.com/oauth2/access_token with body

code=6F3GV6WQ35NGLYB2&client_id=989360fb-9a1f-4d13-929e-0b40111c725a&client_secret=SECRET&grant_type=authorization_code

Response:

{"access_token":"c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU","expires_in":315360000}

(I revoked the token from home.nest.com, so it's safe for me to post here!)

So I do a GET on

https://developer-api.nest.com/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty

and receive the expected 307 redirect, with location

https://firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553/devices.json?auth=c.eDzTiwBeVak0Jq7RWVjBJPXrZT8kI5Hh4rgnYG7eDvzytZbqTJbMsnGBHLUcKOSZ7xjk8NR4oNAE4iUh1EBtkHllg55C0Ckb29jsSqL5VwdMxSUoTSBDkKt8QzMAoUCD3Ru8iSo7XYpPc8qU&print=pretty

Now, when I GET that URL in my Apex code running on Force.com, it fails with

System.CalloutException: Remote host closed connection during handshake

But if I do the same GET from curl on the command line, it succeeds, returning the expected JSON response.

So it looks like there may be some incompatibility in the SSL handshake. I'll investigate at the Force.com end; it would be good if someone at Nest could check the logs at their end - there should be enough detail here.

EDIT - Here's the output from curl -v to that URL:

$ curl -v 'https://firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553/devices.json?auth=c.dPHNEweWehQ47tzSm0sf13o8rX1isO9IdEG1HFwoAmeA2FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3CFma5ozSNbpqUIwGDGc8ixD1etjiIW6TmXN0Rd0p5VzEtk6sDwIe8j10NH1hKDhevX&print=pretty'
* About to connect() to firebase-apiserver01-tah01-iad01.dapi.production.nest.com port 9553 (#0)
*   Trying 54.196.205.148...
* connected
* Connected to firebase-apiserver01-tah01-iad01.dapi.production.nest.com (54.196.205.148) port 9553 (#0)
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS handshake, Server key exchange (12):
* SSLv3, TLS handshake, Server finished (14):
* SSLv3, TLS handshake, Client key exchange (16):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSLv3, TLS change cipher, Client hello (1):
* SSLv3, TLS handshake, Finished (20):
* SSL connection using EDH-RSA-DES-CBC3-SHA
* Server certificate:
*    subject: OU=Domain Control Validated; CN=*.dapi.production.nest.com
*    start date: 2014-05-28 22:31:28 GMT
*    expire date: 2015-05-28 22:31:28 GMT
*    subjectAltName: firebase-apiserver01-tah01-iad01.dapi.production.nest.com matched
*    issuer: C=US; ST=Arizona; L=Scottsdale; O=GoDaddy.com, Inc.; OU=http://certs.godaddy.com/repository/; CN=Go Daddy Secure Certificate Authority - G2
*    SSL certificate verify ok.
> GET /devices.json?auth=c.dPHNEweWehQ47tzSm0sf13o8rX1isO9IdEG1HFwoAmeA2FtBLH1fTiksRtN9DGcPAOyEI3VINz2fD3CFma5ozSNbpqUIwGDGc8ixD1etjiIW6TmXN0Rd0p5VzEtk6sDwIe8j10NH1hKDhevX&print=pretty HTTP/1.1
> User-Agent: curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8y zlib/1.2.5
> Host: firebase-apiserver01-tah01-iad01.dapi.production.nest.com:9553
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=UTF-8
< Access-Control-Allow-Origin: *
< Cache-Control: private, no-cache, max-age=0
< Content-Length: 2218
< 
{
  "thermostats" : {
    "pYo-lbpXuVm_DctuTckA_HdEswRgRkbx" : {
      "locale" : "en-US",
      "temperature_scale" : "F",
      "is_using_emergency_heat" : false,
      "has_fan" : true,
      "software_version" : "4.2.3",
      "has_leaf" : true,
      "device_id" : "pYo-lbpXuVm_DctuTckA_HdEswRgRkbx",
      "name" : "Downstairs",
      "can_heat" : true,
      "can_cool" : true,
      "hvac_mode" : "off",
      "target_temperature_c" : 24.5,
      "target_temperature_f" : 76,
      "target_temperature_high_c" : 24.0,
      "target_temperature_high_f" : 75,
      "target_temperature_low_c" : 20.0,
      "target_temperature_low_f" : 68,
      "ambient_temperature_c" : 25.0,
      "ambient_temperature_f" : 78,
      "away_temperature_high_c" : 24.0,
      "away_temperature_high_f" : 76,
      "away_temperature_low_c" : 15.5,
      "away_temperature_low_f" : 60,
      "structure_id" : "HqSZlH08Jc3CtBNIS4OLPdiWLpcfW5o6dP2DvSox7hcGVpBGOH9cQA",
      "fan_timer_active" : false,
      "name_long" : "Downstairs Thermostat",
      "is_online" : true,
      "last_connection" : "2014-06-26T23:16:24.341Z"
    },
    "pYo-lbpXuVncrx7IdGTWyXdEswRgRkbx" : {
      "locale" : "en-US",
      "temperature_scale" : "F",
      "is_using_emergency_heat" : false,
      "has_fan" : true,
      "software_version" : "4.2.3",
      "has_leaf" : true,
      "device_id" : "pYo-lbpXuVncrx7IdGTWyXdEswRgRkbx",
      "name" : "Upstairs",
      "can_heat" : true,
      "can_cool" : true,
      "hvac_mode" : "off",
      "target_temperature_c" : 24.0,
      "target_temperature_f" : 76,
      "target_temperature_high_c" : 24.0,
      "target_temperature_high_f" : 75,
      "target_temperature_low_c" : 20.0,
      "target_temperature_low_f" : 68,
      "ambient_temperature_c" : 25.0,
      "ambient_temperature_f" : 78,
      "away_temperature_high_c" : 24.0,
      "away_temperature_high_f" : 76,
      "away_temperature_low_c" : 15.5,
      "away_temperature_low_f" : 60,
      "structure_id" : "HqSZlH08Jc3CtBNIS4OLPdiWLpcfW5o6dP2DvSox7hcGVpBGOH9cQA",
      "fan_timer_active" : false,
      "name_long" : "Upstairs Thermostat",
      "is_online" : true,
      "last_connection" : "2014-06-26T23:16:27.849Z"
    }
  }
* Connection #0 to host firebase-apiserver01-tah01-iad01.dapi.production.nest.com left intact
}* Closing connection #0
* SSLv3, TLS alert, Client hello (1):

回答1:


I don't think the server supports SSLv3. Try using --tlsv1 and see if that works.




回答2:


The same callout from Salesforce works just fine now. I guess Nest or Force.com must have tweaked some SSL config.



来源:https://stackoverflow.com/questions/24415551/remote-host-closed-connection-during-handshake-with-nest-api

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!