Apple Pay on the web merchant session in ruby - ruby

I can't seem to be able to get the merchant session validation working with Ruby. Tried HTTParty and RestClient and I'm getting:
OpenSSL::SSL::SSLError (SSL_connect returned=1 errno=0 state=SSLv3 read finished A: sslv3 alert certificate expired):
I tried the same certificate with this node server example, https://github.com/tomdale/apple-pay-merchant-session-server, and it worked fine, so it must be something in my ruby code.
Has anyone managed to get this working?

I was having the same problem. With the help of the example you referenced and the implementation at https://github.com/norfolkmustard/ApplePayJS (see also the discussion about the implementation at https://forums.developer.apple.com/thread/51580) I was able to get it working.
The key for me was passing in the correct certificate (the Apple Pay Merchant Identity certificate) just as Apple provides it and getting the cert key like so:
Once you have your Merchant ID (session) certificate from Apple, import that into keychain.app on your Mac by double-clicking it, right click on the cert in keychain and export the combined private-key and cert as a .p12 file then, in terminal:-
openssl pkcs12 -in your_merchant_identity_cert_name.p12 -out ApplePay.key.pem -nocerts -nodes
After adding the Apple Pay Merchant Identification cert from Apple and the contents of the ApplePay.key.pem file to an environment variable I was able to construct the following request using Ruby's Net::HTTP class...
class YourControllerName < ApplicationController
def apple_pay_validation
respond_to do |format|
format.json { render json: start_apple_session(params[:url]) } if params[:url].include?('apple.com')
end
end
private
def start_apple_session(url)
uri = URI.parse(url) # the url from event.validationURL
data = {'merchantIdentifier' => "merchant.com.your_site_name", 'domainName' => "your_doamin", 'displayName' => "your_company_name"}
pem = File.read('path/to/your/merchant_id.cer')
key = ENV['APPLE_PAY_MERCHANT_ID_ KEY']
passphrase = 'passphrase set up when exporting certificate in keychain' # Should be an environment variable
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.ssl_version = :TLSv1_2
http.ciphers = ['ECDHE-RSA-AES128-GCM-SHA256']
http.cert = OpenSSL::X509::Certificate.new(pem)
http.key = OpenSSL::PKey::RSA.new(key, passphrase)
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
request = Net::HTTP::Post.new(uri.request_uri, 'Content-Type' => 'application/json')
request.body = data.to_json
response = http.request(request)
response.body
end
end
This was called from my performValidation function (modified from the ApplePayJS repo listed above) which looks like this..
performValidation = (valURL) ->
new Promise((resolve, reject) ->
xhr = new XMLHttpRequest
xhr.open 'GET', '/your_controller_name/apple_pay_validation?url=' + valURL
xhr.onerror = reject
xhr.onload = ->
data = JSON.parse(#responseText)
resolve data
xhr.send()
)
Hopefully that helps save someone some time and gray hairs!

Related

Coinbase API Request in Ruby always returns Invalid API Key

I am trying to learn how to make simple requests to the Coinbase API in Ruby. This is mostly for my own entertainment and education. The Ruby GEM is so out of date I thought I'd start working on my own system instead of relying on it. To that end I wanted to get the basics working before I tried to turn it into a gem.
Here is the beginnings of my module.
And no matter what, when I try to get /accounts I get a 401 response.
#status=401 #reason_phrase="Unauthorized" #response_body="{\"message\":\"Invalid API Key\"}
# Simple Coinbase Requests
require 'base64'
require 'openssl'
require 'json'
require 'faraday'
module Coinbase
module Request
class API
def initialize key = '', secret = '', passphrase = '', sandbox = false
#url = sandbox ? 'https://api-public.sandbox.pro.coinbase.com' : 'https://api-public.pro.coinbase.com'
#key = key
#secret = secret
#passphrase = passphrase
#content_type = 'application/json'
end
# Get Accounts
def accounts
self.get '/accounts'
end
# Do the work of a HTTP Get
def get path
timestamp = Time.now.to_i.to_s
headers = auth_headers path, '', 'GET', timestamp
connection = Faraday.new(url: 'https://api-public.sandbox.pro.coinbase.com', headers: headers, ssl: { :verify => false })
# puts connection.inspect
connection.get path
end
# Auth Headers
# CB-ACCESS-KEY The api key as a string.
# CB-ACCESS-SIGN The base64-encoded signature (see Signing a Message).
# CB-ACCESS-TIMESTAMP A timestamp for your request.
# CB-ACCESS-PASSPHRASE The passphrase you specified when
def auth_headers path = '', body = '', method = 'GET', timestamp = nil
{
'Content-Type': 'Application/JSON',
'CB-ACCESS-SIGN': self.signature(path, body, method, timestamp),
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': #key,
'CB-ACCESS-PASSPHRASE': #passphrase
}
end
# Generate a signature
def signature path = '', body = '', method = 'GET', timestamp = nil
body = body.to_json if body.is_a?(Hash)
secret = Base64.decode64 #secret
hash = OpenSSL::HMAC.digest 'sha256', secret, "#{timestamp}#{method}#{path}#{body}"
Base64.strict_encode64 hash
end
end
end
end
I'm calling it using the following (THE KEY/SECRET/PASSPHRASE ARE FAKE for this example)
coinbase = Coinbase::Request::API.new('123426bc3a583fb8393141fb7777fake',
'+FAKEbGoG1eT1WVFWNJxFtTE/y4kIYq2Lbf6FAKEw5j2756GXgaqg5iXTsqPJXKkStZ7nPoTT2RGKwiJfRFAKE==',
'FAKEPASSPHRASE',
true)
puts coinbase.accounts.inspect
The signature "what" (as defined as "#{timestamp}#{method}#{path}#{body}" in your docs) for this simple request would be something like 1624063589GET/accounts
The headers come out to {:"Content-Type"=>"Application/JSON", :"CB-ACCESS-SIGN"=>"rs29GSZuRspthioywb5IkaHQmPIwH5DRDW5LHoYUvw8=", :"CB-ACCESS-TIMESTAMP"=>"1624063726", :"CB-ACCESS-KEY"=>"123426bc3a583fb8393141fb22328113", :"CB-ACCESS-PASSPHRASE"=>"FAKEPASSPHRASE"}
Once connected the headers come out as seen below in the faraday response object.
This is the full response object output which is what I would expect to get with fake keys as used but I get the same thing when I use the keys I generated here: https://pro.coinbase.com/profile/ap
#<Faraday::Response:0x00000001621a8e58 #on_complete_callbacks=[], #env=#<Faraday::Env #method=:get #url=#<URI::HTTPS https://api-public.sandbox.pro.coinbase.com/accounts> #request=#<Faraday::RequestOptions (empty)> #request_headers={"Content-type"=>"Application/JSON", "Cb-access-sign"=>"vEfjUnFy+3qQqRa2lxvEC5O32xOa6t7NgGAxO8OYrpo=", "Cb-access-timestamp"=>"1624063280", "Cb-access-key"=>"123426bc3a583fb8393141fb22328113", "Cb-access-passphrase"=>"FAKEPASSPHRASE", "User-Agent"=>"Faraday v1.4.2"} #ssl=#<Faraday::SSLOptions (empty)> #response=#<Faraday::Response:0x00000001621a8e58 ...> #response_headers={"date"=>"Sat, 19 Jun 2021 00:41:21 GMT", "content-type"=>"application/json; charset=utf-8", "content-length"=>"29", "connection"=>"keep-alive", "access-control-allow-headers"=>"Content-Type, Accept, cb-session, cb-fp, cb-form-factor", "access-control-allow-methods"=>"GET,POST,DELETE,PUT", "access-control-allow-origin"=>"*", "access-control-expose-headers"=>"cb-before, cb-after, cb-gdpr", "access-control-max-age"=>"7200", "cache-control"=>"no-store", "etag"=>"W/\"1d-mmRSeO9uba2rhQtGfy4YjixIkt4\"", "strict-transport-security"=>"max-age=15552000; includeSubDomains", "x-content-type-options"=>"nosniff", "x-dns-prefetch-control"=>"off", "x-download-options"=>"noopen", "x-frame-options"=>"SAMEORIGIN", "x-xss-protection"=>"1; mode=block", "cf-cache-status"=>"MISS", "cf-request-id"=>"0ac3501f6300005ae1c99c3000000001", "expect-ct"=>"max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"", "vary"=>"Accept-Encoding", "server"=>"cloudflare", "cf-ray"=>"6618b6123ba75ae1-IAD"} #status=401 #reason_phrase="Unauthorized" #response_body="{\"message\":\"Invalid API Key\"}">>
I've been plugging away at this for days and I just can't seem to get it to not return Invalid API Key. I feel like I'm so very close.. but no cigar..
Thoughts?
NOTE: On a whim before submitting this I tried /currencies and it worked fine.
It took me some trial and error with this too, but I was hitting invalid signature errors rather than invalid API key errors. Maybe this will help anyway:
Working example:
Gemfile
source 'https://rubygems.org'
ruby '2.6.7'
gem 'httparty'
coinbase.rb
require 'base64'
require 'httparty'
API_KEY = ''
API_PASSPHRASE = ''
API_SECRET = ''
key = Base64.decode64(API_SECRET)
url = "https://api.exchange.coinbase.com"
requestPath = "/accounts"
method = "GET"
body = ""
timestamp = Time.now.to_i.to_s
message = timestamp + method + requestPath + body
hmac = OpenSSL::HMAC.digest('sha256', key, message)
signature = Base64.strict_encode64(hmac)
headers = {
'Content-Type': 'application/json',
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': API_KEY,
'CB-ACCESS-PASSPHRASE': API_PASSPHRASE
}
result = HTTParty.get(url+requestPath, headers: headers)
puts result.response.body
Run:
bundle install
bundle exec coinbase.rb

Ruby Http Post Parameters

How can I add post parameters to what I have right now:
#toSend = {
"nonce" => Time.now.to_i,
"command" => "returnCompleteBalances"
}.to_json
uri = URI.parse("https://poloniex.com/tradingApi")
https = Net::HTTP.new(uri.host,uri.port)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
req = Net::HTTP::Post.new(uri.path, initheader = {'Content-Type' =>'application/json'})
req.set_form_data({"nonce" => Time.now.to_i, "command" => "returnCompleteBalances"})
req['Key'] = '******-N4WZI2OG-******-10RX5JYR'
req['Sign'] = 'secret_key'
req.body = "[ #{#toSend} ]"
res = https.request(req)
puts "Response #{res.code} #{res.message}: #{res.body}"
These are the params I want to send:
"nonce" => Time.now.to_i,
"command" => "returnCompleteBalances"
Thank you.
It appears that you're trying to use Poloniex's trading API. If this is your primary goal, you might wish to consider using a library to handle the nitty-gritty details. For example:
https://github.com/Lowest0ne/poloniex
If your primary goal is not simply to use the API, but to use this as a learning experience, here are a few pointers:
The API documentation indicates that the API accepts form-encoded POST data (not JSON) but responds with JSON.
The key parameter ("Key") is like your user id. It allows Poloniex to understand who is attempting to make a request against the API.
The signature parameter ("Sign") is an HMAC generated from the contents of your secret key and the contents of your message (the encoded form data). This produces a sort of fingerprint that only you and Poloniex have the information to reproduce, giving some level of assurance that your request originated from the owner of the secret key. Of course, this assumes that your secret key is indeed only known by you.
I don't use the Poloniex exchange and cannot test this code, but I believe this is close to what you're attempting to accomplish:
require 'net/http'
require 'openssl'
secret = 'your-secret-key'
api_key = 'your-api-key'
uri = URI('https://poloniex.com/tradingApi')
http = Net::HTTP.new(uri.host)
request = Net::HTTP::Post.new(uri.request_uri)
form_data = URI.encode_www_form({:command => 'returnBalances', :nonce => Time.now.to_i * 1000 })
request.body = form_data
request.add_field('Key', api_key)
request.add_field('Sign', OpenSSL::HMAC.hexdigest( 'sha512', secret, form_data))
res = http.request(request)
puts res.body

How to sign requests with custom access token for 2 legged oAuth

I using oauth-ruby gem for a while, and I already implemented 2 types of auth:
default one
and custom, which uses OTP sent via sms
Both of them works perfectly now
But now i'm trying to implement new(3) 2-legged oauth. And I ran in to issues which I actually can't understand.
All my signed requests using access token from (3) are failing because of invalid token. For (1-2) it works without any issues.
Signing requests is implemented via RestClient.before_execution_proc:
RestClient.add_before_execution_proc do |req, params|
access_token.sign!(req)
end
I suppose the problem comes from access_token = OAuth::AccessToken as there actual difference between other 2.
Any suggestions or advices will be very helpful
1.
def default_oauth(login, pass, device: Device.new)
#cookies = login_req(login, pass).cookies
headers = common_headers.merge("Cookie" => #cookies)
#Get request token
request_token = consumer.get_request_token
# Authorize request key
authorize = RestClient.post(base_url + '/oauth/authorize',
{ requestToken: request_token.token, authorize: 'Authorize'},
headers) {|response, request, result| response }
auth_url_resp = RestClient.get(authorize.headers[:location], headers: headers) {|response, request, result| response }
# Get Access key
access_token = request_token.get_access_token
end
2.
def custom_oauth(phone, pin, otp: nil, device: Device.new)
otp = phone.to_s[-5..-1] if otp.nil?
resp = RestClient.post("#{base_url}/rest/smartphone/otp/sms-sender/#{phone}", '', common_headers) {|response, request, result| response }
request_token = consumer.get_request_token
payload = {
device: device.to_h,
otp: otp,
password: pin.to_s,
requestToken: request_token.token
}
headers = json_headers.merge('Cookie' => otp)
authorize = RestClient.post(base_url + '/oauth/otp-authorization',
payload.to_json, headers) {|response, request, result| response }
#access_token = request_token.get_access_token
end
3.
def new_oauth(login, pass, entry, device: Device.new)
tkn = consumer.get_request_token.token
payload = {
username: login,
password: pass.to_s,
requestToken: tkn,
entryPoint: entry,
device: device.to_h
}
headers =json_headers(device.id)
resp = RestClient.post("#{base_url}/oauth/login-authorization", payload.to_json, headers) {|response, request, result| response}
hsh ={oauth_token: resp.headers[:accesstoken], oauth_token_secret: resp.headers[:tokensecret] }
access_token = OAuth::AccessToken.from_hash(consumer, hsh)
end
Consumer:
def consumer
#consumer ||= build_consumer
end
def build_consumer
key = 'key_string'
secret ='secret_string'
OAuth::Consumer.new(key, secret, :site => base_url)
end
Issue was related to server (Spring) encoding stuff. oauth-ruby gem is escaping token secret (Combined secret or Encryption key) which is used for signature creation. Spring by default doing the same on server side.
Unescaping access_token.secret fixed this issue:
access_token.secret = OAuth::Helper.unescape(access_token.secret)

Ruby Sinatra create post request without PEM and OpenSSL::SSL::VERIFY_NONE

I try create POST request with SSL but without OpenSSL::SSL::VERIFY_NONE because it is opend up security attacks and without PEM certificate.
But I catch problems, my ruby code for send POST request:
post '/test/test1' do
cross_origin
post_data = request.body.read
res_Data = JSON.parse(post_data)
userName = res_Data['username']
#responseFromServer=''
uri = URI('https://test.com/test1')
Net::HTTP.start(uri.host, uri.port,
:use_ssl => uri.scheme == 'https',
:verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
request = Net::HTTP::Post.new uri.request_uri
request.basic_auth 'aa', 'bb'
request.body = {'username' =>userName}.to_json
response = http.request request
#responseFromServer = response.body.to_s
end
newJson = JSON.parse(#responseFromServer)
status_msg = newJson['status']['status_msg']
if (status_msg == "Success")
return 'true'
end
return 'false'
end
It is method worked but he use OpenSSL::SSL::VERIFY_NONE. How to create method for send POST request without OpenSSL::SSL::VERIFY_NONE and PEM sertificate?
EDIT
SSL/HTTPS request
Update: There are some good reasons why this code example is bad. It introduces a potential security vulnerability if it's essential you use the server certificate to verify the identity of the server you're connecting to. There's a fix for the issue though!
require "net/https"
require "uri"
uri = URI.parse("https://secure.com/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
response.body
response.status
response["header-here"] # All headers are lowercase
SSL/HTTPS request with PEM certificate
require "net/https"
require "uri"
uri = URI.parse("https://secure.com/")
pem = File.read("/path/to/my.pem")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.cert = OpenSSL::X509::Certificate.new(pem)
http.key = OpenSSL::PKey::RSA.new(pem)
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
request = Net::HTTP::Get.new(uri.request_uri)
My question: How to create POST method without PEM and OpenSSL::SSL::VERIFY_NONE?
This question is quite misleading, but I try my best to figure it out. Here is my advise:
Do you want to connect to a service that is only available through https and you do not care if the certificate is valid?
Then you can use :verify_mode => OpenSSL::SSL::VERIFY_NONE when initializing the Net::HTTP client.
You will have some kind of transport security, but you cannot be sure the server you are talking to is the one you think it is. You are vulnerable.
Do you want to connect to a service that is available both through https and http, and you do not care about transport security?
Then you should use the http://... endpoint.
Do you want to connect to a service and you care about transport security?
Then you should definitely use the https://... endpoint.
Do not override :verify_mode!
If you are getting certificate verification errors, make sure you have the correct Certificate Authority installed on your system.

Twitter API: Constantly getting 401 status using Oauth and ruby gem (Timestamp Out of bounds)

im new to ruby and trying to play a bit with Twitters API. I got my keys from twitter dev site and set their permissions to 'read, write and access direct messages'. I then tried to use some code found on codecademy to retrieve status code but got 401 error (replaced keys with X). The weird things is that this code in codecademy retrieves 200 status code, so im thinkings is something in my computer:
require 'rubygems'
require 'oauth'
consumer_key = OAuth::Consumer.new(
"X",
"X")
access_token = OAuth::Token.new(
"X",
"X")
# All requests will be sent to this server.
baseurl = "https://api.twitter.com"
# The verify credentials endpoint returns a 200 status if
# the request is signed correctly.
address = URI("#{baseurl}/1.1/account/verify_credentials.json")
# Set up Net::HTTP to use SSL, which is required by Twitter.
http = Net::HTTP.new address.host, address.port
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
I also tried using twitters gem (adding of course the needed keys) and this time i get the following error Timestamp out of bounds (Twitter::Error::Unauthorized)
require 'rubygems'
require 'twitter'
Twitter.configure do |config|
config.consumer_key = YOUR_CONSUMER_KEY
config.consumer_secret = YOUR_CONSUMER_SECRET
config.oauth_token = YOUR_OAUTH_TOKEN
config.oauth_token_secret = YOUR_OAUTH_TOKEN_SECRET
end
Twitter.update("I'm tweeting with #gem!")
I'm running ruby 1.9.3p429.
Any help would be more than appreciated.
I have been having your same issues and looking at oauth docs I have found the following:
Make a regular GET request using AccessToken:
#response = #token.get('/people')
#response = #token.get('/people', { 'Accept'=>'application/xml' })
You will find more details here if you are curious: http://oauth.rubyforge.org/rdoc/classes/OAuth/AccessToken.html
Anyhow here is my code:
def prepare_access_token()
#opts = {
'oauth_token' => 'TOKEN',
'oauth_token_secret' => 'TOKEN_SECRET',
'api_key' => 'API_KEY',
'api_secret' => 'API_SECRET'
}
consumer = OAuth::Consumer.new("#{#opts['api_key']}", "#{#opts['api_secret']}",
{ :site => "http://api.twitter.com",
:scheme => :header
})
# now create the access token object from passed values
token_hash = { :oauth_token => "#{#opts['oauth_token']}",
:oauth_token_secret => "#{#opts['oauth_token_secret']}"
}
access_token = OAuth::AccessToken.from_hash(consumer, token_hash )
return access_token
end
#here is your url
call_url = "https://api.twitter.com/1.1/statuses/home_timeline.json"
uri = URI.encode(call_url)
# Exchange our oauth_token and oauth_token secret for the AccessToken instance.
access_token = prepare_access_token()
# use the access token as an agent to get the home timeline
response = access_token.get(uri)
This worked for me. If you have feedback feel free to share it :)
I was having the same issue yesterday, since a couple days ago. What happened was that I lived overseas for a while, and I had my region set up to region X, but had manually changed my clock to my current one. As soon as I set up my current region and sync'd with the internet timezone, the error was gone.
I had the same problem (401) I resolved it by going into the app in twitter and regenerating the consumer key and Consumer Secret, which is very easy as there is a button to do it, using the new values stopped my 401 errors. Hope this helps someone.

Resources