Skip to content

Instantly share code, notes, and snippets.

@cypres
Last active January 23, 2021 16:39
Show Gist options
  • Save cypres/a197fc9f6dc1ad59f08c to your computer and use it in GitHub Desktop.
Save cypres/a197fc9f6dc1ad59f08c to your computer and use it in GitHub Desktop.
Two-legged OAuth 1.0a in C++11
//
// Two-legged OAuth 1.0a proof of concept
// Feel free to use, copy or modify to your own needs.
//
// Requires glog, gflags and crypto++ (cryptopp.com)
// Compile with:
// clang++ -std=c++11 -lcryptopp -lglog -lgflags oauth.cc -o oauth_test
// Run with:
// ./oauth_test -alsologtostderr
//
// Copyright (C) 2014 OnlineCity Aps <[email protected]>
//
// Licensed under The MIT License:
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <gflags/gflags.h>
#include <glog/logging.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-variable"
#pragma clang diagnostic ignored "-Wunused-function"
#include <cryptopp/cryptlib.h>
#include <cryptopp/hmac.h>
#include <cryptopp/sha.h>
#include <cryptopp/base64.h>
#include <cryptopp/secblock.h>
#include <cryptopp/filters.h>
#pragma clang diagnostic pop
#include <algorithm>
#include <functional>
#include <locale>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
// Encode following the RFC 5859 (OAuth) section 3.6
std::string OauthUrlEncode(const std::string &str) {
static const char *const lut = "0123456789ABCDEF";
std::string out;
out.reserve(str.length()*2);
for (auto c : str) {
if (std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~') {
out.push_back(c);
} else {
out.push_back('%');
out.push_back(lut[c >> 4]);
out.push_back(lut[c & 15]);
}
}
out.shrink_to_fit();
return out;
}
// Decode a RFC3986 encoded string
std::string OauthUrlDecode(const std::string &str) {
std::string decoded;
decoded.reserve(str.length());
for (auto it = str.cbegin(); it != str.cend(); ++it) {
auto const c = *it;
if (
c == '%' &&
it+1 != str.cend() &&
it+2 != str.cend() &&
std::isxdigit(*(it+1)) &&
std::isxdigit(*(it+2))
) {
std::string hex(3, 0);
hex[0] = *(it+1);
hex[1] = *(it+2);
auto cd = static_cast<std::string::value_type>(std::stol(hex, 0, 16));
decoded.push_back(cd);
it += 2;
} else if (c == '+') {
decoded.push_back(' ');
} else {
decoded.push_back(c);
}
}
return decoded;
}
// Convert an URL according to RFC 5849 (OAuth) section 3.4.1.2 + 3.6
// Returns the encoded URL and any query params specified in the URL
// Does not do any normalization of the URL. It's assumed the OAuth client already does this.
std::pair<std::string,std::string> OauthConvertUrl(const std::string &url) {
std::string protocol, host, path, query;
// Protocol
const std::string protocol_sep = "://";
auto proto_it = std::search(url.cbegin(), url.cend(), protocol_sep.cbegin(), protocol_sep.cend());
protocol.reserve(std::distance(url.cbegin(), proto_it));
std::transform(url.cbegin(), proto_it, std::back_inserter(protocol), (int (*)(int))std::tolower);
std::advance(proto_it, protocol_sep.length());
// Host
auto path_it = std::find(proto_it, url.cend(), '/');
host.reserve(std::distance(proto_it, path_it));
std::transform(proto_it, path_it, std::back_inserter(host), (int (*)(int))std::tolower);
// Path
auto query_it = std::find(path_it, url.cend(), '?');
path.assign(path_it, query_it);
// Query
if (query_it != url.cend()) {
++query_it;
}
query.assign(query_it, url.cend());
// Encode
std::string encoded;
encoded.append(OauthUrlEncode(protocol));
encoded.append(OauthUrlEncode(protocol_sep));
encoded.append(OauthUrlEncode(host));
encoded.append(OauthUrlEncode(path));
return std::make_pair(encoded, query);
}
typedef std::map<std::string, std::set<std::string>> OauthParams;
// Normalize params into a single string according to RFC 5849 (OAuth) section 3.4.1.3.2
// 1. Name and value is URL encoded
// 2. The parameters are sorted by name, using ascending byte value ordering
// 3. The name of each parameter is concatenated to its corresponding value using an "=" character
// 4. The sorted name/value pairs are concatenated together into a single string by using an "&" character
std::pair<std::string,OauthParams> OauthConvertParams(const std::string &url_params, const std::string &post_params, const std::string &oauth_header) {
// Use a map with a set, to always keep params sorted in the correct order
// (byte value ordering, first by key then value if same key)
OauthParams params;
// Parse and decode url_params + post_params, inserting them into params
std::string key, lower_key, value, signature;
bool val = false;
for (auto paramstr : { url_params, post_params } ) {
for (auto const c : paramstr) {
if (c == '=') {
val = true;
} else if (c == '&') {
if (key.compare("oauth_signature") != 0) {
params[OauthUrlDecode(key)].insert(OauthUrlDecode(value));
} else {
signature = value;
}
val = false;
key.clear();
value.clear();
} else {
if (val) {
value.push_back(c);
} else {
key.push_back(c);
}
}
}
if (!key.empty()) {
if (key.compare("oauth_signature") != 0) {
params[OauthUrlDecode(key)].insert(OauthUrlDecode(value));
}
key.clear();
value.clear();
}
val = false;
}
// Parse OAuth header
const std::string oauth_head_prefix = "OAuth ";
auto head_it = std::search(oauth_header.cbegin(), oauth_header.cend(), oauth_head_prefix.cbegin(), oauth_head_prefix.cend());
if (head_it != oauth_header.cend()) {
std::advance(head_it, oauth_head_prefix.length());
}
while (head_it != oauth_header.cend()) {
auto const c = *head_it;
if (c == '=') {
val = true;
++head_it;
} else if (++head_it == oauth_header.cend() || c == ',') {
// Convert to lowercase so we can compare it
lower_key.clear();
lower_key.reserve(key.length());
std::transform(key.cbegin(), key.cend(), std::back_inserter(lower_key), (int (*)(int))std::tolower);
// If not oauth_signature or realm (case insensitive) add it
if (lower_key.compare("oauth_signature") == 0) {
signature = value;
} else if (lower_key.compare("realm") != 0) {
params[OauthUrlDecode(key)].insert(OauthUrlDecode(value));
}
val = false;
key.clear();
value.clear();
} else if (c != '"' && !std::isspace(c)) {
if (val) {
value.push_back(c);
} else {
key.push_back(c);
}
}
}
// Encode and concatenate a string with all params
std::string encoded;
for (auto it = params.cbegin(); it != params.cend(); ) {
auto const k = it->first;
for (auto val_it = it->second.cbegin(); val_it != it->second.cend(); ) {
auto const v = *val_it;
encoded.append(OauthUrlEncode(k));
encoded.push_back('=');
encoded.append(OauthUrlEncode(v));
if (++val_it != it->second.cend()) {
encoded.push_back('&');
}
}
if (++it != params.cend()) {
encoded.push_back('&');
}
}
if (!signature.empty()) {
params["oauth_signature"].insert(signature);
}
return std::make_pair(OauthUrlEncode(encoded), params);
}
bool OauthValidateSignature(const std::string &base_string, const std::string &signature_key, const std::string &given_signature) {
using CryptoPP::SecByteBlock;
using CryptoPP::HMAC;
using CryptoPP::SHA1;
using CryptoPP::StringSource;
using CryptoPP::StringSink;
using CryptoPP::ArraySink;
using CryptoPP::Base64Decoder;
using CryptoPP::HashVerificationFilter;
std::string given_signature_raw;
SecByteBlock key(signature_key.length());
key.Assign(reinterpret_cast<const byte*>(signature_key.data()), signature_key.length());
bool result = false;
try {
StringSource(given_signature, true, new Base64Decoder(new StringSink(given_signature_raw)));
HMAC<SHA1> hmac(key, key.size());
const int flags = HashVerificationFilter::PUT_RESULT | HashVerificationFilter::HASH_AT_END;
StringSource(base_string + given_signature_raw, true, new HashVerificationFilter(hmac, new ArraySink((byte*)&result, sizeof(result)), flags));
} catch (const CryptoPP::Exception &e) {
LOG(DFATAL) << e.what();
}
return result;
}
// Do two-legged oauth using non-standard method ( http://oauth.googlecode.com/svn/spec/ext/consumer_request/1.0/drafts/2/spec.html )
int main(int argc, char **argv) {
google::ParseCommandLineFlags(&argc, &argv, true);
google::InitGoogleLogging(argv[0]);
const std::string authorize_header = "OAuth realm=\"http://provider.example.net/\","
"oauth_consumer_key=\"dpf43f3p2l4k3l03\","
"oauth_signature_method=\"HMAC-SHA1\","
"oauth_signature=\"IxyYZfG2BaKh8JyEGuHCOin%2F4bA%3D\","
"oauth_timestamp=\"1191242096\","
"oauth_token=\"\","
"oauth_nonce=\"kllo9940pd9333jh\","
"oauth_version=\"1.0\"";
const std::string http_method = "GET";
const std::string url = "http://provider.example.net/profile";
const std::string request_params;
const std::string ip_address = "127.0.0.1";
const std::string secret = "kd94hf93k423kf44";
// Generate a signature base string
std::string base_string;
for (const auto &c : http_method) {
base_string.push_back(std::toupper(c, std::locale::classic()));
}
base_string.push_back('&');
auto converted_url = OauthConvertUrl(url);
base_string.append(converted_url.first);
base_string.push_back('&');
auto converted_params = OauthConvertParams(converted_url.second, request_params, authorize_header);
base_string.append(converted_params.first);
LOG(INFO) << base_string;
CHECK_STREQ(base_string.c_str(), "GET&http%3A%2F%2Fprovider.example.net%2Fprofile&"
"oauth_consumer_key%3Ddpf43f3p2l4k3l03%26oauth_nonce%3Dkllo9940pd9333jh%26"
"oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26oauth_token%3D%26oauth_version%3D1.0");
// Decode the oauth_signature param
std::string given_signature = OauthUrlDecode(*converted_params.second["oauth_signature"].cbegin());
// Validate the signature
std::string signature_method = OauthUrlDecode(*converted_params.second["oauth_signature_method"].cbegin());
bool signature_valid = false;
if (signature_method.compare("HMAC-SHA1") == 0) {
// Construct the key used for HMAC-SHA1 signatures (consumer_secret + '&' + token_secret)
std::string signature_key(OauthUrlEncode(secret)+'&');
signature_valid = OauthValidateSignature(base_string, signature_key, given_signature);
} else if (signature_method.compare("PLAINTEXT") == 0) {
// Construct the plaintext signature (consumer_secret + '&' + token_secret)
std::string signature_key(OauthUrlEncode(secret)+'&');
signature_valid = signature_key.compare(given_signature) == 0;
} else {
DLOG(FATAL) << "Unsupported signature method: " << signature_method;
}
LOG(INFO) << signature_valid;
CHECK(signature_valid);
// TODO(reader) Validate the timestamp, nonce
}
@awesomeerictech
Copy link

i have a HMAC already and a key how do i get that working

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment