Kraken private API with F# returns EGeneral: invalid arguments - rest

I am trying to access the Kraken private API using F#. The code to access the public API runs perfectly fine, but when i try to access the private API i am always getting the error "EGeneral:Invalid arguments".
#r "FSharp.Data.dll"
open FSharp.Data
open System
open System.Text
open System.Security.Cryptography
let baseUri = "https://api.kraken.com"
let key = MY_KRAKEN_API_KEY
let secret = MY_KRAKEN_API_SECRET
let path = "/0/private/Balance"
let nonce = DateTime.UtcNow.Ticks
let bodyText = "nonce=" + nonce.ToString()
let hmac (key : byte []) (data : byte[]) =
use hmac = new HMACSHA512(key)
hmac.ComputeHash(data)
let sha256 (data : string) =
use sha = SHA256Managed.Create()
sha.ComputeHash(Encoding.UTF8.GetBytes(data))
let createSignature (nonce : int64) body (path : string) secret =
let shaSum = nonce.ToString() + body |> sha256
let data = Array.append (Encoding.UTF8.GetBytes path) shaSum
let key = Convert.FromBase64String secret
hmac key data |> Convert.ToBase64String
let signature = createSignature nonce bodyText path secret
let response = Http.RequestString (
url = baseUri + path,
httpMethod = "POST",
headers = ([("API-Key", key); ("API-Sign", signature)] |> Seq.ofList),
body = TextRequest bodyText
)
Does anybody see what i am doing wrong?
EDIT:
The Kraken.com API documentation is awailable here: https://www.kraken.com/help/api
I suppose the header signature is incorrect. The docu requires the following two values to be submitted in the header:
API-Key = API key API-Sign = Message signature using HMAC-SHA512 of
(URI path + SHA256(nonce + POST data)) and base64 decoded secret API
key
EDIT 2:
The remaining parameters need to be transmitted with a POST method. In my case this is only the "nonce" value in the body part of the HTTP request.

I had the same error while I was writing a C# library for Kraken and I found a solution of this problem:
This error does not appears if the API key or the sign are wrong or missing. The problem is that you do not add a mediatype to your request. I do not know how it works in F# but look at this example:
using (var client = new HttpClient())
{
string address = String.Format("{0}/{1}/public/{2}", _url, _version, method);
// Does not work with this:
// var content = new StringContent(postData, Encoding.UTF8);
var content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = await client.PostAsync(address, content);
return await response.Content.ReadAsStringAsync();
}
The "application/x-www-form-urlencoded" is the critical path. If you do not send a request with that, you get the "EGeneral: invalid arguments"-error. With it, everything works fine. At least in my case.

Related

Downloading files with a manually generated signed url errors with "SignatureDoesNotMatch"

