Remote host closed connection during handshake with Nest API - apex

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):

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

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

Related

How to provide a DER server certificate file to Akka HTTP

I am trying to provide a server certificate file to my Akka HTTP application using the following code.
import java.io.{BufferedInputStream, IOException}
import java.security.{KeyStore, SecureRandom}
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import javax.net.ssl.{KeyManagerFactory, SSLContext, SSLSocketFactory, TrustManagerFactory}
import scala.util.Try
trait TLSSupport extends Logging {
// Add support for self-signed (local) SSL certificates
// Based on http://developer.android.com/training/articles/security-ssl.html#UnknownCa
def https(implicit configuration: Configuration): Either[Throwable, HttpsConnectionContext] = { // Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
val cf = CertificateFactory.getInstance("X.509")
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
configuration.config.get("certificate").map {
x =>
Try {
log.info(s"Retrieving resource ${x}")
import java.io.FileInputStream
val is = new FileInputStream(x)
log.info(s"Creating stream for resource ${x}")
val fis = new BufferedInputStream(is)
log.info(s"Generating Certificate ${x}")
val ca: Certificate = cf.generateCertificate(fis)
log.info(s"Loading keystore Certificate ${x}")
val keyStoreType = KeyStore.getDefaultType
val keyStore = KeyStore.getInstance(keyStoreType)
keyStore.load(null, null)
log.info(s"Adding certificate entry to keystore ")
keyStore.setCertificateEntry("ca", ca)
// Create a TrustManager that trusts the CAs in our KeyStore
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm)
tmf.init(keyStore)
// Create an SSLContext that uses our TrustManager
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, tmf.getTrustManagers, null)
val https = ConnectionContext.httpsServer(sslContext)
https
}.toEither}.getOrElse(Left(throw new RuntimeException(s"Error loading TLS configuration")))
}
}
object Main extends App with Logging with TLSSupport {
def main: Future[Http.ServerBinding] = https.map {
case x: HttpsConnectionContext => log.info("Listening on secure port 8080."); Http().newServerAt("0.0.0.0", 8443).enableHttps(x).bind(routes)
case _ => log.warn(new RuntimeException("Error loading tls configuration").toString); Http().newServerAt("0.0.0.0", 8080).bind(routes)
} .getOrElse( {log.warn(new RuntimeException("Error loading tls configuration").toString); Http().newServerAt("0.0.0.0", 8080).bind(routes)} )
}
val server = new Application
server.main
}
No errors are thrown, but when it try to connect to the application locally, I get the followign error in openssl:
▶ curl --tlsv1.1 -vvv https://localhost:8443/api/umls/search\?searchString\=blood
* Trying 127.0.0.1:8443...
* Connected to localhost (127.0.0.1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* error:1404B410:SSL routines:ST_CONNECT:sslv3 alert handshake failure
* Closing connection 0
curl: (35) error:1404B410:SSL routines:ST_CONNECT:sslv3 alert handshake failure
Doesnt seem to be an issue with ssl versioning etc as i have tested on multiple systems. Frankly I always get very confused with encryption even after reading the documentation cant find something that fits this particular case where I need to provide a preexisting server certificate.

Dart gRPC TLS certificates with PEMs

I'm having a bit of trouble sorting out how to adapt my Dart gRPC client to use the same TLS settings that are working with my Go client. I've already validated that I can interface with the server suppling the correct CA cert, client cert and client key. In Go I'm using:
pemServerCA, err := ioutil.ReadFile("pems/ca-cert.pem")
if err != nil {
return nil, err
}
certPool := x509.NewCertPool()
if !certPool.AppendCertsFromPEM(pemServerCA) {
return nil, fmt.Errorf("failed to add server CA's certificate")
}
// Load client's certificate and private key
clientCert, err := tls.LoadX509KeyPair("pems/client-cert.pem", "pems/client-key.pem")
if err != nil {
return nil, err
}
// Create the credentials and return it
config := &tls.Config{
Certificates: []tls.Certificate{clientCert},
RootCAs: certPool,
}
Just supplying that in case it helps demonstrate what's working. In Dart I'm doing this:
ChannelCredentials credentials = ChannelCredentials.secure(
certificates: utf8.encode(grpcCertificate),
onBadCertificate: (certificate, host) {
return host == apiURL + ':' + apiPort.toString();
},
);
grpcCertificate contains the contents of client-key.pem. I suspect this is not correct. I'm not very skilled with certificates like this so I'm a bit at a loss. What value should I be supplying to certificates to achieve a successful handshake with the server?
From the above it seems like I need to parse my PEMs into X.509. In Go that's super easy, not sure how to handle this in Dart.
Edit: I've made a bit of progress:
List<int> list = grpcCertificate.codeUnits;
Uint8List cert = Uint8List.fromList(list);
ChannelCredentials credentials = ChannelCredentials.secure(
certificates: cert,
authority: 'localhost',
onBadCertificate: (certificate, host) {
return host == apiURL + ':' + apiPort.toString();
},
);
The server seems to hate this less and spits out:
flutter: gRPC Error (code: 14, codeName: UNAVAILABLE, message: Error connecting: TlsException: Failure trusting builtin roots (OS Error:
BAD_PKCS12_DATA(pkcs8_x509.c:645), errno = 0), details: null, rawResponse: null)
Thanks.
I ended up receiving somewhat of a proper answer on the grpc-dart issues page. The solution looks something like this:
class MyChannelCredentials extends ChannelCredentials {
final Uint8List? certificateChain;
final Uint8List? privateKey;
MyChannelCredentials({
Uint8List? trustedRoots,
this.certificateChain,
this.privateKey,
String? authority,
BadCertificateHandler? onBadCertificate,
}) : super.secure(
certificates: trustedRoots,
authority: authority,
onBadCertificate: onBadCertificate);
#override
SecurityContext get securityContext {
final ctx = super.securityContext;
if (certificateChain != null) {
ctx.useCertificateChainBytes(certificateChain);
}
if (privateKey != null) {
ctx.usePrivateKeyBytes(privateKey);
}
return ctx;
}
}
final cred = MyChannelCredentials(
trustedRoots: File('pems/ca-cert.pem').readAsBytesSync(),
certificateChain: File('pems/client-cert.pem').readAsBytesSync(),
privateKey: File('pems/client-key.pem').readAsBytesSync(),
authority: 'localhost',
);

Unable to consume TFS 2015 API. Getting 401 unauthrozed error

I tried TFS 2015 REST API Authentication
However, it mentions request object (as I can't use javascript), not sure where is the request object or what type of it.
I am trying to pass query id and the code should execute the query and get result via API.
The solution works from my local, however, after publishing to server it does not seems working.
I also checked that the TFS is accessible from server using the credentials.
My code below:
private HttpClientHandler GetTfsCredentials()
{
HttpClientHandler handler2 = new HttpClientHandler { UseDefaultCredentials = true };
handler2.Credentials = new NetworkCredential("username", "password", "domain");
return handler2;
}
private async Task<object> GetQueryResults(string queryId)
{
string tfsApiUrl = ConfigurationManager.AppSettings["TfsApiUrl"];
string tfsProjectName = ConfigurationManager.AppSettings["TfsProjectName"];
string TfsProjectGuid = ConfigurationManager.AppSettings["TfsProjectGuid"];
//I tried both credentials and credentials2, but none of them working
string credentials = Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"{""}:{"password"}"));
string credentials2 = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes("domain\\username:password") );
if (!string.IsNullOrEmpty(tfsApiUrl) && !string.IsNullOrEmpty(tfsProjectName)
&& !string.IsNullOrEmpty(Id))
{
log.Info("GetQueryResults:: Config values found");
using (var client = new HttpClient(GetTfsCredentials()) { BaseAddress = new Uri(tfsApiUrl) })
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials2);
HttpResponseMessage response = client.GetAsync($"{tfsProjectName}/_apis/wit/wiql/{Id}").Result;
log.Info("GetQueryResults:: response.ReasonPhrase" + response.ReasonPhrase.ToString());
log.Info("GetQueryResults:: response" + response.ToString());
log.Info("GetQueryResults:: response.IsSuccessStatusCode" + response.IsSuccessStatusCode.ToString());
string workItemList = null;
if (response.IsSuccessStatusCode)
{
//do something
}
}
}
return null;
}
The error I received is:
2020-03-20 16:17:35,382 INFO GetQueryResults:: response.ReasonPhrase Unauthorized
2020-03-20 16:17:35,382 INFO GetQueryResults:: responseStatus Code: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
X-TFS-ProcessId: 115b5bba-0bf4-45e2-a3b2-2913ccc93f09
ActivityId: bb21d947-99a3-44dc-bdb7-317d7af34934
X-TFS-Session: bb21d947-99a3-44dc-bdb7-317d7af34934
X-VSS-E2EID: bb21d947-99a3-44dc-bdb7-317d7af34934
X-FRAME-OPTIONS: SAMEORIGIN
X-TFS-SoapException: %3c%3fxml+version%3d%221.0%22+encoding%3d%22utf-8%22%3f%3e%3csoap%3aEnvelope+xmlns%3asoap%3d%22http%3a%2f%2fwww.w3.org%2f2003%2f05%2fsoap-envelope%22%3e%3csoap%3aBody%3e%3csoap%3aFault%3e%3csoap%3aCode%3e%3csoap%3aValue%3esoap%3aReceiver%3c%2fsoap%3aValue%3e%3csoap%3aSubcode%3e%3csoap%3aValue%3eUnauthorizedRequestException%3c%2fsoap%3aValue%3e%3c%2fsoap%3aSubcode%3e%3c%2fsoap%3aCode%3e%3csoap%3aReason%3e%3csoap%3aText+xml%3alang%3d%22en%22%3eTF400813%3a+The+user+%27CWOPA%5cSTCTCAPD006%24%27+is+not+authorized+to+access+this+resource.%3c%2fsoap%3aText%3e%3c%2fsoap%3aReason%3e%3c%2fsoap%3aFault%3e%3c%2fsoap%3aBody%3e%3c%2fsoap%3aEnvelope%3e
X-TFS-ServiceError: TF400813%3a+The+user+%27CWOPA%5cSTCTCAPD006%24%27+is+not+authorized+to+access+this+resource.
Server: Microsoft-IIS/8.5
WWW-Authenticate: Bearer
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
P3P: CP="CAO DSP COR ADMa DEV CONo TELo CUR PSA PSD TAI IVDo OUR SAMi BUS DEM NAV STA UNI COM INT PHY ONL FIN PUR LOC CNT"
Lfs-Authenticate: NTLM
X-Content-Type-Options: nosniff
Date: Fri, 20 Mar 2020 20:17:35 GMT
Content-Length: 82
Content-Type: text/plain; charset=utf-8
}
2020-03-20 16:17:35,382 INFO GetQueryResults:: response.IsSuccessStatusCode False
It looks like you are doing authentication in two different ways at once:
In the GetTfsCredentials-Method you set up Windows Authentication (NTLM or Kerberos)
By adding client.DefaultRequestHeaders.Authorization your try to set up Basic Authentication
Your TFS indicates (see WWW-Authenticate Header) that it supports Bearer, Negotiate and NTLM; but not Basic.
I would try:
Remove client.DefaultRequestHeaders.Authorization, credentials and credentials2. This should remove Basic-Authentication
Remove UseDefaultCredentials = true since you set explicit credentials the next line. UseDefaultCredentials tells HttpClientHandler to access TFS with the credentials of the running process, which is probably your account when executing locally and a service account when executing on the server.
Whithout this line, the specified NetworkCredential should be used to access TFS.

PayPal Sandbox API SSL handshake error HTTPS request

With new changes in paypal , it started throwing SSL handshake exceptions those who are using old system. "PayPal SSL Certificate Changes"
https://devblog.paypal.com/paypal-ssl-certificate-changes/
This may help someone. After i got SSL Handshake exception , i spent a hell lot of time to resolve it.
Here is the Exception :
javax.net.ssl.SSLHandshakeException: Received fatal alert:
handshake_failure
Solution :
Requirements to resolve this issue :
Start from Jan 19, all sandbox API endpoint need to
1.) Use TLS 1.2 and HTTP/1.1 connection
2.) Upgrade to SHA-256 and use the G5 root certificate to make the HTTPS connection
Point 1 Solution:
If you are using java 6 then better upgrade it to java 7
https://blogs.oracle.com/java-platform-group/entry/diagnosing_tls_ssl_and_https
For my case i am using java 7 so TLSv1 (default) for JDK 7.
We have to enable it manually while starting server
**-Dhttps.protocols=TLSv1.2** passed as vm argument.
Point 2 Solution :
https://knowledge.verisign.com/support/mpki-for-ssl-support/index?page=content&actp=CROSSLINK&id=SO5624
G5 cerificate import: Save it as test.cer
Go to java home/bin then run this command
keytool -importcert -file C:/test.cer
create sanbox account. Get the facilator password and signature pass it as parameters
String encodedData = "USER=XXX-facilitator_api1.XXX.XXX"
+ "&PWD=XXXXXXXXXXXX"
+ "&SIGNATURE=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX-"
+ "&VERSION=95"
+ "&METHOD=SetExpressCheckout"
+ "&PAYMENTREQUEST_0_PAYMENTACTION=Authorization"
+ "&L_PAYMENTREQUEST_0_NAME0="+URLEncoder.encode("Testing","UTF-8")
+ "&L_PAYMENTREQUEST_0_DESC0="+URLEncoder.encode("Testing","UTF-8")
+ "&L_PAYMENTREQUEST_0_AMT0="+URLEncoder.encode("99","UTF-8")
+ "&PAYMENTREQUEST_0_AMT="+URLEncoder.encode("99","UTF-8")
+ "&PAYMENTREQUEST_0_CURRENCYCODE="+URLEncoder.encode("USD","UTF-8")
+ "&LOCALECODE=en_GB"
+ "&RETURNURL=google.com"
+ "&CANCELURL=google.co.in"
+ "&LOGOIMG=imageurl";
String responsepaypal = getHTMLcontent("https://api-3t.sandbox.paypal.com/nvp",encodedData ,"UTF-8");
String token = responsepaypal.toString().replaceAll("TOKEN=(.*?)&TIMESTAMP.*", "$1");//***Token for post request on paypal***
public static String getHTMLcontent(String url,String urlParameters, String encodingDef) throws IOException {
URL obj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) obj.openConnection();
con.setRequestMethod("POST");
con.setRequestProperty("Content-length", String.valueOf(urlParameters.length()));
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36");
con.setRequestProperty("Host", "api-3t.sandbox.paypal.com");
con.setRequestProperty("Upgrade-Insecure-Requests", "1");
con.setRequestProperty("Pragma", "no-cache");
//con.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
con.setRequestProperty("Accept-Encoding", "gzip, deflate, sdch");
con.setRequestProperty("Accept-Language", "en-US,en;q=0.8");
con.setRequestProperty("Connection", "keep-alive");
con.setDoOutput(true);
con.setDoInput(true);
DataOutputStream output = new DataOutputStream(con.getOutputStream());
output.writeBytes(urlParameters);
output.close();
DataInputStream input = new DataInputStream( con.getInputStream() );
StringBuffer sb = new StringBuffer();
String line;
while ((line = input.readLine()) != null) {
sb.append(line);
}
input.close();
return sb.toString();
}}
Follow the steps out here clearly mentioned:
https://developer.paypal.com/docs/classic/express-checkout/ht_ec-singleAuthPayment-curl-etc/
I was testing paypal using the sandbox account and I was getting the same error. I upgraded to java 8 and the error was not there anymore.

Salesforce rest api INVALID_SESSION_ID

I'm trying to connect my asp.net REST api to salesforce. I'm succesfully going through authentification, but when I start to send POST requests, I'm getting an error
{"errorCode":"INVALID_SESSION_ID","message":"Session expired or invalid"}
Here is my POST request:
//SFServerUrl = "https://na17.salesforce.com/services/";
//url = "data/v28.0/sobjects/Account";
ASCIIEncoding ascii = new ASCIIEncoding();
byte[] postBytes = ascii.GetBytes(postBody);
HttpWebRequest request = WebRequest.Create(Globals.SFServerUrl + url) as HttpWebRequest;
request.Method = "POST";
request.ContentType = "application/json";
request.ContentLength = postBytes.Length;
Stream postStream = request.GetRequestStream();
postStream.Write(postBytes, 0, postBytes.Length);
HttpCookie cookie = HttpContext.Current.Request.Cookies[Globals.SFCookie];
var ticket = FormsAuthentication.Decrypt(cookie.Value);
string authToken = ticket.UserData;
request.Headers.Add("Authorization", "Bearer " + authToken);
postStream.Close();
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
StringBuilder sb = new StringBuilder();
byte[] buf = new byte[8192];
Stream resStream = response.GetResponseStream();
string tempString = null;
int count = 0;
do
{
count = resStream.Read(buf, 0, buf.Length);
if (count != 0)
{
tempString = Encoding.ASCII.GetString(buf, 0, count);
sb.Append(tempString);
}
}
while (count > 0);
return new Tuple<bool, string>(true, sb.ToString());
When I'm trying to send GET request - I recieve 200 response.
Also, I've tried to send a POST Request with the same token from Simple Rest Client and it get's 200 response. I tried to change my "Authorization : Bearer" Header to "Authorization : Oauth", but nothing changed. I also tried to catch this error, get refresh token and send a request again with refreshed token, but nothing changed. Please, help me with this.
Using workbench I was able to POST the following JSON to /services/data/v29.0/sobjects/Account and create a new Account.
{
"Name" : "Express Logistics and Transport"
}
Raw Response
HTTP/1.1 201 Created
Date: Sun, 07 Sep 2014 21:32:06 GMT
Set-Cookie: BrowserId=_HC-bzpTQABC1237vFu2hA;Path=/;Domain=.salesforce.com;Expires=Thu, 06-Nov-2014 21:32:06 GMT
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Sforce-Limit-Info: api-usage=209/15000
Location: /services/data/v29.0/sobjects/Account/0010000000000001AAA
Content-Type: application/json;charset=UTF-8
Content-Encoding: gzip
Transfer-Encoding: chunked
{
"id" : "0010000000000001AAA",
"success" : true,
"errors" : [ ]
}
Things to check:
Your URL. It appears to be missing the leading /services
Is SFServerUrl the same Salesforce pod/server that the Session Id was issued to? If the Session Id came from another pod then it would be invalid on na17.
How did you create the Session Id? If you used OAuth, what scopes did you request?
Is the Session Id coming out of the cookie valid?
Has something else using the same Session Id called logout and invalidated the session?
Incidentally, the Salesforce StackExchange site is a great place to ask Salesforce specific questions.
See also:
Using REST API Resources - Create a Record
The problem was that I added Headers after Content. When I switched these lines of code everything worked.