blob: 050857e89be5907dd313d2fea3a6c4130c85e58a [file] [log] [blame]
// Copyright 2019 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/browser/device_authentication.h"
#include <algorithm>
#include <map>
#include "base/base64.h"
#include "base/base64url.h"
#include "base/logging.h"
#include "base/time/time.h"
#include "crypto/hmac.h"
#include "net/base/escape.h"
#include "starboard/system.h"
namespace cobalt {
namespace browser {
namespace {
constexpr size_t kSHA256DigestSize = 32;
#if SB_API_VERSION < 13
bool ComputeSignatureWithSystemPropertySecret(const std::string& message,
uint8_t* signature) {
const size_t kBase64EncodedCertificationSecretLength = 1023;
char base_64_secret_property[kBase64EncodedCertificationSecretLength + 1] = {
0};
bool result = SbSystemGetProperty(
kSbSystemPropertyBase64EncodedCertificationSecret,
base_64_secret_property, kBase64EncodedCertificationSecretLength);
if (!result) {
return false;
}
ComputeHMACSHA256SignatureWithProvidedKey(message, base_64_secret_property,
signature, kSHA256DigestSize);
return true;
}
#endif // SB_API_VERSION < 13
bool ComputeSignatureFromSignAPI(const std::string& message,
uint8_t* signature) {
return SbSystemSignWithCertificationSecretKey(
reinterpret_cast<const uint8_t*>(message.data()), message.size(),
signature, kSHA256DigestSize);
}
// Check to see if we can query the platform for the secret key. If so,
// go ahead and use it to sign the message, otherwise try to use the
// SbSystemSignWithCertificationSecretKey() method to sign the message. If
// both methods fail, return an empty string.
std::string ComputeBase64Signature(const std::string& message) {
uint8_t signature[kSHA256DigestSize];
if (ComputeSignatureFromSignAPI(message, signature)) {
DLOG(INFO) << "Using certification signature provided by "
<< "SbSystemSignWithCertificationSecretKey().";
#if SB_API_VERSION < 13
} else if (ComputeSignatureWithSystemPropertySecret(message, signature)) {
DLOG(INFO) << "Using certification key from SbSystemGetProperty().";
#endif // SB_API_VERSION < 13
} else {
return std::string();
}
std::string base_64_url_signature;
base::Base64UrlEncode(std::string(signature, signature + kSHA256DigestSize),
base::Base64UrlEncodePolicy::OMIT_PADDING,
&base_64_url_signature);
return base_64_url_signature;
}
std::string NumberToFourByteString(size_t n) {
std::string str;
str += static_cast<char>(((n & 0xff000000) >> 24));
str += static_cast<char>(((n & 0x00ff0000) >> 16));
str += static_cast<char>(((n & 0x0000ff00) >> 8));
str += static_cast<char>((n & 0x000000ff));
return str;
}
// Used by ComputeMessage() to create a message component as a string.
std::string BuildMessageFragment(const std::string& key,
const std::string& value) {
std::string msg_fragment = NumberToFourByteString(key.length()) + key +
NumberToFourByteString(value.length()) + value;
return msg_fragment;
}
// Returns the certification scope provided by the platform to use with device
// authentication.
std::string GetCertScopeFromPlatform() {
// Get cert_scope and base_64_secret
const size_t kCertificationScopeLength = 1023;
char cert_scope_property[kCertificationScopeLength + 1] = {0};
bool result =
SbSystemGetProperty(kSbSystemPropertyCertificationScope,
cert_scope_property, kCertificationScopeLength);
if (!result) {
DLOG(ERROR) << "Unable to get kSbSystemPropertyCertificationScope";
return std::string();
}
return cert_scope_property;
}
// Returns the start time provided by the platform for use with device
// authentication.
std::string GetStartTime() {
return std::to_string(static_cast<int64_t>(base::Time::Now().ToDoubleT()));
}
} // namespace
std::string GetDeviceAuthenticationSignedURLQueryString() {
std::string cert_scope = GetCertScopeFromPlatform();
if (cert_scope.empty()) {
LOG(WARNING) << "Error retrieving certification scope required for "
<< "device authentication.";
return std::string();
}
std::string start_time = GetStartTime();
CHECK(!start_time.empty());
std::string base64_signature =
ComputeBase64Signature(ComputeMessage(cert_scope, start_time));
return GetDeviceAuthenticationSignedURLQueryStringFromComponents(
cert_scope, start_time, base64_signature);
}
std::string GetDeviceAuthenticationSignedURLQueryStringFromComponents(
const std::string& cert_scope, const std::string& start_time,
const std::string& base64_signature) {
CHECK(!cert_scope.empty());
CHECK(!start_time.empty());
if (base64_signature.empty()) {
return std::string();
}
std::map<std::string, std::string> signed_query_components;
signed_query_components["cert_scope"] = cert_scope;
signed_query_components["start_time"] = start_time;
signed_query_components["sig"] = base64_signature;
std::string query;
for (const auto& query_component : signed_query_components) {
const std::string& key = query_component.first;
const std::string& value = query_component.second;
if (!query.empty()) query += "&";
query += net::EscapeQueryParamValue(key, true);
if (!value.empty()) {
query += "=" + net::EscapeQueryParamValue(value, true);
}
}
return query;
}
// Combine multiple message components into a string that will be used as the
// message that we will sign.
std::string ComputeMessage(const std::string& cert_scope,
const std::string& start_time) {
// Build message from cert_scope and start_time.
return BuildMessageFragment("cert_scope", cert_scope) +
BuildMessageFragment("start_time", start_time);
}
void ComputeHMACSHA256SignatureWithProvidedKey(const std::string& message,
const std::string& base64_key,
uint8_t* signature,
size_t signature_size_in_bytes) {
CHECK_GE(signature_size_in_bytes, 32U);
std::string key;
base::Base64Decode(base64_key, &key);
// Generate signature from message using HMAC-SHA256.
crypto::HMAC hmac(crypto::HMAC::SHA256);
if (!hmac.Init(key)) {
DLOG(ERROR) << "Unable to initialize HMAC-SHA256.";
}
if (!hmac.Sign(message, signature, signature_size_in_bytes)) {
DLOG(ERROR) << "Unable to sign HMAC-SHA256.";
}
}
} // namespace browser
} // namespace cobalt