I've followed this guide to implement a URL signer for cloud storage download URLs in Rust.
Bucket and service account have necessary permissions.
I'm using signBlob technique to sign the string.
Here is the output of my canonical request.
GET
/abc.pd
x-goog-algorithm=GOOG4%2DRSA%2DSHA256&x-goog-credential=basak%2Dservice%40alibasak%2Eiam%2Egserviceaccount%2Ecom%2F20220521%2Fauto%2Fstorage%2Fgoog4%5Frequest&x-goog-date=20220521T004156Z&x-goog-expires=3600&x-goog-signedHeaders=host
host:basak.storage.googleapis.com
host
UNSIGNED-PAYLOAD
My string to sign.
(Hash is calculated from the canonical request with SHA256)
GOOG4-RSA-SHA256
20220521T004156Z
20220521/auto/storage/goog4_request
43b27d5947adf3b915d8a5a51cfe2f5cf1344a12b3d3731287cf4741525eabef
The final signed url I produce.
https://basak.storage.googleapis.com/abc.pd?x-goog-algorithm=GOOG4%2DRSA%2DSHA256&x-goog-credential=basak%2Dservice%40alibasak%2Eiam%2Egserviceaccount%2Ecom%2F20220521%2Fauto%2Fstorage%2Fgoog4%5Frequest&x-goog-date=20220521T004156Z&x-goog-expires=3600&x-goog-signedHeaders=host&x-goog-signature=TBqhQ9edLBnGC0z8jhWPAt6NDGM87PHcdAZBt2bcVfd9N/zE1i/HY0jUi5XZoMUgABoBvU36dizS4lr8PrOjXG6GT9KgXbEBcrQqPb83outeAfhL2pgXgbQjXcetFX7cYzY3GSULRWs7+7wH0rxMWiQ6E3tahraBUXI9VZ2XqbUGLuZZXtOhExQ14dKWOnvVVEl0C5BehMEXpDzMFXSWUrsuDMpDlN86nwaJgcGlTNBBrot7J2gMde+xGcJ4zC/c3BADoKHGdjhyOzQh7zToQHnpkLHdEVILUD7k4CN6f9TNzvUGsqNABJ3H4t3fwDgZ/2OqSJ9Na6Xisi2OMMgaTQ==
Trying a get request with this URL results with this error,
<?xml version='1.0' encoding='UTF-8'?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.</Message>
<StringToSign>GOOG4-RSA-SHA256
20220521T004156Z
20220521/auto/storage/goog4_request
e38b8ab7532e4fa3009a7234313086ed45551be91398a5eef856eef4bfd857cf</StringToSign>
<CanonicalRequest>GET /abc.pd x-goog-algorithm=GOOG4-RSA-SHA256&x-goog-credential=basak-service%40alibasak.iam.gserviceaccount.com%2F20220521%2Fauto%2Fstorage%2Fgoog4_request&x-goog-date=20220521T004156Z&x-goog-expires=3600&x-goog-signedHeaders=host
host:basak.storage.googleapis.com
host
UNSIGNED-PAYLOAD</CanonicalRequest>
</Error>
I've read that sometimes a header mismatch was causing this, I've tried to make this request in browser, curl and with a node script. Each of those give me the same error.
Could you point me to the right direction about debugging this or maybe you may think of causes which I'm not aware yet.
Thanks a lot.
Update
John Hanley suggested that the problem might be in the encoding.
I've applied the fixes he suggested such as sorting the headers and took the example in the end of this page as a reference to percent encode the URL.
Now the canonical request looks like this
GET
/abc.pd
X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=basak-service%40alibasak.iam.gserviceaccount.com%2F20220521%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20220521T082931Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host
host:basak.storage.googleapis.com
host
UNSIGNED-PAYLOAD
and the final URL looks like this,
https://basak.storage.googleapis.com/abc.pd?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=basak-service%40alibasak.iam.gserviceaccount.com%2F20220521%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20220521T082931Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host&X-Goog-Signature=SEf8qecHjF+T+Uy71jygrEzrj2I1TXeKdUAOpm+BEf0Xm0W9e/LH5MC5BN+JA+OZ44EHN///Ai8ZO7GXqmE6+xbjrVhPlVETkLQSQ84GVecxj6onauRmpDTpxW8RXc+fXdzia75YndT5D1HKjvy8nI26Va8YMG2U3W/A7HMjg5YG+Cmcow3Pw1jpjIG+03gLLobhVMTnyp63S5AnycWT3Wzf7uo6l/WR7MxMK7pKA3isXXhQg7g9o8XUFFMvesDZUsI6mLYakxFHKHL42p1h6/P1SCFke1lpuYa9pV/EVnUMVfLp1ZfkgA1WBLqKZzyPNhMepfgIsUiUv2AKx/VoWQ==
I've also used the cli command,
gsutil -i basak-service#alibasak.iam.gserviceaccount.com signurl -d 10m -u gs://basak/abc.pd
to sign the url for comparison and the result is
https://storage.googleapis.com/basak/abc.pd?x-goog-signature=454801bc0d9ae19b5c5465a4e76846abbb1775549fe2532c839952125a54c5b1cb8f89385ac1cc1a8313e23945951d259a2ddc5a1c95890a205db1ab30a32d6efe8a2e706d03c68de6c4a502f50ff1a1b7fae5b94e3aa85768bfe473abf557eb8ae4e2b15ff9a5bb73ccb3d0bd1b8470cbe0bcb7ec6538fc575664672d641cb9f3c63ec04c41c13a6f2f6329290ce82bc57a700137edcf6fbade0885dd8130ebe2ba9bfe48f91ec94bf6e85b2ac8a7a26aeda77cbd5b0c30136d77defffeb5493f08bf9479f84522c1cb78503693e8ceab79fe0c6282ac4ecaa7e33b6355d2a7f870409b777512819ef54628a86a43b14ce8370477d11a9f857c2ec4ada90d6b&x-goog-algorithm=GOOG4-RSA-SHA256&x-goog-credential=basak-service%40alibasak.iam.gserviceaccount.com%2F20220521%2Fus%2Fstorage%2Fgoog4_request&x-goog-date=20220521T090829Z&x-goog-expires=600&x-goog-signedheaders=host
this url works perfectly.
The differences are:
Header order starts with the signature and continues sorted.
Headers are lowercase
The signature does not contain slashes. (It could be that I need to decode the signature? I'll check.)
I've implemented the first two in my code quickly and nothing changed.
On the other hand I suspect that maybe I'm doing something wrong with the signature since neither the example nor the gsutil generated url have slashes in the signature..
As John Hanley suggested I am sharing my code.
(The code is written in a sketchy way to debug and make it work first. Imports and use statements omitted.)
const SERVICE_ACCOUNT_EMAIL: &str = "basak-service#alibasak.iam.gserviceaccount.com";
#[derive(Serialize)]
struct SignRequest {
// The sequence of service accounts in a delegation chain. Each service account must be granted the roles/iam.serviceAccountTokenCreator role on its next service account in the chain. The last service account in the chain must be granted the roles/iam.serviceAccountTokenCreator role on the service account that is specified in the name field of the request.
// The delegates must have the following format: projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID}. The - wildcard character is required; replacing it with a project ID is invalid.
delegates: Vec<String>,
// Required. The bytes to sign.
// A base64-encoded string.
payload: String,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SignResponse {
key_id: String,
signed_blob: String,
}
fn credential_scope(date: &str, location: &str, service: &str, request_type: &str) -> String {
format!("{date}/{location}/{service}/{request_type}")
}
const FRAGMENT: &AsciiSet = &CONTROLS
// ?=!#$&'()*+,:;#[]."
.add(b'/')
.add(b' ')
.remove(b'=')
.remove(b'!')
.remove(b'#')
.remove(b'$')
.remove(b'&')
.remove(b'\'')
.remove(b'(')
.remove(b')')
.remove(b'*')
.remove(b'+')
.remove(b',')
.remove(b':')
.remove(b';')
.add(b'#')
.remove(b'[')
.remove(b']')
.remove(b'.')
.remove(b'"');
async fn generate_signed_url(
bucket_name: &str,
object_name: &str,
expiration: &str,
token: &gcp_auth::Token,
) -> Result<String, reqwest::Error> {
// We'll use google's signing service to generate a signature.
let sign_blob_url = format!("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{SERVICE_ACCOUNT_EMAIL}:signBlob?alt=json");
let mut query_parameters = std::collections::BTreeMap::new();
let canonical_uri = format!("/{object_name}");
let date = chrono::offset::Utc::today().format("%Y%m%d").to_string();
let time_stamp = chrono::offset::Utc::now()
.format("%Y%m%dT%H%M%SZ")
.to_string();
let credential_scope = credential_scope(&date, "auto", "storage", "goog4_request");
let credential = format!("{SERVICE_ACCOUNT_EMAIL}/{credential_scope}");
let host = format!("{bucket_name}.storage.googleapis.com");
let algorithm = "GOOG4-RSA-SHA256";
query_parameters.insert("x-goog-algorithm", algorithm);
query_parameters.insert("x-goog-credential", &credential);
query_parameters.insert("x-goog-date", &time_stamp);
query_parameters.insert("x-goog-expires", expiration);
query_parameters.insert("x-goog-signedHeaders", "host");
let mut canonical_query_string =
query_parameters
.iter()
.fold("".to_owned(), |mut acc, (k, v)| {
//
let encoded_k = percent_encode(k.as_bytes(), FRAGMENT);
let encoded_v = percent_encode(v.as_bytes(), FRAGMENT);
acc.push_str(&format!("{}={}&", encoded_k, encoded_v));
acc
});
canonical_query_string.pop();
// let canonical_headers = format!("content-type:text/plain\nhost:{host}");
// let signed_headers = "content-type;host";
let canonical_headers = format!("host:{host}");
let signed_headers = "host";
// HTTP_VERB
// PATH_TO_RESOURCE
// CANONICAL_QUERY_STRING
// CANONICAL_HEADERS
// \n
// SIGNED_HEADERS
// PAYLOAD
let canonical_request = format!(
"GET\n{canonical_uri}\n{canonical_query_string}\n{canonical_headers}\n\n{signed_headers}\nUNSIGNED-PAYLOAD"
);
println!("canonical_request: {}", canonical_request);
let mut hasher = Sha256::new();
hasher.update(canonical_request);
let hashed_canonical_request = format!("{:x}", hasher.finalize());
let string_to_sign =
format!("{algorithm}\n{time_stamp}\n{credential_scope}\n{hashed_canonical_request}");
println!("string_to_sign: {}", string_to_sign);
let body = SignRequest {
delegates: vec![],
payload: base64::encode(string_to_sign),
};
let client = reqwest::Client::new();
let response = client
.post(sign_blob_url)
.bearer_auth(token.as_str())
.json(&body)
.send()
.await?
.bytes()
.await?;
dbg!(&response);
let sign_response: SignResponse = serde_json::from_slice(&response).unwrap();
let signed_url = format!(
"https://{host}{canonical_uri}?x-goog-signature={}&{canonical_query_string}",
// percent_encode(response.signed_blob.as_bytes(), FRAGMENT)
sign_response.signed_blob
);
Ok(signed_url)
}
First of all big thanks to John Hanley for motivating me to resolve this.
As he suggested there were multiple issues.
The percent encoding was incorrect and comparing it with the CLI output helped me to correct it.
The headers should be sorted and the same technique of comparing also helped me to find the right sorting.
The major issue was something else though.
It is my mistake that I've missed this in the docs.
In the doc of signedBlob it clearly states that the signature in the response is a Base64 encoded string. I was using it plainly instead.
After receiving the response doing,
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct SignResponse {
key_id: String,
signed_blob: String,
}
let response: SignResponse = serde_json::from_slice(&response).unwrap();
// First decode the signature
let decoded_signature = base64::decode(response.signed_blob).unwrap();
// Then make a string with hex representation of bytes
let hex_encoded_signature = hex::encode(decoded_signature);
// Then construct the url
let signed_url = format!("https://{host}{canonical_uri}?x-goog-signature={hex_encoded_signature}&{canonical_query_string}");
resolved the issue and I can even use the resulting URL in the browser!
By experimentation I've also derived that using lowercase (x-goog-..) or uppercase (X-Goog..) headers or even mixing them do not change anything.
This comment also confused me a little throughout the process:
When defining the resource path, you must percent encode the following reserved characters: ?=!#$&'()*+,:;#[]." Any other percent encoding used in the URL should also be included in the resource path.
which is taken from this source
Because it says to percent encode that character set but does not include characters such as /, etc. which should be percent encoded also and such as . shouldn't be encoded.
I am still confused about it a little but maybe I should read more about percent encoding techniques.
Anyway I'm happy that it is resolved and hope it helps someone later.

How to get a new ChildByAutoID from the FIrebase REST API without writing any actual data

In Swift, I can do the following using the Firebase API to obtain a new ID generated by Firebase:
let newListingRef = self.ref!.child("listings").child(location).childByAutoId()
let newBookId = newListingRef.key
I then use this ID to create a filename for an image I upload to Firebase Storage.
I am trying to do the same using the REST API, I know how to upload data to a given location using a POST request, but is there a way to replicate the above behaviour using the REST API?
UPDATE:
let location = locationTextField.Text
let url = "https://proj_id.firebaseio.com/listings/"
let random = "grjirjgrirj"
let postfix = ".json"
let slash = "/"
let strings = [url;location;slash;random;postfix]
let url = System.String.Concat(strings)
let req = WebRequest.Create(Uri(url))
req.Method <- "POST"
req.ContentLength <- int64(0)
req.ContentType <- "application/json; charset=utf-8"
let reqStream = req.GetRequestStream()
reqStream.Close()
use resp = req.GetResponse()
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
The resulting URL is the following:
https://proj_id.firebaseio.com/listings/London/grjirjgrirj.json
If you just want a push ID (how it's more commonly known in other Firebase SDKs) with no data behind it, perform the POST with an empty object at a location that is known not to exist:
curl -X POST -d '{}' 'https://your-project.firebaseio.com/definitely/doesnt/exist.json'
It will make no changes to your database, and will return you a push ID in the JSON response, which you can later use to add actual data.

Kraken EAPI: Invalid key

I am trying to do a client with swift however I cannot communicate with the private api {"error": ["EAPI: Invalid key"]}
I use CCHmac function and tried to have a look at different api implementation.
I just can't get whats wrong...
Here is my code:
func getTradeBalance(basedAsset: String, completionBlock: #escaping BlockResult) {
guard let url = URL(string: "\(krakenUtils.rootUrl)/\(krakenUtils.version)/private/Balance") else {
return
}
let nonce: String = String(Int(Date().timeIntervalSince1970.rounded()))
let path = "/\(krakenUtils.version)/private/Balance"
let pubKey = krakenUtils.publicKey
let params = ["nonce": nonce]
//Sign = HMAC-SHA512 of URI + SHA256-POSTDATAS + base64decodedSecret
let sign = getMessageSignature(path: path,
nonce: nonce)
Alamofire.request(url, method: .post,
parameters: params, encoding: JSONEncoding.default,
headers: ["API-Key": pubKey,
"API-Sign": sign])
.responseJSON { resp in
let result = self.handleResponse(result: resp)
guard let json = result.0 else {
completionBlock(nil, result.1)
return
}
print(json)
}
}
private func getMessageSignature(path: String, nonce: String) -> String {
let secretDecoded = Data(base64Encoded: krakenUtils.privateKey, options: Data.Base64DecodingOptions.init(rawValue: 0))!
let np = (nonce + "nonce=" + nonce).sha256().data(using: .utf8, allowLossyConversion: false)!
var pathNP = path.data(using: .utf8, allowLossyConversion: false)!
pathNP.append(contentsOf: np)
let lRet = HMAC.sign(data: pathNP, algorithm: .sha512, key: secretDecoded).base64EncodedString()
return lRet
}
public static func sign(data: Data, algorithm: Algorithm, key: Data) -> Data {
let signature = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: algorithm.digestLength)
data.withUnsafeBytes { dataBytes in
key.withUnsafeBytes { keyBytes in
CCHmac(algorithm.algorithm, keyBytes, key.count, dataBytes, data.count, signature)
}
}
return Data(bytes: signature, count: algorithm.digestLength)
}
This is guide for authenticated call HTTPS Header:
API-Key = API key
API-Sign = Message signature using HMAC-SHA512 of (URI path + SHA256(nonce + POST data)) and base64 decoded secret API key
This is guide for authenticated call POST Data:
nonce = always increasing unsigned 64 bit integer
otp = two-factor password (if two-factor enabled, otherwise not required)
For the API-Key you use krakenUtils.publicKey. The name suggests you use some public key (do not know where you got it)
However this should be your personal API-Key. You can get it at the kraken site (login with your account) and create an API key. You also get your API-code here. They go together as a pair
Although this is an old question, many users (including myself) who have attempted to use Kraken’s API have encountered and reported similar problems (INVALID KEY). Recently I reported the issue I was having to a Kraken rep…after an exchange with several reps it was discovered that there was a flaw in the posted Kraken example on their website. Here is some of the exchange:
...”In the method QueryPrivateEndpoint there is a line of code (should be line 256 from the file downloaded) that looks like this:
String apiEndpointFullURL = baseDomain + privatePath + endPointName + "?" + inputParameters;
It needs to be modified to look like this:
String apiEndpointFullURL = baseDomain + privatePath + endPointName;
After you make that code change, the Invalid Key error should go away.
…….
Thank you for your gratitude and pointing out the mistake to our attention.
We are pleased to know the issue has been resolved.
“……
They will post the update to their example code on their website.
Also, it’s worth noting that an improper/inconsistent ORDER of the parameters in a call that has parameters associated with it can cause an INVALID KEY error.

Swift Vapor unsupported_grant_type invalid signature / OAuth access token

I am running Xcode 8.1 with Vapor and SWIFT 3.
I am posting a request to to google server to get an auth token, so I can call FireBaseDB API, but I get error: unsupported_grant_type/Invalid grant_type.
On developers.google.com it says that I have to encode in a URL the following: https://www.googleapis.com/oauth2/v4/token + grant_type + assertion, and pass the encoded URL in the body of the POST request. I pass it as a string.
I have noticed that the private key from the JSON file downloaded from my service account contains characters such as /n , ----,==, should I delete them before posting the key?
let dateNow = Date()
var expDate = String(Int(dateNow.timeIntervalSince1970 + (60 * 60)))
var iatDate = String(Int(dateNow.timeIntervalSince1970))
let headerJWT = ["alg":"HS256","typ":"JWT"]
let jwtClaimSet =
["iss":"firebase-adminsdk-c7i48#fir-10c2e.iam.gserviceaccount.com",
"scope":"https://www.googleapis.com/auth/firebase.database",
"aud":"https://www.googleapis.com/oauth2/v4/token",
"exp": expDate,
"iat": iatDate]
//create and sign JSON Web Token
let jwt = try JWT(headers: Node(node: headerJWT),
payload: Node(node: jwtClaimSet),
signer: HS256(key:"-----BEGIN PRIVATE KEY-----\nMIIEvAWvQ== \n-----END PRIVATE KEY-----\n"))
// Store JSON Web Token
let JWTtoken = try jwt.createToken()
func createUrlWithString() -> NSURL {
var urlString = "https://www.googleapis.com/oauth2/v4/token"
urlString.append("?grant_type=")
urlString.append("urn:ietf:params:oauth:grant-type:jwt-bearer")
urlString.append("&assertion=")
urlString.append(JWTtoken)
return NSURL(string: urlString)!
}
// make the body input for our POST
let bodyURL = createUrlWithString().absoluteURL
drop.get("any") { request in
let response =
try drop.client.request(.other(method:"Post"),
"https://www.googleapis.com/oauth2/v4/token",
headers: ["Content-Type": "application/x-www-form-urlencoded"],
query: [:],
body: String(describing: bodyURL) )
let serverResp = response.headers
let serverBody = response.body.bytes
let serverJson = try JSON(bytes: serverBody!)
print(serverJson)
return "POST Request went through"
}
Update
As per Karol Gasienica suggestion, I am passing grant_type and assertion parameters as POST request parameters. Now I get "error_description": Node.Node.string("SSL is required to perform this operation.")]))
func createUrlWithString() -> NSURL {
var urlString = "https://www.googleapis.com/oauth2/v4/token"
urlString.append("?grant_type=")
urlString.append("urn:ietf:params:oauth:grant-type:jwt-bearer")
urlString.append("&assertion=")
urlString.append(JWTtoken)
return NSURL(string: urlString)!
}
let response = try drop.client.request(.other(method:"Post"),
String(describing: bodyURL),
headers: ["Content-Type": "application/x-www-form-urlencoded"],
query: [:])
When you generate a service account credential you need to have in mind the following, taken from https://cloud.google.com/storage/docs/authentication:
You can create a private key in the Cloud Platform Console by creating an OAuth Client ID for a service account. You can get your private key in JSON and PKCS12 format:
JSON keys are required if you are using Application Default Credentials in a production environment outside of Google Cloud Platform. JSON keys cannot be converted to other formats.
PKCS12 (.p12) is supported by many different programming languages and libraries. If needed, you can convert the key into other formats using OpenSSL (see Converting the private key to other formats). However, PKCS12 keys cannot be converted to JSON format.
Create your service account and then download your .p12 file.
Convert the p.12 (a.k.a pkcs12) file to .pem (a.k.a pkcs1) using OpenSSL
cat /path/to/xxxx-privatekey.p12 | openssl pkcs12 -nodes -nocerts -passin pass:notasecret | openssl rsa > /path/to/secret.pem
Go to github and search VaporJWT and import it in Xcode. It will help you create a signed JSON Web Token.
On this github page you will learn how to extract the private key for RSA use.
// convert .pem to der
openssl rsa -in /path/to/secret.pem -outform der -out /path/to/private.der
//convert .der to .base64
openssl base64 -in /path/to/private.der -out /path/to/Desktop/private.txt
In private.txt you have the private key encoded in base64 which you can finally use to sign you JWT. Then you can make calls to Google API with the signed JWT.
import Vapor
import VaporJWT
let drop = Droplet()
var tokenID:String!
//set current date
let dateNow = Date()
// assign to expDate the validity period of the token returnd by OAuth server (3600 seconds)
var expDate = String(Int(dateNow.timeIntervalSince1970 + (60 * 60)))
// assign to iatDate the time when the call was made to request an access token
var iatDate = String(Int(dateNow.timeIntervalSince1970))
// the header of the JSON Web Token (first part of the JWT)
let headerJWT = ["alg":"RS256","typ":"JWT"]
// the claim set of the JSON Web Token
let jwtClaimSet =
["iss":"firebase-adminsdk-c7i38#fir-30c9e.iam.gserviceaccount.com",
"scope":"https://www.googleapis.com/auth/firebase.database",
"aud":"https://www.googleapis.com/oauth2/v4/token",
"exp": expDate,
"iat": iatDate]
//Using VaporJWT construct a JSON Web Token and sign it with RS256 algorithm
//The only signing algorithm supported by the Google OAuth 2.0 Authorization Server is RSA using SHA-256 hashing algorithm.
let jwt = try JWT(headers: Node(node: headerJWT), payload: Node(node:jwtClaimSet), encoding: Base64URLEncoding(), signer: RS256(encodedKey: "copy paste here what you have in private.txt as explained at point 7 above "))
// create the JSON Web Token
let JWTtoken = try jwt.createToken()
let grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer" // this value must not be changed
let unreserved = "*-._"
let allowed = NSMutableCharacterSet.alphanumeric()
allowed.addCharacters(in: unreserved)
// percent or URL encode grant_type
let grant_URLEncoded = grant_type.addingPercentEncoding(withAllowedCharacters: allowed as CharacterSet)
// create a string made of grant_type and assertion. NOTE!!! only grant_type's value is URL encoded.
//JSON Web Token value does not need to be URL encoded
var fullString = "grant_type=\(grant_URLEncoded!)&assertion=\(JWTtoken)"
//pass fullString in the body parameter
drop.get("call") { request in
let response = try drop.client.post("https://www.googleapis.com/oauth2/v4/token", headers: ["Content-Type": "application/x-www-form-urlencoded"], query: [:],body: fullString)
let serverResp = response.headers
let serverBody = response.body.bytes
let serverJson = try JSON(bytes: serverBody!)
print(serverJson)
return "Success"
}
It seems like you dont set properly grant_type in your code:
urlString.append("?grant_type=")
In your case it might be grant_type=authorization_code
or
grant_type=jwt-bearer
It seems like you set grant_type in wrong place.
Update
Also I think grant_type and assertion parameters are not passed as request headers but as post request parameters
Update
Im not really sure you are using correct way to put POST (body) parameters. In this documentation is example how to create request with post prams and it looks like follows:
try drop.client.request(.other(method: "POST"), "http://some-domain", headers: ["My": "Header"], query: ["key": "value"], body: [])

Cloudant function-clause error at HTTP GET request

This is my first question here and I have not much experience in coding so please bear with me. Thanks!
I defined some documents in my Bluemix Cloudant account with different cars which have different characteristics. I want to get one entry from an IOS Swift front-end App.
This is an example query url:
https://$ACCOUNT-bluemix.cloudant.com/cars/_design/car_index/_search/car_index_name?q=size:small
Now the problem: If I use this url in a browser I get the correct results in JSON format back without any error. But if the app makes the request a function-clause error is logged while the request itself seems to be successful.
I read that a function_clause error is caused by some bug in the Javascript Cloudant uses for indexing the documents. The Javascript I'm using is exactely the same as Cloudant states it in the tutorials.
Has anyone an idea why it works in the browser but not in the App?
Thank you very much for any help!
Here is all the code:
This is the method I use in swift to make the request:
func databaseRequest(size: String, interior: String, fuel: String) {
let baseURL = "https://$ACCOUNT-bluemix.cloudant.com/cars/_design/car_index/_search/car_index_name?q="
let queryURL = "size:\(size)"
let completeURL: String = baseURL + queryURL
let completeURLModified = completeURL.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
let requestURL = URL(string: completeURLModified!)
var request = URLRequest(url: requestURL!)
request.httpMethod = "GET"
request.setValue("Basic \(credentials)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request){data, response, error in
guard error == nil else{
print("There was an error:", error as Any)
return
}
guard data == data else{
print("Data is empty")
return
}
let jsonResponse = try! JSONSerialization.jsonObject(with: data!, options: [])
print("This is JSON Response", jsonResponse)
}; task.resume()
}
This is the response from the JSON answer:
This is JSON Response {
error = "unknown_error";
reason = "function_clause";
ref = 1944801346;
}
The rest of log from http headers if this is helpful:
Optional(<NSHTTPURLResponse: 0x6080000349c0> { URL: https://$ACCOUNT-bluemix.cloudant.com/cars/_design/car_index/_search/car_index_name?q=size:small } { status code: 500, headers {
"Cache-Control" = "must-revalidate";
"Content-Length" = 70;
"Content-Type" = "application/json";
Date = "Thu, 24 Nov 2016 04:41:03 GMT";
Server = "CouchDB/2.0.0 (Erlang OTP/17)";
"Strict-Transport-Security" = "max-age=31536000";
Via = "1.1 lb1.bm-cc-dal-01 (Glum/1.31.3)";
"X-Cloudant-Backend" = "bm-cc-dal-01";
"X-Content-Type-Options" = nosniff;
"X-Couch-Request-ID" = 51e5e0b5e1;
"X-Couch-Stack-Hash" = 1944801346;
"X-CouchDB-Body-Time" = 0;
Last but not least the Javascript file I use as Index in the design document in Cloudant:
function (doc) {
index("name", doc.name, {"store": true});
if (doc.fuel){ index("fuel", doc.fuel, {"store": true});}
if (doc.interior){ index("interior", doc.interior, {"store": true});}
if (doc.size){index("size", doc.size, {"store": true});
}}
I think this error is due to cloudant trying to decode whatever you passed as \(credentials) as a base64 encoded string. If \(credentials) is not a valid base64 encoded string (e.g. contains characters other than a-z, A-Z, 0-9, +, / and =), then my guess is that cloudant's base64 decoding function fails with the above error.
You need to make sure that \(credentials) is the string <your_username>:<your_password> encoded correctly. E.g. if your username is john and your password is doe, then \(credentials) should be am9objpkb2U=.